@mka-rainmaker/ama 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +149 -0
  3. package/dist/analyzers/baseline/analyzer.d.ts +47 -0
  4. package/dist/analyzers/baseline/analyzer.d.ts.map +1 -0
  5. package/dist/analyzers/baseline/analyzer.js +84 -0
  6. package/dist/analyzers/baseline/analyzer.js.map +1 -0
  7. package/dist/analyzers/baseline/c.d.ts +12 -0
  8. package/dist/analyzers/baseline/c.d.ts.map +1 -0
  9. package/dist/analyzers/baseline/c.js +56 -0
  10. package/dist/analyzers/baseline/c.js.map +1 -0
  11. package/dist/analyzers/baseline/config.d.ts +21 -0
  12. package/dist/analyzers/baseline/config.d.ts.map +1 -0
  13. package/dist/analyzers/baseline/config.js +32 -0
  14. package/dist/analyzers/baseline/config.js.map +1 -0
  15. package/dist/analyzers/baseline/csharp.d.ts +9 -0
  16. package/dist/analyzers/baseline/csharp.d.ts.map +1 -0
  17. package/dist/analyzers/baseline/csharp.js +107 -0
  18. package/dist/analyzers/baseline/csharp.js.map +1 -0
  19. package/dist/analyzers/baseline/go.d.ts +11 -0
  20. package/dist/analyzers/baseline/go.d.ts.map +1 -0
  21. package/dist/analyzers/baseline/go.js +66 -0
  22. package/dist/analyzers/baseline/go.js.map +1 -0
  23. package/dist/analyzers/baseline/java.d.ts +9 -0
  24. package/dist/analyzers/baseline/java.d.ts.map +1 -0
  25. package/dist/analyzers/baseline/java.js +50 -0
  26. package/dist/analyzers/baseline/java.js.map +1 -0
  27. package/dist/analyzers/baseline/javascript.d.ts +10 -0
  28. package/dist/analyzers/baseline/javascript.d.ts.map +1 -0
  29. package/dist/analyzers/baseline/javascript.js +55 -0
  30. package/dist/analyzers/baseline/javascript.js.map +1 -0
  31. package/dist/analyzers/baseline/kotlin.d.ts +11 -0
  32. package/dist/analyzers/baseline/kotlin.d.ts.map +1 -0
  33. package/dist/analyzers/baseline/kotlin.js +67 -0
  34. package/dist/analyzers/baseline/kotlin.js.map +1 -0
  35. package/dist/analyzers/baseline/paths.d.ts +6 -0
  36. package/dist/analyzers/baseline/paths.d.ts.map +1 -0
  37. package/dist/analyzers/baseline/paths.js +17 -0
  38. package/dist/analyzers/baseline/paths.js.map +1 -0
  39. package/dist/analyzers/baseline/php.d.ts +11 -0
  40. package/dist/analyzers/baseline/php.d.ts.map +1 -0
  41. package/dist/analyzers/baseline/php.js +76 -0
  42. package/dist/analyzers/baseline/php.js.map +1 -0
  43. package/dist/analyzers/baseline/python.d.ts +10 -0
  44. package/dist/analyzers/baseline/python.d.ts.map +1 -0
  45. package/dist/analyzers/baseline/python.js +63 -0
  46. package/dist/analyzers/baseline/python.js.map +1 -0
  47. package/dist/analyzers/baseline/rust.d.ts +10 -0
  48. package/dist/analyzers/baseline/rust.d.ts.map +1 -0
  49. package/dist/analyzers/baseline/rust.js +45 -0
  50. package/dist/analyzers/baseline/rust.js.map +1 -0
  51. package/dist/analyzers/baseline/swift.d.ts +11 -0
  52. package/dist/analyzers/baseline/swift.d.ts.map +1 -0
  53. package/dist/analyzers/baseline/swift.js +19 -0
  54. package/dist/analyzers/baseline/swift.js.map +1 -0
  55. package/dist/analyzers/baseline/treesitter.d.ts +11 -0
  56. package/dist/analyzers/baseline/treesitter.d.ts.map +1 -0
  57. package/dist/analyzers/baseline/treesitter.js +87 -0
  58. package/dist/analyzers/baseline/treesitter.js.map +1 -0
  59. package/dist/analyzers/baseline/walk.d.ts +26 -0
  60. package/dist/analyzers/baseline/walk.d.ts.map +1 -0
  61. package/dist/analyzers/baseline/walk.js +76 -0
  62. package/dist/analyzers/baseline/walk.js.map +1 -0
  63. package/dist/analyzers/registry.d.ts +19 -0
  64. package/dist/analyzers/registry.d.ts.map +1 -0
  65. package/dist/analyzers/registry.js +43 -0
  66. package/dist/analyzers/registry.js.map +1 -0
  67. package/dist/analyzers/sfc/analyzer.d.ts +17 -0
  68. package/dist/analyzers/sfc/analyzer.d.ts.map +1 -0
  69. package/dist/analyzers/sfc/analyzer.js +141 -0
  70. package/dist/analyzers/sfc/analyzer.js.map +1 -0
  71. package/dist/analyzers/sidecar/analyzer.d.ts +29 -0
  72. package/dist/analyzers/sidecar/analyzer.d.ts.map +1 -0
  73. package/dist/analyzers/sidecar/analyzer.js +114 -0
  74. package/dist/analyzers/sidecar/analyzer.js.map +1 -0
  75. package/dist/analyzers/sidecar/protocol.d.ts +508 -0
  76. package/dist/analyzers/sidecar/protocol.d.ts.map +1 -0
  77. package/dist/analyzers/sidecar/protocol.js +102 -0
  78. package/dist/analyzers/sidecar/protocol.js.map +1 -0
  79. package/dist/analyzers/types.d.ts +46 -0
  80. package/dist/analyzers/types.d.ts.map +1 -0
  81. package/dist/analyzers/types.js +2 -0
  82. package/dist/analyzers/types.js.map +1 -0
  83. package/dist/analyzers/typescript/analyzer.d.ts +126 -0
  84. package/dist/analyzers/typescript/analyzer.d.ts.map +1 -0
  85. package/dist/analyzers/typescript/analyzer.js +1600 -0
  86. package/dist/analyzers/typescript/analyzer.js.map +1 -0
  87. package/dist/cli/commands/cycles.d.ts +6 -0
  88. package/dist/cli/commands/cycles.d.ts.map +1 -0
  89. package/dist/cli/commands/cycles.js +27 -0
  90. package/dist/cli/commands/cycles.js.map +1 -0
  91. package/dist/cli/commands/files.d.ts +6 -0
  92. package/dist/cli/commands/files.d.ts.map +1 -0
  93. package/dist/cli/commands/files.js +33 -0
  94. package/dist/cli/commands/files.js.map +1 -0
  95. package/dist/cli/commands/impact.d.ts +18 -0
  96. package/dist/cli/commands/impact.d.ts.map +1 -0
  97. package/dist/cli/commands/impact.js +113 -0
  98. package/dist/cli/commands/impact.js.map +1 -0
  99. package/dist/cli/commands/lifecycle.d.ts +5 -0
  100. package/dist/cli/commands/lifecycle.d.ts.map +1 -0
  101. package/dist/cli/commands/lifecycle.js +83 -0
  102. package/dist/cli/commands/lifecycle.js.map +1 -0
  103. package/dist/cli/commands/query.d.ts +31 -0
  104. package/dist/cli/commands/query.d.ts.map +1 -0
  105. package/dist/cli/commands/query.js +187 -0
  106. package/dist/cli/commands/query.js.map +1 -0
  107. package/dist/cli/commands/search.d.ts +21 -0
  108. package/dist/cli/commands/search.d.ts.map +1 -0
  109. package/dist/cli/commands/search.js +160 -0
  110. package/dist/cli/commands/search.js.map +1 -0
  111. package/dist/cli/commands/status.d.ts +6 -0
  112. package/dist/cli/commands/status.d.ts.map +1 -0
  113. package/dist/cli/commands/status.js +63 -0
  114. package/dist/cli/commands/status.js.map +1 -0
  115. package/dist/cli/commands/sync.d.ts +6 -0
  116. package/dist/cli/commands/sync.d.ts.map +1 -0
  117. package/dist/cli/commands/sync.js +57 -0
  118. package/dist/cli/commands/sync.js.map +1 -0
  119. package/dist/cli/emit.d.ts +9 -0
  120. package/dist/cli/emit.d.ts.map +1 -0
  121. package/dist/cli/emit.js +10 -0
  122. package/dist/cli/emit.js.map +1 -0
  123. package/dist/cli/index.d.ts +37 -0
  124. package/dist/cli/index.d.ts.map +1 -0
  125. package/dist/cli/index.js +128 -0
  126. package/dist/cli/index.js.map +1 -0
  127. package/dist/cli/paths.d.ts +7 -0
  128. package/dist/cli/paths.d.ts.map +1 -0
  129. package/dist/cli/paths.js +10 -0
  130. package/dist/cli/paths.js.map +1 -0
  131. package/dist/cli/query-runner.d.ts +13 -0
  132. package/dist/cli/query-runner.d.ts.map +1 -0
  133. package/dist/cli/query-runner.js +33 -0
  134. package/dist/cli/query-runner.js.map +1 -0
  135. package/dist/graph/dispatch.d.ts +17 -0
  136. package/dist/graph/dispatch.d.ts.map +1 -0
  137. package/dist/graph/dispatch.js +82 -0
  138. package/dist/graph/dispatch.js.map +1 -0
  139. package/dist/graph/id.d.ts +19 -0
  140. package/dist/graph/id.d.ts.map +1 -0
  141. package/dist/graph/id.js +17 -0
  142. package/dist/graph/id.js.map +1 -0
  143. package/dist/graph/index.d.ts +6 -0
  144. package/dist/graph/index.d.ts.map +1 -0
  145. package/dist/graph/index.js +4 -0
  146. package/dist/graph/index.js.map +1 -0
  147. package/dist/graph/types.d.ts +71 -0
  148. package/dist/graph/types.d.ts.map +1 -0
  149. package/dist/graph/types.js +52 -0
  150. package/dist/graph/types.js.map +1 -0
  151. package/dist/indexer/debouncer.d.ts +32 -0
  152. package/dist/indexer/debouncer.d.ts.map +1 -0
  153. package/dist/indexer/debouncer.js +81 -0
  154. package/dist/indexer/debouncer.js.map +1 -0
  155. package/dist/indexer/ignore.d.ts +55 -0
  156. package/dist/indexer/ignore.d.ts.map +1 -0
  157. package/dist/indexer/ignore.js +170 -0
  158. package/dist/indexer/ignore.js.map +1 -0
  159. package/dist/indexer/indexer.d.ts +112 -0
  160. package/dist/indexer/indexer.d.ts.map +1 -0
  161. package/dist/indexer/indexer.js +392 -0
  162. package/dist/indexer/indexer.js.map +1 -0
  163. package/dist/indexer/watcher.d.ts +50 -0
  164. package/dist/indexer/watcher.d.ts.map +1 -0
  165. package/dist/indexer/watcher.js +86 -0
  166. package/dist/indexer/watcher.js.map +1 -0
  167. package/dist/mcp/build-info.d.ts +16 -0
  168. package/dist/mcp/build-info.d.ts.map +1 -0
  169. package/dist/mcp/build-info.js +54 -0
  170. package/dist/mcp/build-info.js.map +1 -0
  171. package/dist/mcp/http.d.ts +18 -0
  172. package/dist/mcp/http.d.ts.map +1 -0
  173. package/dist/mcp/http.js +145 -0
  174. package/dist/mcp/http.js.map +1 -0
  175. package/dist/mcp/server.d.ts +22 -0
  176. package/dist/mcp/server.d.ts.map +1 -0
  177. package/dist/mcp/server.js +401 -0
  178. package/dist/mcp/server.js.map +1 -0
  179. package/dist/mcp/session.d.ts +155 -0
  180. package/dist/mcp/session.d.ts.map +1 -0
  181. package/dist/mcp/session.js +319 -0
  182. package/dist/mcp/session.js.map +1 -0
  183. package/dist/query/service.d.ts +329 -0
  184. package/dist/query/service.d.ts.map +1 -0
  185. package/dist/query/service.js +959 -0
  186. package/dist/query/service.js.map +1 -0
  187. package/dist/runtime/entrypoint.d.ts +11 -0
  188. package/dist/runtime/entrypoint.d.ts.map +1 -0
  189. package/dist/runtime/entrypoint.js +22 -0
  190. package/dist/runtime/entrypoint.js.map +1 -0
  191. package/dist/runtime/quiet-sqlite-warning.d.ts +14 -0
  192. package/dist/runtime/quiet-sqlite-warning.d.ts.map +1 -0
  193. package/dist/runtime/quiet-sqlite-warning.js +26 -0
  194. package/dist/runtime/quiet-sqlite-warning.js.map +1 -0
  195. package/dist/runtime/wasm-tier.d.ts +2 -0
  196. package/dist/runtime/wasm-tier.d.ts.map +1 -0
  197. package/dist/runtime/wasm-tier.js +54 -0
  198. package/dist/runtime/wasm-tier.js.map +1 -0
  199. package/dist/store/memory.d.ts +54 -0
  200. package/dist/store/memory.d.ts.map +1 -0
  201. package/dist/store/memory.js +210 -0
  202. package/dist/store/memory.js.map +1 -0
  203. package/dist/store/sqlite.d.ts +38 -0
  204. package/dist/store/sqlite.d.ts.map +1 -0
  205. package/dist/store/sqlite.js +298 -0
  206. package/dist/store/sqlite.js.map +1 -0
  207. package/dist/store/types.d.ts +76 -0
  208. package/dist/store/types.d.ts.map +1 -0
  209. package/dist/store/types.js +2 -0
  210. package/dist/store/types.js.map +1 -0
  211. package/package.json +59 -0
