brainclaw 1.9.0 → 1.10.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 (149) hide show
  1. package/README.md +631 -499
  2. package/dist/brainclaw-vscode.vsix +0 -0
  3. package/dist/cli.js +18 -1
  4. package/dist/commands/code-map.js +129 -0
  5. package/dist/commands/codev.js +7 -0
  6. package/dist/commands/harvest.js +1 -1
  7. package/dist/commands/hooks.js +73 -73
  8. package/dist/commands/init.js +1 -1
  9. package/dist/commands/install-hooks.js +78 -78
  10. package/dist/commands/mcp-read-handlers.js +57 -14
  11. package/dist/commands/mcp.js +200 -13
  12. package/dist/commands/run-profile.js +3 -2
  13. package/dist/commands/switch.js +125 -93
  14. package/dist/commands/version.js +1 -1
  15. package/dist/core/agent-capability.js +19 -4
  16. package/dist/core/agent-files.js +131 -119
  17. package/dist/core/code-map/backend.js +123 -0
  18. package/dist/core/code-map/core.js +81 -0
  19. package/dist/core/code-map/drafts.js +2 -0
  20. package/dist/core/code-map/extractor.js +29 -0
  21. package/dist/core/code-map/finalizer.js +191 -0
  22. package/dist/core/code-map/freshness.js +108 -0
  23. package/dist/core/code-map/ids.js +0 -0
  24. package/dist/core/code-map/importable.js +35 -0
  25. package/dist/core/code-map/indexes.js +197 -0
  26. package/dist/core/code-map/lang/java/imports.scm +17 -0
  27. package/dist/core/code-map/lang/java/index.js +254 -0
  28. package/dist/core/code-map/lang/java/tags.scm +48 -0
  29. package/dist/core/code-map/lang/php/imports.scm +21 -0
  30. package/dist/core/code-map/lang/php/index.js +251 -0
  31. package/dist/core/code-map/lang/php/tags.scm +44 -0
  32. package/dist/core/code-map/lang/provider.js +9 -0
  33. package/dist/core/code-map/lang/providers.js +24 -0
  34. package/dist/core/code-map/lang/python/imports.scm +90 -0
  35. package/dist/core/code-map/lang/python/index.js +364 -0
  36. package/dist/core/code-map/lang/python/tags.scm +81 -0
  37. package/dist/core/code-map/lang/query-runtime.js +374 -0
  38. package/dist/core/code-map/lang/registry.js +125 -0
  39. package/dist/core/code-map/lang/typescript/imports.scm +90 -0
  40. package/dist/core/code-map/lang/typescript/index.js +306 -0
  41. package/dist/core/code-map/lang/typescript/tags.js.scm +106 -0
  42. package/dist/core/code-map/lang/typescript/tags.scm +151 -0
  43. package/dist/core/code-map/lock.js +210 -0
  44. package/dist/core/code-map/materialized.js +51 -0
  45. package/dist/core/code-map/memory-reader.js +59 -0
  46. package/dist/core/code-map/paths.js +53 -0
  47. package/dist/core/code-map/query.js +568 -0
  48. package/dist/core/code-map/refresh.js +0 -0
  49. package/dist/core/code-map/resolve.js +177 -0
  50. package/dist/core/code-map/store.js +206 -0
  51. package/dist/core/code-map/types.js +288 -0
  52. package/dist/core/code-map/vocabulary.js +57 -0
  53. package/dist/core/code-map/wasm-loader.js +294 -0
  54. package/dist/core/code-map/work-section.js +206 -0
  55. package/dist/core/codev-prompts.js +38 -38
  56. package/dist/core/codev-rounds.js +4 -0
  57. package/dist/core/default-profiles/doctor.yaml +11 -11
  58. package/dist/core/default-profiles/janitor.yaml +11 -11
  59. package/dist/core/default-profiles/onboarder.yaml +11 -11
  60. package/dist/core/default-profiles/reviewer.yaml +13 -13
  61. package/dist/core/dispatcher.js +1 -1
  62. package/dist/core/entity-operations.js +29 -3
  63. package/dist/core/execution-adapters.js +11 -10
  64. package/dist/core/execution-profile.js +58 -0
  65. package/dist/core/execution.js +1 -1
  66. package/dist/core/facade-schema.js +9 -0
  67. package/dist/core/instruction-templates.js +2 -0
  68. package/dist/core/loops/verbs.js +0 -1
  69. package/dist/core/mcp-command-resolution.js +3 -1
  70. package/dist/core/messaging.js +2 -2
  71. package/dist/core/protocol-skills.js +164 -164
  72. package/dist/core/runtime-signals.js +1 -1
  73. package/dist/core/search.js +19 -2
  74. package/dist/core/security-guard.js +207 -207
  75. package/dist/core/spawn-check.js +16 -2
  76. package/dist/core/staleness.js +1 -1
  77. package/dist/core/store-resolution.js +67 -11
  78. package/dist/core/worktree.js +18 -18
  79. package/dist/facts.js +9 -5
  80. package/dist/facts.json +8 -4
  81. package/dist/vendor/web-tree-sitter/tree-sitter.js +3980 -0
  82. package/dist/vendor/web-tree-sitter/tree-sitter.wasm +0 -0
  83. package/dist/wasm/tree-sitter-java.wasm +0 -0
  84. package/dist/wasm/tree-sitter-javascript.wasm +0 -0
  85. package/dist/wasm/tree-sitter-php.wasm +0 -0
  86. package/dist/wasm/tree-sitter-python.wasm +0 -0
  87. package/dist/wasm/tree-sitter-tsx.wasm +0 -0
  88. package/dist/wasm/tree-sitter-typescript.wasm +0 -0
  89. package/dist/wasm/tree-sitter.wasm +0 -0
  90. package/docs/PROTOCOL.md +1 -1
  91. package/docs/adapters/openclaw.md +43 -43
  92. package/docs/architecture/project-refs.md +328 -328
  93. package/docs/cli.md +2131 -2093
  94. package/docs/code-map.md +198 -0
  95. package/docs/concepts/coordination.md +52 -52
  96. package/docs/concepts/coordinator-runbook.md +129 -129
  97. package/docs/concepts/dispatch-lifecycle.md +245 -245
  98. package/docs/concepts/event-log-store.md +928 -928
  99. package/docs/concepts/ideation-loop.md +317 -317
  100. package/docs/concepts/loop-engine.md +520 -511
  101. package/docs/concepts/mcp-governance.md +268 -268
  102. package/docs/concepts/memory.md +84 -84
  103. package/docs/concepts/multi-agent-workflows.md +167 -167
  104. package/docs/concepts/observer-protocol.md +361 -361
  105. package/docs/concepts/plans-and-claims.md +217 -217
  106. package/docs/concepts/project-md-convention.md +35 -35
  107. package/docs/concepts/runtime-notes.md +38 -38
  108. package/docs/concepts/troubleshooting.md +254 -254
  109. package/docs/concepts/workspace-bootstrapping.md +142 -142
  110. package/docs/context-format-changelog.md +35 -35
  111. package/docs/context-format.md +48 -48
  112. package/docs/index.md +65 -65
  113. package/docs/integrations/agents.md +158 -158
  114. package/docs/integrations/claude-code.md +23 -23
  115. package/docs/integrations/cline.md +77 -77
  116. package/docs/integrations/continue.md +55 -55
  117. package/docs/integrations/copilot.md +68 -68
  118. package/docs/integrations/cursor.md +23 -23
  119. package/docs/integrations/kilocode.md +72 -72
  120. package/docs/integrations/mcp.md +385 -378
  121. package/docs/integrations/mistral-vibe.md +122 -122
  122. package/docs/integrations/openclaw.md +92 -92
  123. package/docs/integrations/opencode.md +84 -84
  124. package/docs/integrations/overview.md +115 -115
  125. package/docs/integrations/roo.md +71 -71
  126. package/docs/integrations/windsurf.md +77 -77
  127. package/docs/mcp-schema-changelog.md +364 -356
  128. package/docs/playbooks/integration/index.md +121 -121
  129. package/docs/playbooks/orchestration.md +37 -0
  130. package/docs/playbooks/productivity/index.md +99 -99
  131. package/docs/playbooks/team/index.md +117 -117
  132. package/docs/product/agent-first-model.md +184 -184
  133. package/docs/product/entity-model-audit.md +462 -462
  134. package/docs/product/positioning.md +86 -86
  135. package/docs/quickstart-existing-project.md +107 -107
  136. package/docs/quickstart.md +183 -183
  137. package/docs/release-maintenance.md +79 -79
  138. package/docs/reputation.md +52 -52
  139. package/docs/review.md +45 -45
  140. package/docs/security.md +212 -212
  141. package/docs/server-operations.md +118 -118
  142. package/docs/storage.md +106 -106
  143. package/package.json +86 -66
  144. package/docs/concepts/event-log-store-critique-A.md +0 -333
  145. package/docs/concepts/event-log-store-critique-B.md +0 -353
  146. package/docs/concepts/event-log-store-phase0-measurements.md +0 -58
  147. package/docs/concepts/event-log-store-proposal-A.md +0 -365
  148. package/docs/concepts/event-log-store-proposal-B.md +0 -404
  149. package/docs/concepts/identity-model-proposal.md +0 -371
