devscout-rs 0.10.0

Fast code index for C# and TypeScript codebases -- map, find, refs, impact from the CLI.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
# Architecture

How `devscout` is put together: what each module owns, what it must keep true, and where a
change goes. Read this file before the code. `tools/check-architecture.sh` fails CI when a
module under `src/` has no row here or a row names a module that no longer exists, so the
table below is complete by construction.

## Pipeline

One `devscout map` run flows left to right; every query verb reads what it wrote.

```
walk ─► parse ─► extract ──┐
          │      markup ───┼─► resolve (C#) ─┐
          └── preproc      │   tsgraph (TS) ─┼─► graph ─► query ─► render ─► cli
                           │   rsgraph (Rust)┘                             │
                           └───────────────► manifest ─────────────────────┘
project ─► resolve        repo / initcmd / mapcmd / store / hookio / suggest / audit / offsets
```

- **Indexing** (`map`): `walk` lists files, `parse` and `preproc` turn C# into a syntax tree,
  `extract` and `markup` turn the tree into per-file fragments, `resolve` / `tsgraph` turn
  fragments into a graph, `rsgraph` appends Rust definitions and the edges its module tree and
  `use` scope resolve after the resolve,
  `graph` persists it, `manifest` persists the file-purpose index.
- **Querying** (`find`, `refs`, `read`, `impact`, `tests`): `query` builds an in-memory index
  over the persisted graph and answers; `render` formats; `cli` parses arguments and returns
  `(exit code, output)`.
- **Agent integration** (`init`, `hook`): `initcmd` installs hooks and registers the repo,
  `hookio` rewrites tool results, `store` keeps the freshness caches.
- **Measurement** (`audit --semantic`): `audit` scores `uses-member` edges against an
  oracle export.

## Modules

One row per file under `src/`. Invariants are the facts the rest of the crate relies on;
breaking one is a behaviour change even when every test still passes.