@@ -0,0 +1,392 @@
1
+ import * as crypto from "node:crypto";
2
+ import * as fs from "node:fs";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+ import { BaselineAnalyzer } from "../analyzers/baseline/analyzer.js";
6
+ import { cSpec, cppSpec } from "../analyzers/baseline/c.js";
7
+ import { csharpSpec } from "../analyzers/baseline/csharp.js";
8
+ import { goSpec } from "../analyzers/baseline/go.js";
9
+ import { javaSpec } from "../analyzers/baseline/java.js";
10
+ import { javascriptSpec } from "../analyzers/baseline/javascript.js";
11
+ import { kotlinSpec } from "../analyzers/baseline/kotlin.js";
12
+ import { phpSpec } from "../analyzers/baseline/php.js";
13
+ import { pythonSpec } from "../analyzers/baseline/python.js";
14
+ import { rustSpec } from "../analyzers/baseline/rust.js";
15
+ import { swiftSpec } from "../analyzers/baseline/swift.js";
16
+ import { AnalyzerRegistry } from "../analyzers/registry.js";
17
+ import { SfcAnalyzer } from "../analyzers/sfc/analyzer.js";
18
+ import { TypeScriptAnalyzer } from "../analyzers/typescript/analyzer.js";
19
+ import { deriveDispatchEdges } from "../graph/index.js";
20
+ import { InMemoryStore } from "../store/memory.js";
21
+ import { MAX_FILE_SIZE_BYTES, isIgnoredPath, loadIgnoreRules, withNestedIgnore, } from "./ignore.js";
22
+ /**
23
+ * Bumped whenever the persisted store's schema or the shape of what we write
24
+ * into it changes. A persisted index stamped with a different version is treated
25
+ * as unusable and rebuilt rather than reopened.
26
+ */
27
+ const SCHEMA_VERSION = 4; // 2: provenance (m8k.1); 3: source-location (hft.9); 4: call sites (hft.10)
28
+ /** Absolute directories far too broad to index — likely to pull in secrets,
29
+ * exhaust memory, or never finish. A real project lives in a subdirectory, so
30
+ * refusing these never blocks legitimate use. (ama-m8k.10) */
31
+ const UNSAFE_DIRS = new Set([
32
+ "/usr",
33
+ "/etc",
34
+ "/bin",
35
+ "/sbin",
36
+ "/var",
37
+ "/opt",
38
+ "/lib",
39
+ "/dev",
40
+ "/proc",
41
+ "/System",
42
+ "/Library",
43
+ ].map((p) => path.resolve(p)));
44
+ /** Throw if `root` resolves to the filesystem root, the user's home directory,
45
+ * or a well-known system directory — a guardrail so a stray `index_repository`
46
+ * call (an agent, a typo) can't walk the whole machine. (ama-m8k.10) */
47
+ export function assertSafeRoot(root) {
48
+ const abs = path.resolve(root);
49
+ if (abs === path.parse(abs).root || abs === path.resolve(os.homedir()) || UNSAFE_DIRS.has(abs)) {
50
+ throw new Error(`Refusing to index ${abs}: that's the filesystem root, your home directory, or a system directory — far too broad. Point Ama at a specific project directory.`);
51
+ }
52
+ }
53
+ /**
54
+ * Turns a directory into a graph: discover source files, hand each to the
55
+ * analyzer that claims its extension, and collect the resulting nodes/edges
56
+ * into a store. All files for one analyzer are analyzed together so it can
57
+ * resolve cross-file references (e.g. an import's call target).
58
+ */
59
+ export class Indexer {
60
+ registry;
61
+ createStore;
62
+ constructor(registry,
63
+ /** How to create the backing store for a (resolved) project root — swap this to
64
+ * persist to SQLite. The root is passed so a multi-project session can give each
65
+ * project an independent store; a factory that ignores it and returns one shared
66
+ * store would alias every project onto the last index. (ama-mnj) */
67
+ createStore = () => new InMemoryStore()) {
68
+ this.registry = registry;
69
+ this.createStore = createStore;
70
+ }
71
+ /** The (language, tier) that owns a file, or undefined if no analyzer claims it.
72
+ * Lets a caller recompute per-language coverage live from the current file set,
73
+ * so index_status's census stays correct after incremental syncs — not only
74
+ * after a full index, which is the only thing that writes the cached coverage
75
+ * metadata. (ama-okg) */
76
+ languageOf(rel) {
77
+ const analyzer = this.registry.forFile(rel);
78
+ return analyzer ? { language: analyzer.language, tier: analyzer.tier } : undefined;
79
+ }
80
+ async index(root) {
81
+ // Refuse dangerously broad roots before touching the filesystem.
82
+ assertSafeRoot(root);
83
+ // A clear error beats a raw ENOTDIR/ENOENT when the root isn't a directory.
84
+ if (!fs.statSync(root, { throwIfNoEntry: false })?.isDirectory()) {
85
+ throw new Error(`Not a directory: ${root}`);
86
+ }
87
+ // Discover files BEFORE touching the store: a failing walk must not clear a
88
+ // persistent store reused across indexes — that corrupts the live index.
89
+ // Walking first keeps a failed re-index a no-op.
90
+ const skippedLarge = [];
91
+ const files = discoverFiles(root, (rel) => skippedLarge.push(rel));
92
+ if (skippedLarge.length > 0) {
93
+ // Honest about omissions, like the per-analyzer isolation below: a file too big
94
+ // to parse safely is left out, but said so on stderr (stdout is JSON-RPC only).
95
+ console.error(`[ama] skipped ${skippedLarge.length} file(s) over the ` +
96
+ `${MAX_FILE_SIZE_BYTES / 1024 / 1024} MB parse cap (too large to index): ` +
97
+ skippedLarge.join(", "));
98
+ }
99
+ const store = this.createStore(root);
100
+ store.clear(); // a persistent store may hold a previous index; rebuild clean
101
+ const byAnalyzer = new Map();
102
+ for (const rel of files) {
103
+ const analyzer = this.registry.forFile(rel);
104
+ if (!analyzer)
105
+ continue;
106
+ const list = byAnalyzer.get(analyzer);
107
+ if (list)
108
+ list.push(rel);
109
+ else
110
+ byAnalyzer.set(analyzer, [rel]);
111
+ }
112
+ const languages = [];
113
+ const resolution = { callsTotal: 0, callsResolved: 0, unresolved: {} };
114
+ let fileCount = 0;
115
+ for (const [analyzer, files] of byAnalyzer) {
116
+ // Isolate each analyzer: a crash on one language's batch (a pathological
117
+ // file, an analyzer bug) must not abort the whole index — the other
118
+ // languages still produce a usable graph. The failure is reported to
119
+ // stderr (never silently dropped) and that language is left out of
120
+ // coverage so the index honestly reflects what was analyzed. (ama-m8k.9)
121
+ let result;
122
+ try {
123
+ result = await analyzer.analyze(root, files);
124
+ }
125
+ catch (err) {
126
+ console.error(`[ama] ${analyzer.language} analyzer failed on ${files.length} file(s); ` +
127
+ `skipping them. ${err instanceof Error ? err.message : String(err)}`);
128
+ continue;
129
+ }
130
+ for (const n of result.nodes)
131
+ store.addNode(n);
132
+ for (const e of result.edges)
133
+ store.addEdge(e);
134
+ for (const rel of files) {
135
+ const meta = fingerprint(root, rel);
136
+ if (meta)
137
+ store.recordFile(meta);
138
+ else
139
+ store.removeFile(rel); // vanished mid-index — drop its just-added nodes
140
+ }
141
+ fileCount += files.length;
142
+ if (result.resolution) {
143
+ resolution.callsTotal += result.resolution.callsTotal;
144
+ resolution.callsResolved += result.resolution.callsResolved;
145
+ for (const [name, n] of Object.entries(result.resolution.unresolved)) {
146
+ resolution.unresolved[name] = (resolution.unresolved[name] ?? 0) + n;
147
+ }
148
+ }
149
+ languages.push({
150
+ language: analyzer.language,
151
+ tier: analyzer.tier,
152
+ files: files.length,
153
+ });
154
+ }
155
+ // Persist enough to reopen this index next process without re-analyzing:
156
+ // coverage + resolution (for index_status), the root it was built for, and
157
+ // the schema version that wrote it.
158
+ store.setMeta("ama:coverage", JSON.stringify({ fileCount, languages }));
159
+ store.setMeta("ama:resolution", JSON.stringify(resolution));
160
+ store.setMeta("ama:root", root);
161
+ store.setMeta("ama:schema", String(SCHEMA_VERSION));
162
+ return {
163
+ store,
164
+ stats: {
165
+ root,
166
+ nodeCount: store.nodeCount,
167
+ edgeCount: store.edgeCount,
168
+ fileCount,
169
+ languages,
170
+ resolution,
171
+ },
172
+ };
173
+ }
174
+ /**
175
+ * Reopen a previously-persisted index without re-analyzing: open the store and,
176
+ * if it holds a usable index for `root` (matching schema version and root, with
177
+ * nodes present), reconstruct its {@link IndexStats} from the persisted
178
+ * coverage metadata. Returns undefined — and closes the freshly-opened store —
179
+ * when there is nothing usable (an empty in-memory store, a different root, or
180
+ * an incompatible schema), so the caller falls back to a full {@link index}.
181
+ */
182
+ async open(root) {
183
+ const store = this.createStore(root);
184
+ const coverageRaw = store.getMeta("ama:coverage");
185
+ const usable = store.getMeta("ama:schema") === String(SCHEMA_VERSION) &&
186
+ store.getMeta("ama:root") === root &&
187
+ store.nodeCount > 0 &&
188
+ coverageRaw !== undefined;
189
+ if (!usable) {
190
+ store.close();
191
+ return undefined;
192
+ }
193
+ const { fileCount, languages } = JSON.parse(coverageRaw);
194
+ // Resolution coverage is additive — an index written before ama-m8k.12 simply
195
+ // lacks it, so it stays undefined rather than gating reopen.
196
+ const resolutionRaw = store.getMeta("ama:resolution");
197
+ const parsedResolution = resolutionRaw
198
+ ? JSON.parse(resolutionRaw)
199
+ : undefined;
200
+ // An index written before ama-qbn has no `unresolved` map; default it so the
201
+ // field is always present once `resolution` is.
202
+ const resolution = parsedResolution
203
+ ? { ...parsedResolution, unresolved: parsedResolution.unresolved ?? {} }
204
+ : undefined;
205
+ return {
206
+ store,
207
+ stats: {
208
+ root,
209
+ nodeCount: store.nodeCount,
210
+ edgeCount: store.edgeCount,
211
+ fileCount,
212
+ languages,
213
+ ...(resolution ? { resolution } : {}),
214
+ },
215
+ };
216
+ }
217
+ /**
218
+ * Re-index a single changed file into an existing store, in place. Re-analyzes
219
+ * just `rel` and reconciles its delta (so an edit churns only what changed);
220
+ * if `rel` was deleted or is no longer analyzable, its data is dropped instead.
221
+ * Edges from `rel` into files this pass never walks still resolve, because the
222
+ * analyzer falls back to location-derived ids for nodes already in the store.
223
+ */
224
+ async reindexFile(store, root, rel) {
225
+ const abs = path.resolve(root, rel);
226
+ const analyzer = this.registry.forFile(rel);
227
+ const meta = analyzer && fs.existsSync(abs) ? fingerprint(root, rel) : null;
228
+ if (!analyzer || !meta) {
229
+ store.removeFile(rel); // unhandled language, or the file is gone
230
+ }
231
+ else {
232
+ const { nodes, edges } = await analyzer.analyze(root, [rel]);
233
+ store.reconcileFile(rel, nodes, edges);
234
+ store.recordFile(meta);
235
+ }
236
+ // Dispatch fan-out (interface/override) is a whole-graph inference: a single-file
237
+ // analyze can't see other files' implementers, so reconcileFile would drop this
238
+ // file's cross-file dispatch edges. Re-derive them over the full store after the
239
+ // structural change, restoring full-index parity. (ama-tr1)
240
+ redispatch(store);
241
+ }
242
+ /**
243
+ * Catch-up reconcile: compare the tree on disk against the stored fingerprints
244
+ * and re-index everything that drifted — files added or modified since the
245
+ * last index, and files that have since vanished. Detection is cheap
246
+ * (size + mtime, with a content hash only as the tiebreaker), and unchanged
247
+ * files are skipped entirely. The manual counterpart to the live watcher.
248
+ */
249
+ async sync(store, root) {
250
+ const changed = [];
251
+ const removed = [];
252
+ const current = new Set();
253
+ for (const rel of discoverFiles(root)) {
254
+ if (this.registry.forFile(rel))
255
+ current.add(rel);
256
+ }
257
+ for (const rel of current) {
258
+ const meta = store.getFile(rel);
259
+ if (meta && !isStale(root, rel, meta))
260
+ continue;
261
+ await this.reindexFile(store, root, rel);
262
+ changed.push(rel);
263
+ }
264
+ for (const meta of store.allFiles()) {
265
+ if (!current.has(meta.path)) {
266
+ await this.reindexFile(store, root, meta.path); // gone on disk → removeFile
267
+ removed.push(meta.path);
268
+ }
269
+ }
270
+ return { changed, removed };
271
+ }
272
+ }
273
+ /**
274
+ * An indexer wired with the analyzers Ama ships today. Pass a `createStore`
275
+ * factory to persist into SQLite instead of the default in-memory store.
276
+ */
277
+ export function createDefaultIndexer(createStore) {
278
+ const registry = new AnalyzerRegistry();
279
+ registry.register(new TypeScriptAnalyzer());
280
+ registry.register(new BaselineAnalyzer(pythonSpec));
281
+ registry.register(new BaselineAnalyzer(javascriptSpec));
282
+ registry.register(new BaselineAnalyzer(javaSpec));
283
+ registry.register(new BaselineAnalyzer(csharpSpec));
284
+ registry.register(new BaselineAnalyzer(goSpec));
285
+ registry.register(new BaselineAnalyzer(rustSpec));
286
+ registry.register(new BaselineAnalyzer(phpSpec));
287
+ registry.register(new BaselineAnalyzer(cSpec));
288
+ registry.register(new BaselineAnalyzer(cppSpec));
289
+ registry.register(new BaselineAnalyzer(kotlinSpec));
290
+ registry.register(new BaselineAnalyzer(swiftSpec));
291
+ registry.register(new SfcAnalyzer("vue", [".vue"]));
292
+ registry.register(new SfcAnalyzer("svelte", [".svelte"]));
293
+ return new Indexer(registry, createStore);
294
+ }
295
+ /**
296
+ * Re-derive the whole-graph dispatch edges (interface/override fan-out) over the
297
+ * full store, replacing the prior ones. A full index gets these from the analyzer's
298
+ * per-batch pass, but a single-file reindex can't (it lacks other files' subtypes),
299
+ * so we recompute them store-wide after every reindex — clearing the stale tagged
300
+ * set and re-adding the fresh derivation keeps incremental sync at full-index
301
+ * parity. (ama-tr1) */
302
+ function redispatch(store) {
303
+ const nodes = [...store.allNodes()];
304
+ const base = store.allEdges().filter((e) => e.provenance !== "dispatch");
305
+ store.replaceEdgesByProvenance("dispatch", deriveDispatchEdges(nodes, base));
306
+ }
307
+ /** Fingerprint a file for staleness tracking: size, mtime, and content hash.
308
+ * Returns null when the file has vanished (gone between discovery and now — an
309
+ * editor's atomic save or temp file) so the caller can drop it instead of
310
+ * crashing the index. (ama-7r5) */
311
+ export function fingerprint(root, rel) {
312
+ const abs = path.resolve(root, rel);
313
+ try {
314
+ const stat = fs.statSync(abs);
315
+ const hash = crypto.createHash("sha1").update(fs.readFileSync(abs)).digest("hex");
316
+ return { path: rel, size: stat.size, mtimeMs: stat.mtimeMs, hash };
317
+ }
318
+ catch {
319
+ return null;
320
+ }
321
+ }
322
+ /**
323
+ * Whether a file differs from its recorded fingerprint. Size and mtime are the
324
+ * cheap first check; the content hash is consulted only when they are
325
+ * inconclusive (mtime can change without the bytes changing), so an unchanged
326
+ * file is never re-hashed. A file that has vanished counts as stale, so the
327
+ * caller reindexes it and its `existsSync` check reconciles the removal. (ama-7r5)
328
+ */
329
+ export function isStale(root, rel, meta) {
330
+ const abs = path.resolve(root, rel);
331
+ let stat;
332
+ try {
333
+ stat = fs.statSync(abs);
334
+ }
335
+ catch {
336
+ return true;
337
+ }
338
+ if (stat.size === meta.size && stat.mtimeMs === meta.mtimeMs)
339
+ return false;
340
+ try {
341
+ const hash = crypto.createHash("sha1").update(fs.readFileSync(abs)).digest("hex");
342
+ return hash !== meta.hash;
343
+ }
344
+ catch {
345
+ return true;
346
+ }
347
+ }
348
+ /**
349
+ * Repo-relative paths of every file under `root`, skipping ignored trees. Files over the
350
+ * parse cap are left out; `onSkipLarge` is invoked with each so a caller can report them
351
+ * instead of dropping them silently. (ama-j0y)
352
+ */
353
+ function discoverFiles(root, onSkipLarge) {
354
+ const rootRules = loadIgnoreRules(root); // dotfiles + IGNORED_DIRS + the root .gitignore
355
+ const out = [];
356
+ const walk = (dir, dirRel, parent) => {
357
+ // A directory's own .gitignore augments the rules for its subtree, dir-relative
358
+ // (the root's is already folded into `rootRules`). (ama-pyk)
359
+ const rules = dirRel === "" ? parent : withNestedIgnore(dir, dirRel, parent);
360
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
361
+ const abs = path.join(dir, entry.name);
362
+ const rel = path.relative(root, abs);
363
+ // Path-aware so anchored .gitignore patterns (/build, pkg/internal) match
364
+ // root-relatively, not at any depth; covers dotfiles + names/globs too. (ama-yhu)
365
+ if (isIgnoredPath(rel, rules))
366
+ continue;
367
+ if (entry.isDirectory())
368
+ walk(abs, rel, rules);
369
+ else if (entry.isFile()) {
370
+ // Skip oversized files (minified bundles, data blobs) — the same cap the
371
+ // watcher enforces, so the initial index and re-index agree. Report the skip
372
+ // (never silently dropped, like the per-analyzer isolation) so a caller knows a
373
+ // file was omitted. A vanished file is just skipped.
374
+ let size;
375
+ try {
376
+ size = fs.statSync(abs).size;
377
+ }
378
+ catch {
379
+ continue;
380
+ }
381
+ if (size > MAX_FILE_SIZE_BYTES) {
382
+ onSkipLarge?.(rel);
383
+ continue;
384
+ }
385
+ out.push(rel);
386
+ }
387
+ }
388
+ };
389
+ walk(root, "", rootRules);
390
+ return out;
391
+ }
392
+ //# sourceMappingURL=indexer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexer.js","sourceRoot":"","sources":["../../src/indexer/indexer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AACrE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAC7D,OAAO,EAAE,MAAM,EAAE,MAAM,6BAA6B,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AACrE,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAC7D,OAAO,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAC7D,OAAO,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAE3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,OAAO,EAEL,mBAAmB,EACnB,aAAa,EACb,eAAe,EACf,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAqBrB;;;;GAIG;AACH,MAAM,cAAc,GAAG,CAAC,CAAC,CAAC,4EAA4E;AAUtG;;+DAE+D;AAC/D,MAAM,WAAW,GAAG,IAAI,GAAG,CACzB;IACE,MAAM;IACN,MAAM;IACN,MAAM;IACN,OAAO;IACP,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,OAAO;IACP,SAAS;IACT,UAAU;CACX,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAC9B,CAAC;AAEF;;yEAEyE;AACzE,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,GAAG,KAAK,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,GAAG,KAAK,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/F,MAAM,IAAI,KAAK,CACb,qBAAqB,GAAG,sIAAsI,CAC/J,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,OAAO,OAAO;IAEC;IAKA;IANnB,YACmB,QAA0B;IAC3C;;;yEAGqE;IACpD,cAAuC,GAAG,EAAE,CAAC,IAAI,aAAa,EAAE;QALhE,aAAQ,GAAR,QAAQ,CAAkB;QAK1B,gBAAW,GAAX,WAAW,CAAqD;IAChF,CAAC;IAEJ;;;;8BAI0B;IAC1B,UAAU,CAAC,GAAW;QACpB,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC5C,OAAO,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IACrF,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,IAAY;QACtB,iEAAiE;QACjE,cAAc,CAAC,IAAI,CAAC,CAAC;QACrB,4EAA4E;QAC5E,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,EAAE,WAAW,EAAE,EAAE,CAAC;YACjE,MAAM,IAAI,KAAK,CAAC,oBAAoB,IAAI,EAAE,CAAC,CAAC;QAC9C,CAAC;QACD,4EAA4E;QAC5E,yEAAyE;QACzE,iDAAiD;QACjD,MAAM,YAAY,GAAa,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QACnE,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,gFAAgF;YAChF,gFAAgF;YAChF,OAAO,CAAC,KAAK,CACX,iBAAiB,YAAY,CAAC,MAAM,oBAAoB;gBACtD,GAAG,mBAAmB,GAAG,IAAI,GAAG,IAAI,sCAAsC;gBAC1E,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAC1B,CAAC;QACJ,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QACrC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,8DAA8D;QAE7E,MAAM,UAAU,GAAG,IAAI,GAAG,EAAsB,CAAC;QACjD,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;YACxB,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAC5C,IAAI,CAAC,QAAQ;gBAAE,SAAS;YACxB,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACtC,IAAI,IAAI;gBAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;;gBACpB,UAAU,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACvC,CAAC;QAED,MAAM,SAAS,GAAuB,EAAE,CAAC;QACzC,MAAM,UAAU,GAAoB,EAAE,UAAU,EAAE,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;QACxF,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,KAAK,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,UAAU,EAAE,CAAC;YAC3C,yEAAyE;YACzE,oEAAoE;YACpE,qEAAqE;YACrE,mEAAmE;YACnE,yEAAyE;YACzE,IAAI,MAAsB,CAAC;YAC3B,IAAI,CAAC;gBACH,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAC/C,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CACX,SAAS,QAAQ,CAAC,QAAQ,uBAAuB,KAAK,CAAC,MAAM,YAAY;oBACvE,kBAAkB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CACvE,CAAC;gBACF,SAAS;YACX,CAAC;YACD,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,KAAK;gBAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAC/C,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,KAAK;gBAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAC/C,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;gBACxB,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;gBACpC,IAAI,IAAI;oBAAE,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;;oBAC5B,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,iDAAiD;YAC/E,CAAC;YACD,SAAS,IAAI,KAAK,CAAC,MAAM,CAAC;YAC1B,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;gBACtB,UAAU,CAAC,UAAU,IAAI,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC;gBACtD,UAAU,CAAC,aAAa,IAAI,MAAM,CAAC,UAAU,CAAC,aAAa,CAAC;gBAC5D,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;oBACrE,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;gBACvE,CAAC;YACH,CAAC;YACD,SAAS,CAAC,IAAI,CAAC;gBACb,QAAQ,EAAE,QAAQ,CAAC,QAAQ;gBAC3B,IAAI,EAAE,QAAQ,CAAC,IAAI;gBACnB,KAAK,EAAE,KAAK,CAAC,MAAM;aACpB,CAAC,CAAC;QACL,CAAC;QAED,yEAAyE;QACzE,2EAA2E;QAC3E,oCAAoC;QACpC,KAAK,CAAC,OAAO,CAAC,cAAc,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;QACxE,KAAK,CAAC,OAAO,CAAC,gBAAgB,EAAE,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC;QAC5D,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QAChC,KAAK,CAAC,OAAO,CAAC,YAAY,EAAE,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC;QAEpD,OAAO;YACL,KAAK;YACL,KAAK,EAAE;gBACL,IAAI;gBACJ,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,SAAS;gBACT,SAAS;gBACT,UAAU;aACX;SACF,CAAC;IACJ,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,IAAI,CAAC,IAAY;QACrB,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QACrC,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QAClD,MAAM,MAAM,GACV,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,KAAK,MAAM,CAAC,cAAc,CAAC;YACtD,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI;YAClC,KAAK,CAAC,SAAS,GAAG,CAAC;YACnB,WAAW,KAAK,SAAS,CAAC;QAC5B,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,KAAK,CAAC,KAAK,EAAE,CAAC;YACd,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAGtD,CAAC;QACF,8EAA8E;QAC9E,6DAA6D;QAC7D,MAAM,aAAa,GAAG,KAAK,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;QACtD,MAAM,gBAAgB,GAAG,aAAa;YACpC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,aAAa,CAAqB;YAChD,CAAC,CAAC,SAAS,CAAC;QACd,6EAA6E;QAC7E,gDAAgD;QAChD,MAAM,UAAU,GAAG,gBAAgB;YACjC,CAAC,CAAC,EAAE,GAAG,gBAAgB,EAAE,UAAU,EAAE,gBAAgB,CAAC,UAAU,IAAI,EAAE,EAAE;YACxE,CAAC,CAAC,SAAS,CAAC;QACd,OAAO;YACL,KAAK;YACL,KAAK,EAAE;gBACL,IAAI;gBACJ,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,SAAS;gBACT,SAAS;gBACT,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACtC;SACF,CAAC;IACJ,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,WAAW,CAAC,KAAY,EAAE,IAAY,EAAE,GAAW;QACvD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC5C,MAAM,IAAI,GAAG,QAAQ,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC5E,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;YACvB,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,0CAA0C;QACnE,CAAC;aAAM,CAAC;YACN,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;YAC7D,KAAK,CAAC,aAAa,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;YACvC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACzB,CAAC;QACD,kFAAkF;QAClF,gFAAgF;QAChF,iFAAiF;QACjF,4DAA4D;QAC5D,UAAU,CAAC,KAAK,CAAC,CAAC;IACpB,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,IAAI,CAAC,KAAY,EAAE,IAAY;QACnC,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;QAClC,KAAK,MAAM,GAAG,IAAI,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC;YACtC,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC;gBAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnD,CAAC;QACD,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;YAC1B,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAChC,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC;gBAAE,SAAS;YAChD,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;YACzC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACpB,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC;YACpC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC5B,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,4BAA4B;gBAC5E,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;IAC9B,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,WAAqC;IACxE,MAAM,QAAQ,GAAG,IAAI,gBAAgB,EAAE,CAAC;IACxC,QAAQ,CAAC,QAAQ,CAAC,IAAI,kBAAkB,EAAE,CAAC,CAAC;IAC5C,QAAQ,CAAC,QAAQ,CAAC,IAAI,gBAAgB,CAAC,UAAU,CAAC,CAAC,CAAC;IACpD,QAAQ,CAAC,QAAQ,CAAC,IAAI,gBAAgB,CAAC,cAAc,CAAC,CAAC,CAAC;IACxD,QAAQ,CAAC,QAAQ,CAAC,IAAI,gBAAgB,CAAC,QAAQ,CAAC,CAAC,CAAC;IAClD,QAAQ,CAAC,QAAQ,CAAC,IAAI,gBAAgB,CAAC,UAAU,CAAC,CAAC,CAAC;IACpD,QAAQ,CAAC,QAAQ,CAAC,IAAI,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC;IAChD,QAAQ,CAAC,QAAQ,CAAC,IAAI,gBAAgB,CAAC,QAAQ,CAAC,CAAC,CAAC;IAClD,QAAQ,CAAC,QAAQ,CAAC,IAAI,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC;IACjD,QAAQ,CAAC,QAAQ,CAAC,IAAI,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC;IAC/C,QAAQ,CAAC,QAAQ,CAAC,IAAI,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC;IACjD,QAAQ,CAAC,QAAQ,CAAC,IAAI,gBAAgB,CAAC,UAAU,CAAC,CAAC,CAAC;IACpD,QAAQ,CAAC,QAAQ,CAAC,IAAI,gBAAgB,CAAC,SAAS,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,QAAQ,CAAC,IAAI,WAAW,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACpD,QAAQ,CAAC,QAAQ,CAAC,IAAI,WAAW,CAAC,QAAQ,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAC1D,OAAO,IAAI,OAAO,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;AAC5C,CAAC;AAED;;;;;;uBAMuB;AACvB,SAAS,UAAU,CAAC,KAAY;IAC9B,MAAM,KAAK,GAAG,CAAC,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,UAAU,CAAC,CAAC;IACzE,KAAK,CAAC,wBAAwB,CAAC,UAAU,EAAE,mBAAmB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;AAC/E,CAAC;AAED;;;oCAGoC;AACpC,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,GAAW;IACnD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC9B,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAClF,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,IAAY,EAAE,GAAW,EAAE,IAAc;IAC/D,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACpC,IAAI,IAAc,CAAC;IACnB,IAAI,CAAC;QACH,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IAC3E,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAClF,OAAO,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,aAAa,CAAC,IAAY,EAAE,WAAmC;IACtE,MAAM,SAAS,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,gDAAgD;IACzF,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,MAAM,IAAI,GAAG,CAAC,GAAW,EAAE,MAAc,EAAE,MAAmB,EAAQ,EAAE;QACtE,gFAAgF;QAChF,6DAA6D;QAC7D,MAAM,KAAK,GAAG,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QAC7E,KAAK,MAAM,KAAK,IAAI,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;YACjE,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YACvC,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YACrC,0EAA0E;YAC1E,kFAAkF;YAClF,IAAI,aAAa,CAAC,GAAG,EAAE,KAAK,CAAC;gBAAE,SAAS;YACxC,IAAI,KAAK,CAAC,WAAW,EAAE;gBAAE,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;iBAC1C,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;gBACxB,yEAAyE;gBACzE,6EAA6E;gBAC7E,gFAAgF;gBAChF,qDAAqD;gBACrD,IAAI,IAAY,CAAC;gBACjB,IAAI,CAAC;oBACH,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBAC/B,CAAC;gBAAC,MAAM,CAAC;oBACP,SAAS;gBACX,CAAC;gBACD,IAAI,IAAI,GAAG,mBAAmB,EAAE,CAAC;oBAC/B,WAAW,EAAE,CAAC,GAAG,CAAC,CAAC;oBACnB,SAAS;gBACX,CAAC;gBACD,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAChB,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IACF,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,SAAS,CAAC,CAAC;IAC1B,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * How a {@link FileWatcher} receives raw change events: given the root and a
3
+ * callback, wire up event delivery and return a handle to stop it. Injectable
4
+ * so tests can drive events synchronously instead of waiting on OS file-event
5
+ * latency (the source of flaky timing tests); the default is {@link fsWatchSource}.
6
+ */
7
+ export type WatchSource = (root: string, onEvent: (rel: string) => void) => {
8
+ close(): void;
9
+ };
10
+ export interface FileWatcherOptions {
11
+ /** Files larger than this are not reported (default 1 MB). */
12
+ maxFileSizeBytes?: number;
13
+ /** Event source (default: fs.watch). Tests inject a synchronous source. */
14
+ source?: WatchSource;
15
+ }
16
+ /**
17
+ * Recursively watches a directory and reports each file that changes, as a
18
+ * repo-relative path, applying the same ignore rules as the indexer (dot-paths,
19
+ * `node_modules`/`dist`/… , and files over a size cap). It does *not* classify
20
+ * create vs. modify vs. delete — the consumer re-indexes the path and lets the
21
+ * indexer decide (a vanished file is reconciled away). Debouncing bursts of
22
+ * edits is a separate concern (ama-gd5.3); this emits raw change events.
23
+ *
24
+ * Built on Node's native `fs.watch` to avoid a dependency. Recursive watching
25
+ * is supported on macOS and Windows; on Linux the recursive option may not be
26
+ * available, in which case this watches only the top level.
27
+ */
28
+ export declare class FileWatcher {
29
+ private readonly root;
30
+ private readonly onChange;
31
+ private subscription?;
32
+ private readonly maxFileSizeBytes;
33
+ private readonly source;
34
+ /** Loaded once so the watched set matches what the index built (incl .gitignore). */
35
+ private readonly ignoreRules;
36
+ /** Per-directory accumulated rules (root + each ancestor's nested .gitignore),
37
+ * memoized so a burst of events in one directory reads each .gitignore once. */
38
+ private readonly rulesByDir;
39
+ constructor(root: string, onChange: (rel: string) => void, options?: FileWatcherOptions);
40
+ start(): void;
41
+ close(): void;
42
+ /** Ignore rules in effect inside `dirRel`: the root rules plus every ancestor
43
+ * directory's nested .gitignore, each rebased to its directory, so a changed
44
+ * file is judged exactly as the discovery walk would (ama-pyk). Memoized per
45
+ * directory; like the root rules, a .gitignore edited after start isn't
46
+ * reloaded — restart the watcher for that. (ama-ezf) */
47
+ private rulesForDir;
48
+ private handle;
49
+ }
50
+ //# sourceMappingURL=watcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../../src/indexer/watcher.ts"],"names":[],"mappings":"AAUA;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,KAAK;IAAE,KAAK,IAAI,IAAI,CAAA;CAAE,CAAC;AAU9F,MAAM,WAAW,kBAAkB;IACjC,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,2EAA2E;IAC3E,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,WAAW;IAWpB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAX3B,OAAO,CAAC,YAAY,CAAC,CAAoB;IACzC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,qFAAqF;IACrF,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C;qFACiF;IACjF,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkC;gBAG1C,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,EAChD,OAAO,GAAE,kBAAuB;IAOlC,KAAK,IAAI,IAAI;IAKb,KAAK,IAAI,IAAI;IAKb;;;;6DAIyD;IACzD,OAAO,CAAC,WAAW;IAcnB,OAAO,CAAC,MAAM;CAef"}
@@ -0,0 +1,86 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { MAX_FILE_SIZE_BYTES, isIgnoredPath, loadIgnoreRules, withNestedIgnore, } from "./ignore.js";
4
+ /** The production source: Node's native recursive `fs.watch`. */
5
+ const fsWatchSource = (root, onEvent) => {
6
+ const watcher = fs.watch(root, { recursive: true }, (_event, filename) => {
7
+ if (filename !== null)
8
+ onEvent(filename.toString());
9
+ });
10
+ return { close: () => watcher.close() };
11
+ };
12
+ /**
13
+ * Recursively watches a directory and reports each file that changes, as a
14
+ * repo-relative path, applying the same ignore rules as the indexer (dot-paths,
15
+ * `node_modules`/`dist`/… , and files over a size cap). It does *not* classify
16
+ * create vs. modify vs. delete — the consumer re-indexes the path and lets the
17
+ * indexer decide (a vanished file is reconciled away). Debouncing bursts of
18
+ * edits is a separate concern (ama-gd5.3); this emits raw change events.
19
+ *
20
+ * Built on Node's native `fs.watch` to avoid a dependency. Recursive watching
21
+ * is supported on macOS and Windows; on Linux the recursive option may not be
22
+ * available, in which case this watches only the top level.
23
+ */
24
+ export class FileWatcher {
25
+ root;
26
+ onChange;
27
+ subscription;
28
+ maxFileSizeBytes;
29
+ source;
30
+ /** Loaded once so the watched set matches what the index built (incl .gitignore). */
31
+ ignoreRules;
32
+ /** Per-directory accumulated rules (root + each ancestor's nested .gitignore),
33
+ * memoized so a burst of events in one directory reads each .gitignore once. */
34
+ rulesByDir = new Map();
35
+ constructor(root, onChange, options = {}) {
36
+ this.root = root;
37
+ this.onChange = onChange;
38
+ this.maxFileSizeBytes = options.maxFileSizeBytes ?? MAX_FILE_SIZE_BYTES;
39
+ this.source = options.source ?? fsWatchSource;
40
+ this.ignoreRules = loadIgnoreRules(root);
41
+ }
42
+ start() {
43
+ if (this.subscription)
44
+ return;
45
+ this.subscription = this.source(this.root, (rel) => this.handle(rel));
46
+ }
47
+ close() {
48
+ this.subscription?.close();
49
+ this.subscription = undefined;
50
+ }
51
+ /** Ignore rules in effect inside `dirRel`: the root rules plus every ancestor
52
+ * directory's nested .gitignore, each rebased to its directory, so a changed
53
+ * file is judged exactly as the discovery walk would (ama-pyk). Memoized per
54
+ * directory; like the root rules, a .gitignore edited after start isn't
55
+ * reloaded — restart the watcher for that. (ama-ezf) */
56
+ rulesForDir(dirRel) {
57
+ if (dirRel === "" || dirRel === ".")
58
+ return this.ignoreRules;
59
+ const cached = this.rulesByDir.get(dirRel);
60
+ if (cached)
61
+ return cached;
62
+ const parent = path.dirname(dirRel);
63
+ const rules = withNestedIgnore(path.join(this.root, dirRel), dirRel, this.rulesForDir(parent === "." ? "" : parent));
64
+ this.rulesByDir.set(dirRel, rules);
65
+ return rules;
66
+ }
67
+ handle(rel) {
68
+ const dir = path.dirname(rel);
69
+ if (isIgnoredPath(rel, this.rulesForDir(dir === "." ? "" : dir)))
70
+ return;
71
+ let stat;
72
+ try {
73
+ stat = fs.statSync(path.join(this.root, rel));
74
+ }
75
+ catch {
76
+ // The path is gone (a deletion) — still report it so the consumer can drop it.
77
+ this.onChange(rel);
78
+ return;
79
+ }
80
+ // A directory event or an oversized file is not something to re-index.
81
+ if (!stat.isFile() || stat.size > this.maxFileSizeBytes)
82
+ return;
83
+ this.onChange(rel);
84
+ }
85
+ }
86
+ //# sourceMappingURL=watcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watcher.js","sourceRoot":"","sources":["../../src/indexer/watcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * A stamp identifying the running server build, surfaced on `index_status` so a
3
+ * caller (e.g. the self-improvement loop's Step 0) can detect a stale server —
4
+ * one started before the latest commit. It is captured ONCE at module load, so
5
+ * `revision` reflects the code the process was launched with, not live HEAD: if
6
+ * you commit without restarting, the stamp lags HEAD and the staleness shows.
7
+ */
8
+ export interface ServerStamp {
9
+ /** Package version from package.json. */
10
+ version: string;
11
+ /** Git HEAD revision at server start, or null when run outside a git repo. */
12
+ revision: string | null;
13
+ }
14
+ /** Captured once at module load — the code the running server was started with. */
15
+ export declare const serverStamp: ServerStamp;
16
+ //# sourceMappingURL=build-info.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-info.d.ts","sourceRoot":"","sources":["../../src/mcp/build-info.ts"],"names":[],"mappings":"AAIA;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AA8CD,mFAAmF;AACnF,eAAO,MAAM,WAAW,EAAE,WAGzB,CAAC"}
@@ -0,0 +1,54 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ const here = path.dirname(fileURLToPath(import.meta.url));
5
+ // src/mcp/build-info.ts and dist/mcp/build-info.js both sit two levels under
6
+ // the repo root, so the same relative hop finds package.json and .git either way.
7
+ const repoRoot = path.resolve(here, "../..");
8
+ function readVersion(root) {
9
+ try {
10
+ const pkg = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
11
+ return pkg.version ?? "0.0.0";
12
+ }
13
+ catch {
14
+ return "0.0.0";
15
+ }
16
+ }
17
+ /** Resolve HEAD to a commit SHA from the filesystem (loose ref, then packed-refs). */
18
+ function readRevision(root) {
19
+ try {
20
+ const gitDir = path.join(root, ".git");
21
+ const head = fs.readFileSync(path.join(gitDir, "HEAD"), "utf8").trim();
22
+ const ref = head.match(/^ref:\s*(.+)$/)?.[1];
23
+ if (!ref) {
24
+ // Detached HEAD: the HEAD file holds the SHA directly.
25
+ return /^[0-9a-f]{40}$/.test(head) ? head : null;
26
+ }
27
+ try {
28
+ const loose = fs.readFileSync(path.join(gitDir, ref), "utf8").trim();
29
+ if (/^[0-9a-f]{40}$/.test(loose))
30
+ return loose;
31
+ }
32
+ catch {
33
+ // No loose ref file — fall through to packed-refs.
34
+ }
35
+ const packed = fs.readFileSync(path.join(gitDir, "packed-refs"), "utf8");
36
+ for (const line of packed.split("\n")) {
37
+ if (!line || line.startsWith("#") || line.startsWith("^"))
38
+ continue;
39
+ const [sha, name] = line.split(" ");
40
+ if (name === ref && sha)
41
+ return sha;
42
+ }
43
+ return null;
44
+ }
45
+ catch {
46
+ return null;
47
+ }
48
+ }
49
+ /** Captured once at module load — the code the running server was started with. */
50
+ export const serverStamp = {
51
+ version: readVersion(repoRoot),
52
+ revision: readRevision(repoRoot),
53
+ };
54
+ //# sourceMappingURL=build-info.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-info.js","sourceRoot":"","sources":["../../src/mcp/build-info.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAgBzC,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,6EAA6E;AAC7E,kFAAkF;AAClF,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAE7C,SAAS,WAAW,CAAC,IAAY;IAC/B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAE9E,CAAC;QACF,OAAO,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC;AAED,sFAAsF;AACtF,SAAS,YAAY,CAAC,IAAY;IAChC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACvC,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QACvE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7C,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,uDAAuD;YACvD,OAAO,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;QACnD,CAAC;QACD,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;YACrE,IAAI,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAC;QACjD,CAAC;QAAC,MAAM,CAAC;YACP,mDAAmD;QACrD,CAAC;QACD,MAAM,MAAM,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,CAAC,CAAC;QACzE,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACtC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,SAAS;YACpE,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACpC,IAAI,IAAI,KAAK,GAAG,IAAI,GAAG;gBAAE,OAAO,GAAG,CAAC;QACtC,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,mFAAmF;AACnF,MAAM,CAAC,MAAM,WAAW,GAAgB;IACtC,OAAO,EAAE,WAAW,CAAC,QAAQ,CAAC;IAC9B,QAAQ,EAAE,YAAY,CAAC,QAAQ,CAAC;CACjC,CAAC"}