@@ -0,0 +1,374 @@
1
+ import { getParser, getQueryClass } from '../wasm-loader.js';
2
+ /** Process-wide compiled-query cache, keyed `${providerId}|${lang}|${queryHash}`. */
3
+ const queryCache = new Map();
4
+ /**
5
+ * Compile a query asset ONCE per `(providerId, lang, queryHash)` and cache it.
6
+ * Compile failure is fatal + loud (spec §7 — a broken bundled asset fails here,
7
+ * never a silent per-file skip).
8
+ */
9
+ async function compileCached(providerId, lang, queryHash, grammar, source) {
10
+ const key = `${providerId}|${lang}|${queryHash}`;
11
+ const hit = queryCache.get(key);
12
+ if (hit)
13
+ return hit;
14
+ const Query = await getQueryClass();
15
+ // The loader's `Query` comes from the SAME engine-glue instance the grammar was
16
+ // loaded against (and after initEngine), so the Emscripten Module is live here.
17
+ // Cast to the structural shape we consume.
18
+ const compiled = new Query(grammar, source); // throws loudly on a broken asset
19
+ queryCache.set(key, compiled);
20
+ return compiled;
21
+ }
22
+ /** Test/maintenance seam: drop the compiled-query cache. */
23
+ export function __resetQueryCache() {
24
+ queryCache.clear();
25
+ }
26
+ /** Test seam: number of distinct compiled queries currently cached. */
27
+ export function __queryCacheSize() {
28
+ return queryCache.size;
29
+ }
30
+ function spanOf(node) {
31
+ return {
32
+ start_line: node.startPosition.row + 1,
33
+ start_col: node.startPosition.column + 1,
34
+ end_line: node.endPosition.row + 1,
35
+ end_col: node.endPosition.column + 1,
36
+ };
37
+ }
38
+ /** Strip surrounding quotes from a module specifier text. */
39
+ function stripQuotes(text) {
40
+ return text.replace(/^['"`]|['"`]$/g, '');
41
+ }
42
+ /**
43
+ * Walk up from a capture node to the enclosing import/export STATEMENT for
44
+ * span/ordinal anchoring, using the PROVIDER-LOCAL set of grammar node types that
45
+ * count as an import/export statement (cadrage §3 / Codex R1 langs#3-4).
46
+ *
47
+ * Each provider declares its OWN statement node types via
48
+ * {@link QueryRuntimeInput.enclosingStatementNodeTypes} (JS/TS
49
+ * `import_statement`/`export_statement`; Python `import_statement`/
50
+ * `import_from_statement`; PHP `namespace_use_declaration`; Java
51
+ * `import_declaration`). The runtime uses ONLY the set passed for the CURRENT file
52
+ * and NEVER imports/consults a central registry — adding a language is a provider
53
+ * declaration, not an edit here. This is the rule-of-N generalization the P1b
54
+ * review predicted: the runtime previously hard-coded the JS/Python statement
55
+ * names, which would have silently mis-spanned PHP/Java import statements.
56
+ */
57
+ function enclosingStatement(node, statementTypes) {
58
+ let n = node;
59
+ while (n) {
60
+ if (statementTypes.has(n.type))
61
+ return n;
62
+ n = n.parent;
63
+ }
64
+ return node;
65
+ }
66
+ /** Parse a `@definition.<subtype>.<role>` capture name into its parts. */
67
+ function parseDefinitionCapture(name) {
68
+ const m = /^definition\.(.+)\.(node|name|exported)$/.exec(name);
69
+ if (!m)
70
+ return null;
71
+ return { subtype: m[1], role: m[2] };
72
+ }
73
+ /**
74
+ * The fixed import/export capture roles the runtime `switch` honors. Together with
75
+ * the `definition.<subtype>.(node|name|exported)` pattern, this is the COMPLETE
76
+ * hard-coded capture convention — THE contract (P1b §3.4). A provider's captureMap
77
+ * may only use these capture names.
78
+ */
79
+ const FIXED_IMPORT_EXPORT_CAPTURES = new Set([
80
+ 'import.source',
81
+ 'import.default.name',
82
+ 'import.namespace.name',
83
+ 'import.named.name',
84
+ 'export.name',
85
+ ]);
86
+ /** True iff `name` is a capture role the hard-coded runtime convention recognizes. */
87
+ export function isConventionCapture(name) {
88
+ return parseDefinitionCapture(name) !== null || FIXED_IMPORT_EXPORT_CAPTURES.has(name);
89
+ }
90
+ /**
91
+ * Validate that a provider's declared `captureMap` mirrors the hard-coded capture
92
+ * convention the runtime actually drives off (P1b §3.4). Throws (loud — a provider
93
+ * authoring bug, not a per-file error) listing any capture the runtime would
94
+ * silently ignore. The runtime does NOT call this on the hot path; providers/tests
95
+ * call it once to assert their captureMap is honest. Returns the offending captures
96
+ * for assertion ergonomics.
97
+ */
98
+ export function assertCaptureMapConforms(captureMap) {
99
+ const unknown = captureMap.map((m) => m.capture).filter((c) => !isConventionCapture(c));
100
+ if (unknown.length > 0) {
101
+ throw new Error(`captureMap declares captures the runtime convention does not recognize: ${unknown.join(', ')}. ` +
102
+ `The hard-coded convention is THE contract (definition.<subtype>.(node|name|exported) + ` +
103
+ `import.source/import.default.name/import.namespace.name/import.named.name/export.name).`);
104
+ }
105
+ return unknown;
106
+ }
107
+ /**
108
+ * Run the provider's tags + imports queries over one file and produce a draft.
109
+ * Never throws: parse/timeout failures set `attributes.parseStatus='parse_error'`
110
+ * and return a file-only draft; query exceptions return the partial draft + an
111
+ * `extraction_error` fact.
112
+ */
113
+ export async function extractWithQueries(input) {
114
+ const facts = [];
115
+ // PROVIDER-LOCAL enclosing-statement node types — the only source of truth for
116
+ // import/export span anchoring (cadrage §3 / Codex R1). No registry lookup.
117
+ const statementTypes = new Set(input.enclosingStatementNodeTypes);
118
+ const emptyDraft = (parseStatus, tree) => ({
119
+ file: { path: input.path },
120
+ definitions: [],
121
+ imports: [],
122
+ exports: [],
123
+ tests: [],
124
+ facts,
125
+ attributes: { parseStatus, __tree: tree },
126
+ });
127
+ // Oversized — never parse (mirrors legacy skipped_too_large; core emits file node).
128
+ if (input.sizeBytes > input.maxParseFileBytes) {
129
+ facts.push({
130
+ code: 'skipped_too_large',
131
+ message: `file ${input.sizeBytes} bytes exceeds max_parse_file_bytes ${input.maxParseFileBytes}`,
132
+ });
133
+ return emptyDraft('skipped_too_large', null);
134
+ }
135
+ // --- Parse (bounded by max_query_wait_ms) ---
136
+ let tree;
137
+ let parser = null;
138
+ try {
139
+ const grammar = await input.grammarForLang(input.lang);
140
+ const Parser = await getParser();
141
+ parser = new Parser();
142
+ // `grammarForLang` returns the engine-opaque grammar handle (typed `unknown` so
143
+ // the runtime stays provider-agnostic); it IS a web-tree-sitter Language here.
144
+ parser.setLanguage(grammar);
145
+ const deadline = input.maxQueryWaitMs;
146
+ if (typeof deadline === 'number' && deadline > 0) {
147
+ // web-tree-sitter parse is synchronous; the bound is advisory. We honor it by
148
+ // measuring wall-clock and flagging overruns (the legacy path had no timeout,
149
+ // so overrun → parse_error keeps us strictly no-worse than legacy).
150
+ const t0 = Date.now();
151
+ const parsed = parser.parse(input.source);
152
+ if (Date.now() - t0 > deadline) {
153
+ try {
154
+ parsed?.delete();
155
+ }
156
+ catch {
157
+ /* best effort */
158
+ }
159
+ facts.push({ code: 'parse_error', message: `parse exceeded max_query_wait_ms ${deadline}` });
160
+ return emptyDraft('parse_error', null);
161
+ }
162
+ if (!parsed) {
163
+ facts.push({ code: 'parse_error', message: 'parser returned null' });
164
+ return emptyDraft('parse_error', null);
165
+ }
166
+ tree = parsed;
167
+ }
168
+ else {
169
+ const parsed = parser.parse(input.source);
170
+ if (!parsed) {
171
+ facts.push({ code: 'parse_error', message: 'parser returned null' });
172
+ return emptyDraft('parse_error', null);
173
+ }
174
+ tree = parsed;
175
+ }
176
+ }
177
+ catch (err) {
178
+ facts.push({ code: 'parse_error', message: err instanceof Error ? err.message : String(err) });
179
+ return emptyDraft('parse_error', null);
180
+ }
181
+ finally {
182
+ // The per-file parser is no longer needed once parse() returns; the TREE stays
183
+ // alive for refine/finalize. Delete the parser (best effort) so each file does
184
+ // not leak a web-tree-sitter Parser instance.
185
+ if (parser) {
186
+ try {
187
+ parser.delete();
188
+ }
189
+ catch {
190
+ /* best effort */
191
+ }
192
+ }
193
+ }
194
+ const definitions = [];
195
+ const imports = [];
196
+ const exports = [];
197
+ try {
198
+ const grammar = await input.grammarForLang(input.lang);
199
+ const tagsQuery = await compileCached(input.providerId, input.lang, input.tagsHash, grammar, input.tagsSource);
200
+ const importsQuery = await compileCached(input.providerId, input.lang, input.importsHash, grammar, input.importsSource);
201
+ // --- DEFINITIONS: one DefScratch per match (each match has a .node + .name). ---
202
+ const defScratch = [];
203
+ for (const match of tagsQuery.matches(tree.rootNode)) {
204
+ let subtype = '';
205
+ let nodeNode = null;
206
+ let nameNode = null;
207
+ let exported = false;
208
+ for (const cap of match.captures) {
209
+ const parsed = parseDefinitionCapture(cap.name);
210
+ if (!parsed)
211
+ continue;
212
+ if (parsed.role === 'node') {
213
+ subtype = parsed.subtype;
214
+ nodeNode = cap.node;
215
+ }
216
+ else if (parsed.role === 'name') {
217
+ nameNode = cap.node;
218
+ }
219
+ else {
220
+ exported = true;
221
+ }
222
+ }
223
+ if (!nodeNode || !nameNode || !subtype)
224
+ continue;
225
+ defScratch.push({
226
+ subtype,
227
+ name: nameNode.text,
228
+ node: nodeNode,
229
+ nameNode,
230
+ exported,
231
+ ordinalIndex: nameNode.startIndex,
232
+ });
233
+ }
234
+ // --- IMPORTS / RE-EXPORTS / LOCAL EXPORTS ---
235
+ // Keyed by the captured @import.source node id so one statement with N
236
+ // sources yields N module nodes (multi-source aware). JS/TS = one source per
237
+ // statement → identical to the legacy per-statement grouping.
238
+ const importBySource = new Map();
239
+ const exportScratch = [];
240
+ for (const match of importsQuery.matches(tree.rootNode)) {
241
+ let sourceNode = null;
242
+ let defaultName = null;
243
+ let namespaceName = null;
244
+ let namedName = null;
245
+ let exportName = null;
246
+ for (const cap of match.captures) {
247
+ switch (cap.name) {
248
+ case 'import.source':
249
+ sourceNode = cap.node;
250
+ break;
251
+ case 'import.default.name':
252
+ defaultName = cap.node;
253
+ break;
254
+ case 'import.namespace.name':
255
+ namespaceName = cap.node;
256
+ break;
257
+ case 'import.named.name':
258
+ namedName = cap.node;
259
+ break;
260
+ case 'export.name':
261
+ exportName = cap.node;
262
+ break;
263
+ default:
264
+ break;
265
+ }
266
+ }
267
+ if (sourceNode) {
268
+ // import statement OR re-export (export … from). Group PER captured source
269
+ // node so a statement with N sources emits N module nodes (Python
270
+ // `import a, b`). span/ordinal stay anchored on the enclosing statement,
271
+ // which for single-source JS/TS is byte-identical to per-statement grouping.
272
+ const stmt = enclosingStatement(sourceNode, statementTypes);
273
+ const isReExport = stmt.type === 'export_statement';
274
+ let scratch = importBySource.get(sourceNode.id);
275
+ if (!scratch) {
276
+ scratch = {
277
+ source: stripQuotes(sourceNode.text),
278
+ statement: stmt,
279
+ span: spanOf(stmt),
280
+ names: [],
281
+ ordinalIndex: stmt.startIndex,
282
+ isReExport,
283
+ };
284
+ importBySource.set(sourceNode.id, scratch);
285
+ }
286
+ if (defaultName)
287
+ scratch.names.push('default');
288
+ if (namespaceName)
289
+ scratch.names.push('*');
290
+ if (namedName)
291
+ scratch.names.push(namedName.text);
292
+ }
293
+ else if (exportName) {
294
+ // Local export clause / default-identifier — mark-or-add at finalize.
295
+ exportScratch.push({
296
+ name: exportName.text,
297
+ node: exportName,
298
+ ordinalIndex: exportName.startIndex,
299
+ });
300
+ }
301
+ }
302
+ // `export * from 'm'` matches a pattern with no name capture → names empty.
303
+ // Legacy supplies "*" for a sourced re-export that carried no specifier.
304
+ for (const scratch of importBySource.values()) {
305
+ if (scratch.isReExport && scratch.names.length === 0)
306
+ scratch.names.push('*');
307
+ }
308
+ const anchored = [];
309
+ for (const d of defScratch) {
310
+ anchored.push({
311
+ ordinalIndex: d.ordinalIndex,
312
+ emit: (ordinal) => {
313
+ definitions.push({
314
+ ordinal,
315
+ captureName: `definition.${d.subtype}.node`,
316
+ name: d.name,
317
+ subtype: d.subtype,
318
+ span: spanOf(d.node),
319
+ nameSpan: spanOf(d.nameNode),
320
+ exported: d.exported || undefined,
321
+ sourceNode: { node: d.node, nameNode: d.nameNode },
322
+ });
323
+ },
324
+ });
325
+ }
326
+ for (const im of importBySource.values()) {
327
+ anchored.push({
328
+ ordinalIndex: im.ordinalIndex,
329
+ emit: (ordinal) => {
330
+ imports.push({
331
+ ordinal,
332
+ source: im.source,
333
+ span: im.span,
334
+ importedNames: im.names,
335
+ isReExport: im.isReExport || undefined,
336
+ });
337
+ },
338
+ });
339
+ }
340
+ for (const ex of exportScratch) {
341
+ anchored.push({
342
+ ordinalIndex: ex.ordinalIndex,
343
+ emit: (ordinal) => {
344
+ exports.push({ ordinal, name: ex.name, span: spanOf(enclosingStatement(ex.node, statementTypes)) });
345
+ },
346
+ });
347
+ }
348
+ // Stable sort by source byte offset; ties keep insertion (def→import→export) order.
349
+ anchored
350
+ .map((a, i) => ({ a, i }))
351
+ .sort((x, y) => (x.a.ordinalIndex - y.a.ordinalIndex) || (x.i - y.i))
352
+ .forEach((wrapped, ordinal) => wrapped.a.emit(ordinal));
353
+ }
354
+ catch (err) {
355
+ facts.push({
356
+ code: 'extraction_error',
357
+ message: err instanceof Error ? err.message : String(err),
358
+ });
359
+ }
360
+ const parseStatus = tree.rootNode.hasError ? 'parse_error' : 'parsed';
361
+ if (parseStatus === 'parse_error') {
362
+ facts.push({ code: 'parse_error', message: 'tree contains syntax errors' });
363
+ }
364
+ return {
365
+ file: { path: input.path },
366
+ definitions,
367
+ imports,
368
+ exports,
369
+ tests: [],
370
+ facts,
371
+ attributes: { parseStatus, __tree: tree },
372
+ };
373
+ }
374
+ //# sourceMappingURL=query-runtime.js.map
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Code Map P1a — CodeLanguageRegistry implementation (spec §4).
3
+ *
4
+ * Maps file extensions → providers (deterministic on collision: higher `priority`
5
+ * wins, then earlier registration), and runtime langs → providers. Surfaces the
6
+ * freshness inputs (provider versions + every query-asset hash per lang) and the
7
+ * `manifest.languages` entries keyed by runtime CodeLang.
8
+ *
9
+ * No behavior change in P1a — refresh.ts is NOT yet routed through this (Sprint 4
10
+ * cutover). The registry exists so the new core path can resolve a provider for a
11
+ * path and so freshness can fold in query-asset hashes.
12
+ */
13
+ import path from 'node:path';
14
+ export class DefaultCodeLanguageRegistry {
15
+ registrations = [];
16
+ nextOrder = 0;
17
+ register(p) {
18
+ this.registrations.push({ provider: p, order: this.nextOrder++ });
19
+ }
20
+ /**
21
+ * Resolve the provider owning a path's extension. On collision: highest
22
+ * `priority` (default 0) wins; ties broken by EARLIEST registration order.
23
+ */
24
+ providerForPath(path_) {
25
+ const ext = path.extname(path_).toLowerCase();
26
+ if (!ext)
27
+ return null;
28
+ const candidates = this.registrations.filter((r) => r.provider.extensions.some((e) => e.toLowerCase() === ext));
29
+ if (candidates.length === 0)
30
+ return null;
31
+ candidates.sort((a, b) => {
32
+ const pa = a.provider.priority ?? 0;
33
+ const pb = b.provider.priority ?? 0;
34
+ if (pa !== pb)
35
+ return pb - pa; // higher priority first
36
+ return a.order - b.order; // then earliest registration
37
+ });
38
+ const provider = candidates[0].provider;
39
+ return { provider, lang: provider.langForPath(path_) };
40
+ }
41
+ providerForLang(lang) {
42
+ // Same collision rule as providerForPath, applied over `languages`.
43
+ const candidates = this.registrations.filter((r) => r.provider.languages.includes(lang));
44
+ if (candidates.length === 0)
45
+ return null;
46
+ candidates.sort((a, b) => {
47
+ const pa = a.provider.priority ?? 0;
48
+ const pb = b.provider.priority ?? 0;
49
+ if (pa !== pb)
50
+ return pb - pa;
51
+ return a.order - b.order;
52
+ });
53
+ return candidates[0].provider;
54
+ }
55
+ activeLanguages() {
56
+ const seen = new Set();
57
+ const out = [];
58
+ for (const { provider } of this.registrations) {
59
+ for (const lang of provider.languages) {
60
+ if (!seen.has(lang)) {
61
+ seen.add(lang);
62
+ out.push(lang);
63
+ }
64
+ }
65
+ }
66
+ return out;
67
+ }
68
+ includedExtensions() {
69
+ const seen = new Set();
70
+ const out = [];
71
+ for (const { provider } of this.registrations) {
72
+ for (const ext of provider.extensions) {
73
+ const e = ext.toLowerCase();
74
+ if (!seen.has(e)) {
75
+ seen.add(e);
76
+ out.push(e);
77
+ }
78
+ }
79
+ }
80
+ return out;
81
+ }
82
+ /**
83
+ * `manifest.languages` entries keyed by runtime CodeLang. The winning provider
84
+ * per lang (collision rule) supplies the grammar name/version/hash.
85
+ */
86
+ languageEntries() {
87
+ const out = {};
88
+ for (const lang of this.activeLanguages()) {
89
+ const provider = this.providerForLang(lang);
90
+ if (!provider)
91
+ continue;
92
+ out[lang] = {
93
+ enabled: true,
94
+ grammar_name: provider.parser.grammarNameForLang(lang),
95
+ grammar_version: provider.version,
96
+ tree_sitter_grammar_hash: provider.parser.grammarHashForLang(lang),
97
+ };
98
+ }
99
+ return out;
100
+ }
101
+ /**
102
+ * Freshness inputs: per provider, its `version` + every query-asset hash for
103
+ * every lang it owns. Editing a `.scm` flips a hash here → `stale_extractor`.
104
+ * Deterministically ordered (registration order, then lang order).
105
+ */
106
+ configHashInputs() {
107
+ return this.registrations.map(({ provider }) => ({
108
+ id: provider.id,
109
+ version: provider.version,
110
+ queries: provider.languages.map((lang) => ({
111
+ lang,
112
+ tags: provider.queries.tags.hashForLang(lang),
113
+ imports: provider.queries.imports.hashForLang(lang),
114
+ })),
115
+ }));
116
+ }
117
+ }
118
+ /** Convenience: build a registry pre-loaded with the given providers. */
119
+ export function createRegistry(...providers) {
120
+ const reg = new DefaultCodeLanguageRegistry();
121
+ for (const p of providers)
122
+ reg.register(p);
123
+ return reg;
124
+ }
125
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1,90 @@
1
+ ; Brainclaw Code Map — TypeScript/TSX/JavaScript IMPORTS + EXPORTS query (curated, vendored).
2
+ ; See ./README.md for the capture-name convention and provenance.
3
+ ;
4
+ ; Capture -> draft mapping performed by the query-runtime:
5
+ ; @import.source -> ImportDraft.source (string literal text; quotes stripped
6
+ ; by the runtime). The @import.source node ALSO anchors the
7
+ ; ImportDraft.span = enclosing import/export statement span.
8
+ ; @import.default.name -> contributes source-side imported name "default"
9
+ ; @import.namespace.name -> contributes source-side imported name "*"
10
+ ; @import.named.name -> contributes source-side imported name = specifier `name`
11
+ ; (NOT the local alias)
12
+ ; @export.name -> ExportDraft.name (a re-export / mark-or-add export target)
13
+ ;
14
+ ; This asset covers: `import` statements, `export { ... }` clauses (no source),
15
+ ; `export ... from '...'` / `export * from '...'` re-exports (WITH source), and
16
+ ; `export default <identifier>`. Declaration exports (`export function/class/...`) are
17
+ ; NOT handled here — they are definitions and carry their `exported` flag from tags.scm.
18
+ ;
19
+ ; Re-export source detection is the discriminator the provider's refine() uses to build
20
+ ; a module node (no phantom symbol) vs. a local `export {a}` mark-or-add. The runtime
21
+ ; surfaces both @import.source and @export.name on a re-export match; refine() routes
22
+ ; on source presence.
23
+
24
+ ; ===========================================================================
25
+ ; IMPORT STATEMENTS
26
+ ; ===========================================================================
27
+
28
+ ; bare side-effect import: import 'm';
29
+ (import_statement
30
+ source: (string (string_fragment) @import.source))
31
+
32
+ ; default import: import def from 'm';
33
+ (import_statement
34
+ (import_clause (identifier) @import.default.name)
35
+ source: (string (string_fragment) @import.source))
36
+
37
+ ; namespace import: import * as ns from 'm';
38
+ (import_statement
39
+ (import_clause (namespace_import (identifier) @import.namespace.name))
40
+ source: (string (string_fragment) @import.source))
41
+
42
+ ; named imports: import { a, b as c } from 'm';
43
+ ; `name:` is the source-side specifier name (`a`, `b`) — the alias (`c`) is the
44
+ ; `alias:` field and is deliberately NOT captured (importedNames are source-side).
45
+ (import_statement
46
+ (import_clause
47
+ (named_imports
48
+ (import_specifier
49
+ name: (identifier) @import.named.name)))
50
+ source: (string (string_fragment) @import.source))
51
+
52
+ ; ===========================================================================
53
+ ; RE-EXPORTS WITH A SOURCE MODULE (export { a } from 'm'; export * from 'm';)
54
+ ; These carry @import.source so refine() builds a module node + imports edge.
55
+ ; ===========================================================================
56
+
57
+ ; export { a, b as c } from 'm'; -> source-side names a, b
58
+ (export_statement
59
+ (export_clause
60
+ (export_specifier
61
+ name: (identifier) @import.named.name))
62
+ source: (string (string_fragment) @import.source))
63
+
64
+ ; export * from 'm'; (no export_clause) -> the runtime supplies "*" for the names
65
+ (export_statement
66
+ "*"
67
+ source: (string (string_fragment) @import.source))
68
+
69
+ ; export * as ns from 'm'; -> namespace re-export
70
+ (export_statement
71
+ (namespace_export (identifier) @import.namespace.name)
72
+ source: (string (string_fragment) @import.source))
73
+
74
+ ; ===========================================================================
75
+ ; LOCAL EXPORT CLAUSES (export { a, b as c }; no source module)
76
+ ; -> @export.name per source-side specifier name; refine() does mark-or-add-export.
77
+ ; ===========================================================================
78
+ (export_statement
79
+ (export_clause
80
+ (export_specifier
81
+ name: (identifier) @export.name))
82
+ !source)
83
+
84
+ ; ===========================================================================
85
+ ; DEFAULT EXPORT OF AN IDENTIFIER (export default foo;)
86
+ ; -> @export.name = the referenced identifier; refine() links it if it names a symbol.
87
+ ; ===========================================================================
88
+ (export_statement
89
+ "default"
90
+ (identifier) @export.name)