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
use fs;
use io;
use ;
use ;
use Serialize;
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.
// 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.
// 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
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