| Module | Responsibility | Invariants |
| --- | --- | --- |
| `src/lib.rs` | Crate root: declares every module (including `rsgraph`) and documents it in one line. | Every module under `src/` is declared here with a one-line doc. |
| `src/main.rs` | Binary entry point. | Hands `std::env::args()` to `cli::dispatch` and does nothing else. |
| `src/audit.rs` | Root of `audit --semantic`: the module header (join rule, split rationale), the `mod` list and the public re-export. | Holds no code. The public surface is `cmd_audit`; every other item is `pub` at most within `audit`'s own module tree. |
| `src/audit/model.rs` | The shared row/`Inputs` shapes: `Tier`, `EdgeRow`, `DefRow`, `OracleRef`, `Unit`, `Inputs`, `AuditOptions`. | No I/O and no scoring logic; every field is `pub` within the crate so the sibling modules below can read it directly. |
| `src/audit/load.rs` | Filesystem reads: `load`, `parse_graph`, `read_jsonl`, `tier_of`, `build_universe`; the lane comes from `lane.rs`. | Returns a plain `String` error, the shape every `cmd_*` in `cli.rs` wraps as `"error: {msg}"`. `graph.json` is read as a bare `serde_json::Value`, never through `graph::read_graph`. |
| `src/audit/lane.rs` | The lane `audit` reports: `AuditLane`, its `read` from graph.json's `stats.lane`/`stats.semantic`, the text header token and the `lane_detail`/`compilations` JSON keys. | `enriched-partial` when `stats.lane.state` is `partial`, else `enriched` when `stats.semantic` is present, else `syntax`. A graph without `stats.lane` reads, renders and gains no key exactly as before; the header names the partial or not-applied reason only when the record carries one. |
| `src/audit/scoring.rs` | Pure matching/structural-check primitives: `target_matches`, `member_matches`, `target_known`, `short_name`, `file_to_unit`, `reach`, `is_structural`. | No I/O. `target_matches`/`target_known` both apply the enum-member two-spelling allowance identically. |
| `src/audit/report.rs` | `TierStats`, the per-tier counters `score.rs` fills and `render.rs` reads. | `structural` counts false positives only, never a structurally-impossible true positive. |
| `src/audit/score.rs` | `AuditReport` and `score`: the pure `Inputs -> AuditReport` scoring pass, including the `--fp-sites` row collection when `Inputs.collect_fp_sites` is set. | Pure: every branch reads only `Inputs` and locally built indexes over it. Oracle records are joined to edges by site (file and line), target and member; a `partial file mismatch` only affects the file join of true positives, never precision. |
| `src/audit/render.rs` | Text (default) and `--json` rendering: `render_text`, `render_json`, `ratio_text`, `ratio_j`. | A zero-edge tier row is omitted, never printed as zero. |
| `src/audit/assert_check.rs` | `--assert <file>` threshold evaluation: `evaluate_assert`, `lookup_metric`, `fmt_num`. | Collects every violated or missing key before returning, never stops at the first. |
| `src/audit/fp_sites.rs` | One row per false positive written by `audit --fp-sites <file>`, built inside `score.rs`'s own scoring loop from the same evidence set that decided the edge's class. | Built only when `Inputs.collect_fp_sites` is set; the row count of one class and tier always equals exactly that tier's counter for the class, by construction. |
| `src/audit/cmd.rs` | `cmd_audit`, the CLI entry point `cli.rs` dispatches `audit` to. | Reads the graph read-only. `--json` keeps `--assert` violations on stderr so stdout stays one parseable JSON object; text mode appends them to stdout. With `--json` anywhere in the arguments every error also goes to stderr and the returned output is empty, which the `audit` dispatch arm does not print. `--fp-sites <file>` writes `fp_sites.rs`'s JSON-lines rendering independent of `--json`/`--assert`. |
| `src/audit/tests.rs` | Shared fixture builders for the audit tests (`fragments_for`, `graph_value_for`, `oracle_ref`) and the `mod` list for the nested test files. | Builds `graph.json` through the real extractor/resolver, never a hand-written `Value`, so no test bypasses the parse path. |
| `src/audit/tests/scoring.rs` | Matching, leak and structural-impossibility scoring cases. | One case per named scoring rule in `scoring.rs`/`score.rs`; every case builds its graph through `tests.rs`'s real-extractor helper. |
| `src/audit/tests/render.rs` | The three rendering-pin cases (`--assert` pass/fail/missing, JSON key order, text layout) plus the fixture-backed `--assert` tests. | Pins the exact text/JSON shape `render.rs` promises; a key-order or column change must update these tests deliberately. |
| `src/audit/tests/semantic.rs` | The audit lane over hand-written `stats` blocks: the label, the text header token, `lane_detail`/`compilations`, their key position, and `--assert` over `compilations.partial`/`compilations.total`. | A graph without `stats.lane` is pinned to render with no new key, so a report from before the lane record reads the same. |
| `src/bus_seed.rs` | The message-bus seed vocabulary, ten dispatch roots, six guard words, one whole-word predicate, handler bases, install prefixes and role suffixes. | Extraction and resolution read the same predicate when admitting house publish verbs, while only the five seed verbs start wrapper closure. Keeping the rule in a crate-level leaf module prevents the two call-shape gates from drifting apart without giving either layer ownership of the vocabulary. |
| `src/cli.rs` | Root of CLI dispatch: the module overview, the `mod` list and the public re-exports. | Holds no code. The public surface is `dispatch`; `require_repo` stays `pub(crate)` so `audit.rs` can resolve its repo root the same way every command here does. |
| `src/cli/admin.rs` | The `map`, `stats` and `clear` commands: the index build, the cache-dedup summary, and the three prune forms. | `map` prints its summary line and, when the rebuilt graph carries a `stats.lane` record, the lane line after it. None of the three read the graph. `clear`'s whole-store form never opens the store just to remove it; `--older-than` and `--session` are checked in that order, so the first one present wins when both are given. |
| `src/cli/answer.rs` | The answer shape every verb funnels through: the zero-hit exit code and its stderr note, the freshness warning, the two ambiguous-outcome renderers and the candidate row/object pair both of them and a `--pick` section label are built from, the not-found fallback, the `--json` `freshness` splice, and the one telemetry call a resolved query makes. | `EXIT_NO_RESULT` (see `exit.rs`) is the one code that means a resolved, empty answer, distinct from failure and from a usage error. An ambiguous type and an ambiguous member answer under the same JSON keys from two different renderers, so a caller branching on `outcome` never has to tell the two apart. `finish_query`'s `json` flag gates `append_freshness`, which splices `freshness` (from `freshness::index_freshness_state`) onto the answer as its true last key on `refs`/`read`/`impact`/`tests`; `find` has no `--json` shape and always passes `false`. `freshness_json` is `pub(crate)` because the fact export states the same vocabulary through it, so the two cannot drift. |
| `src/cli/args.rs` | Argument-parsing primitives shared by two or more verbs: the bare-query scan, `--pick`, the lenient `--hops`-style integer parse, `--no-guess`, and the `--json`/`--compact` conflict check. | `parse_int_js` accepts trailing garbage after the longest leading digit run (`"3abc"` parses to `3`); `parse_pick` does not -- a bad `--pick` value is always a usage error. `Pick` is that flag's own closed value set: a positive integer, or exactly `all`. `All` and `ALL` are errors, so the set is one a caller reads off the usage line. `--no-dispatch` and `--no-bus` are parsed in `index_options` exactly the way `--no-guess` already is, on all four verbs. |
| `src/cli/compiler_facts.rs` | The `compiler-facts run|import|status` verb group: local one-shot engine acquisition, build/CI-artifact import, and read-only status. | `run` and `import` converge on the identical `graph::admit_and_publish` call -- the one admission path for both producers. `status` never opens `graph.json`, reports `coverage: syntax-only` when no artifact was ever admitted, prints `coverage: complete` only when no unit is incomplete and every counted compilation is complete (otherwise `partial (K of M compilations: <tally>)`, or the `incomplete (...)` wording with the tally appended), and reports occurrence coverage (admitted count, non-confirmed states, an explicit unavailable state) by an independent read-only parse of the already-admitted bytes. `run`'s own wrapper omits `--capabilities` entirely when the caller passed none, so the spawned engine's own default (not a second, stale definition here) decides. |
| `src/cli/coverage.rs` | The `tests` verb -- tests reaching a symbol. Named `coverage`, not `tests`, to avoid colliding with this module's own `#[cfg(test)] mod tests`. | Mirrors `refs`' own check order and ambiguity handling exactly; only the resolved arm's renderer and JSON builder differ. Its `--pick` narrowing runs through `cli/sections.rs`, shared with the other three verbs. |
| `src/cli/dispatch.rs` | Top-level argument dispatch: `--help`/`--version`, the four dev/diagnostic subcommands, and wiring each verb's return value to stdout/stderr and the process exit code. | `init` dispatches through `initcmd::cmd_init_full`, never `cmd_init` directly. `hook read`/`hook bash` always exit 0 -- a broken scout must never break the tool call it is post-processing. |
| `src/cli/export.rs` | The `export` verb: `--out` parsing and its own output path. | The one verb that writes its own output: `print_out` appends a newline and the stream terminates every line it writes. A malformed invocation exits 2 with the usage line on stderr and nothing on stdout; a refusal and a failed `--out` write both exit 1, so a caller can branch on the code alone. |
| `src/cli/find.rs` | The `find` verb: the manifest search plus the declaration block a code or markup name resolves to. | The declaration and manifest pools are capped and ranked independently (`FIND_NAMES_CAP`, `FIND_FULL_CAP`, `FIND_FALLBACK_CAP`); tier 3 (a resource key) is a one-line trailer by default, inline only under `--resources`. |
| `src/cli/impact.rs` | The `impact` verb: its own numeric flags (`--hops`, `--iface-max-fanin`, `--hub-max-indegree`), a file-or-symbol seed rewritten repo-relative, and the blast-radius answer. | A resolved seed that reaches nothing beyond its own files is a zero hit, the same signal an unresolved seed gets. Its `--pick` narrowing runs through `cli/sections.rs`, shared with the other three verbs, and a picked type candidate starts from its def id rather than a seed spelling. |
| `src/cli/import_edges.rs` | The `import-edges` verb: validates a cross-repo edge export and writes the auxiliary artifact `impact` reads. | `--repo <id>` is required and never inferred. A refusal (exit 1, naming the offending value) never calls the writer -- `write_imported_edges` is reached only once parsing already returned `Ok`. |
| `src/cli/read.rs` | The `read` verb: a declaration span layered on `refs`' own resolution. | Every outcome but the resolved one -- not-found, ambiguous, member-ambiguous, the bare-member answer -- is `refs`' own renderer, unchanged; only the resolved arm adds the span. Its `--pick` narrowing runs through `cli/sections.rs`, shared with the other three verbs. |
| `src/cli/refs.rs` | The `refs` verb: argument parsing, and the outcome-to-answer mapping every result shape renders through. | `refs_answer`, `member_models_out` and `refs_model_row_count` are shared with `read`, whose own bare-member answer is refs' own. Each returns a `SectionAnswer`, so a narrowed answer and a base answer reach `finish_query` through one shape. Its `--pick` narrowing runs through `cli/sections.rs`, shared with the other three verbs. |
| `src/cli/root.rs` | Repo-root and graph-artifact resolution shared by every verb: the cwd a command runs from, the `.scout`/`.git` ancestor climb, the `-C` global flag, and the mapped `graph.json` every graph-reading verb needs. | `require_repo_for_path`'s argument-named-file fallback only applies once the caller's own directory has come up empty; `-C` composes on repeat exactly as git's does. |
| `src/cli/sections.rs` | The `--pick` narrowing `refs`/`read`/`impact`/`tests` share: `Candidates` (the refusal's rows, its JSON objects and the seed each candidate answers from, all in the refusal's own order), `SectionAnswer` (one candidate's rendered answer plus the facts telemetry reports), and `narrow`. | One helper serves `--pick <n>` and `--pick all`, because section `n` IS candidate `n`'s answer: both call the verb's own per-candidate builder with the same seed, so the two forms cannot drift. An out-of-range `n` is the verb's usage error. The wrapper's outcome is `hit` when at least one section is, the rule the bare-member wrapper applies to its members; its row count is the sum of the sections' and its suggestions their union. Rows are never merged, deduplicated or moved between sections. |
| `src/cli/tests.rs` | Shared fixture builder for the CLI tests and the list of test topic modules. | `json_refs_model` is the only builder shared across more than one topic; everything else lives local to the topic file that uses it. |
| `src/cli/tests/admin.rs` | `js_math_round` and `js_float_string`'s rounding/formatting parity, and every `clear` form: whole-store, `--older-than`, `--session` (unique, ambiguous, no-match, missing-value), and the flag-precedence order. | Pins that a rejected `--older-than` value creates no store, and that `--older-than` wins when both flags are given. |
| `src/cli/tests/answer.rs` | The `J` JSON encoder's object/array whitespace and string escaping. | Pins the exact escapes and that no extra whitespace appears, matching `JSON.stringify`. |
| `src/cli/tests/args.rs` | `parse_int_js`'s lenient-integer parity with `parseInt`. | Pins that trailing non-digit garbage is dropped rather than rejected, and that a leading sign with no digits is `None`. |
| `src/cli/tests/coverage.rs` | `tests_model_to_json`'s `via` placement -- appended last and omitted on an attribute row -- and its `schema_version` lead. | Pins that a project-vouched row carries `via` but an attribute row carries none at all. |
| `src/cli/tests/find.rs` | `cmd_find`'s output caps, its zero-hit exit code, tier-1/2/3 handling, and the declaration-block-above-manifest-block ordering, built through the real extractor and resolver rather than hand-authored JSON. | Pins that a resource-key-only match brakes by default and that a manifest row with no declaration falls back to line 1. |
| `src/cli/tests/impact.rs` | `impact_model_to_json`'s key order: `heuristicCount` then `heuristic` after `score`, `tier` between `heuristic` and `ifaceVia`, and `heuristicAffected` after `manifestGap`. | Pins that every appended key is omitted, not merely empty, on a precise row. |
| `src/cli/tests/read.rs` | `read_model_to_json`'s `span` placement. | Pins that `span` sits immediately after `kind`, unmoved by the `schema_version` key gaining the lead. |
| `src/cli/tests/refs.rs` | `refs_model_to_json`'s and `member_refs_to_json`'s key order and omission rules: `heuristic`/`tier`/`source` appended last, the `outbound` table gated on `--out`, and the member wrapper's own envelope. | Pins that a precise row carries no trace of `tier` and that a member entry inside the wrapper is the resolved object unchanged. |
| `src/exit.rs` | The four process exit statuses: `EXIT_OK`, `EXIT_FAILURE`, `EXIT_USAGE` and `EXIT_NO_RESULT`. | One module holds every status a verb can return, and no verb passes a bare integer to `process::exit` or returns one as a status, so the exit-code reference can name each outcome and `tests/docs_sync.rs` fails on a numeric literal. The values are unchanged from the literals they replaced. |
| `src/extract.rs` | Root of C#, TypeScript-family and Rust extraction: the `mod` list and the public re-exports. | Holds no code. The public surface is `extract`, `run_extract_dump`, `extraction_to_json`, `ts_extraction_to_json`, `extract_ts_file`, `extract_ts_fragment`, `TsFileExtraction`, `extract_rs_file`, `RsFileExtraction`, `RsFragment`, `RsItem`, the `TsFragment` record types, `compose_hybrid_ts_purpose`, `extract_ts_purpose`, `extract_ts_purpose_with_heuristic`, and the C# data model (`DefRecord`, `Extraction`, `ExtensionMethod`, `Fact`, `LambdaSlot`, `NameRecord`, `RefRecord`, `RegistrationRecord`, `UsingRecord`); every other item is `pub(super)` at most. |
| `src/extract/text.rs` | Tree-sitter/text primitives shared by the C# and TypeScript-family extraction: node text, declared names, the token-built dotted name (`token_path`, `path_text`), namespace-level type discovery, and the truncated one-line purpose signature. | `truncate` counts UTF-16 code units, not bytes; `MAX_PURPOSE` (200) is the one limit both language extractors truncate to. `path_text` owns the one rule for turning a qualified name into a dotted string: the name's own tokens joined with the trivia between them left out, or -- when the subtree holds an ERROR or MISSING node, which this grammar reports as an extra one -- the raw source text, so a damaged name earns no path it never spelled. |
| `src/extract/types.rs` | The C# data model extraction produces: `DefRecord`, `ExtensionMethod`, `RefRecord`, `RegistrationRecord`, `UsingRecord`, `Extraction`, `NameRecord`, `Fact`, `LambdaSlot`, `PublishRecord` (now also `arg_count` and `enclosing_call: Option<EnclosingCallFact>`), `EnclosingCallFact`, and `is_reserved_method_params_key`. | Plain data; the one predicate is `is_reserved_method_params_key`, the single list of reserved `methodParams` spellings (keys that start with `@result:`, `@property:` or `@field:`, and the marks `@variant`, `@implicit-conversion` and `@interpolated-string-handler`). A verbatim method name such as `@event` is not reserved. A `Fact` carries the declared type name plus its top-level type arguments, and two facts agree only when both halves do. A `LambdaSlot` records where an untyped lambda sits so the resolver can type it. A `RegistrationRecord` carries its two type arguments' raw text verbatim; splitting a dotted name into a resolvable pair is the resolver's job. `PublishRecord.arg_count` is the call's own argument count; `enclosing_call` is set only when the call sits inside a lambda body that is itself an argument of an enclosing invocation. |
| `src/extract/refs.rs` | Building `RefRecord` candidates -- `push_ref`, `push_ctor_param_ref`, `push_member_ref`, `record_single_type`, `record_base_list` -- the type/generic-descriptor primitives that feed them, and `registration_fact`, the two-type-argument DI registration shape rule. | A member-access qualifier's `generic` flag is type-certainty syntax only a type can carry; `receiver_type` is set only for a bare, non-generic qualifier. `registration_fact` fires only for an invocation whose generic method name begins `Add`/`TryAdd` and ends `Singleton`/`Scoped`/`Transient` and carries exactly two type arguments; every other shape (one argument, a non-invoked method-group reference, an unmatched name) records nothing. |
| `src/extract/argument_types.rs` | Records a call's ordered argument descriptors from syntax, declared symbols, and deferred member lookups. | A named argument withholds positional comparison for the whole call; by-reference and unrecognized expressions have unknown slots. The callee's own invocation supplies the list. An interpolated string records its own reserved descriptor rather than `string`, because C# may bind it to an `[InterpolatedStringHandler]` parameter. |
| `src/extract/argument_member_types.rs` | Adds method result, property, field, conversion, variance and interpolated-string-handler facts needed for argument comparison (`append_argument_member_types`, `append_declaration_marks`). | Stores these in reserved `methodParams` entries while preserving existing member projections. The handler mark is read from the declaration's own `[InterpolatedStringHandler]` attribute through `members.rs`'s attribute-name normalization, and carries one empty overload, so no reader of a member's parameter descriptors sees one there. |
| `src/extract/members.rs` | Raw per-member fact extraction for one type declaration: methods, properties, fields, bases, extension methods, arities, parameter descriptors, test methods, `override`-marked method names. | No accessibility filter on properties or fields; `methods` and `non_public_methods` partition method declarations with no overlap and no gap. `raw_override_method_names` draws from the same method population `raw_method_arities` does, so an override's own arity fact is read off that one table. |
| `src/extract/type_defs.rs` | Assembling `DefRecord`/`NameRecord`/`UsingRecord` instances from the raw member facts: `record_type_def`, `record_enum_members`, `record_declared_members`, `record_using`, `type_id`. | Nested-type ids join with `+`, never `.`; an enum member's id appends `.` even under a nested type. |
| `src/extract/type_arity_facts.rs` | Type-parameter and type-argument counts: a declaration's own type-parameter count, each method overload's count aligned with `raw_method_arities`, and the length of a type-argument list written on an invoked member. | `raw_method_type_arities` walks and skips exactly the declarations `raw_method_arities` does, so position k of both lists is one overload. A written list is counted by its direct commas, so a nested argument's own commas never count; a member written without a list records nothing. |
| `src/extract/type_param_sites.rs` | Where each overload's own type parameters occur in its parameter types (`methodTypeParamSites`), and a delegate's return and type-parameter occurrences (`delegateSignature`). | Walks exactly the declarations and parameters `raw_method_params` walks, so the sites line up with the descriptors overload for overload and parameter for parameter; a site names the top-level type argument it sits in after one `Expression<...>` wrapper, or -1 outside every type argument. |
| `src/extract/bus.rs` | Message-bus publish-site facts (`publish_fact`), the nested base type-argument fact a generic consumer base needs (`nested_base_type_args`), and the array-marked subset of a def's own base names (`array_message_bases`, position 0 of a base's type-argument list an `array_type` node); also the call's own argument count (via `refs::invocation_arg_count`) and `enclosing_call_through_lambda`, which walks UP from the call to an enclosing lambda ancestor and one level further to that lambda's own parent invocation. | `publish_fact` resolves a message from a generic type argument, a constructed type (direct or nested in a lambda argument, `object_creation_expression` or `array_creation_expression` alike via `Construction::Typed`), an implicitly typed array creation whose elements all construct the same type (`Construction::ImplicitArray`, `implicit_array_message`), or a plain identifier's declared type read through the ordinary scope ladder -- appending `[]` when `Fact::is_array` says the identifier's own declared type was an array -- never from a bare name merely co-located on the same source line. A message the call writes keeps its written qualifier (`written_message`: `new Ns.M()` records `Ns.M`, `new Ns.M[n]` records `Ns.M[]`), the same segments the site's own type reference records, rebuilt through `text::path_text` -- the one token-path helper every extractor site that records a dotted name shares -- so whitespace, comments and other trivia between the qualifier's tokens are left out on every call shape (type argument, construction, explicit and implicit array), the resolver's last-dot split always sees `Ns.M`, and the message reads through the qualified steps; `type_descriptor` itself, shared with method parameters and consumer bases, still drops it, and an identifier argument's message keeps its receiver fact's bare name. A message the call NAMES itself is recorded whatever the method is called, since which verbs count is settled at resolve; the identifier shape alone stays gated on the shipped verbs, being far too common to record everywhere. A message that is the enclosing method's own type parameter records that method's name instead, which is what marks a forwarding wrapper. `nested_base_type_args` records an entry only for a base carrying a top-level type argument that is itself generic; a flat base contributes nothing new beyond `base_generic_args`, which is what `array_message_bases` exists to recover the array bit for, since `base_generic_args`/`generic_arg_descriptors` look through an `array_type` to its bare element. `enclosing_call_through_lambda` is a plain node-kind walk (`lambda_expression`/`anonymous_method_expression` ancestor, then that lambda's own enclosing `argument`/`argument_list`/`invocation_expression`) -- no new tree-sitter query -- feeding the resolver's two structural refusals (see `resolve/bus.rs`) without validating any receiver's own declared type. |
| `src/extract/bus_vocab.rs` | Facts a repository declares about its own message vocabulary: one-type-argument handler registrations (`handler_registration_fact`) and the single type arguments a type's properties wrap (`property_message_args`). | A registration is recognized on an installation-shaped name carrying exactly one type argument, which is the whole discriminator against the two-argument DI registration. Both rules are deliberately permissive: neither decides anything, and the resolver keeps only a registration whose named type carries a generic base and only a property argument on a type it already holds to be a handler, so a loose match here costs fragment bytes rather than edges. |
| `src/extract/receivers.rs` | The local/field fact tables (`FactTable`, `Fact`) a member-access qualifier resolves against: `collect_type_facts`, `collect_member_facts`, `visit_member_facts`, and `type_fact`, the single declared-fact producer that also records the type node's written path. | The member table keeps one fact per name and shadows the type table; a conflicting or untyped local, and every name a `var (…)` deconstruction designates, is stored as `None` rather than omitted, so it never falls through to a same-named field. Every entry is entered with its declaring node, so its declaring-construct scope is recorded (see `member_scope.rs`), and a second-pass hop (`var x = q.M()`, a `foreach` collection, a lambda's receiver or callee qualifier) reads nothing from a name its declaring constructs do not reach. |
| `src/extract/member_scope.rs` | The scoped member table (`MemberTable`) and the declaring-construct rule: which construct declares each member-table name, and the names a declaration enters with no type (`untyped_declared_names`: every `var (…)` deconstruction name, a `var` or property/positional pattern's designation, and LINQ range variables). | A member's own parameter spans the member; a block local spans its block (a switch section's statement locals span the switch body, a `for`/`using`/`fixed` declaration its statement); a lambda, anonymous-method or local-function parameter spans that construct; a `foreach`, `catch` or query variable spans its statement, clause or query; a pattern or `out` designation spans its nearest enclosing block or switch section, or the whole member under an expression body. A lookup whose use lies outside every declaring construct answers taken-but-unknown, and a declaration whose construct cannot be placed takes its name for the whole member with no type. A `foreach (Box<Item> x in xs)` the grammar misreads as a comparison only adds its loop to an existing entry for `x`, so the member's other declaration still supplies the fact in the loop body; with no other declaration `x` is taken-but-unknown. The check only withholds: it never makes a fact more specific than the table's. |
| `src/extract/lambdas.rs` | Untyped lambda-parameter slot typing: `lambda_argument_slot`, `lambda_slot_fact`, `lambda_receiver_element_fact`. | A lambda parameter's type is read positionally off its callee's delegate parameter, never off a fact table directly. |
| `src/extract/delegate_args.rs` | A call argument's own delegate-shaped parameter count: `delegate_argument_param_count`, for a lambda literal (counted from its own parameter list) or a bare identifier naming an in-scope local function, passed as a method group. | A named argument and every other method group -- a type's member, an extension method, an ambiguous or shadowed name -- record nothing rather than a guess; a local function counts only when exactly one same-named declaration is in scope and no nearer lambda, local, or `foreach` variable rebinds the name first. |
| `src/extract/lambda_shape.rs` | A call's per-position lambda-literal shape (`lambdaArgShape`): whether the body can only produce a value, and whether the parameters are implicitly typed. | Value-only is a positive reading: only expression kinds C# can never use as a statement expression, or a block with a `return <expr>;` of its own, count; every other body, including a kind the list does not know, reads as possibly void-compatible. A named argument, a method group and an anonymous method record nothing. |
| `src/extract/qualifiers.rs` | `Scope` and member-access qualifier resolution: `resolve_member_qualifier`, `chain_tail_receiver_type`, `resolve_call_chain_tail`. | `this`/`base` resolve against `type_stack`, never against a same-named local or field. A dotted qualifier never inherits a receiver fact. Every argument and receiver fact lookup passes its use site's byte offset, so a member-table name read outside every construct that declares it yields no type, and still counts as a claimed local rather than falling through to a same-named field. |
| `src/extract/walk.rs` | The top-level recursive-descent AST walk (`walk`, `walk_list`) and its `pub fn extract` entry point. | `type_stack` doubles as the "inside a type" signal; ref extraction runs at every depth regardless of nesting. A registration fact is checked at every `member_access_expression` that is actually invoked, independent of and alongside the uses-member/uses-type candidates the same call already builds. |
| `src/extract/dump.rs` | The `extract-dump` plumbing verb: `run_extract_dump`. | The only extraction code path that touches the filesystem; dispatches on extension to the C# or the TS-family extractor. |
| `src/extract/json.rs` | Hand-rolled JSON serialisation for both the C# and the TS-family fragment shapes: `extraction_to_json`, `ts_extraction_to_json`. | Matches `JSON.stringify(value, null, 2)` byte-for-byte; no serde dependency. |
| `src/extract/ts_purpose.rs` | TypeScript-family purpose composition: exported top-level declarations, CommonJS and ESM export shapes, re-export bucket segments. | Never builds a def/ref/usings candidate; purposes only. |
| `src/extract/rs_fragment.rs` | Rust item extraction: `extract_rs_file` records every item with its path, kind and span, marks `mod x;` declarations and their `path` facts, tuple and unit structs, test functions (`#[test]` or `test` carried by `cfg_attr`, read by `rs_attrs`) and `impl` blocks, and composes the file purpose, off one UTF-16 parse. | Walks only the file root and inline module, `impl`, `trait` and `extern` bodies; never a function body, an initializer or a token tree. Paths are file-local (`module::Owner::item`), a repeated path takes a `-N` suffix in source order, and no path carries generic or trait text. The declaration facts are keyed by the suffixed path. A readable file always gets a fragment, empty when the parse fails. |
| `src/extract/rs_facts_types.rs` | The Rust resolution facts a fragment carries: `RsImpl`, `RsModPath`, `RsUse`, `RsBlock`, `RsSite`, `RsRole`. | Every field is omitted when empty, so a file with none of the facts serializes as items only. A scope is the inline-module path inside the file; a block index points into the fragment's `blocks`, which holds only blocks with a direct `use`. |
| `src/extract/rs_attrs.rs` | A Rust item's outer attributes in source order, each `cfg_attr` standing for the attributes it carries: `is_test` (`#[test]` or a path ending in `::test`, plainly or carried) and `path_fact` (a `mod x;` declaration's first `path` attribute). | The `cfg_attr` predicate is never evaluated. Inside a `cfg_attr` only an exact attribute form counts, and a malformed `cfg_attr` stands for nothing. A `path` is read only from a plain string literal without escapes or a raw literal; any other value, or a `path` carried by `cfg_attr`, is unread. Nested `cfg_attr` lists unwind on an explicit stack. |
| `src/extract/rs_paths.rs` | Rust path nodes as segment lists (`path_segments`, `join`, `type_path_node`). | Walks a path's nested `path` fields in a loop; generic arguments are dropped and a leading `::` is an empty first segment. A qualified path or a metavariable has no segments. |
| `src/extract/rs_macro_bind.rs` | The names a Rust macro invocation's token trees may bind (`tree_names`): identifiers outside a call or path position, items, generic parameters and `use` names declared inside them, and whether they hold an `impl` or trait. `narrow` gives the context the invocation's own calls are read in; possible names from every nested group reach the code around a statement or module-level invocation, and `is_statement` marks the former. | An uppercase name inside angle brackets or after a type-position token is a type, and a name opening a `field:` entry in a struct body is a field, not a binding. A lowercase name may declare an item. Can drop a call, never binds a local to an item. Trees are walked on an explicit stack. Invocation names are consulted alongside inherited scope sets without copying those sets per invocation. |
| `src/extract/rs_macro_scan.rs` | Calls inside Rust macro arguments: `scan` reads an invocation's token trees for `ident (` and `path :: ident (`, turbofish allowed. | A name after `.`, `$` or `fn`, a nested macro's name and anything inside a string literal is no call; hits are `call` sites marked `mac`, shadowed like any other call and by every name the invocation's trees may bind. Nested trees are scanned on an explicit stack. |
| `src/extract/rs_scope.rs` | Extraction-time Rust scope: the names a function body's patterns and statement macros bind (`body_locals`), a block's own items, the items a block's or module's macros may declare (`macro_items`), modules extended by item macros (`macro_scopes`), and an item's generic parameters. | Locals are flow-insensitive over the whole body and a nested function is its own body; an uppercase pattern identifier binds nothing. A module macro may add unlisted names even when its arguments contain no identifier, as with `include!`. Every walk is on an explicit stack. |
| `src/extract/rs_sites.rs` | Rust reference sites: `collect_sites` records every path the edge pass binds, with its role (`call`, `value`, `type`, `lit`, `impl`, `pat`), scope, block and `Self` owner, in source order. | No site for a `use` declaration, a method or field name, a macro name, an attribute, a `macro_rules!` body, a literal, a primitive type or an `ERROR` region. A first segment shadowed by a block item, an item a block's or module's macro may declare, or a generic parameter, or a single-segment value shadowed by a local, gives no site; `Self` alone gives none. |
| `src/extract/rs_uses.rs` | Rust `use` declarations flattened into one leaf per bound name, with their scope and block (`collect_uses`). | Groups, globs, `self` in a group and renames each give a leaf; `as _` gives none. Token trees, macro definitions, attributes and modules inside function bodies are never entered, and every walk is on an explicit stack. |
| `src/extract/ts_fragment_types.rs` | The TS-family fragment data model: `TsFragmentDef`, `TsBinding`, `TsImport`, `TsReexportName`, `TsReexport`, `TsRef`, `TsFragment`. | A TS fragment is tagged `ts: 1` so the resolver routes it away from the C# resolver; the two fragment shapes share no field beyond `defs`. A non-exported definition carries `local: true`; exported rows omit the key. |
| `src/extract/rs_fragment_types.rs` | The Rust fragment data model: `RsItem`, `RsFragment`, `RsFileExtraction`. | A Rust fragment is tagged `rs: 1`, first and required on read, and carries neither `ts` nor `usings`, so each fragment shape rejects the other two. The resolution facts follow `items`, each omitted when empty; reference sites are keyed `sites` because the other two shapes require `refs`. `opaqueScopes` names modules whose macro output cannot be listed by glob lookup. `modPaths`, last, lists each `mod x;` declaration a `path` attribute decides, with its literal, or without one when the file cannot be told (a `cfg_attr`-carried or non-literal `path`, or an enclosing inline module that carries a `path`). |
| `src/extract/ts_file_scope.rs` | Ordered file-scope TS/JS declarations, direct and assigned top-level IIFE bodies, and the shared `require` binding predicate. | Program declarations win duplicate names before IIFE bodies; ordinary function and block bodies are not entered; unexported `require` bindings are not local definitions, while exported bindings remain exported definitions. |
| `src/extract/ts_fragment.rs` | TypeScript-family reference-fact extraction: imports, exports, calls, JSX uses, dispatching calls -- `extract_ts_fragment`, `extract_ts_file`. | Records only what a file says; local bare references join the fragment while member qualifiers retain the exported/imported name filter. Resolving a specifier or a name to a declaration is `tsgraph.rs`'s job. |
| `src/extract/ts_fragment_forms.rs` | Parses import-equals, export-equals, namespace re-exports and literal dynamic imports from the existing syntax tree. | Records module names and syntax form only; package and tsconfig rules belong to the resolver. |
| `src/extract/ts_component.rs` | Angular component facts of one TS file: each top-level class with a `@Component({...})` decorator, its instance members (`TsMember`), its literal `templateUrl` (`TsTemplateUrl`) or the scan of its inline `template`, and a gap for a value that is not one plain literal. | Matches the decorator on the identifier `Component` and an object-literal argument only. A static member gets no row; a get and set pair is one `property` row. An inline template keeps the raw text between its quotes, so its lines stay the file's own. |
| `src/extract/tests.rs` | Shared fixture helpers for the extractor tests and the list of test modules. | `extract_src`/`find_def` are the shared entry points every topic file builds on. |
| `src/extract/tests/ts_file_scope.rs` | TS/JS fragment serialization and nested IIFE declaration order. | Exported fragment rows omit the local marker; unexported require bindings are not definitions. |
| `src/extract/tests/fqn.rs` | FQN building: dotted namespaces, `+`-joined nested-type chains, enum-member ids. | Pins that a `+`-joined id never collides with a literal dotted path. |
| `src/extract/tests/generic_arity.rs` | Declaration spans (`line`/`end_line`) and generic-arity refs from a type-argument list. | Pins that a nested type's span stays inside its own node. |
| `src/extract/tests/using_aliases.rs` | Using-directive parsing: alias, plain, global and static forms. | Pins that only an alias directive carries both a name and a target. |
| `src/extract/tests/publish_message_qualifier.rs` | The written qualifier on a publish message, per call shape. | Pins the qualified type argument, construction (direct and inside a lambda), explicit and implicit arrays, nested, generic and `global::`-qualified messages, whitespace and comments between qualifier segments dropped on every call shape, and that bare, identifier, pass-through and consumer-side texts are unchanged. |
| `src/extract/tests/using_namespace_fact.rs` | The declaring namespace a plain using records, for block, nested, file-scoped, compilation-unit and `global` placements. | Pins that a using's namespace equals the one its own block's refs record, and that `extract-dump` prints the key only when it is set. |
| `src/extract/tests/qualified_path_tokens.rs` | Every dotted name the C# extractor records, written with whitespace, a line break or a comment between its segments: a receiver's declared type (field, local, parameter), nullable, array and `global::`-qualified wrappers, a type reference and a type argument, a base entry, a DI registration's two types, a namespace name, and a using target or alias. | Pins that each reads as its clean spelling, that the trivia spelling records the clean `qualifiedType`/`receiverTypeQualified`, and that a name whose subtree holds a parse error keeps its raw source text instead of a token-built path. |
| `src/extract/tests/qualifier_capture.rs` | Member-access qualifier text capture, deep chains, and `declaration_expression` out-declarations. | Pins that a deep chain flattens each window's own qualifier rather than inheriting one. |
| `src/extract/tests/misc_ref_edge_cases.rs` | Purpose-signature composition, `primary_constructor_base_type` inherits refs, tuple-type refs. | Pins the 200-UTF-16-unit purpose truncation. |
| `src/extract/tests/preproc_fluent_chain.rs` | `#if`/`#if-else` groups interrupting a fluent call chain. | Pins that a dead arm drops out without breaking the surviving chain. |
| `src/extract/tests/ts_purpose.rs` | TS/JS purpose composition acceptance cases, the `reexportBarrel.ts` fixture, the leading-comment hybrid purpose, and degrade paths. | Pins the hybrid purpose's comment-prefix composition and the zero-export/parse-failure degrade to `None`. |
| `src/extract/tests/preproc_duplicate_header.rs` | The duplicate-header preproc shape: nested defs and ascending line order across a blanked `#if` group. | Pins that defs and refs stay in ascending line order across the blanked arm. |
| `src/extract/tests/def_member_facts.rs` | Def-level member facts: properties, fields and method-return types recorded alongside `methods`. | Pins that a type declaring none of the new members still records all three as empty. |
| `src/extract/tests/receiver_facts.rs` | Receiver-fact construction: locals, `var`/`new`, parameters, fields, primary-ctor parameters. | Pins that `receiver_type` is set only on a ref that actually earned a fact, and that a qualified declaration keeps its written path in `receiver_type_qualified`. |
| `src/extract/tests/extension_method_facts.rs` | Extension-method def facts. | Pins source-order recording deduped by name, this-type and arity. |
| `src/extract/tests/arg_count_fact.rs` | The ref-side `argCount` fact on a member-access callee. | Pins that a chain ref never inherits the wrapping call's own arg count. |
| `src/extract/tests/argument_types_fact.rs` | Static argument fact sources and unknown forms. | Pins source order, literal and declared types, deferred member values, and positional withholding. |
| `src/extract/tests/test_methods_fact.rs` | `testMethods`: xUnit/NUnit/MSTest attribute recognition. | Pins that suffixed, qualified, targeted and shared-bracket attribute forms all match. |
| `src/extract/tests/outer_types_stack.rs` | The ref `outer_types` enclosing-type stack. | Pins outermost-first ordering and that a namespace-level ref carries none. |
| `src/extract/tests/names_list.rs` | The per-file `names` list. | Pins that a `names` line is the name token's own row, not the attribute line. |
| `src/extract/tests/ts_ref_fragment.rs` | The TS/TSX reference fragment: imports, exports, calls, JSX uses. | Pins default/named/aliased/namespace import-binding recording. |
| `src/extract/tests/ts_component.rs` | Component detection, member kinds and lines, inline template lines and escapes, and the non-literal gaps. | Pins that a file without a component serializes without the `components` key. |
| `src/extract/tests/rs_attrs.rs` | Attribute facts: each `cfg_attr` test form, plain test attributes, the first `path` literal of a module declaration, suffixed declarations. | Pins the negatives (`derive`, `allow`, `test_case`, every malformed `cfg_attr`, a test attribute on a non-function), unread `path` values (escaped, byte string, macro, carried by `cfg_attr`), children of a `path`-carrying inline module at any depth, a file without the forms serializing without `modPaths`, and deep `cfg_attr` and module nesting on a small stack. |
| `src/extract/tests/rs_facts.rs` | The Rust declaration and `use` facts: every `use` form, block-level uses and their blocks, `mod x;` against inline modules, constructors, test attributes, `impl` records. | Pins that `as _`, uses inside macros and function-body modules, a nested test function and a tuple self type are not recorded, and that a file with no facts serializes as items only. |
| `src/extract/tests/rs_fragment.rs` | Rust item extraction: every kind in every walked context, span starts, unrecorded constructs, syntax errors, self-type heads, path suffixes, raw identifiers. | Pins the line rules (doc comments and attributes start the span, a plain comment never does) and that every path is safe as a shell argument. |
| `src/extract/tests/rs_fragment_cost.rs` | Test-only generated item-cost row over frozen Rust sources. | Reports recorded items, modeled ancestry work, parent calls, path bytes and fragment bytes without changing release artifacts. |
| `src/extract/tests/rs_macro_scan.rs` | Calls inside macro arguments: `assert_eq!`, `assert!`, `vec!`, `matches!`, `format!` after the format string, nested invocations, path and turbofish calls. | Pins the misses: `$f(` in `macro_rules!`, `.f(`, names inside strings, a nested macro's name, `fn f(` in a token tree, attribute arguments; shadowing, including by a name the tree may bind and a statement macro's possible item, but not by a type written in the tree; source order; a deep token tree on a small stack. |
| `src/extract/tests/rs_sites.rs` | Rust reference sites: every callee form, every listed type position, literals, impl traits, values, pattern qualifiers, `Self` owners, per-segment lines, scope and block. | Pins every drop (locals, closures, nested functions, generics, method names, skipped regions, qualified paths) and a deeply nested body walked on a small stack. |
| `src/extract/tests/rs_purpose.rs` | The Rust file purpose: inner-doc and plain-comment prefixes, grouped item segments, the shared length cap. | Pins that an outer doc, an attribute or an item first gives no prefix, and that a file with neither prefix nor item gives `None`. |
| `src/extract/tests/property_types_fact.rs` | Property types and `var`-from-invocation call facts. | Pins that `receiverPropertyOwner` is recorded only for a typed two-segment chain. |
| `src/extract/tests/foreach_element_fact.rs` | `foreach` element-type facts over a generic collection. | Pins that a foreach variable used as another foreach's collection is never a fact. |
| `src/extract/tests/stage4_receivers.rs` | `this`/`base`/conditional receivers and await/cast/pattern facts. | Pins that a `base.` qualifier starts lookup at the bases, never the enclosing type. |
| `src/extract/tests/chain_tail_and_lambda_element.rs` | Chain-tail receivers (`a.B().C()`) and lambda-parameter element typing. | Pins that a second hop of a chain emits no ref. |
| `src/extract/tests/method_params_fact.rs` | `method_params`: per-overload parameter descriptors. | Pins that a type parameter is recorded as a wildcard and a delegate's parameters land under `Invoke`. |
| `src/extract/tests/member_scope_facts.rs` | Declaring-construct scoping of the member table: deconstruction names, uses outside every declaring construct, second-pass hops, and in-construct controls. | Pins that a deconstruction name or an out-of-scope use records no argument type and no receiver type while still counting as a claimed local, and that a use inside its declaring construct keeps its fact. |
| `src/extract/tests/method_type_arity_facts.rs` | `methodTypeArgCount`, `methodTypeArities` and an extension entry's `typeParamCount`. | Pins that only the invoked member's own list counts, that per-overload counts stay aligned with the value ranges in every part of a partial type, and that two extension overloads differing only in type-parameter count stay separate entries. |
| `src/extract/tests/type_param_site_facts.rs` | `methodTypeParamSites` and `delegateSignature`. | Pins that a method's own type parameter is told apart from its type's, the site positions for `Func`, `Action`, a seed parameter, an expression-tree wrapper and arrays, per-part alignment in a partial type, and a delegate's return and type-parameter occurrences. |
| `src/extract/tests/lambda_slot_fact.rs` | `LambdaSlot`: the site-side untyped-lambda callee slot. | Pins that the collection-element rule wins over a lambda slot when both shapes match. |
| `src/extract/tests/lambda_shape_fact.rs` | `lambdaArgShape`: value-only and implicitly typed lambda bodies at each argument position. | Pins the value-only and statement-expression forms, returns owned by a nested function, explicit against implicit parameter typing, and that named arguments, method groups and anonymous methods record nothing. |
| `src/extract/tests/delegate_arg_arity_fact.rs` | The `lambdaArgArity` fact: a lambda literal's and an in-scope local function's own parameter count, recorded per call-argument position. | Pins that a nearer lambda, local, or `foreach` variable of the same name shadows the local function and that a named argument or any other method group records nothing. |
| `src/extract/tests/explicit_implementation_facts.rs` | Explicit interface implementations (`IFace.Member` declarations) against the ordinary-lookup member lists and the class-scope receiver facts. | Pins that an explicit declaration never takes a property name's first-declaration slot while the per-overload tables and `names` keep it. |
| `src/extract/tests/reserved_method_params_keys.rs` | `is_reserved_method_params_key` against the keys the extractor writes. | Pins each reserved spelling, that verbatim method names and lookalikes are not reserved, and that each `methodParams` key one C# source writes is classified as its writer intends. |
| `src/extract/tests/registration_fact.rs` | The two-type-argument DI registration shape rule. | Pins the `Add`/`TryAdd`-prefix, `Singleton`/`Scoped`/`Transient`-suffix rule against keyed and named spellings, and that a one-type-argument or non-invoked form records nothing. |
| `src/extract/tests/handler_registration_fact.rs` | The one-type-argument handler-registration name rule. | Pins every install prefix and role suffix against the list it comes from, the name boundary (a bare `starts_with`/`ends_with` with no word boundary, case-sensitive), and that a call with no type argument, a `typeof` argument, two type arguments, the enclosing type's own type parameter, a non-invoked method group, a receiver-less call and a conditional-access call record nothing, while an invoked member call and a type argument naming the enclosing METHOD's own parameter each record one fact. |
| `src/extract/tests/bus_facts.rs` | Message-bus publish-site facts and the nested base type-argument fact. | Pins that a same-line bare name never supplies a publish site's message, that a call split across several source lines is still one site, and that a base whose arguments are all flat records no `baseTypeArgs` entry. |
| `src/export.rs` | Root of `devscout export`: the module header, the `mod` list, `ExportError` and `run`. | Holds no reading logic. The export is a VIEW over the index, never a promoted storage file, and it writes nothing. |
| `src/export/snapshot.rs` | The one consistent read: `Snapshot`, `load`/`load_bracketed`, and the bracket that turns a record, digest table, graph, fragment cache and compiler-fact artifact into one published generation or a refusal. | Every input comes from one generation: the record is read first, the query layer's own validation re-runs at the end, and every identity is re-read after it. The fragment cache is read through the strict reader, keyed to the RECORD's owner, because the exporting worktree is usually not the one that ran `map`. A refusal never emits a byte, so all inputs are in memory before anything is written. |
| `src/export/facts.rs` | One view struct per family, one line each, plus `Fact` and the family rank order. | A view is not a storage type: field declaration order IS the exported key order, `family` and `evidence` always lead, and nothing here resolves anything. Multiplicity is kept, so two identical facts are two lines. |
| `src/export/evidence.rs` | The closed evidence vocabulary and the pure mapping from a recorded `Edge` onto one word. | The mapping is one match with no wildcard over the variants, so a new `Edge` variant fails to compile until it is mapped. An unrecognized `ctor-di` resolution word reads `unresolved`: a consumer is never told an injection bound when it did not. |
| `src/export/header.rs` | The header line: `source`, `analysis` and `index` provenance kept apart, the release's static coverage table, and the freshness vocabulary. | Coverage is what THIS release extracts, not what the index happens to contain, so an extracted family with no facts reads `extracted`. Nothing machine-local and no export-time clock is written; the freshness object is the query layer's own renderer, spliced as a serialized part. |
| `src/export/stream.rs` | Canonical order, the payload digest, the trailer, and the two destinations (`stdout`, `--out` by temp-and-rename). | The order is total -- family rank, file, line, then the serialized line -- so two exports of one index are byte-identical. The digest covers the fact lines' exact bytes and nothing else. `--out` removes its temporary file on any failure, the rename included, and never creates the target directory. |
| `src/export/refusal.rs` | `ExportRefusal` and its one renderer: seven reasons, one stderr line each. | One refusal path for the whole verb, with a reason word a consumer branches on rather than prose, and `devscout map` named in every line because it is the one recovery. |
| `src/export/tests.rs` | Unit cases for the pieces that need no repository: the evidence mapping, the refusal vocabulary, the coverage table, and the stream's order and digest over a hand-built index. | The index is written through this crate's own writers, so the identities the record certifies are the ones on disk, and the mid-run replacement case uses the loader's seam rather than a real `map`. |
| `src/export/transport.rs` | The `transport-address` family: one line per `graph.json` transport fact, `family` and `evidence` first. | Reads only the certified graph; nothing here resolves anything. |
| `src/freshness.rs` | Query-time index-freshness state for a programmatic consumer: `FreshnessState` (`Fresh`/`Stale`/`Unknown`), `UnknownReason` with its five reasons, the one-line stderr renderers, and `index_freshness_state`. | `fresh` means "this worktree published this index and no indexed byte differs", never anything weaker. Stderr and `--json` come from ONE validation per invocation, so the two can never describe different moments. The stale line keeps the 0.9.0 wording byte for byte; a missing record and unavailable Git stay silent, every other verdict gets its own line. |
| `src/freshness/status.rs` | The crate's one `git status` reader (`-z`, `--no-renames`, `--untracked-files=all`, `--no-optional-locks`) and the `is_working_tree_dirty` bool over it. | Fail-closed to `GitUnavailable` on any spawn failure, non-zero exit, malformed record or non-UTF-8 path -- never an empty set, so no caller mistakes "could not check" for "confirmed clean". A bounded read reports `BudgetExceeded` rather than a truncated set. |
| `src/freshness/relevance.rs` | Which paths a change to can invalidate an index: indexed paths (scope, `SKIP_DIRS`, `SOURCE_EXT`) and configuration inputs (`.csproj`, `Directory.Build.props`, `Cargo.toml`), plus the pathspecs derived from a scope -- those same pathspecs are what the walk's ignore query reads. | Reuses `walk`'s and `project`'s own constants rather than a second copy of their rules, so the predicate and discovery cannot drift. `Cargo.toml` counts only in scope; the other two also count as direct children of a scope directory's ancestors. The shape stays name-based: `git status` without `--ignored` omits ignored untracked files, and a tracked file under an ignored pattern is both walked and reported, so the predicate and the walk describe the same file set. |
| `src/freshness/record.rs` | The on-disk freshness record: the published v2 shape, the in-progress marker, the content-digest table (`read_digests` and `read_digests_strict`, which names what stopped it), artifact identities and the owning worktree. | Keeps the 0.9.0 file name but not its shape, so neither build can certify the other's index and both recover with one `map`. Artifacts are named by filesystem identity, never by a token written into them: every artifact write is an atomic rename, so any later writer changes the identity without cooperating. Anything malformed, unrecognised or missing a required field certifies nothing. |
| `src/freshness/publish.rs` | The `map` side: the exclusive lock, the in-progress marker, and the record that certifies the run. | The marker precedes every artifact write and the record follows all of them, so a reader sees the previous generation intact, or a run in flight, and never a half-written pair. A run that fails keeps the marker: an uncertified index is honest. The lock file is never deleted, which would let a second process lock a different inode. |
| `src/freshness/validate.rs` | The query side: the begin/finish bracket over a record snapshot, candidate selection, the three caps, parallel hashing and the reason precedence. | `begin` snapshots the record before the caller reads its artifacts and `finish` re-reads it and re-stats both afterward, so a `map` landing mid-query reads `index-changed` instead of certifying a graph the answer did not come from. Candidates are compared by digest; size and mtime are budget estimates only. |
| `src/graph.rs` | Root of the persisted graph layer: the schema notes in its header, the `mod` list and the public re-exports. | Holds no code. Every public item keeps the path it had before the split; everything else is `pub(crate)` at most. |
| `src/graph/argument_types.rs` | `FragArgType`, the fragment shape of one written call argument's static type: a known descriptor, or a member whose declared type is read after every fragment is indexed. | Untagged, so a known type is a bare string; rides on `FragRef.argTypes`, whose null slots are unknown. An interpolated string argument arrives as its own reserved known descriptor, which the resolver reads as `string` at every parameter but a marked handler type. |
| `src/graph/artifact.rs` | The `Graph` artifact itself: `Stats`, `GraphName`, `GraphUnit`, `GRAPH_SCHEMA_VERSION`, and `read_graph`/`write_graph`. | `schema_version` is the first key written and the one `rebuild_graph` checks before reusing an artifact; `names` then `units` are appended last, each omitted entirely when empty rather than written as `[]`. `Stats.lane` is the last stats key and is written only when a lane record is due. |
| `src/graph/cache.rs` | The owner-stamped fragments cache: `FragmentCacheEntry`, its read/write pair, the key-only index (`read_fragments_index`/`write_fragments_index`), `index_is_stale`, and the strict reader (`read_fragments_messaging`/`FragMessaging`/`StrictCacheError`) the fact export uses. | The index is stale on any key mismatch or a differing file count. Both files carry the worktree that wrote them, and a reader whose owner differs gets an empty cache, so no fragment crosses worktrees. Both files also carry a required payload stamp: a pair without the current one reads as no cache, and the strict reader reports it unreadable, so reuse planning re-extracts a pair that an earlier build of the same generation wrote in another shape. The `mtime` field carries a content-derived key. The strict reader answers which of missing, unreadable, foreign-owned or changed it found instead of failing open, keys its owner check to the CALLER's owner rather than the reading worktree's, and deserializes only the messaging arrays, so a reader that republishes what it read can state why it could not. |
| `src/graph/call_shape_types.rs` | The fragment shapes of the lambda-applicability facts: `FragLambdaShape` on a ref and `FragDelegateSignature` on a delegate def. | Field order is significant and each rides on an omitted-when-absent key of `FragRef`/`FragDef`. |
| `src/graph/compiler_facts.rs` | Root of the compiler-facts artifact: the `mod` list, the public re-exports, the engine locator (`SCOUT_COMPILER_ENGINE`), `admit_and_publish`, `read_compiler_facts`, and `PublishError`. | Holds the one publication function: on `admit` returning `Ok`, atomic-writes the original candidate bytes unchanged via `super::paths::atomic_write_bytes`, never a re-serialized form. A refusal writes nothing. |
| `src/graph/compiler_facts/context_summary.rs` | The derived context-summary fold: `recompute`, pure, no I/O. | Canonicalizes each compilation's `identity` via `serde_json`'s default sorted-key `Value` serialization, so the same logical compilation list folds identically regardless of the producer's own JSON key order; a `null` fingerprint is a fixed sentinel that cannot collide with a real 40-hex-character one. Independently reimplemented, byte-for-byte, on the C# producer side. |
| `src/graph/compiler_facts/compilation_tally.rs` | The counted compilations of an admitted artifact: `CompilationTally` (`from_artifact`, `from_states`), its `K of M` summary and lane detail. Pure, no I/O. | Counts only `complete`, `partial` and `failed`; `unsupported`, `excluded` and a state-less entry are not counted. Reasons of the incomplete ones are listed in ascending byte order, an empty reason as `unspecified`. The coverage line and the `map` lane read this one definition, and admission never calls it. |
| `src/graph/compiler_facts/admit.rs` | The pure admission core: `admit`, `AdmittedFacts`. No I/O. | Fixed check order (malformed encoding, completion record, inventory coherence, occurrence self-consistency, then every identity field) so every refusal test asserts a predictable first-failing-check story. The context-fingerprint identity check recomputes the header summary from the embedded envelope rather than comparing two producer-written copies of one value. A declared-incomplete artifact is admitted, never refused. |
| `src/graph/compiler_facts/artifact.rs` | The candidate header type and its parser: `CandidateHeader`, `Profile`, `Coverage`, `IncompleteUnit`, `CompilationRef`, `parse_header`, and the three `COMPILER_FACTS_*` version constants. | The header is parsed from a `serde_json::Value` view; the bulk payload (diagnostics, symbols, every occurrence field beyond its own `compilation` reference, the rest of the embedded context-envelope body) is never deserialized into an exhaustive struct, so an additive producer field cannot be silently dropped by a re-serialization this module never performs. `CompilationRef` is the one shape admission reads out of a compilation entry: `identity` (opaque) and `fingerprint`. |
| `src/graph/compiler_facts/expectations.rs` | What one admission call checks a candidate against: `AdmissionExpectations`, `expectations_for`, and the `EXPECTED_*` compiled-in constants. | `expectations_for` calls the crate's one existing `git` shell-out (`manifest::git_head`) for the source-snapshot expectation; no new shell-out site is added anywhere in this crate. `EXPECTED_ENGINE_REVISION` names the engine revision that can emit occurrence facts. |
| `src/graph/compiler_facts/paths.rs` | Compiler-facts artifact path resolution and its superseded-generation sweep. | Reuses the already-public `graph_json_path` instead of the private `graph_dir`, so this file needs no `super::paths` access despite living beside `graph.json`. |
| `src/graph/compiler_facts/reasons.rs` | The closed refusal vocabulary: `RefusalReason` and its stable machine-readable token per variant. | `Display` writes the exact token named in each variant's doc comment; nothing here is reworded once shipped, since callers match on it. Three variants are occurrence-specific, broken-run family: a capability/payload disagreement, an occurrence naming an unknown compilation, and one naming an unsupported compilation. |
| `src/graph/compiler_facts/run.rs` | The local-acquisition entry point: `run_engine`, its default timeout/output-cap constants. | Captures stdout under a byte cap and a wall-clock timeout with no shell (`std::process::Command` invoked directly); no code path here writes to the artifact path -- only `admit_and_publish` does. |
| `src/graph/def.rs` | The persisted def row: `Def` and its `also_in` partial-class sidecar, `AlsoIn`. | A partial class's later declaring sites land in `also_in`, never as a second top-level def; `also_in` and `end_line` are omitted when empty/zero. Only file-local TS/JS defs write `local: true`. |
| `src/graph/edge.rs` | The persisted edge shapes: `Edge`'s kinds, `Candidate`, `HeuristicTier`, and the two edge-count summaries `EdgesByKind`/`HeuristicByTier`. | Edges are shape-heterogeneous by `kind`, tagged first; `heuristic` is appended last and omitted when false, and a `uses-member` edge's `tier`/`member` pair is present exactly when `heuristic` is. `Edge::Implements`/`Edge::Overrides` never carry a heuristic tier; `member` is absent on a type-level `implements` edge and present, naming the satisfied or overridden member, on a member-level one. `EdgesByKind` counts both kinds after `ctor-di`, before the optional TS keys. |
| `src/graph/fragment.rs` | Building a fragment from an extractor `Extraction` (`fragment_from_extraction`) and from a markup/resource file's line scan (`markup_fragment`, and `markup_fragment_text` for bytes the caller already read). | `fragment_from_extraction` is graph-only -- `Extraction.purpose` has no fragment counterpart. A markup file's ref carries an empty (not absent) enclosing namespace, the same shape a file-scope C# ref carries. Each `extract::PublishRecord` maps to one `FragPublish` field-by-field, including its `arg_count`/`enclosing_call`; a markup fragment's own `publishes` stays `Vec::new()`, so it needs no `FragPublish` construction at all. |
| `src/graph/fragment_types.rs` | Every serde struct for a raw per-file fragment: `FragDef`, `FragRef`, `FragUsing`, `FragName`, `Fragment`, and `AnyFragment`, the untagged union of a C#, a TypeScript, a Rust and an `.html` template fragment (`HtmlFragment`, tagged `html: 1`); `FragPublish` now also carries `argCount`/`enclosingCall: Option<FragEnclosingCall>`; `FragDef` also carries `arrayMessageBases: Vec<String>`, appended LAST after `propertyMessageArgs`. | `AnyFragment`'s three arms are mutually exclusive by field shape (a TS fragment carries `ts`, a C# one `usings`, a Rust one `rs`, and none carries another's required key), so a fragment cache file can hold all three without a discriminant key; every added field on `FragDef`/`FragRef` is appended last and omitted when absent. `FragDef.override_methods` and `Fragment.registrations` are both purely additive and omitted when empty, so a fragment written before either existed still deserializes. `FragPublish.argCount` is omitted when zero, `enclosingCall` when absent -- both stay within the fragment generation already current. `FragDef.arrayMessageBases` is omitted when empty; it names the subset of `bases` whose message-position argument at `baseGenericArgs`' own position 0 is an array type, the one bit `baseGenericArgs` itself cannot carry. `FragDef.methodTypeParamSites`/`delegateSignature` and `FragRef.lambdaArgShape` joined with the v27 cache generation and are omitted when empty or absent. |
| `src/graph/imports.rs` | The imported-edges artifact: `ImportedEdges`, `ImportRecord`, `EdgeEnd`, `Provenance`, `parse_imported_edges`, `read_imported_edges`/`write_imported_edges`. | Validation is total before any write: a format/schema-version mismatch, a record missing `kind`/`from`/`to`, or a kind outside the fixed set names the offending value and writes nothing, so a refusal never touches whatever artifact already existed. A re-import always overwrites the file wholesale; there is no merge. |
| `src/graph/lane.rs` | The `stats.lane` record: `LaneStats`, its `applied`/`partial`/`not_applied` constructors and `with_compilations`, `LaneCompilations`, `LaneReason`, and `line`, the text `map` prints. | The persisted fields are strings so a graph carrying a state or reason this build does not know still reads; this build writes only what its constructors produce. `reason`, `detail` and `compilations` are omitted when absent; `compilations` is appended last. |
| `src/graph/messaging_types.rs` | The messaging fact records of a C# fragment: `FragRegistration`, `FragPublish`, `FragEnclosingCall` and `FragHandlerRegistration`, re-exported through `fragment_types`. | Moved out of `fragment_types.rs` with no change to a field, a name or a serialized byte, so the parent stays under its size ceiling. |
| `src/graph/ordered.rs` | `OrderedMap`, the insertion-order-preserving map backing every persisted `{rel: ...}` object, and `Percent1`, the whole-or-one-decimal percentage the audit stats print. | A `HashMap` has no serialization order and serde_json's default map sorts by key; neither matches graph.json's file-walk order, so both need the hand-rolled `Serialize`/`Deserialize` here. `Percent1` prints without a decimal point when the tenths value is a whole ten. |
| `src/graph/paths.rs` | Graph-directory and artifact-path resolution (including the project-units and Rust crate-model sidecars), the superseded fragment-cache sweep, and the atomic (tmp-then-rename) write helpers every persisted file goes through. | The def/ref graph and its caches live under the git common directory when there is one, `<root>/.scout/graph/` otherwise; a write's tmp file and its target share a directory so the rename is same-filesystem and atomic. The fragment cache filename carries the cache-generation number; bump it whenever a cached payload or its stamp changes, and delete every superseded generation. |
| `src/graph/rebuild.rs` | `devscout map`'s cache-then-resolve-then-write cycle: `GraphFile`, `RebuildOutcome`, `rebuild_graph` and `rebuild_graph_with_crates`, which also takes the Cargo package model and mirrors it into its sidecar. | The unchanged fast path is gated on both `changed == false` and the on-disk artifact already carrying the current schema version; a cache hit reuses a fragment by matching key even when the caller's `fresh_fragments` is empty, and only from a cache this worktree owns. Resolution uses only the lane decision's layer, and its record is written to `stats.lane`. Rust fragments reach neither the C# nor TypeScript resolver; `rsgraph::append_rust` and `append_rust_edges` project them after resolution. |
| `src/graph/tests.rs` | Shared fixture builders for the graph tests and the list of test modules. | `temp_dir` is the only builder shared across more than one topic; everything else lives local to the topic file that uses it. |
| `src/graph/tests/artifact.rs` | `Stats`'s appended tail counters and `Graph`'s `names`/`units` append-last shape. | Pins that `heuristic_by_tier` stays last even when a TS `stats.ts` block is present, and that an empty `units` list writes no `units` key at all. |
| `src/graph/tests/cache.rs` | The fragments-cache generation bump: superseded-file cleanup and the current cache/index path pair. | Pins that every superseded generation is deleted on a rebuild and that the current pair is named for the fragment schema version. |
| `src/graph/tests/cache/qualified_receiver.rs` | The current generation's rename as the invalidation of a cached receiver fact that lacks its written qualified type. | Pins that a prior-generation entry is re-parsed and binds the declared type like a cold run, while the same entry at the current generation is reused and binds the same-named decoy. |
| `src/graph/tests/cache/qualified_path_tokens.rs` | The same rename as the invalidation of a cached written path that still holds the raw source text a name was written with. | Pins that a prior-generation entry is re-parsed and binds the written path like a cold run, while the same entry at the current generation is reused and degrades the precise edge to a guess at both same-named types. |
| `src/graph/tests/compiler_facts.rs` | `admit`'s fixed check order and refusal tokens, the declared-incomplete-coverage admit path, `admit_and_publish`'s atomic-write/refusal-leaves-artifact-untouched behaviour, and occurrence-payload admission (the three new refusal classes, lossless round-trip, and freshness pairing). | Pins that a contract-version mismatch is reported before an engine-revision mismatch even when both are wrong, that a refused republish leaves a previously admitted artifact byte-identical with no stray tmp file, and that admission succeeds against a real multi-compilation envelope with no top-level `fingerprint` field -- the shape the pre-delta self-referential check could never have admitted. |
| `src/graph/tests/def.rs` | `Def`'s `also_in`/`end_line` omission and the test-coverage `testMethods` slot. | Pins that `testMethods` sits between `methods` and `also_in`, and that either is omitted entirely when the def carries none. |
| `src/graph/tests/edge.rs` | `Edge`'s per-kind shape and `EdgesByKind`'s fixed field order. | Pins that `heuristic` is omitted on a precise edge and that `edges_by_kind` never serializes alphabetically. |
| `src/graph/tests/fragment_types.rs` | `FragDef`/`FragRef`'s appended-field order and omission-when-empty, and the `AnyFragment` untagged round trip. | Pins each field's exact byte position and that a fragment cached before a field existed still deserializes with it defaulted. |
| `src/graph/tests/fragment_types/argument_types.rs` | `FragRef.argTypes` serialization. | Pins that the slots keep source order, an unknown slot is `null`, a member slot omits absent `receiverArgs`, and a ref without the fact omits the key. |
| `src/graph/tests/fragment_types/html_fragment.rs` | The template fragment's round trip through `AnyFragment`. | Pins that no other fragment shape reads as a template and that an empty scan serializes as `{"html":1}`. |
| `src/graph/tests/imports.rs` | `parse_imported_edges`'s acceptance and refusal shapes, plus the artifact's write/read round trip. | Pins that a format or schema-version mismatch, a record missing `kind`/`from`/`to`, and a kind outside the closed set each name the offending value; pins that a missing artifact reads as `None`, never an error. |
| `src/graph/tests/lane.rs` | `LaneStats` serialization, reason tokens and the text line. | Pins the exact bytes of an applied and a not-applied record, that an unknown state reads back, and each line form, including a detail that itself contains `:`. |
| `src/graph/tests/ordered.rs` | `OrderedMap` and `Percent1` serialization. | Pins insertion order (not sorted) through a JSON round trip and that `Percent1` prints a whole value without a decimal point. |
| `src/graph/tests/paths.rs` | The atomic write helper under a simulated crash. | Pins that a stray leftover tmp file never corrupts the target and that no tmp file survives a clean write. |
| `src/graph/tests/rebuild.rs` | `rebuild_graph`'s unchanged-path skip, its old-schema and truncated-artifact rebuild triggers, and per-file cache reuse. | Pins that `changed == false` alone is not enough to skip a rebuild when the on-disk schema is stale. |
| `src/hookio.rs` | `hook read` and `hook bash`: rewrite a tool result on stdin when the same content was already delivered in the session. | Fails open: malformed, non-UTF-8 or incomplete input yields empty output and never an error. Only these hooks write the freshness stores; plain CLI use never touches them. The read offer and its gate live in `hookio/offer.rs`. |
| `src/hookio/offer.rs` | The read hook's one-line offer, the symbol it names, and `offer_permitted`, the gate that admits it. | An offer is made only when the reading worktree owns a certified record at this HEAD, the graph identity still matches, and the file's bytes on disk hash to its recorded digest. No `git status` per read: the HEAD is the `rev-parse` the read path already ran. |
| `src/initcmd.rs` | `init`: registers the root, creates the artifact directory, installs the agent hooks into the settings file, runs a first map. | Merges hook entries into an existing settings file after backing it up; a hook-install failure never fails `init`. `--no-hooks` skips the install and nothing else. Adds `.scout` to the local git exclude file. The language census lives in `initcmd/census.rs`. |
| `src/initcmd/census.rs` | The `init` language census: counts the walked files by extension and groups them by support tier (fully supported, indexed and graphed, indexed and graphed with path-resolved edges, not indexed). | Counts exactly the file set `walk::list_source_files` enumerates, never a second walker. Its own sibling file because `initcmd.rs` sits at its size-ratchet ceiling. |
| `src/layout.rs` | Every artifact file name and directory segment devscout writes: the `scout`, `graph`, `log`, `.claude` and `.scout` directories, and the manifest, registry, record, digest, lock, graph, fragment-pair, sidecar, imported-edges, telemetry and debug file names. | Symbols only: the module imports nothing, so a path builder or a message in any layer can name an artifact without depending on the layer that owns it. A listed name appears as a literal nowhere else in `src/`, so a rename reaches every path builder and every message that names it. Superseded names stay with the code that retires them, in `graph/paths.rs`. |
| `src/manifest.rs` | `manifest.json` read/write, `find` search, `scope_for`, and the crate's one `git rev-parse HEAD` shell-out. | `find` ANDs whitespace-split lowercase tokens over `path + purpose` and falls back to any-token matches ranked by hit count. The manifest is schema-agnostic: unknown top-level fields round-trip unchanged. Freshness state lives in `freshness/`, not here. |
| `src/mapcmd.rs` | `map`: assembles walk, extract, resolve, graph and manifest into one incremental run, under the map lock, around the freshness record. | Every file is read exactly once and every reuse key is content-derived, so the digest the record certifies and the facts that were extracted describe the same bytes. A graph is reused only when the worktree published it and the artifact on disk is still the one that record named. A `.csproj` edit is detected through the project-units sidecar and a `Cargo.toml` edit through the Rust crate-model sidecar, not through the fragment index. The per-language reparse dispatch lives in `mapcmd/dispatch.rs`. |
| `src/mapcmd/dispatch.rs` | The per-language half of `map` (C#, markup, TS/JS, Rust): which files are graph sources, the lossy source read, the best-source rule, the per-file reparse, the fold into purposes and fragments, and the `ast-none` fallback. | Its own sibling file because `mapcmd.rs` sits at its size-ratchet ceiling; a new language is added here, and `map_repo` keeps only the language-free orchestration. |
| `src/mapcmd/options.rs` | `MapOptions`: `hash_reuse`, `no_semantic` and `from_env`. | Its own sibling file, not inline in `mapcmd.rs`, because that file sits at its exact size-ratchet ceiling with zero headroom. `no_semantic` is a CLI flag (`map --no-semantic`), never an environment switch; a `--no-semantic` run always rebuilds and records its lane. |
| `src/markup.rs` | XAML, RESW and RESX extraction: `x:Class` definitions, element references and resource keys. | A markup `x:Class` def carries the same id as its code-behind `partial class`, so the two merge into one symbol with two declaring sites. |
| `src/ngtemplate.rs` | The Angular template scan facade: `scan_template`, `TemplateScan`, `TemplateRead`, `TemplateGap` and the closed `GapReason` vocabulary. | Pure over the text and the line it starts on. Locals are resolved here, so a scan holds only reads no local hides; whether a read names a member is the graph join's question. |
| `src/ngtemplate/markup.rs` | The template markup pass: tags, attributes, text, comments, raw-text and void elements, `{{ }}` and control flow lines, as events with 1-based lines. | One linear pass with no backtracking. An attribute value that never closes ends the scan with one gap. |
| `src/ngtemplate/expr.rs` | The template expression lexer and parser: the root identifiers an expression reads, or a refusal. | Builds no tree. One depth counter on `pipe` and `unary` bounds the recursion, so deep input is a gap, not a stack overflow. |
| `src/ngtemplate/micro.rs` | Structural directive microsyntax: the locals a directive declares and the reads of its expressions. | A `let x = key` and a `key as v` read nothing; only expressions read. |
| `src/ngtemplate/scope.rs` | The local scope walk over the markup events: `#ref` for the whole template, structural and `let-x` locals for the host element and its children, `$event` for its own binding. | A stray end tag pops nothing; an end tag pops exactly the frames its element pushed. |
| `src/ngtemplate/tests.rs` | Each binding form, the expression rules, each local kind, and the gap cases. | Pins that a crafted deep expression gives one gap and that an assignment chain costs no depth. |
| `src/offsets.rs` | UTF-8 byte offset to UTF-16 code-unit offset table. | Off every code path today; kept unit-tested for a caller that must cross a UTF-8 buffer back into the UTF-16 offset convention `parse` uses. |
| `src/parse.rs` | Tree-sitter parsing for C#, the TypeScript-family grammars and Rust (`is_rust`, `parse_rust`); span collection; the `parse` and `spans` plumbing verbs. | Source is fed to the grammar as UTF-16 code units, so every node offset the crate sees is already UTF-16. Grammar crates are pinned to exact versions in `Cargo.toml`, and `docs/reference/csharp-constructs.md` names the pin its tables were checked against. The plumbing verbs stay C#-only. |
| `src/preproc.rs` | C# conditional-compilation pre-pass: blanks inactive `#if` / `#elif` / `#else` arms. | Evaluates with no predefined symbols. Inactive bytes become spaces and every line break stays where it was, so line and column of surviving code do not move. |
| `src/project.rs` | Hand-scanned `.csproj` and `Directory.Build.props` model: discovery, project units, reference closure, test-project detection. `discover_with_digests` records the SHA256 of every configuration file it parsed. | Discovery never fails because nothing was found; zero projects is `Ok(None)`. The sidecar comparison is a raw byte compare against `project-units.json`, so an edit rebuilds even though no walked file's key moves. |
| `src/project/scan.rs` | The filesystem half of `.csproj` discovery: `ancestors_up_to`, the non-recursive ancestor scan, the `Directory.Build.props` name test, and the lossy reader that records what it read. | The digest is of the bytes the caller parses, not of a second read of the file. A missing or unreadable ancestor directory is skipped, never an error. |
| `src/query.rs` | Root of the query layer: the verb/substrate map in its header, the `mod` list and the public re-exports. | Holds no code. Every public item keeps the path it had before the split; everything else is `pub(super)` at most. |
| `src/query/bus.rs` | `bus-hop` query-layer plumbing: `record_bus_edge` (the index-build-time adjacency push, the one-kind mirror of `dispatch.rs`'s own `record_dispatch_edge`), `BusDirection`/`BusHopRow` (the provenance row `refs`/`read` render), and `symbol_bus_rows` (the row builder for one resolved symbol, inbound then outbound). | `impact`'s own reach needs no code here: `bus_hop` joining `inbound_walk_kinds` (`dispatch.rs`) and `why.rs` naming `Why::BusHop` are enough. `evidence` is a pass-through of `resolve/bus.rs`'s own closed word list, never respelled. |
| `src/query/dispatch.rs` | `implements`/`overrides` plumbing shared by `index.rs` and `refs.rs`: `record_dispatch_edge` (the index-build-time adjacency push), `implemented_interfaces` (the interface hop's own `inherits`-plus-`implements` widening), the bare-member fallback's exact-match half (`member_dispatch_edges`, `dispatch_table`, `split_dispatch_rows`), the outbound kind indices and ranking helpers (`RankedOutbound`, `outbound_foreign`, `push_ranked`), `inbound_walk_kinds` (the seam `impact` crosses every inbound kind through, `bus-hop` and the four TS kinds included), and `note_dispatch_files` (the manifest-flag/hub-count bookkeeping `implements`/`overrides`/`bus-hop` all share). | Split out of `index.rs`/`refs.rs` to keep both under this crate's per-file line budget, not a verb of its own. `member_dispatch_edges` verifies by an exact match on the edge's own `member` field, never `line_has_token`. |
| `src/query/coverage.rs` | The tests-reaching-a-symbol verb (named to avoid colliding with this module's own `tests`): `build_tests_model`, `tests_model_for_id` (the already-resolved half `query/pick.rs` calls), `TestsModel` (now also `bus: Table<BusHopRow>` and `name_hint: Vec<NameHintRow>`), `TestVia`, `test_bus_rows`. | A file's own attributed test def outranks the project-model vouch, which outranks a TS/TSX/JS test-file name (`test_path.rs`); a symbol nothing tests answers "none", never a name-convention guess. A member seed (via `member.rs`), tried only once the type ladder has missed, answers as its unique declaring type; two or more declaring types answers `MemberAmbiguous`. `implements`/`overrides` join the inbound kinds `tests` folds through, gated by `--no-dispatch` at index build time like every other consumer. `test_bus_rows` reuses `query::bus::symbol_bus_rows` (uncapped, then capped after filtering) rather than a second bus-hop lookup, keeping only `In`-direction rows whose publish site is itself a test file (the SAME `test_defs_by_file`/`is_test_file` rule `collect_test_rows` applies); a separate field by construction, so a possible route can never inflate `test_file_count`/`ref_count` or their heuristic twins. |
| `src/query/find.rs` | The `find` verb: `find_names`, `first_decl_line_by_file`, `file_inbound_counts`, `name_tier`, `source_line`. | `file_inbound_counts` excludes heuristic edges, `imports`, `ctor-di`, `implements`, `overrides`, `ambiguous`, and a file's references to itself. |
| `src/query/impact.rs` | The `impact` verb: `impact_walk`'s reverse k-hop traversal PLUS its own forward `bus-hop` half (a file whose declared def contains a publish site also affects every handler that site's message reaches, reading `index.outbound_by_file` the reverse walk never needed), the interface hop and its fan-in brake, the hub brake, `build_impact_model`, `impact_model_for_seed` (the already-resolved half `query/pick.rs` calls). Seed resolution lives in `impact_seed.rs`. `KindLines`/`Hit`/`note_line`/`BusOrigin` live in `impact_why.rs`; this file only builds and consumes them. | Records a file once, at its minimum hop. A heuristic edge reaches a file and stops there -- it never widens the walk further. A member seed (via `member.rs`) answers as its unique declaring type, tried only once the file-path check and the type ladder have both missed; two or more declaring types answers `MemberAmbiguous`. `implements`/`overrides` join the plain inbound walk directly, and `implemented_interfaces` (in `dispatch.rs`) folds `implements` in beside `inherits` for the interface hop, so a registration-driven implementation widens exactly the way a base-list one already did. `--no-bus` needs no special case for the forward half: `outbound_by_file[file].bus_hop` is already empty whenever the suppressor was on at index-build time, the same mechanism the reverse half already relied on. |
| `src/query/impact_seed.rs` | `impact`'s seed resolution: `looks_like_file_path`, `SeedResolution`, `resolve_impact_seed`. Split out of `impact.rs` to keep that file under its size ceiling; a pure move, re-exported through `query.rs` at the same paths. | The non-local symbol ladder runs first, then the member fallback, then local symbols; a member seed answers as its unique declaring type. A dotted member seed looks like a file path, so it is tried on a file-path miss instead. `next.rs` calls `resolve_impact_seed` to check an `impact` suggestion resolves the same way the verb would. |
| `src/query/impact_why.rs` | `KindLines`, `Hit` (the walk's own per-file bookkeeping) and `BusOrigin` (one `bus-hop` edge's own identity -- `(file, line, message, to, to_file)`, ordered so "the lowest by publisher file/line/message/handler" is one comparison) all live here now, plus an `ImpactRow`'s `why`: `Hit`'s `note_direct`/`note_direct_amb`/`note_iface`/`note_heuristic`/`note_bus` methods, each pairing a `KindLines` slot with the edge that set it (and, for `note_bus`, the edge's own `BusOrigin`), `note_kind_edge`'s bus/direct dispatch, `note_taint`'s possible-route bookkeeping (now carrying the inherited `BusOrigin` forward instead of a bare flag), `primary_why`/`build_visited_entry`, which fold that pairing into one value per file, and `from_lines_of`. Split out of `impact.rs` to keep that file under its size ceiling. | The strongest evidence wins when more than one edge kind reached a file: an explicit `ctor-di` site over a plain resolved reference, over the broader interface hop, over a guess, over a `bus-hop`, weakest of all. `note_taint` clears `bus_only` the moment any edge to a file traces back to the seed without crossing a bus hop; a file that inherits taint from every one of its own reaching defs stays possible-route-only across hops, carrying the ORIGINATING hop's own identity forward so a downstream row can disclose the hop its marker came from, not just that one exists. A file reached by both a bus-only path and a real path keeps its stronger `why` and drops the origin identity entirely, gated at `build_visited_entry`. |
| `src/query/imported.rs` | Imported-edge reach for `impact`: `ImportedRow`, `ImportedSection`, `build_imported_section`. Split out of `impact.rs`, which has no room left under its own size ceiling. | A lookup over the native walk's already-finished `ImpactModel`, never a widening of `impact_walk` itself: an imported edge reaches a file and stops there, the same rule a heuristic edge already follows, so native rows are unaffected by an import's presence. Capped and counted apart from the native rows. |
| `src/query/index.rs` | `GraphIndex` and its two-phase load (`load_graph_index`, `load_graph_index_with`), `IndexOptions`, plus the def-site helpers `def_files`/`def_sites`. | Heuristic edges are indexed in their own adjacency, never mixed into the precise one; `--no-guess` admits only the extension tier. A corrupt manifest.json fails open. `implements`/`overrides` are recorded against the IMPLEMENTATION or `override` type's own declaring file and line -- never a registration call site -- so they join `inbound`/`outbound_by_file` through the same file attribution `inherits` already uses; `IndexOptions.include_dispatch` (`--no-dispatch`) admits neither kind into either adjacency when `false`, and neither kind ever carries a heuristic tier. The four TS kinds join through `ts_edges::record_ts_edge`, by kind rather than language, ungated by any of the three flags. `bus-hop` joins the same way, keyed by its own handler (`to`) inbound and publish-site file outbound, gated by `IndexOptions.include_bus` (`--no-bus`) on the same terms. |
| `src/query/infra.rs` | The hub-file name-pattern classification `impact` widens against: `is_infra_file`, `DEFAULT_HUB_MAX_INDEGREE`. | A classification of what a file is FOR, always on; the in-degree half of the hub brake is a separate, disable-able threshold. |
| `src/query/json.rs` | `--json` rendering of every query model except `tests` and the `--pick all` wrapper (`json/sections.rs`) (the hand-rolled `J` value tree and its encoder, `refs_model_to_json`, `read_model_to_json`, `member_refs_to_json`, `impact_model_to_json`), moved out of `cli.rs` to keep that file under its size ratchet; `j_table`/`push_heuristic`/`j_bus_row` are `pub(super)` so `json/tests_model.rs` and `json/next.rs` reuse them rather than respelling the same shapes. | `pub(crate)`, called from the matching `src/cli/*.rs` file as `query::json::...`; `audit.rs` reuses the same `J` tree. Key order is part of the contract -- see each builder's own doc comment. Every builder leads with a top-level `schema_version` and appends a top-level `outcome` (`query::Outcome`) last; every hit row carries a `why` (`query::Why`), derived from the row's own kind and, for `uses-member`, its tier. The `implements`/`overrides` keys are present only when that table's own total is non-zero, the same present-only-when-applicable rule `memberRefs` already follows; `bus-hop` (after `memberRefs`, before `outcome`) follows it too. An inbound/outbound row's `occurrenceIndex` is appended after `why`, the row's true last key, present only when `occurrence_index` is `Some`. |
| `src/query/json/tests_model.rs` | `tests_model_to_json`, split out of `json.rs` to keep that file under its flat line limit. | `pub(crate)`, re-exported from `json.rs` so every existing caller keeps reading `query::json::tests_model_to_json` unchanged. Reuses `json.rs`'s own `J`/`j_table`/`push_heuristic`/`j_bus_row` rather than a second copy of any of them. The `bus-hop` key (a test file publishing to this handler over a bus hop) is appended after the heuristic pair, before `outcome`, present only when the model's own `bus.total` is non-zero. |
| `src/query/json/next.rs` | The `next` key: `j_next` and the `*_to_json_with_next` builders for `refs`, `read`, `tests`, `impact` (plain and with imports) and the bare-member shape. Split out of `json.rs` to keep that file under its flat line limit. | `next` is inserted immediately before `outcome`, so `freshness` stays last and no existing key moves. It is an array of argv arrays without the binary name. Each member object of a bare-member answer carries its own `next`, and the wrapper carries their ordered union. Ambiguous and not-found shapes never call these builders, so they carry no `next`. `j_next` is also what `json/sections.rs` states the sections' own union with. |
| `src/query/json/sections.rs` | `sections_to_json`: the `--pick all` wrapper for `refs`/`read`/`impact`/`tests`. Split into its own module so `json.rs` needs one `mod` line and no more, and so `J` gains no variant for a pre-rendered object. | Key order is `schema_version`, `status` (`"sections"`), `query`, `sections`, `next`, `outcome`, and `freshness` is spliced once at the end by `cli/answer.rs`'s `finish_query` -- the bare-member wrapper's order, so a consumer learns one shape. Each section is `pick`, `candidate` (the refusal's own object at that place) and `answer`, and `answer` is the candidate's already-rendered `--json` object spliced in verbatim rather than rebuilt, so a section's bytes and a plain `--pick <n> --json` answer cannot drift. The list key is `sections`, never `candidates`, so no consumer reads an answer as the refusal's own list. |
| `src/query/json/ts_tables.rs` | `ts_table_fields`: the `call`/`jsx-use`/`dispatch`/`import` keys of a `refs`/`read` `inbound` or `outbound` object. | Split out of `json.rs` to keep that file under its flat line limit. A key joins after every older key and only when its table's total is non-zero, so an answer with no TS edge keeps its bytes. |
| `src/query/name_hint.rs` | The `tests` file-name hint: `stem_matches` (the name rule), `NameHintRow`, `NAME_HINT_REFERENCE_CAP` and `name_hint_rows`. | Reads only the built index and walks no edge outside the files it names. A candidate must match the symbol's own name under the affix rule, be vouched as a test file by the verb's own rule, and be none of the symbol's declaring files, so a file name never becomes evidence of a reference. Its references come from the precise outbound kinds `tests` already reads, capped at five per file with the uncapped distinct count reported beside them, and never from a heuristic edge, a `bus-hop` or the namespace `imports` list. |
| `src/query/member.rs` | Member-seed resolution shared by `refs`/`read`/`impact`/`tests`: `split_member_seed`, `qualified_member_owners`, `exact_member_owner`, `resolve_member_seed`, `MemberSeedResolution`, `MemberCandidate`, `qualified_seed` (the string a typed `Owner.Member` seed is spelled as). | Reached only once `resolve_symbol`'s type ladder has already missed. A qualifier (`Type.Member`/`Namespace.Type.Member`) admits an owner by exact id or dotted suffix, the same tail rule the type ladder's own dotted step uses. `exact_member_owner` is the same scan without the suffix rule, for a caller that already holds one chosen candidate: admitting a suffix there would name both owners of a suffix pair again. |
| `src/query/next.rs` | The follow-up suggestions every resolved or zero-hit answer ends with: `NextArgv`, `refs_next` (also used by `read`), `tests_next`, `impact_next`, `find_next`, `merge_suggestions`. | Reads only the already-built model, runs no new query, and spells symbols as def ids and files repo-relative. Every candidate is re-resolved through the non-local, member and local ladders its target verb uses (`resolve_symbol`, the member fallback, `resolve_impact_seed`); one that does not resolve to exactly one target, or that a verb would read as a flag, is dropped, so every emitted argv runs. |
| `src/query/occurrence.rs` | Query-time occurrence identity: `tag_inbound_rows`/`tag_outbound_rows` assign `InboundRow`/`OutboundRow.occurrence_index`, and the generic `assign_occurrence_indices` core they share. Called from `refs.rs` once per table, immediately after that table's existing ranking/capping step. | Computes no new fact and touches no persisted graph state -- grouping is by every row field except `occurrence_index` itself, in the table's existing stable emission order; a row with no colliding sibling in its own table gets `None`, and a row dropped by an existing cap is never grouped or assigned one. Scoped to one table of one answer against one graph snapshot. |
| `src/query/outcome.rs` | The closed `--json` outcome vocabulary every `refs`/`read`/`impact`/`tests` answer carries: `Outcome` (`hit`/`zero-hit`/`ambiguous`/`fallback-advised`), and `SCHEMA_VERSION`, the answer shape's own version number. | The only place any of the four outcome words is spelled; no file under `src/cli/` ever writes one as a literal string. `SCHEMA_VERSION` bumps only when an existing key is renamed, removed, or given a different meaning -- never for an added key. `sections` is a fourth `status`, not a fifth `Outcome`: the wrapper still answers `hit` or `zero-hit` out of its sections. |
| `src/query/pick.rs` | One candidate of an ambiguous seed, answered on its own: `PickedSeed`, `pick_refs`/`pick_read`/`pick_tests`/`pick_impact`, and the refusal's own row text and candidate order (`type_candidate_row`, `member_candidate_row`, `type_candidates_in_refusal_order`, `candidate_def`). | A `--pick <n>` answer and one `--pick all` section both come through here, so the two cannot disagree. A member candidate resolves by its EXACT owner id through `member.rs`'s `exact_member_owner`, never the seed ladder: the ladder's dotted-suffix rule would name both owners of a suffix pair again, and a chosen candidate has nothing left to narrow. A type candidate starts from its def id, because `impact_seed.rs`'s `looks_like_file_path` reads a dotted type id as a file path. The refusal's own candidate order comes from here, so the row a caller reads at line `n` is the candidate `--pick n` answers. |
| `src/query/rank.rs` | Personalized PageRank over a file-level subgraph: `personalized_page_rank`, `DEFAULT_DAMPING`, `DEFAULT_ITERATIONS`. | Ranking only, never removes a node; callers must build the `nodes` array in a fixed order for a bit-stable result. |
| `src/query/read.rs` | The `read` verb: `build_read_model`, `read_result_from_refs` (the already-resolved half `query/pick.rs` calls), the declaration span (`ReadSpan`). | Reuses `refs` resolution and inbound machinery wholesale with `out` off; a span past EOF or before a shrunk file's start degrades to no span, never invented text. |
| `src/query/refs.rs` | The `refs` verb: `build_refs_model`, the member-seed fallback (bare, `Type.Member`, `Namespace.Type.Member`, via `member.rs`) through `member_refs_from_owners`, ranked inbound/outbound capping. The enum member-ref rollup lives in `refs_member.rs`. | The member path follows the non-local symbol ladder, and the local ladder follows a member miss. A member hit is verified by a whole-token match on its referencing line; more than one declaring type surviving that verification answers `MemberAmbiguous`, never the bare type list `Ambiguous` renders. `implements`/`overrides` and the four TS tables share the one inbound cap, but never share a table header: `refs`/`read` print each only when its own total is non-zero, unlike the first three. `RefsModel.bus` (`bus.rs`'s `symbol_bus_rows`) is a separate, always-built table combining inbound and outbound `bus-hop` rows for the resolved symbol. |
| `src/query/refs_member.rs` | The enum member-ref rollup: `MemberRefEntry`, `MemberRefs`, `enum_member_refs`. Split out of `refs.rs` to keep that file under its size ceiling; a pure move, both types re-exported through `query.rs` at the same paths. | Member order is declaration (def-table) order, never by count, so two runs print the same line; at most `MEMBER_NAME_CAP` member rows are kept and the rest are counted as dropped. |
| `src/query/refs_outbound.rs` | `build_outbound_tables`: the `refs --out` tables of one resolved symbol, every outbound kind ranked under one shared cap. | Split out of `refs.rs` to keep that file under its flat line limit. The ranking is the inbound one mirrored: resolved before heuristic, own project before foreign, then file and line. |
| `src/query/refs_tables.rs` | The row/table shaping `refs`, `read` and `impact` all reuse: `Table`, `InboundRow`/`OutboundRow`/`ImportRow`/`AmbiguousRow`, the row caps (`DEFAULT_CAP`, `INBOUND_CAP`, `OUTBOUND_CAP`, `SOURCE_MAX`). | Location sorts use plain `str::cmp`; `row_tier` never reports a stronger tier than the edges behind a row actually prove. `InboundTables`/`OutboundTables` carry `implements`/`overrides` alongside `inherits`/`uses-type`/`uses-member`; `edge_loc` covers both new kinds the same way it covers the first three, and now covers `bus-hop` too, which `bus.rs`'s own row builder sorts by. `InboundRow`/`OutboundRow` carry `occurrence_index`, set only by `occurrence.rs`, `None` everywhere else. |
| `src/query/seq.rs` | Insertion-order-preserving scratch structures: `SeqSet`, `SeqMap`. | First insertion wins a slot; neither type is a persisted artifact shape. |
| `src/query/symbol.rs` | Symbol resolution: `resolve_symbol`, `resolve_local_symbol` and `Resolution`. | An exact id selects any def. Bare names first run the earlier ladder over non-local defs, preserving prior answers; ambiguity at a step includes matching locals. Only after member lookup misses does the local ladder run. |
| `src/query/symbol_refs.rs` | `SymbolRefs` and `symbol_refs`: one resolved symbol's immediate inbound and outbound edge lists, precise, heuristic and ambiguous, read from a built `GraphIndex`. | Split out of `index.rs` to keep that file under its flat line limit. An enum's inbound `uses-member` unions every member's edges; outbound unions every declaring file of a partial type. |
| `src/query/test_path.rs` | The one test-file definition `tests`, `impact` and the `tests` bus filter share: `test_via` (attribute, then project, then name) and `is_test_path`, the TypeScript/JavaScript naming rule. | The case-sensitive name rule covers `.ts`, `.tsx`, `.mts`, `.cts`, `.js`, `.jsx`, `.mjs` and `.cjs` -- `*.spec.*`/`*.test.*` in one of them, or a directory segment exactly `__tests__`; a C# file cannot match, so no C# answer moves. |
| `src/query/ts_edges.rs` | `TsKinds` (one def's or file's `call`/`jsx-use`/`dispatch`/`import` edge lists) and `record_ts_edge`, the index-build-time admission of those four kinds. | Admission is by edge kind, never by the file's language. An `import` edge is keyed inbound under each of its `bound_defs` and is not admitted when it binds none; every admitted edge counts toward the hub in-degree. |
| `src/query/tests.rs` | Shared fixture builders for the query tests and the list of test modules. | Fixtures build `graph::Graph` directly, no JSON round-trip; a temp `.git`-shaped dir backs the one manifest fixture that needs disk. |
| `src/query/tests/bus.rs` | `impact`'s possible-route taint: a file reached only through a `bus-hop` edge, at any depth, versus one that also has an independent non-bus path. | Pins that the taint threads through a multi-hop walk and that an independent path clears it, the same way `ctor-di` already outranks a plain reference. |
| `src/query/tests/bus_hub.rs` | The hub in-degree brake reused for `bus-hop` referrers, at its default boundary, and the `--no-bus` suppressor at a case chosen to cross it. | Pins that a bus hop's referring file counts toward the same brake as any other edge kind, one below and one at the threshold. |
| `src/query/tests/coverage.rs` | `build_tests_model`, the attribute and project-model vouches. | Pins that a harness file in a test project earns a row with no test defs of its own. |
| `src/query/tests/dispatch.rs` | `implements`/`overrides` in `refs`, `impact`'s widened interface hop, `tests`, and `--no-dispatch`. | Pins that `--no-dispatch` removes both the inbound table and the interface-hop widening a registration-driven `implements` edge provides, with no effect on an ordinary base-list `inherits` edge. |
| `src/query/tests/find.rs` | `find_names`, `file_inbound_counts`, `first_decl_line_by_file`. | Pins which edge kinds count as inbound interest and that a file's self-references never do. |
| `src/query/tests/heuristic_ordering.rs` | Heuristic-tier row ordering shared by `refs` and `impact`. | Pins that every precise row sorts before any heuristic row and that a full cap of precise rows leaves no room for guesses. |
| `src/query/tests/impact_from_lines.rs` | The per-edge-kind referencing line on an impact row. | Pins the lowest-line-per-kind rule and that an ambiguous-only hit still names its line under `direct`. |
| `src/query/tests/impact_hub.rs` | The hub-file brake: in-degree and `is_infra_file`'s name patterns. | Pins the four `is_infra_file` shapes and that a hub reached on the last hop is never reported as braked. |
| `src/query/tests/impact_iface.rs` | The ctor-di and interface-hop widening path. | Pins that an ambiguous ctor-di edge and a same-named foreign interface never widen the walk. |
| `src/query/tests/impact_iface_brake.rs` | The broad-interface fan-in brake. | Pins that the brake narrows only the braked interface's own hop, never a narrower sibling's. |
| `src/query/tests/impact_misc.rs` | Enum-reached files, `uses-member` blast radius, PPR sanity, seed-argument parsing. | Pins that `looks_like_file_path` matches the same shape a `.`-suffixed symbol id would. |
| `src/query/tests/impact_ranking.rs` | `build_impact_model`'s hop limit, file-path seeding, PageRank-ordered ranking. | Pins that ranking is deterministic across repeated runs and never drops a row below the cap. |
| `src/query/tests/imported.rs` | `build_imported_section`'s direct and composed reach, the mapped-repo guard, the message-key join, and its own cap/drop accounting. | Pins that a foreign edge landing on a native row reports one hop further out, that duplicate records landing on the same foreign file collapse to one row, and that the composed join never echoes the publisher's own arriving site back as its consumer. |
| `src/query/tests/index.rs` | `load_graph_index`'s manifest join, hub in-degree, `--no-guess`, the heuristic adjacency. | Pins that a guess never enters the precise adjacency and that `--no-guess` keeps the extension tier only. |
| `src/query/tests/member.rs` | Member-seed resolution across `impact`/`tests` (bare, `Type.Member`, `Namespace.Type.Member`), and the type-ladder-first order shared with `refs`/`read`. | Pins that a name resolving to a type never falls through to the member ladder, and that a member ambiguity answers candidate rows, never a bare type list. |
| `src/query/tests/name_hint.rs` | The name-match rule and the hint rows over a hand-built index. | Pins the affix rule's matches and its non-matches, the declaring-file and unvouched-file refusals, the count-then-id reference order with its cap and total, path order, and that any row or a possible route suppresses the hint. |
| `src/query/tests/next.rs` | `refs_next`, `tests_next`, `impact_next`, `find_next` and the resolve filter they share. | Pins that a candidate whose target is ambiguous, no longer resolves, names a file the index does not hold, or starts with `--` is dropped while its siblings are kept, and that a member seed and an `also_in` declaring site still get their suggestion. |
| `src/query/tests/pick.rs` | The picked-candidate cases: the suffix pair (one exact owner answers where the typed qualifier admits two, and an owner that declares nothing answers nothing), and the `impact` type pick compared against both the def id as a single resolved symbol seed and the verb run on that id. | Every case builds its graph through the real extractor helpers in `tests.rs`, never a hand-written index, so the member-verification path reads real source text the way the built binary does. |
| `src/query/tests/read.rs` | `build_read_model`'s declaration span. | Pins that a reference inside the declaration's own span is excluded from its inbound count. |
| `src/query/tests/refs_bare_member.rs` | The bare-member fallback: `line_has_token`, verified single- and multi-owner lookups. | Pins that a longer identifier sharing the query's prefix is refused and that a type answers before a same-named member. |
| `src/query/tests/refs_enum.rs` | `build_refs_model` on an enum and its members. | Pins that a member's inbound edges union into the enum's own answer and that two same-named members stay ambiguous. |
| `src/query/tests/refs_tables.rs` | `build_refs_model`'s inbound/outbound/ambiguous tables, caps and ranking. | Pins the rank order (resolved before heuristic, own project before foreign) and that `--all` lifts both caps together. |
| `src/query/tests/symbol.rs` | `resolve_symbol`'s ladder, including the enum-member tail and the Rust `::` path. | Pins the exact-id/unique-name/case-insensitive/dotted-tail/`::`-path order, that two candidates at any step stay ambiguous, and that a `::` query never answers for a C# or TS id. |
| `src/query/tests/test_path.rs` | The TS/TSX/JS test-file name rule and the vouch precedence. | Pins the ten suffixes and the directory segment, the near misses it refuses -- `.jsx` and `.mts`/`.cts` names, case variants, partial segments and a `tests` directory -- and that an attributed def and a test project outrank the name. |
| `src/query/tests/ts_edges.rs` | TS edge admission into the query index: per kind, the unbound-import refusal, the three narrowing flags, the hub in-degree. | Pins that an import binding no definition earns no adjacency and that `--no-dispatch`/`--no-guess`/`--no-bus` leave the TS lists alone. |
| `src/query/why.rs` | The closed `why` vocabulary every hit row on `refs`/`read`/`impact`/`tests` carries in `--json`: `Why`, and `why_for_edge`/`why_for_uses_member`, which derive it from an edge's kind and (for `uses-member`) its tier. | The only place any of its words is spelled. `implements` and `overrides` are appended after the words that pre-date the two edge kinds, so no existing word moves; `bus-hop` follows `imported-edge`, and the TS words `call`, `jsx-use`, `dispatch`, `import` and the `tests` vouch `test-path` are appended after it, the nineteenth and newest. |
| `src/render.rs` | Root of the render layer: the module overview, the `mod` list and the public re-exports. | Holds no code. Every public item keeps the path it had before the split; everything else is `pub(crate)` at most. |
| `src/render/blocks.rs` | The table-to-lines primitives every renderer builds on: `ref_kind_block`, `compact_block`, `group_by_file`, `rle`. | `ref_kind_block` prints its header whenever the table is present even at `total == 0`; `compact_block` additionally suppresses on a zero-total table. Both accept a missing (`None`) table, a path only the tests exercise directly. `ref_kind_block_if_any` prints a header only when the table it is given carries at least one row, unlike `ref_kind_block`, which prints one for a present-but-empty table. |
| `src/render/coverage.rs` | The `tests` verb's renderers: `render_tests_text`, `render_tests_compact`. | The zero-hit case is one line, never a header over an empty table; heuristic files carry the same word/character suffix the refs and impact renderers use, and the summary counts stay precise-only. A symbol reached only over a possible-route bus hop (no precise/heuristic row at all) still gets the header and its `bus-hop` block; only a symbol with neither prints the zero-case line. Both renderers reuse `refs.rs`'s own `bus_hop_line` (text) and the same marker-plus-path-to-full-row compact shape, rather than a second copy of either. |
| `src/render/impact.rs` | The `impact` verb's renderers: `render_impact_text`, `render_impact_compact`, and `bus_origin_suffix`. | The `affected` count is precise-only; a heuristic-reached file's `via` reports its guess count, never the zero `via_count` a heuristic-only row carries by definition. `bus_origin_suffix` states the SAME disclosure sentence `refs`/`read`'s own bus-hop rows carry (`markers::BUS_HOP_UNVERIFIED`), plus the row's own `bus_origin` identity -- an impact row is a file-level aggregate with no message/handler fields of its own to show otherwise. Compact mode carries a marker (`?`) and a pointer to the full row instead. |
| `src/render/markers.rs` | The heuristic/guess suffix vocabulary shared by every renderer: `heuristic_suffix`, `compact_marker`, `test_via_suffix`, `source_suffix`, `seed_kind_str`, `BUS_HOP_UNVERIFIED`. | `heuristic_suffix`/`compact_marker` read `tier` only when `heuristic` is set; an extension and a scored guess get their own word (`(extension)`/`(guess)`, `x`/`h`), never the umbrella `(heuristic)`, once a tier is known. `BUS_HOP_UNVERIFIED` is the one wording every bus-hop disclosure states, shared by `refs.rs`'s `bus_hop_line` and `impact.rs`'s `bus_origin_suffix` so the two renderers can never drift from each other's wording again. |
| `src/render/next.rs` | The `next:` trailer: `next_line`. | Returns `None` for an empty list, so an answer with nothing to suggest prints no line. An argument outside the shell-safe set is POSIX single-quoted; everything else is printed bare. |
| `src/render/read.rs` | The `read` verb's renderers: `render_read_text`, `render_read_compact`. | Reuses `refs`'s own renderers for the inbound section wholesale, only replacing the `def:` line with the declaration span. |
| `src/render/refs.rs` | The `refs` verb's renderers: `render_refs_text`, `render_refs_compact`, the enum member-count line, and `bus_hop_line` (`pub(super)`, reused verbatim by `render/coverage.rs`'s own `tests` bus block). | The member-count line prints only when the model carries at least one member-level reference; an imports row never carries a heuristic suffix, having no flag to read. The default renderer prints an `implements`/`overrides` header only when that table's own total is non-zero, so a symbol untouched by dispatch edges renders byte-identical to before the pair existed; `--compact`'s blocks already self-skip an empty table for every kind. The `bus-hop` provenance section (publisher, message, handler, evidence, direction) follows the same non-zero-only rule, and its disclosure sentence is `markers::BUS_HOP_UNVERIFIED`, shared with `impact.rs`'s own bus-only suffix. |
| `src/render/ts_tables.rs` | The `call`/`jsx-use`/`dispatch`/`import` blocks of the default and `--compact` `refs`/`read` renderers, and `sums` for the trailer and summary lines. | Split out of `refs.rs` to keep that file's two renderers readable. A block prints only when its total is non-zero; an outbound row names the target definition, since one import statement can bind two names in one file. |
| `src/render/tests.rs` | Shared fixture builders for the render tests and the list of test modules. | `table` is the only builder shared across more than one topic; everything else lives local to the topic file that uses it. |
| `src/render/tests/blocks.rs` | `ref_kind_block`/`compact_block`'s missing- and empty-table tolerance, and `rle`. | Pins the asymmetry: a present-but-empty table still prints its header under `ref_kind_block`, but not under `compact_block`. |
| `src/render/tests/coverage.rs` | `render_tests_text`/`render_tests_compact`'s zero-case, heuristic-suffix and test-project-vouch shapes. | Pins that a project-vouched row prints its lines with no def id and that the header counts stay precise-only. |
| `src/render/tests/impact.rs` | `render_impact_text`/`render_impact_compact`'s heuristic/tier suffixes, hop grouping and the tests-affected summary term. | Pins that a heuristic-only row's `via` reports its guess count, and that `tests=` joins the summary only when the blast radius actually reaches a test file. |
| `src/render/tests/markers.rs` | `seed_kind_str`'s two string literals. | Pins the exact strings the `impact` JSON and CLI error messages key on. |
| `src/render/tests/next.rs` | `next_line`'s empty, single and multi-candidate shapes and its quoting rule. | Pins that no candidates prints no line, that a `::` path or dotted id stays bare, and that an argument a shell would read differently is single-quoted. |
| `src/render/tests/refs.rs` | `render_refs_text`/`render_refs_compact`'s per-kind headers, RLE grouping, the member-count line and the heuristic/tier suffixes. | Pins that a kind with zero rows prints no header in `--compact` and that two tiers off one line number stay distinct RLE entries. |
| `src/render/tests/ts_tables.rs` | The TS tables in `render_refs_text`/`render_refs_compact`. | Pins that a TS table prints only when non-empty, after every older kind, and that its drops join the shared trailer and the compact summary. |
| `src/repo.rs` | Root discovery (`.scout` ancestor, git worktree, git common dir) and the registry file. | Root discovery returns absolute, normalised paths. A missing registry reads as empty; a corrupt one is an error, so a lost registry is never mistaken for an empty one. |
| `src/resolve.rs` | Root of C# resolution: the ladder rules in its header, the `mod` list and the public re-exports. | Holds no code. The public surface is `resolve_graph`, `resolve_graph_with_ts`, `resolve_graph_with_model`, `DefIndex`, `ExtCandidate`, `MemberLists`, `MethodOverloadParams`; every other item is `pub(super)` at most. |
| `src/resolve/index.rs` | The def index: `DefIndex`, `MemberLists`, `ExtCandidate`, `MethodOverloadParams`, `build_def_index`, `name_probe`. | Defs keep first-insertion order; a partial class's later files land in `also_in`, never as a second def. Enum members are keyed by qualified name but excluded from the simple-name pool. `MemberLists.override_methods` is merged across a partial class the same way `non_public_methods` is, a plain union in first-insertion order. `MemberLists.overload_counts` pairs each part's value ranges with its type-parameter counts position for position before the union, and a name any part cannot pair is listed in `unpaired_overload_names`. Each `MethodOverloadParams` carries its own type-parameter count and sites (`OwnTypeParams`) only when both are recorded and aligned with its descriptors, and the partial-class union keeps two overloads with the same descriptors but different type-parameter facts. |
| `src/resolve/delegate_shape.rs` | `delegate_shape`: the parameter list, `void` return and parameter-type arguments of the delegate a parameter descriptor names -- `Action`/`Func`/`Predicate` structurally, an in-graph delegate through its `Invoke` list and signature fact. | An array answers nothing, one `Expression<...>` wrapper is unwrapped, and an in-graph delegate without a signature fact leaves its return and type-argument roles unknown so each reader fails open. |
| `src/resolve/arity.rs` | Arity admission for instance overloads and extension entries, including generic-argument unification and a call's written type-argument count (`CallArity`, `method_call_admits`). | An optional parameter makes an entry a range and a `params` array accepts any count; concrete type arguments must match, a wildcard unifies with an unbound method type parameter. A written type-argument count must match the type-parameter count of the same overload whose range admits the value count; an unknown count on either side keeps the value-only rule. |
| `src/resolve/argument_types.rs` | Resolves deferred argument members from indexed declarations and a fixed set of common library members. | Unresolved owners or conflicting return types become unknown; receiver type parameters substitute only when their binding is certain. |
| `src/resolve/argument_compatibility.rs` | Pairs arity, lambda and argument-type checks on one overload and vetoes definite type mismatches. | Unknown positions admit; a candidate with any applicable overload admits. Proven inheritance, numeric conversion and possible user conversion keep an overload eligible. An interpolated string argument is admitted at a parameter whose resolved in-graph type carries the handler mark and compares as `string` at every other parameter. An invariant same-head generic comparison (`canonically_equal`) declines only when each side's descriptor, rewritten to one canonical identity in that side's own file context, still names different types: a `using` alias resolves through that file's alias map and falls back to its target text normalized the way `type_descriptor` renders a type, `Nullable<T>` becomes its argument where `Nullable` is no in-graph type, `dynamic` becomes `object`, and a `?` placeholder compares equal at its position. A descriptor it cannot split into balanced, non-empty arguments keeps the text comparison. A type named like a CLR primitive (`String`, `Boolean`, `Int32` and the other names `canonical_primitives` rewrites) is read two ways and the pair admits when either reading admits: the keyword reading rewrites every such name to its keyword, and the in-graph reading, run only when the graph declares a type with such a simple name, keeps a name that `resolve_type` resolves in-graph from its side's scope and rewrites one that does not resolve or that the text writes `System.`-qualified. The in-graph reading thus only turns a decline into an admission, and a descriptor with unbalanced type arguments takes the keyword reading alone. |
| `src/resolve/tests/argument_compatibility.rs` | End-to-end must-admit cases for conversion and uncertain argument shapes. | Pins inheritance, numeric, nullable, null, object, dynamic, params, and variance admission, plus the handler-parameter admissions, the partial-declaration mark and the plain-string and unmarked-type vetoes, and the spelling-equivalent generic argument admissions (aliases at file, namespace and global scope, `Nullable<T>` against `T?`, `object` against `dynamic`, nested, array and positional spellings) with their distinct-type declines, and the in-graph types named like CLR primitives: their base, interface and conversion admissions at the top level, in type arguments and in alias targets, the keyword spellings beside them that still admit, and the unrelated ones that still decline. |
| `src/resolve/lambda_arity.rs` | `lambda_arity_admits`: checks a ref's recorded lambda facts against each overload of a candidate: the delegate's parameter count, a void return refused for a value-only body, and a generic overload refused when an own type parameter occurs only in parameter types of delegates filled by implicitly typed lambdas. | One overload must pass at every position, as C# applicability holds per overload. Fails open where there is nothing to judge -- no recorded fact, or no overload named `member`; the return check admits an unreadable return, and the inference check applies only without a written type-argument list and admits on missing facts, an explicitly typed lambda, any other occurrence of the type parameter, or an unreadable delegate. A zero-parameter `Action`/`Func<TResult>` shape still admits nothing at a position with an arity fact. |
| `src/resolve/members.rs` | Member declaration checks (`declares_member`, `member_shape`, `member_vouched`) and the inheritance-closure walk that finds the base declaring a member. | The base walk visits class bases in declaration order before any interface, terminates on a cycle, and never binds a class-typed receiver to an interface declaration. For a read through an interface receiver, a declaring base that is itself a base of another declaring base holding the member as a property or field is hidden and skipped; the first survivor in walk order answers, and a call keeps the first declaring base. A scored guess never vouches through a non-public member. `first_base_declaring` is `pub(super)` so `dispatch.rs` can reuse the SAME nearest-base search for an `overrides` edge's own base lookup. `first_base_declaring` is `pub(super)` so `dispatch.rs` can reuse the SAME nearest-base search for an `overrides` edge's own base lookup. |
| `src/resolve/scope.rs` | Per-file scope (`FileContext`: usings as `FileUsings`, aliases), candidate scoring, and global usings per project unit. | Global usings scope to the declaring unit when a project model exists, are repo-wide without one, and fall open for a file no project owns. An empty namespace is absent, not a matchable prefix. `FileUsings` groups each plain using by the namespace that declares it, with compilation-unit and in-scope `global using` directives at `""`, for the ladder's bare-name walk; every other reader sees the flat set it dereferences to. `FileUsings::reaches` answers, per def, whether the file's project can name it (set by `reach.rs`; always true with no model or for a file no project owns). |
| `src/resolve/reach.rs` | `attach_reach`: gives each project-owned file's context the per-def answer to "can this file's project name it", for the ladder's namespace-level walk. | Reachability is the transitive `ProjectReference` closure alone, with no test-project direction. A def is inside when any declaring file (partial parts and duplicate declarations included) is owned by a reachable project, or when some declaring file has no owning project. No model, or a file no project owns, gets no reach and sees every def. One table per project, shared by its files. |
| `src/resolve/receiver.rs` | Receiver typing: field and property types, property and call-return hops, delegate and lambda slots, descriptor unification, the assignability cache, candidate admission, nested-type visibility. | A lambda parameter is typed positionally from the callee's delegate parameter and stays untyped when overloads disagree. An external receiver refuses a candidate not assignable to it. A chain tail whose hop fails emits no guess. |
| `src/resolve/ladder.rs` | The ladder itself: `resolve_ref` steps 0 to 4, the final-segment type-argument strip (`final_type_argument_list`), dotted-suffix and nested-qualifier fallbacks, project-reachability narrowing (`Admission`, `narrow_by_reachability`), `capped_candidates`. | Step 1 strips one well-formed type-argument list closing a dotted reference's final segment and keys that lookup by the probe's recorded arity when it is one or more, else by the written list's top-level count; only the final segment's list comes off, because def ids join nesting with `+` and a dot-joined candidate can never name a nested type, and any other written shape -- a list on an earlier segment, an alias qualifier, an unbalanced or empty list -- keeps the verbatim lookup. A step that finds exactly one candidate resolves; two or more stop as ambiguous. Ambiguous lists are capped at five and sorted by id. A dotted reference the exact step misses never reaches steps 2 to 4. Steps 2 and 3 run one namespace level at a time, innermost first and ending with the global namespace: the level's own type, then the usings declared at exactly that level, so an enclosing namespace's type beats a using declared further out and a using declared in a block that does not enclose the site never applies. With a project model, the walk passes over a level's type or a using-imported candidate the site file's project cannot reference (`FileUsings::reaches`), after each directive's own first-match reading; steps 0 to 1.5 and 4 do not consult it, and `narrow_by_reachability` still narrows the ambiguities that reach the three consumers. |
| `src/resolve/edges.rs` | Edge construction (`type_edge`, the heuristic dedup key) and constructor-injection resolution (`resolve_ctor_param`, the implementor index, infrastructure-namespace detection). | `ctor-di` binds a sole implementor, prefers a closed implementor over an open generic one, and records a tie as ambiguous. Byte-identical guesses collapse to one edge. `type_probe` builds the bare-or-dotted synthetic type reference `dispatch.rs`/`bus_vocab.rs` resolve a registration's or a bus-hop message's own type text through, now carrying the reference site's own enclosing-type chain (`outer_types`) so the ordinary ladder's nested-shadow step runs for it exactly as it would for a plain `uses-type` reference at that position; `dispatch.rs`'s own registration-target lookup passes `&[]` (unchanged, not itself a reference site with a stack of its own). |
| `src/resolve/dispatch.rs` | Dispatch edges: `resolve_registrations` (the type-level `implements` edge from a two-type-argument DI registration) and `resolve_member_dispatch` (member-level `implements`/`overrides`, scoped to exactly the implementation types a registration resolved). | A registration whose service or implementation type does not resolve to exactly one in-graph def emits no edge. Both edge kinds are recorded against the IMPLEMENTATION type's own declaring file and line, never the registration call site, so they reuse `inherits`'s own file-attribution with no new index. `matching_overload_count`'s pair count is the one arity-uniqueness test both `implements` and `overrides` use: exactly 1 resolves, 0 or 2-or-more emits nothing. `overrides` is computed once per implementation type, not once per registration. A repository with no registration facts runs both passes over an empty list. |
| `src/resolve/bus.rs` | Message-bus `bus-hop` edges: `kept_publish_sites` (every publish site the pass keeps, resolved to a `MessageIdentity` and filtered ONCE, ahead of vocabulary derivation), `append_bus_edges`, the consumer/handler table it builds from raw `FragDef` facts against the derived vocabulary (full or binding-only role), the closed `evidence` word list, and the two structural refusals (`structural_non_dispatch`, `is_test_double_lambda`) plus the closed public `TEST_DOUBLE_DIRECT_VERBS` seed (Moq's own `Setup*`/`Verify*` surface). | A hop is emitted only when the publish site's own message and the consumer's own base-argument message resolve, independently, to the SAME `bus_vocab::MessageIdentity` (element def plus array rank) through the ordinary ladder in each site's own file context -- never a bare-name or same-line match, so two same-named types in different namespaces, or a message and an array of it, never link. `kept_publish_sites`' own resolved identities are what the vocabulary's admission rule (`bus_vocab::derive_consumer_bases`) reads as "what this corpus sends," so emission and admission never read two independently-filtered publish lists that could disagree. A binding-only base recognizes a def for its property scan alone; its own base-list argument earns no `RawConsumer`. The batch wrapper's inner type argument is read structurally off `base_type_args`' own rendered descriptor, never by naming the wrapper; a base's own FLAT (position-0) argument instead reads `FragDef::array_message_bases` for its array bit, since `base_generic_args` alone cannot carry it. A repository with no publish site, or no consumer, adds nothing. `resolved_consumers` keeps only the FIRST `ResolvedConsumer` per `(def_idx, MessageIdentity)` -- base-list evidence structurally precedes property evidence in iteration order, and an earlier-declared base precedes a later one, so at most one edge exists per route with no separate precedence table. `structural_non_dispatch` refuses a recognized-verb call only when the repository declares EVERY arity-matching overload of that name to take the message's own resolved identity as an input, resolved in the CALL SITE's own context; `is_test_double_lambda` refuses a call inside a lambda sitting in an `Expression<>`-typed parameter of a repository-declared helper, or inside a directly Moq-shaped setup/verify call the repository does not itself declare, reading each candidate overload's own required/total argument-count range paired with its parameter descriptors (`verb_overloads`, built once off the raw `FragDef::method_arities`/`method_params` entries of the defs `DefIndex::member_name_to_defs` lists for a verb name -- a public method, property, field or extension method of that name, so a non-public method never stands in for a library call -- paired only WITHIN one def record, since `DefIndex`'s merged `MemberLists::method_params` dedupes each list separately and cannot re-pair them) and trying both the direct reading (the call's own argument count in range, lambda at its own position) and, when the overload's first parameter is `this`-marked, the extension reading (argument count plus one in range, lambda one position over, the receiver implicit) -- a reading that would land the lambda on the `this`-marked receiver itself is never admissible -- so a helper's own extension-form and static-form calls, with an optional trailing parameter passed or left out, all align correctly with no signal beyond each overload's own declared shape; refusal holds only when some overload aligns and every admissible reading lands on an `Expression<>` parameter, an overload with no paired range read as exact-arity -- neither refusal validates a receiver's own declared type, both stay structural. |
| `src/resolve/bus_vocab.rs` | The message-handling vocabulary a repository declares about itself: `derive_publish_verbs` (roots plus wrapper closure, independent of what is sent); `derive_consumer_bases` and its admission rule (`harvest_registrations`, `def_receives_message`, `def_binds_sent_message`) gated on `sent` -- the resolved `MessageIdentity` set of every kept publish site; the pass-through closure (`close_over_intermediates`, now role-aware); `resolve_type_text` (the ladder entry point a bare-or-dotted type text, never a message identity itself, resolves through, taking the reference site's own `outer_types`); `MessageIdentity` (element def plus array rank) and `resolve_message_identity`, the one entry point every message text -- publish, base argument, property binding -- resolves through, splitting a trailing run of `[]` off first (`split_array_suffix`) so a message and an array of it are never the same identity. | A registered type's own generic base is admitted only when the registered type is not itself a sent message AND actually receives -- through a declared parameter (`def_receives_message`, which skips each reserved `methodParams` entry by `is_reserved_method_params_key` because a result, property or field type or a type mark is not a parameter, peeling a wrapped parameter's own generic arguments at any depth via `descriptor_type_texts`, each resolved to a full `MessageIdentity`) or a bound property (`def_binds_sent_message`) -- a message the corpus does send; this is what keeps a library equality base (`IEquatable<T>`) from contaminating the vocabulary, since a self-referential argument can never satisfy both halves at once. The registered type ITSELF is compared as `MessageIdentity::plain` (rank 0): a registration is never on an array. A base admitted only through a bound property is kept binding-only (`binding_only_bases`), never promoted to a full consumer base. Inheritance closure runs to a fixpoint so a base reached through a local pass-through intermediate takes the SAME role the base it passes through to holds; wrapper closure is read against a frozen copy of the seed, so forwarding is followed exactly one hop whatever the corpus iteration order. A project or `ProjectReference` is not a boundary: hops cross them freely within one repository. A registration's own handler lookup (`harvest_registrations`) passes `&[]` for `outer_types` -- its facts carry no enclosing-type stack of their own, unlike a publish site's (`publish.outer_types`) or a handler def's (`receiver::def_outer_types`). `concrete_base_argument`'s flat (position-0) reading appends `[]` when the caller's own `first_arg_is_array` flag (`FragDef::array_message_bases`) says so; its nested-wrapper reading needs no such flag, since `base_type_args`' own rendered descriptor already carries any array suffix it has. |
| `src/resolve/assembly.rs` | `resolve_graph`, `resolve_graph_with_ts`, `resolve_graph_with_model`: the pass over every file's fragments, the `Ext` and `Guess` tiers, stats. | A ref an earlier tier answered never gets a heuristic duplicate. The scored tier refuses a member name carried by more than `SCORED_UNIQUENESS_CAP` defs and emits at most `SCORED_EMIT_CAP` edges. A `None` project model leaves the graph byte-identical. `dispatch::resolve_registrations`/`resolve_member_dispatch` run once, after the main ref loop; both count under `edges_by_kind.implements`/`.overrides` regardless of which pass emitted the edge. |
| `src/resolve/property_hop.rs` | Resolves a two-segment receiver through its owner's recorded property type, then checks the requested member on that type. | The property's recorded type-argument count selects a same-named generic sibling; an absent list keeps the receiver helper's existing fallback. The target itself must declare an applicable public member, including the typed-receiver delegate gate. The hop does not walk the target's bases. |
| `src/resolve/provenance.rs` | Records which emission arm answered each `uses-member` edge, only when `SCOUT_EDGE_PROVENANCE` names an output path. | `note` runs strictly AFTER the edge it describes is pushed, so nothing here can reach the answer; with the variable unset it returns before touching anything and the graph stays byte-identical. `flush` writes one JSON object per surviving edge in graph order; an edge no arm claimed carries `"step": null`. |
| `src/resolve/tests.rs` | Shared fixture builders for the resolver tests and the list of test modules. | Fragments are built by hand, never parsed; `no_git_root` keeps `built_at_head` at `None`. |
| `src/resolve/tests/argument_binding.rs` | End-to-end member binding with known and unknown call arguments. | Pins extension, sibling-interface, base and generic selection plus fail-open controls. |
| `src/resolve/tests/ladder.rs` | Head hash, aliases, ambiguity caps, enum-member ids. | Pins the alias short-circuit and the five-entry ambiguous cap. |
| `src/resolve/tests/member_qualifiers.rs` | `uses-member` qualifier rules and declaration-expression type refs. | Pins when a bare or dotted qualifier emits and when it is dropped silently. |
| `src/resolve/tests/member_scope.rs` | End-to-end argument and receiver binding for names the member table scopes to their declaring constructs. | Pins that a deconstruction name or an out-of-scope use beside a same-named field binds the overload C# selects precisely, and that the in-construct shadowing controls stay precise. |
| `src/resolve/tests/method_type_arity.rs` | A written type-argument count held to each candidate's own overloads, precise and extension tiers alike. | Pins that the count and the value range must hold for the same overload, that the extension tier and the scored tier's receiver rule drop extensions of another count, and that missing or misaligned counts leave the value-only rule. |
| `src/resolve/tests/lambda_applicability.rs` | A lambda argument held to an overload it can convert to: void delegates for value-only bodies, type parameters only an implicitly typed lambda would fix, per-overload pairing, and the property hop. | Pins that a written type-argument list, an explicitly typed lambda, another argument or a delegate return keeps the overload, that an in-graph delegate without a signature fact admits, and that a call with no lambda keeps its binding. |
| `src/resolve/tests/qualified_names.rs` | Enclosing-namespace prefixes and foreign-qualified names. | Pins that a foreign-qualified name never binds a same-named in-tree type. |
| `src/resolve/tests/dotted_suffix.rs` | Dotted-suffix matches, global aliases, nested types through a derived type, same-namespace and global-using resolution. | Pins that two path-suffix matches stay ambiguous. |
| `src/resolve/tests/receiver_tier.rs` | The static-qualifier and declared-receiver tiers. | Pins that a receiver whose type does not declare the member earns no edge. |
| `src/resolve/tests/qualified_path_tokens.rs` | Names written with whitespace, a line break or a comment between their segments, from real C# through extract and fragment conversion: a receiver, a type reference, a type argument, a base entry, a `using` target and an alias target. | Pins that each resolves to the same target, tier and edge the clean spelling resolves to, and that a whole file of them produces the clean spelling's edge set exactly. |
| `src/resolve/tests/qualified_receiver.rs` | A receiver declared with a qualified type, from real C# through extract and fragment conversion. | Pins that the written path binds the declared type over a same-named sibling or decoy, that a missing or doubly matched path earns no precise edge, that an ambiguous qualified answer is still narrowed by project reachability, that a qualified and a bare generic receiver each take their declared arity's sibling in either declaration order, and that an extension-only member keeps its extension-tier edge on a qualified receiver. |
| `src/resolve/tests/generic_receiver_path.rs` | A receiver declared with a qualified generic type, from real C# through extract and fragment conversion, plus hand-built ladder probes. | Pins that a local, parameter, pattern, nullable and field/property-fallback receiver binds the enclosing-namespace twin its non-generic sibling binds; that the declared arity selects between a generic and a non-generic def of one name in either declaration order; that a written list's top-level count gates an arity-less probe (a nested list, a tuple and an array each counting one argument) while a recorded arity of one or more outranks it; and that every other written shape keeps the verbatim lookup. |
| `src/resolve/tests/nested_end_to_end.rs` | Multi-level nested qualifiers and instance receivers named like a type. | Pins that a namespace type never shadows a same-named nested type. |
| `src/resolve/tests/extension_tier.rs` | Extension-method admission: namespaces, ranges, vetoes by declared members. | Pins that an instance member shadows a visible extension of the same name. |
| `src/resolve/tests/extension_generics.rs` | Generic `this` parameters against concrete and wildcard receivers. | Pins exact concrete-argument matching. |
| `src/resolve/tests/explicit_implementations.rs` | `this.` calls, property hops and bare names inside a type that implements an interface member explicitly. | Pins that only an interface-typed receiver reaches an explicit implementation and that `implements` pairing still counts it. |
| `src/resolve/tests/scored_tier.rs` | The scored guess tier and the member-shape rule. | Pins the uniqueness and emit caps and the emitted order. |
| `src/resolve/tests/receiver_rule.rs` | External-receiver assignability and generic-sibling selection. | Pins that a generic receiver binds the generic sibling, not the first indexed def. |
| `src/resolve/tests/bus.rs` | `bus-hop` edge resolution: message identity matching, fan-out, batch and mediator-request shapes, inherited consumer bases, the negative cases. | Pins that a publish site links only when both its own and the consumer's message resolve to the same def, that a wildcard base argument and a same-named message in a different namespace never link, and that one message with two handlers emits one edge per handler. |
| `src/resolve/tests/bus_reserved_param_keys.rs` | The parameter route of the consumer-base admission rule against reserved `methodParams` entries, on hand-made fragments. | Pins that a descriptor under each of the six reserved keys admits no base, and that the same descriptor under a method name does, also when the carrier's partial declarations split the two. |
| `src/resolve/tests/byte_identity.rs` | Serialization stability of the edge array with and without heuristics or a project model. | Pins the byte-identity gate the moves are held to. |
| `src/resolve/tests/project_admission.rs` | Project reachability: admission, unit-scoped global usings, narrowing. | Pins that a non-test site never names a def in a test project. |
| `src/resolve/tests/graph_invariants.rs` | Edge schema, stats, partial-class merging, dedup, counts. | Pins that only heuristic edges carry a tier and the heuristic count is appended last. |
| `src/resolve/tests/level_walk_reach.rs` | The namespace-level walk under a project model. | Pins that a level's type or using candidate from an unreferenced project is passed over (with the model-less answer beside it), that unknown ownership, a reachable partial part and a referenced test project keep a def inside, that a base resolved at its declaration uses the declaring project's closure, that a directive whose own reading is passed over is not read further out, and that step 4 can still answer with a passed-over def. |
| `src/resolve/tests/qualified_publish.rs` | A publish whose message is written with a qualifier. | Pins that the hop reaches the consumer the qualifier names (a `global::` spelling and a comment between qualifier segments included), and that the same call written bare binds the enclosing namespace's type and earns no hop to it. |
| `src/resolve/tests/using_levels.rs` | Bare type names against enclosing-namespace types and usings declared at different namespace levels. | Pins the per-level order (a level's type, then that level's usings, compilation-unit and `global using` directives last), the per-level ambiguity, and that a sibling block's using never applies. |
| `src/resolve/tests/nested_step.rs` | The enclosing-type step of the ladder. | Pins that the innermost enclosing type wins and a dotted ref never enters the step. |
| `src/resolve/tests/ctor_di.rs` | Constructor-injection edges. | Pins sole-implementor binding and ambiguity on a tie. |
| `src/resolve/tests/dispatch.rs` | The type-level and member-level `implements`/`overrides` edges. | Pins that an unresolvable registration type emits no edge, that a repository with no registration facts gains no dispatch edges at all, and that two arity-tied overloads leave a member-level edge unemitted. |
| `src/resolve/tests/receiver_hops.rs` | Property hops and `var` typed from a call's return. | Pins that an unrecorded property or an ambiguous type stops the hop. |
| `src/resolve/tests/base_members.rs` | `this`, `base` and inherited field receivers; local, catch and parameter shadowing. | Pins that an in-file local shadows a same-named field fact. |
| `src/resolve/tests/base_walk.rs` | Base-walk order, overload arity admission, chain tails through base methods, static qualifiers on bases. | Pins declaration-order base visiting before interfaces. |
| `src/resolve/tests/member_hiding.rs` | Property reads through interface receivers whose base closure holds more than one declaration of the member. | Pins that a redeclaring interface hides the declaration in its own bases at any depth, and that calls, class receivers, method-only redeclarations and unrelated declarations bind as before. |
| `src/resolve/tests/lambda_parameters.rs` | Lambda parameters typed from delegate parameters of in-graph callees. | Pins positional typing and the untyped outcome for disagreeing overloads. |
| `src/semantic.rs` | Root of the resolve-time compiler-fact consumption path: the `mod` list and the public re-exports. | Holds no code. Reached only from `resolve::assembly`'s own per-reference wrapper, the load point `lane::decide` (called by `mapcmd::map_repo`) and `lane::record` (called by `graph::rebuild_graph`); the resolver itself performs no file I/O. |
| `src/semantic/lane.rs` | The map-time lane decision: `LaneDecision`, `decide` (re-exported as `decide_lane`), `record` (as `record_lane`), and the artifact presence check. | A record is due exactly when `--no-semantic` is given or an entry sits at the compiler-facts path, and the same condition forces a rebuild. `--no-semantic` never reads the artifact; any load failure is `artifact-unreadable`; a stale layer, or one with no complete compilation, is dropped before resolution, so it leaves no `stats.semantic`. A layer that reaches resolution is settled by `record` from `compilation_lane.rs`. |
| `src/semantic/compilation_lane.rs` | The compilation half of the lane decision: `before_resolution` (`no-complete-compilation`) and `after_resolution` (`nothing-confirmed`, `partial`, `applied`). Pure. | Precedence: the freshness reasons are decided first and never carry `compilations`; every record this module writes does. `nothing-confirmed` and `no-complete-compilation` drop `stats.semantic`, so their graph's edges equal the no-artifact graph's. |
| `src/semantic/layer.rs` | The admitted artifact's own reader and compatibility join: `SemanticLayer`, `load`, `lookup`, the Roslyn-to-devscout identity translation (`translate_type_id`, `split_qualified_type`). | Reads the admitted artifact's raw bytes directly, never oracle output and never a second admission path. `lookup` checks freshness first; more than one matching site at one compatibility key collapses to ambiguous rather than choosing one. `freshness` exposes the verdict `load` evaluated, and `compilation_tally` the counted compilations of the same bytes. |
| `src/semantic/precedence.rs` | The same-context override decision and its glue into the resolver: `decide`, `apply`, `track_reference`, `finish`, `SemanticDiagnostics`. | `apply` undoes exactly the edges the untouched ladder just pushed for one reference and replaces them with one compiler-vouched edge; a miss or absent layer is a no-op. Never a post-pass over the finished edge vector. |
| `src/semantic/discovered.rs` | Compiler-discovered-site projection: `project`. | Runs once per admitted artifact, after the main reference loop, not once per reference. Its edges carry `SemanticProvenance::SemanticDiscovered`, never pooled with a per-reference override, and never move the syntax-lane recall denominator. |
| `src/semantic/freshness.rs` | Whole-artifact freshness: `Freshness`, `evaluate`, and the stale-token class (`stale_reason`, `is_import_trigger`). | Compares the artifact's own recorded `sourceSnapshot` against the checkout's current git head and working-tree dirty state, then its recorded MSBuild imports against their files; any mismatch invalidates every fact the artifact carries. The import class is decided beside the code that mints its tokens. |
| `src/semantic/uncertainty.rs` | The eight-state uncertainty vocabulary: `Uncertainty`, `from_resolution`, `from_compilation_state`. | Only `Confirmed` participates in the resolver's override; every other state falls through to the syntax ladder unchanged, and an unrecognised resolution string maps to `Candidate`, never `Confirmed`. |
| `src/store.rs` | SQLite freshness stores: `cache.db` per root and the shared content database. | Opened with WAL and a five-second busy timeout; migrations are idempotent and a fresh database takes none of them. Populated only by the hooks; `stats` reads and `clear` prunes. |
| `src/suggest.rs` | Did-you-mean suggestions for a query that matched nothing. | Suggestions are printed and never substituted for the query or run. At most `SUGGESTION_CAP` names, nearest first. |
| `src/telemetry.rs` | The one place `find`/`refs`/`read`/`impact`/`tests` invocations are logged: one JSON line (`ts`, `schema_version`, `verb`, `seed`, `outcome`, `elapsed_ms`, `result_bytes`, `candidate_count`) appended to the query log when `SCOUT_TELEMETRY=1`. | Gated on the literal value `"1"`; every other value, including unset, is off. The log path mirrors `graph/paths.rs`'s `graph_dir`: the git common dir when there is one, `<root>/.scout/log/` otherwise. Every failure -- the switch off, an uncreatable directory, an unwritable file -- is silent; a verb's exit code and output never change. |
| `src/truth.rs` | Root of the semantic-truth harness: module overview, the `mod` list, no code of its own. | Declared in `src/lib.rs` as `pub mod truth` with a one-line doc, exactly like every other top-level module; no shipped CLI verb reaches it. |
| `src/truth/capability.rs` | The machine-readable capability matrix: `CapabilityAxis`, `CapabilityState`, `MatrixEntry`, `build_matrix`, `matrix_to_json`. | `Passing` is constructible only from an `ExecutedObligation` witness, itself constructible only when at least one obligation actually ran; the registry-only `build_matrix` never produces one. |
| `src/truth/fault_controls.rs` | The registered fault-control battery: nine wrong-system mutations plus `run_battery`'s exhaustiveness meta-check. | Every registered row's mutation is applied to an in-memory copy only, never a tracked fixture byte; an uncaught control fails its own test rather than being silently dropped. |
| `src/truth/freshness.rs` | Freshness and transformation controls: `TransformationPair`, `FreshnessTrigger`, `evaluate_pair`, run in both directions. | A pair's expected verdict (`Stale`/`Preserved`) is declared before it is ever run, never inferred from what the pair happens to produce. |
| `src/truth/identity.rs` | Symbol and occurrence identity: `SymbolIdentity`, `OccurrenceSpan`, and the legacy `CompatibilityJoinKey`. | `CompatibilityJoinKey` derives neither `PartialEq` nor `Hash`, so nothing can reach for it as identity by mistake; `joins` is its one explicit comparison. |
| `src/truth/manifest.rs` | The case-manifest schema, `validate_case`, and `parse_manifest`. | `validate_case` rejects a case missing any required field, naming it, and refuses a case whose expectation provenance names `scout-semantic`, the producer this harness grades. |
| `src/truth/pin.rs` | The pinned foundation-candidate digest: `compute_pin`, `verify_pin`, `Disagreement`. | `verify_pin` has no mutating counterpart; a disagreeing consumer can only report a `Disagreement`, never rewrite the pin. |
| `src/truth/producer_reader.rs` | Readers that turn committed producer bytes into observations: `read_oracle_refs`, `read_oracle_units`, `read_flowtrace_facts`, `native_dispatch_implements_edges`. | `native_dispatch_implements_edges` runs `crate::extract`/`crate::graph`/`crate::resolve` exactly as `devscout map` does -- real resolver output, never an asserted edge. |
| `src/truth/profile.rs` | The compatibility-profile registry: seven dimensions, `Profile`, `REGISTERED_PROFILES`. | No registered profile claims `Passing`/`Failing`; the four demonstrated targets claim at most `SmokeTested`. |
| `src/truth/red_baseline.rs` | The committed truthful red baseline: `NamedMismatch`, `RED_BASELINE`, `evaluate_run`. | `harness_health` and `analyzer_verdict` are reported as two separate fields on one run; an id absent from `RED_BASELINE` is reported as new, never silently absorbed. |
| `src/truth/report.rs` | Case evaluation and the two-lane report: `evaluate_case`, `ratio_or_undefined`, `TruthReport`, `report_to_json`. | `evaluate_case` fails an empty result against a positive obligation and never reports a failed/partial context as complete; the two lanes are never summed. |
| `src/truth/uncertainty.rs` | The eight-state `Uncertainty` and five-state `ContextHealth` vocabulary. | Every non-`Confirmed`/non-`Complete` variant is only constructible with an explicit reason string. |
| `src/rsgraph.rs` | Projection of Rust fragments into the graph: one def and one name row per item, a test function (`#[test]`, or `test` carried by `cfg_attr`) carrying its own name in `testMethods` (`rs_def_id`, `append_rust`), then the edges the module tree and `use` scope resolve (`append_rust_edges`). | Ids are `<file>#<path>` (file-local path, never a crate path). The rows and the edges are appended after every C# and TypeScript row, and the edges are counted in `stats.edges_by_kind` only, so a tree with no `.rs` file serializes exactly as before. |
| `src/rsgraph/bind.rs` | Rust path binding: `Binder::walk` binds a path segment by segment (scope for the first, then the module, type or trait before), resolves every `use` leaf to a fixed point, and indexes each type's `impl` items and each trait's own items. | Scope order: enclosing blocks, the module's items and explicit imports, its globs, then only the package's library name. An unlistable glob stops ordinary lookup, while only the first segment of a `use` path may pass it. One candidate binds, two or more stop the walk; an alias, a generic and a qualified path bind nothing. The leaf worklist requeues only the leaves that consulted a changed module, so a cycle resolves to nothing and nothing recurses. |
| `src/rsgraph/crates.rs` | Cargo packages in the mapped scope: `discover`/`discover_with_digests` read each `Cargo.toml` by a line scan into `RustCrates` (package directory, name, library name, crate roots, including cargo's nested `tests`, `examples` and `benches` `<name>/main.rs` targets), and `sidecar_differs` compares it with `rust-crates.json`. | A root is only a file the walk listed inside the package: an escaping or unwalked `path` adds nothing, and a `[workspace]`-only manifest names no root. The library name exists only when a library root does. No manifest in scope gives `None` and no sidecar. |
| `src/rsgraph/emit.rs` | Rust edges from bound sites: `uses-member` for a call bound to a function, `uses-type` for a type-naming segment in a type position, literal, constructor or qualifier, type-level `implements` for a crate trait, one `ambiguous` edge per site with capped candidates. | No field or kind is added; every Rust edge is precise, and the C# ambiguity and unresolved counters do not count Rust. Edges follow file, site and segment order. |
| `src/rsgraph/edge_tests.rs` | Shared harness for the Rust edge tests: scratch trees, the crate model over them, and `edges_for`, which extracts synthetic sources and returns the Rust edges. | Scratch trees live outside every git work tree. |
| `src/rsgraph/edge_tests/bind.rs` | Binding: same-named functions each keep their calls, two globs give one ambiguous edge with capped candidates, no candidate gives nothing, a long `pub use` chain and deep nesting resolve on a small stack. | The C# ambiguity counter stays zero for a Rust ambiguity. |
| `src/rsgraph/edge_tests/cost.rs` | Test-only generated glob graphs and cost rows for deep, wide, diamond and cyclic imports. | Pins stable-query reuse, ordered probe replay, fixed-point invalidation, namespace and block/module behavior against the registered work bounds. |
| `src/rsgraph/edge_tests/crates.rs` | Cargo discovery: default target files, nested `<name>/main.rs` targets and the library name binding from them, `path` keys, `[lib] name`, the package-name fallback, workspace-only manifests, malformed and CRLF lines, the sidecar truth table and its write and removal by the rebuild. | Each rule has a negative case. |
| `src/rsgraph/edge_tests/emit.rs` | Emission: the exact bytes of each callee form's `uses-member`, every type position, literal, constructor and `impl`, macro-argument calls, the order after existing edges, and the test carriers with `test_def_count`. | Pins the no-edge cases: method calls, locals, closures, nested functions, generics, alias qualifiers, std calls, std traits. |
| `src/rsgraph/edge_tests/macro_scope.rs` | Names bound inside macro token trees: closure, `let`, `match`, guard, `if let`, `while let`, `for`, tuple and field-shorthand patterns and parameters of functions written in item-like macros shadow the calls beside them; a statement macro's bindings and items shadow the calls after it; generics, items, `use` and `impl` inside a tree shadow its paths and `Self`. | An expression macro can contain a nested item macro that declares a module, so its possible lowercase name shadows qualified calls inside the same invocation. A call in a macro with no such binding still gives its edge, and an expression macro's names do not leak out. |
| `src/rsgraph/edge_tests/modtree.rs` | The module tree: the no-manifest roots, orphan roots, `mod x;` from a root, a `mod.rs` and a plain file, inline modules and their out-of-line children, missing and doubled files, the first-position rule. | Each rule is observed through the edges a call across it gives. |
| `src/rsgraph/edge_tests/opaque_globs.rs` | Globs that cannot be listed: a block glob of std, of an enum, of a crate module re-exporting an external item or itself holding such a glob shadows the enclosing module's items; a module-level one shadows the library name for an expression path but not for the first segment of a `use` path. | Direct, qualified, aliased and re-exported lookup inside a macro-extended module cannot fall through to its fallback glob. Explicit listed items and unaffected calls still bind. |
| `src/rsgraph/edge_tests/path_modules.rs` | `#[path]` module declarations: file level beside the default file, `..` into a sibling directory, a non-mod-rs file's directory, inline modules of mod-rs and non-mod-rs files, the reached file's own children, the first attribute winning, a path naming its own file, and names with spaces, non-ASCII text or a leading dash. | Each rule has a decoy default or misread file that gets no edge; absolute, root-leaving, unwalked and directory-like paths, a `cfg_attr` path and a child of a `path`-carrying inline module reach nothing; no edge from a compiled file lands in a bypassed default file. |
| `src/rsgraph/edge_tests/scope.rs` | The `use` scope: groups, nested groups, `self` in a group, globs, renames and `as _`, `super` chains, `crate`, `self`, the library name by root kind, `pub use` chains and cycles, block-level uses, explicit over glob, a leading `::`. | Each form has a negative case; a `use` declaration itself gives no edge. |
| `src/rsgraph/modtree.rs` | The Rust module tree: crate roots from the package model or the no-manifest rule, `mod` declarations joined to files breadth first (by the default rule, or to the file a `#[path]` attribute names), inline modules, orphan roots, and the library each root reaches by name. | The first position to reach a file places it; a missing or doubled module file reaches nothing; only binary, test, example and bench roots reach the library by name. A `#[path]` is joined lexically against the walk's file index only and never falls back to the default file; an unread, absolute, root-leaving or unwalked one reaches nothing, and a file it reaches owns its directory. |
| `src/rsgraph/scope.rs` | Rust name tables: items by namespace per module, `use` leaves per module and block, macro-extended module scopes, and the lookups over them (`explicit`, `in_module`, `through_globs`, `in_blocks`). | The type and value namespaces are kept apart; an unresolved explicit import still shadows a glob; glob chains are walked breadth first with a visited set. An unlistable glob or a macro-extended module is opaque: ordinary lookup cannot fall through to a lower-precedence item, and a qualified lookup inside the known module cannot use its fallback globs. Only an import's first segment may pass an unlistable glob when nothing concrete binds there. |
| `src/transport.rs` | Root of the transport facts: the module declarations, the stage entry points (`sites`, `assemble`, `discover`, `sidecar_differs`, `write_sidecar`) and the public word lists (`catalogue_entries`, `role_words`, `type_role_words`, `unknown_reasons`). | Recognition runs in two stages. The extractor records provisional sites per file; graph rebuild keeps a site only when the project's usings qualify every written name to its catalogue entry, then resolves constants, settings values and scopes. |
| `src/transport/assemble.rs` | The rebuild stage: keeps or refuses each provisional site and builds the `TransportFact` list each kept site states. | A refused site takes every site below it along. A chain start is refused when a repository extension method of the same name is in scope, or when its declared receiver is a repository or predefined type. |
| `src/transport/catalogue.rs` | The closed catalogue: every MassTransit and options-binding type and member by fully qualified name, its role, its overloads with their marked value positions and lambda parameter types, the marked options properties, and the SDK implicit-using table. | The only production file that spells a catalogue name; every simple name the walker matches on is derived from these entries. An entry absent from the pinned library source is a defect, never a tuning point. |
| `src/transport/catalogue/members.rs` | The catalogue's bus root, transport, host, receive-endpoint and configured-type members, and the overload builders both member tables use. | Part of the catalogue module, so it is allowed to spell catalogue names; the member tables are split only to stay under the file size limit. |
| `src/transport/catalogue/registrations.rs` | The catalogue's registration, formatter, address, entity-name, options-binding, outbox and definition members. | Part of the catalogue module; `catalogue::members()` chains both tables in this order. |
| `src/transport/catalogue_tests.rs` | The catalogue's own checks: no entry twice, every name fully qualified and inside the catalogue, every role word known. | A new entry that names a type outside the catalogue fails here before any walk reads it. |
| `src/transport/configuration.rs` | Reading the values `appsettings.json` and `appsettings.*.json` state for a configuration key: a lenient JSON reader and the case-insensitive key lookup. | A file that does not parse, a key two entries match, a non-string value and a value outside its kind's grammar make the whole value unknown, so no value is guessed. |
| `src/transport/configuration_tests.rs` | The settings reader's parse and lookup cases. | Comments, trailing commas, duplicate keys under ASCII case and non-string values. |
| `src/transport/constants.rs` | Resolving a recorded value to its evidence class: the string literals of a joined value, read again from the site's file at their cached positions, and field references through the repository's `const string` declarations, read from the declaring file only for a field a kept site names. | A constant of a constant is followed to a fixed depth and a cycle is refused; a `static readonly` or instance field reads unknown, because its value can change at run time. A literal that is no longer at its position, or no longer reads as a name, makes the value unknown. |
| `src/transport/fact_types.rs` | The resolved fact shapes `graph.json` stores and the export publishes: `TransportFact`, `FactType`, `FactProperty`, `TransportScope`, `ValueObj`, `FieldRef`, `ConfigValue`. | Field order is the serialized key order the export keeps; every key a kind does not state is omitted. |
| `src/transport/inputs.rs` | The non-source inputs: the namespaces each project's SDK and `Using` items import, the settings files in each project directory, and the `transport-inputs.json` sidecar that notices an edit to either. | The sidecar is written only for a repository with a transport site, so a repository that states none keeps its artifact set and bytes. |
| `src/transport/inputs_tests.rs` | The project-usings reader and the settings-file name rule. | An aliased or static `Using` item adds nothing; an SDK outside the table adds nothing. |
| `src/transport/qualify.rs` | The repository-wide context the rebuild stage reads: each file's imported namespaces, the repository's types and extension methods, and the qualification of a written type name against the catalogue and the repository at once. | A written name is kept only when exactly one candidate exists and it is the catalogue type the walk matched; a repository type in scope beside it makes the name ambiguous. |
| `src/transport/scope.rs` | Transport scopes: the host, virtual host, region and account parts, the parts each transport uses, and the bus, host and address scope rules. | An address part comes from its own URI first, then from its bus; two host calls make every used bus part unknown rather than picking one. |
| `src/transport/site_types.rs` | The provisional site shapes a fragment caches: `TransportSite`, `SiteType`, `SiteValue`, `RawValue`, `Part`, `Position`, `Parsed`, `SiteFormatter`. | A value is stored only at a position the catalogue marks and only after it passes its kind's grammar, and a URI never keeps its user information, port, query or fragment. A joined value keeps its string literals only as positions, because their text can turn out to be user information once the constants are joined. |
| `src/transport/sites.rs` | The per-file syntax walk: the typed name scope (catalogue-typed lambda parameters, declared fields, properties, parameters and locals), definition base types, attributes, and receiver resolution. | A call is a site only through a typed receiver, a static catalogue type, another site's return value, or, for a chain start, any receiver at all. |
| `src/transport/sites_call.rs` | Matching one call or assignment against the catalogue overloads by argument form, and reading the values, types, formatter and filter at the positions the matched overloads agree on. | Overloads that disagree on a position give that value the `overload` reason and leave its lambda untyped, so no fact rests on a guessed overload. |
| `src/transport/syntax.rs` | Syntax readers the walk and the constant reader share: node text, string literals, the pieces of a joined string value and the string literal at a cached position. | Every form other than literals and field references joined with `+` reads as a reason word, never as a value. |
| `src/transport/syntax_tests.rs` | The literal position round trip and the cached form of a joined value. | A joined value's cached site holds its field references and no literal text. |
| `src/transport/values.rs` | Reading one stated string as a value of a known kind: the name, host, virtual host, region, account and key grammars, the address URI, host URI and AWS resource name forms, and the closed reason list. | A string outside its kind's grammar is unknown, which is what keeps a credential out of every artifact. |
| `src/transport/values_tests.rs` | The value grammars and URI forms. | Each scheme, each reason word a string alone can produce, and the removal of URI user information. |
| `src/tsgraph.rs` | Root of TypeScript-family fragment resolution: legacy relative and alias rules, the def and name tables, and `call`/`jsx-use`/`dispatch` reference resolution over the binding map `tsgraph/bindings.rs` returns. Its sibling modules own declared-mode configuration and package targets. | A file's scope map includes local defs, while its importable map excludes them; same-file bare references can reach locals but import, barrel and re-export paths cannot. A scope with no declared mode keeps the legacy ladder; a declared-mode miss never retries that ladder. External dependencies stay external. Barrel hops are bounded and the named barrel is kept as `via`. |
| `src/tsgraph/bindings.rs` | What a TS file's import and re-export statements bind: `resolve_reexports`, the barrel-following export lookup (`Exports::lookup`, capped at `BARREL_HOPS` with a per-lookup visited map), and `file_imports`, which emits a file's `import` edges, fills each edge's `bound_defs`, and returns its local-name binding map. | Split out of `tsgraph.rs` to keep that file under its size ceiling. A barrel import emits the statement edge plus at most one `via` edge per distinct declaring file; a bound definition rides the edge whose `to_file` declares it, so binding never adds an edge. The reference resolution that reads the binding map stays in `tsgraph.rs`. |
| `src/tsgraph/bindings/tests.rs` | Which definitions each import and re-export form binds, and on which edge. | Pins that binding adds no edge and changes no other field, and that every bound definition is declared in its edge's `to_file`. |
| `src/tsgraph/templates.rs` | The Angular template join run after resolution: member name rows owned by `file#Class`, precise `uses-member` edges from each template line, and the sorted `template_unsupported` rows with their `stats.ts` count. | Only an exported component with a def joins. A template no component names adds nothing. The join reads every fragment on every rebuild, so a template-only edit moves only the template's own cache key. |
| `src/tsgraph/modes.rs` | Finds the owning tsconfig chain for each indexed TypeScript-family file and validates declared module settings. | A chain with refused settings declares no mode; the file can inherit a mode from the next ancestor. |
| `src/tsgraph/modes/tests.rs` | Declared-mode ownership, implied modes, classic, and refused settings. | Synthetic config trees keep the owner walk independent of checked-in applications. |
| `src/tsgraph/packages.rs` | Reads and validates package manifests with source-ordered export and import maps. | A malformed manifest contributes no package facts to declared resolution. |
| `src/tsgraph/packages/tests.rs` | Manifest condition order and malformed-field refusal. | The recorded order matters because TypeScript selects the first matching condition. |
| `src/tsgraph/workspaces.rs` | Reads npm, yarn and pnpm workspace declarations and expands member patterns. | The nearest enclosing workspace owns bare package names; duplicate names stay ambiguous. |
| `src/tsgraph/workspaces/tests.rs` | Workspace glob, YAML and refusal cases. | Patterns match directory segments and pnpm's unrelated top-level keys are ignored. |
| `src/tsgraph/package_map.rs` | Selects package `exports` and `imports` targets by subpath and ordered conditions. | A present map that misses never invites directory probing. |
| `src/tsgraph/package_map/tests.rs` | Condition precedence, subpath patterns, null targets and custom conditions. | Explicit target order follows manifest source order. |
| `src/tsgraph/build_output.rs` | Maps declared `outDir` targets back through `rootDir` or `composite` to indexed source. | A mapped source takes precedence over a built file at the manifest target. |
| `src/tsgraph/build_output/tests.rs` | Output extension substitution and source preference. | Targets outside the declared output directory have no inferred source mapping. |
| `src/tsgraph/config.rs` | Builds the immutable module configuration and returns it with unchanged path aliases. | Repositories without a declared mode skip all manifest and workspace reads. |
| `src/tsgraph/config/tests.rs` | Inertness and declared-mode manifest refusal. | A malformed package is invisible to a no-mode repository. |
| `src/tsgraph/disclosure.rs` | Closed unresolved-import reason vocabulary and the serialized per-import record. | Declared-mode failures carry one stable reason. |
| `src/tsgraph/declared.rs` | Pure per-mode specifier resolution over indexed files and immutable module configuration. | Declared-mode misses never retry the legacy probe ladder. |
| `src/tsgraph/declared/tests.rs` | Relative, workspace, build-output and package-map resolution through the read phase. | Each case names only indexed implementation files. |
| `src/tsgraph/extend.rs` | Extends a resolved graph with TypeScript definitions, edges, counters and unresolved imports. | The read phase runs once before pure per-import resolution. |
| `src/usage.rs` | One `usage: devscout …` constant per verb and per verb group. | The single definition of every usage line: a verb names its constant where it would print the line, and `tests/docs_sync.rs` renders these values into `cli.md` and fails on a usage literal anywhere else in `src/`. |
| `src/walk.rs` | Source-tree walking, `SKIP_DIRS`, `SOURCE_EXT` and the default file-purpose heuristic (with a text-taking form for bytes a caller already read). Every entry point takes the run's `Ignored` set. | `SOURCE_EXT` is the one list of indexed extensions; a file outside it is invisible to every verb. `.rs` is in it unconditionally: no `Cargo.toml` probe, flag or setting gates Rust. `SKIP_DIRS` matches a single path component exactly, at any depth. A path the `Ignored` set covers is skipped too, by the same component rule, before `SKIP_DIRS` is consulted; an empty set skips nothing and computes no relative path. Roots handed in are absolute and normalised. |
| `src/walk/ignored.rs` | The crate's one Git ignore reader: `Ignored::for_scope` runs one `git ls-files --others --ignored --exclude-standard --directory -z` over a scope's pathspecs, and `covers` answers for a repo-relative path. | Git owns the rule, so nested ignore files, negations, `core.excludesFile` and the tracked-file exception need no parser here. A root with no `.git` entry yields the empty set and spawns nothing. In a Git root the answer is fail-closed: a spawn failure, a non-zero exit or an unparseable record is one error, never a partial set, and no caller mistakes "could not check" for "nothing is ignored". |

## Where a change goes

| To add or change | Go to |
| --- | --- |
| A C# construct the index should record | `src/extract/walk.rs` for the walker, `src/extract/members.rs` or `src/extract/type_defs.rs` for the member/def facts it carries, `src/graph/fragment_types.rs` for the fragment shape and `src/graph/paths.rs` for its cache version, a fixture under `fixtures/` with its README, the construct's row in `docs/reference/csharp-constructs.md`. |
| A resolution rule or tier for `uses-member` | `src/resolve/ladder.rs` for a type-resolution step, `src/resolve/receiver.rs` for receiver typing, `src/resolve/assembly.rs` for a heuristic tier, with the test file of the same topic under `src/resolve/tests/`; if the edge gains a tag, `src/graph/edge.rs` (`Edge`) and `src/graph/artifact.rs` (schema version), and `src/audit/score.rs` (tier scoring); then re-run the semantic audit on the fixture and record the numbers in `docs/benchmarks/`. |
| A Rust construct the index should record | `src/extract/rs_fragment.rs` for the walker, kinds, identity and purpose, `src/extract/rs_fragment_types.rs` plus `src/graph/fragment_types.rs` for the fragment shape and `src/graph/paths.rs` for its cache generation, `src/rsgraph.rs` for the def and name rows, `fixtures/rust-definitions/` with its README, the construct's row in `docs/reference/rust-constructs.md`. |
| A Rust path the edge pass should bind | `src/extract/rs_sites.rs` or `src/extract/rs_macro_scan.rs` for the site, `src/extract/rs_uses.rs` for a `use` form, `src/extract/rs_attrs.rs` for an attribute fact, `src/rsgraph/crates.rs` for a crate root, `src/rsgraph/modtree.rs` for the module tree, `src/rsgraph/scope.rs` and `src/rsgraph/bind.rs` for binding, `src/rsgraph/emit.rs` for the edge, `src/graph/paths.rs` for the cache generation when the fragment shape changes, `fixtures/rust-edges/` or `fixtures/rust-targets/` with its README, and `docs/reference/rust-constructs.md`. |
| A TypeScript edge kind or alias rule | `src/extract/ts_fragment.rs` (and `src/extract/ts_purpose.rs` for a purpose-only change); legacy aliases in `src/tsgraph.rs`, declared-mode resolution in its `modes`, `config`, `workspaces`, `packages`, `package_map`, `build_output` and `declared` siblings; fixtures under `fixtures/ts-grammar/` and `fixtures/module-resolution/`; the limit in `docs/limitations.md` and the mode table in `docs/reference/typescript-modules.md`. |
| A TypeScript reference kind in the query verbs | Admission in `src/query/ts_edges.rs`, its table in `TsTables` (`src/query/refs_tables.rs`), JSON in `src/query/json/ts_tables.rs`, text and compact in `src/render/ts_tables.rs`, its `why` word in `src/query/why.rs`, the walk in `inbound_walk_kinds`, a fixture case under `fixtures/ts-query-edges/` and its test in `tests/cli_ts_query_edges.rs`. |
| A query verb | A model and builder in the matching `src/query/*.rs` (`find.rs`, `refs.rs`/`refs_tables.rs`, `read.rs`, `impact.rs`, `coverage.rs` for tests-reaching-a-symbol), the matching test topic under `src/query/tests/`, text and compact renderers in the matching `src/render/*.rs`, JSON rendering plus argument parsing in the matching `src/cli/*.rs` file, its usage line in `src/usage.rs`, a `src/telemetry.rs` `record` call at the verb's own resolved-answer arm in that same file, the command table in `docs/reference/cli.md`, an integration test under `tests/`. |
| A CLI flag | The verb's argument loop and usage string in its own `src/cli/*.rs` file (`refs.rs`, `read.rs`, `impact.rs`, `coverage.rs`, `find.rs`), the matching option on the query model, `docs/reference/cli.md`. |
| A fixture | A directory under `fixtures/` with a README stating what it pins, plus the integration test under `tests/` that drives the binary against it; fixture vocabulary is invented, never copied from a real codebase. |
| A DI-registration or dispatch edge | `src/extract/refs.rs` for the registration shape rule and `src/graph/fragment_types.rs` for `Fragment.registrations`/`FragDef.override_methods`, with `src/graph/paths.rs` for the fragment cache generation; `src/resolve/dispatch.rs` for resolution and `src/graph/edge.rs`/`src/graph/artifact.rs` for the edge kind and schema version; `src/query/index.rs` and `src/query/dispatch.rs` for the adjacency wiring and `IndexOptions.include_dispatch`, `src/query/refs.rs`/`refs_tables.rs`/`impact.rs`/`coverage.rs` for the verbs that read it; `src/render/refs.rs`, `src/query/json.rs` and `src/cli/args.rs` for text/compact/JSON rendering and `--no-dispatch`; a fixture under `fixtures/` with its README, an integration test under `tests/`. |
| A markup format | `src/markup.rs` for the facts and `src/walk.rs` for the extension. |
| An Angular template form | `src/ngtemplate/markup.rs` or `src/ngtemplate/scope.rs` for a binding or a local, `src/ngtemplate/expr.rs` for expression syntax, `src/extract/ts_component.rs` for a decorator fact, `src/tsgraph/templates.rs` for the edge, `src/graph/paths.rs` for the cache generation, `fixtures/angular-templates/` with its README and `tests/cli_angular_templates.rs`. |
| A semantic-truth case, profile, or fault control | The case manifest and fixture pack under `fixtures/csharp-truth/`, the relevant `src/truth/*.rs` module (`manifest.rs` for the schema, `fault_controls.rs` for a registered mutation, `freshness.rs` for a transformation pair, `profile.rs`/`capability.rs` for a dimension or axis, `producer_reader.rs` for a new committed-bytes reader), the matching `tests/semantic_truth_*.rs` file, `capability-matrix.json`/`red-baseline.json`/`reports/syntax-only.json`/`reports/enriched.json` regenerated and re-committed where the change touches them. |
| A hook behaviour | `src/hookio.rs` for the rewrite, `src/store.rs` for what is remembered, `src/initcmd.rs` for how it is installed. |
| A persisted artifact or its location | `src/graph/artifact.rs` (the file shape) or `src/graph/paths.rs` (its location), or `src/manifest.rs` for the manifest, `src/repo.rs` for where roots and the git common dir come from, the storage table in `docs/reference/storage.md`. |
| A compiler-fact artifact | `src/graph/compiler_facts/artifact.rs` for the header shape, `admit.rs` for an admission or refusal rule, `expectations.rs` for a compiled-in expectation, `run.rs` for local-acquisition process control, `paths.rs` for its location; `src/cli/compiler_facts.rs` for the verb group; a fixture under `fixtures/` with its README, an integration test under `tests/`, the tables in `docs/reference/storage.md` and `docs/reference/environment.md`. |
| An environment variable | Read it where it applies, list it in `docs/reference/environment.md`. |
| A module | Declare it in `src/lib.rs` with a one-line doc, add its row above, keep it clean under `cargo clippy --all-targets -- -D warnings` as `CONTRIBUTING.md` asks. |

## Tests

Unit tests live beside the code they cover; integration tests under `tests/` drive the built
binary against `fixtures/`. `tests/conformance.rs` runs the public conformance suite;
`tests/semantic_audit.rs` and `tests/semantic_audit_direction.rs` score the resolver against the
committed oracle snapshots that CI also regenerates. Nothing in the suite reaches the network.