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
use fs;
use io;
use ;
use ;
use Serialize;
use cratelayout;
use crate;
// ---------------------------------------------------------------------------
// Path resolution (private helpers for the graph directory and cache paths).
// ---------------------------------------------------------------------------
// The def/ref graph lives in the git COMMON dir (shared by every linked
// worktree, alongside the manifest), one level down; a non-git root falls
// back to `<root>/.scout/graph/`.
pub
/// Path to the graph artifact (`graph.json`) for `root`.
/// Path to the project-model staleness sidecar for `root`.
///
/// Holds exactly the bytes `graph.json`'s `units` array would carry -- the
/// serialized `Vec<GraphUnit>` and nothing else. `map` compares this file
/// against a freshly discovered model to notice a `.csproj` edit, which no
/// mtime in the fragments index can see: `.csproj` is not a `SOURCE_EXT`, so
/// editing one moves no graph file and `index_is_stale` stays false. Written
/// by `rebuild_graph` only when a model exists and DELETED when one does not,
/// so a repo that never had a `.csproj` never grows the file and one whose
/// last `.csproj` was removed still sees a difference on the next run.
/// Path to the Rust crate-model sidecar for `root`.
///
/// Holds the serialized Cargo package model (`rsgraph::crates::RustCrates`)
/// the last rebuild resolved Rust paths against. `map` compares it with a
/// freshly discovered model to notice a `Cargo.toml` edit, which moves no
/// walked mtime. Written only when a package is in scope and deleted
/// otherwise, the same rule as `project_units_path`.
/// Path to the transport-input sidecar for `root`.
///
/// Holds the serialized `transport::TransportInputs` the last rebuild read.
/// `map` compares it with freshly discovered inputs to notice an edit of a
/// settings file or of a project's implicit usings, which moves no graph
/// source. Written only when a fragment holds a transport site and deleted
/// otherwise.
// The version in both cache filenames is the fragment SCHEMA version, bumped
// whenever the extractor starts recording something old cached fragments
// lack -- v10 added def `type_params`/`base_generic_args` and the new
// `ctor-param` ref kind (with its `args` field), v11 added markup graph
// facts (a `.xaml` file's fragment now carries the `x:Class` def and its
// element/`x:Bind` refs, where every cached markup fragment before it carried
// names only), v12 added the TS/JS reference fragment, v13 added def
// `propertyTypes` and ref `receiverPropertyOwner` plus the ref
// `receiverCallOwner`/`receiverCallMember` pair, plus the foreach
// element-type fact -- no new field (it settles into the existing
// `receiver_type`), but a cached fragment from before it can still disagree
// with a fresh one for the same unchanged file, so it rides the same bump
// rather than skipping it. v16 added the ref `receiverBase` flag, set only
// for a `base.` qualifier -- a cached v15 fragment carries none, so every
// `base.` receiver would silently resolve (or fail to resolve) as if it
// were a plain `this.` receiver. v17 added no field: the extractor now
// blanks the inactive arms of `#if`/`#else` groups before parsing, so a
// cached v16 fragment of an unchanged file can carry twin defs and refs
// from both arms that a fresh one no longer records -- the same
// "disagrees for an unchanged file" case that moved v13, so it rides a
// bump too. v18 added def `methodParams` and ref `receiverLambda`, the
// untyped-lambda-parameter callee slot -- a cached v17 fragment carries
// neither, so every per-overload parameter shape and every untyped-lambda
// callee-slot lookup they back would silently see no candidates. v19
// added def `overrideMethods` (the `override`-marked method names a type
// declares) and the fragment-level `registrations` list (each two-type-argument
// DI service registration a file's invocations record) -- a cached v18
// fragment carries neither, so every override-chain lookup and every
// registration-driven `implements` edge would silently see no facts. v20
// added ref `receiverNullable` and `lambdaArgArity` -- a cached v19
// fragment carries neither, so a `T?` receiver's own `Nullable<T>.Value`/
// `HasValue`/`GetValueOrDefault` unwrap would silently keep resolving
// against `T`'s own same-named member, and a delegate-shaped argument whose
// own parameter count disagrees with a candidate's delegate parameter
// shape would silently keep binding it. v21 fills ref `typeArgCount` on
// every `uses-member` ref -- a cached v20 fragment carries `null` there, so
// a member qualifier written with a type-argument list would silently keep
// binding a same-named sibling of the WRONG arity, and a bare qualifier
// would silently keep binding whichever same-named sibling the def index
// happened to order first. v22 added def `baseTypeArgs` (a base's NESTED
// generic-argument descriptors, alongside the already-flat
// `baseGenericArgs`), the fragment-level `publishes` list (each
// message-bus publish-site fact a file's invocations record), def
// `propertyMessageArgs` (the single type arguments a type's own properties
// wrap), publish `enclosingMethod` (set when a publish hands on its own
// caller's message, which makes the enclosing method a forwarding
// wrapper), the fragment-level `handlerRegistrations` list (each
// one-type-argument handler registration a file's invocations record), and
// publish sites whose message is named by the call itself under a verb
// this engine does not ship -- a cached v21 fragment carries none of them,
// so a nested base's inner type argument (`IConsumer<Batch<T>>`'s `T`),
// every publish-site fact and every message vocabulary a repository
// declares about itself would silently be missing from a reused cache.
// v23 added def `arrayMessageBases` (the array-marked subset of a def's own
// `bases`, for a base whose message-position argument is an array type) --
// a cached v22 fragment carries none, so a single message and an array of
// it would read as the same base-list argument. v22 never shipped in an
// accepted or released build; every run that wrote one, including this
// lane's own measurement runs, is superseded by the rename same as any
// other generation.
// v24 drops explicit interface implementations (`IFace.Member` declarations)
// from a def's `methods`, `methodReturns`, `methodReturnArgs`,
// `nonPublicMethods`, `properties` and `propertyTypes`, and from the
// class-scope receiver facts a bare property name reads -- a cached v23
// fragment of an unchanged file still lists them, so `this.M()`, a property
// hop and a bare name inside the implementing type would keep binding a
// member C# lookup never finds there.
// v25 adds `namespace` to a plain `usings` entry: the namespace the directive
// is declared in, which bare type-name resolution reads to consult a using
// only at its own namespace level, after that level's types. A cached v24
// fragment of an unchanged file carries no such key and reads every using as
// declared at compilation-unit level, so a using written inside a namespace
// block would lose to a type in a shallower namespace and one written in a
// sibling block would still apply. The same generation keeps the written
// qualifier on a publish `message` the call names itself (`new Ns.M()` is
// `Ns.M`, no longer `M`), so the message resolves through that qualifier; a
// cached v24 fragment of an unchanged file still carries the bare name, which
// the call site's namespace can bind to a different same-named type.
// v26 adds ref `methodTypeArgCount` (the length of a type-argument list
// written on the invoked member itself), def `methodTypeArities` (each
// overload's own type-parameter count, aligned with `methodArities`) and
// extension-entry `typeParamCount`, and keeps two extension overloads that
// differ only in their type-parameter count as separate entries. A cached v25
// fragment of an unchanged file carries none of them, so `x.M<A, B>()` would
// keep binding an `M<T>` overload C# refuses, where a fresh fragment of the
// same file binds only an overload with two type parameters.
// v27 adds ref `lambdaArgShape` (whether each positional lambda literal's body
// can only produce a value, and whether its parameters are implicitly typed),
// def `methodTypeParamSites` (where each overload's own type parameters occur
// in its parameter types, aligned with `methodParams`) and def
// `delegateSignature` (a delegate's return and where its type parameters
// occur). A cached v26 fragment of an unchanged file carries none of them, so
// `list.Any(m => true)` would keep binding an `Any(Action<F>)` or an
// uninferable `Any<T>(FilterDelegate<IX<T>>)` overload C# refuses, where a
// fresh fragment of the same file skips both.
// v28 adds the Rust fragment arm (`rs: 1`). No fragment an earlier build
// wrote changes shape; the bump exists so a build without that arm never
// meets a Rust fragment, because one cache entry no arm accepts makes the
// whole cache fail to parse and every file reparse on every run.
// v29 adds Rust resolution facts (`modFiles`, `ctors`, `tests`, `impls`,
// `uses`, `blocks`, `sites`) and module scopes extended by item macros.
// A cached v28 Rust fragment lacks these facts, so edges of an unchanged
// file would be missing and globs reaching an item-macro module could be
// resolved incorrectly.
// v30 appends Rust `modPaths` (the file a `#[path]` attribute names for a
// `mod x;` declaration) and lists in `tests` the functions a `cfg_attr`
// marks as tests. A cached v29 Rust fragment of an unchanged file carries
// neither, so its `#[path]` modules would keep reaching the default file
// rustc does not compile, and its `cfg_attr` tests would stay unmarked.
// v31 adds `bound_defs` to a TS `import` edge: the definitions its named,
// default and type-only bindings resolve to. The fragments themselves are
// unchanged, but an older cache pair of an unchanged repository would let `map`
// skip the rebuild and keep a `graph.json` whose import edges bind nothing,
// so every TS import row would stay missing until some file changed.
// v32 adds ref `receiverTypeQualified` and fact `qualifiedType` -- the
// written path of a receiver's own declared type, alongside the bare last
// segment `receiverType`/`type` already carry. A cached v31 fragment of an
// unchanged file carries neither, so a receiver declared `Outer.Type` would
// keep resolving through its tail, which the ladder's using and
// graph-wide-uniqueness steps answer with whatever same-named type they
// reach, where a fresh fragment of the same file resolves the written path.
// v33 adds ref `argTypes`, an ordered static-type fact for each written call
// argument, and records conversion, variance and selected result descriptors
// in the existing `methodParams` table. A cached v32 fragment has none of
// these facts, so a same-count overload that rejects the arguments could
// still bind precisely ahead of an applicable base or extension method.
// v34 records every dotted name the C# extractor builds out of a qualified
// name's own tokens, with the whitespace, line breaks and comments between
// them left out, so `Widgets . Widget` reads as `Widgets.Widget`. A cached
// v33 fragment of an unchanged file keeps the raw text in its receiver and
// type-reference fields, and no lookup matches that text: the written path
// reaches the ladder with trivia inside it, the last-dot split yields a name
// with a blank or a comment in it, and every precise edge the name earned
// degrades to a bare-name guess. A fresh extraction of the same unchanged
// bytes disagrees, which is why the pair has to move.
// v35 records an interpolated string argument under its own descriptor and marks a type declared
// `[InterpolatedStringHandler]` in the `methodParams` table. A cached v33 fragment has neither, and
// a cached v34 pair carries the other change's facts only, so a map that reused either would still
// compare the interpolated string as a plain `string` and veto the overload C# selects.
// v36 adds no extraction field: it stamps the pair with the worktree that
// wrote it, and changes the file shape from a bare rel-keyed object to an
// `{owner, entries}` one. The owner is the writing worktree's Git
// administrative directory, so a fragment another worktree extracted is
// never reused here even when the relative path and the content key both
// match: another worktree's scope and its own configuration are what those
// bytes were read under. A v35 payload carries no owner and does not parse
// as this generation either, so the bump is also what stops a pre-stamp
// cache from being read.
// v37 records identifier-argument publish sites for unguarded dispatch-root
// verbs. A cached v36 fragment of an unchanged file lacks those records, so
// its newly admitted calls would still produce no hop until re-extracted.
// v38 appends import-equals, export-equals, namespace re-export and dynamic
// import facts to TS fragments. Older fragments cannot supply those facts.
// v39 is another extractor change's generation (file-scope TS/JS
// declarations), so it is skipped here rather than reused for different facts.
// v40 scopes every C# member-table entry to the constructs that declare it and
// enters `var (…)` deconstruction names as taken-but-unknown. A cached v38
// fragment of an unchanged file still types a bare argument or receiver from a
// declaration its use never sees, so a map reusing it would keep vetoing the
// overload C# selects.
// v41 appends the transport configuration sites a C# file states. A cached v40
// fragment of an unchanged file carries none, so a map reusing it would record
// no transport fact for that file.
// v42 appends the `@Component` facts of a TS file and adds the `.html`
// template fragment. A cached v41 fragment of an unchanged component carries
// no members and no template, so a map reusing it would give no template edge.
// v43 joins the v42 facts with the file-scope TS/JS declarations and local
// references that v39 carried on its own. A cached v42 fragment has no local
// declarations, and a cached v39 one has no v40-v42 facts, so neither is reused.
// v44 moves how a default export binds: a cached v43 fragment of a file whose
// `export { X as default }` clause names a local declaration carries no
// `default` name, so a map reusing it would keep the importer unbound.
// The rename IS the invalidation mechanism:
// pre-bump caches stop being found, every file reparses
// once, no reader carries version-compat logic. Writers delete every
// superseded generation (see `remove_superseded_caches`).
pub
/// The fragment generation this build reads and writes.
///
/// Parsed from the cache file name: the rename IS the invalidation mechanism,
/// so the number a reader reports can only ever be the one this build's own
/// path carries.
pub
// Every generation below the current one, not just the immediately previous:
// a repo mapped last under v1 and never since would otherwise keep its v1
// pair forever.
pub const SUPERSEDED_CACHE_FILES: & = &;
pub
// ---------------------------------------------------------------------------
// Atomic writes -- tmp file in the SAME directory as the target (so
// the final `rename` is same-filesystem and therefore atomic), unique name
// so concurrent writers (this process's own sequential graph/fragments/index
// writes, or a genuinely concurrent process) never collide. Writing to a
// tmp file and renaming is a deliberate upgrade over an in-place write --
// output BYTES are unaffected, only the write's crash-safety.
// ---------------------------------------------------------------------------
static TMP_COUNTER: AtomicU64 = new;
pub
pub