brainclaw 1.9.1 → 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 (71) hide show
  1. package/README.md +47 -1
  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/mcp.js +121 -0
  7. package/dist/commands/run-profile.js +3 -2
  8. package/dist/commands/switch.js +100 -89
  9. package/dist/core/agent-files.js +12 -0
  10. package/dist/core/code-map/backend.js +123 -0
  11. package/dist/core/code-map/core.js +81 -0
  12. package/dist/core/code-map/drafts.js +2 -0
  13. package/dist/core/code-map/extractor.js +29 -0
  14. package/dist/core/code-map/finalizer.js +191 -0
  15. package/dist/core/code-map/freshness.js +108 -0
  16. package/dist/core/code-map/ids.js +0 -0
  17. package/dist/core/code-map/importable.js +35 -0
  18. package/dist/core/code-map/indexes.js +197 -0
  19. package/dist/core/code-map/lang/java/imports.scm +17 -0
  20. package/dist/core/code-map/lang/java/index.js +254 -0
  21. package/dist/core/code-map/lang/java/tags.scm +48 -0
  22. package/dist/core/code-map/lang/php/imports.scm +21 -0
  23. package/dist/core/code-map/lang/php/index.js +251 -0
  24. package/dist/core/code-map/lang/php/tags.scm +44 -0
  25. package/dist/core/code-map/lang/provider.js +9 -0
  26. package/dist/core/code-map/lang/providers.js +24 -0
  27. package/dist/core/code-map/lang/python/imports.scm +90 -0
  28. package/dist/core/code-map/lang/python/index.js +364 -0
  29. package/dist/core/code-map/lang/python/tags.scm +81 -0
  30. package/dist/core/code-map/lang/query-runtime.js +374 -0
  31. package/dist/core/code-map/lang/registry.js +125 -0
  32. package/dist/core/code-map/lang/typescript/imports.scm +90 -0
  33. package/dist/core/code-map/lang/typescript/index.js +306 -0
  34. package/dist/core/code-map/lang/typescript/tags.js.scm +106 -0
  35. package/dist/core/code-map/lang/typescript/tags.scm +151 -0
  36. package/dist/core/code-map/lock.js +210 -0
  37. package/dist/core/code-map/materialized.js +51 -0
  38. package/dist/core/code-map/memory-reader.js +59 -0
  39. package/dist/core/code-map/paths.js +53 -0
  40. package/dist/core/code-map/query.js +568 -0
  41. package/dist/core/code-map/refresh.js +0 -0
  42. package/dist/core/code-map/resolve.js +177 -0
  43. package/dist/core/code-map/store.js +206 -0
  44. package/dist/core/code-map/types.js +288 -0
  45. package/dist/core/code-map/vocabulary.js +57 -0
  46. package/dist/core/code-map/wasm-loader.js +294 -0
  47. package/dist/core/code-map/work-section.js +206 -0
  48. package/dist/core/codev-rounds.js +4 -0
  49. package/dist/core/execution-adapters.js +11 -10
  50. package/dist/core/execution-profile.js +58 -0
  51. package/dist/core/facade-schema.js +9 -0
  52. package/dist/core/instruction-templates.js +2 -0
  53. package/dist/core/mcp-command-resolution.js +3 -1
  54. package/dist/core/store-resolution.js +41 -4
  55. package/dist/facts.js +9 -5
  56. package/dist/facts.json +8 -4
  57. package/dist/vendor/web-tree-sitter/tree-sitter.js +3980 -0
  58. package/dist/vendor/web-tree-sitter/tree-sitter.wasm +0 -0
  59. package/dist/wasm/tree-sitter-java.wasm +0 -0
  60. package/dist/wasm/tree-sitter-javascript.wasm +0 -0
  61. package/dist/wasm/tree-sitter-php.wasm +0 -0
  62. package/dist/wasm/tree-sitter-python.wasm +0 -0
  63. package/dist/wasm/tree-sitter-tsx.wasm +0 -0
  64. package/dist/wasm/tree-sitter-typescript.wasm +0 -0
  65. package/dist/wasm/tree-sitter.wasm +0 -0
  66. package/docs/cli.md +46 -8
  67. package/docs/code-map.md +198 -0
  68. package/docs/integrations/mcp.md +13 -6
  69. package/docs/mcp-schema-changelog.md +7 -3
  70. package/docs/quickstart.md +1 -1
  71. package/package.json +11 -6
@@ -1,6 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import { loadActiveProject, saveActiveProject, clearActiveProject } from '../core/active-project.js';
3
- import { buildOperationalIdentity, loadCurrentSession, loadSessionById, saveCurrentSession } from '../core/identity.js';
3
+ import { buildOperationalIdentity, loadCurrentSession, loadSessionById, resolveCurrentSessionId, saveCurrentSession } from '../core/identity.js';
4
4
  import { memoryExists } from '../core/io.js';
5
5
  import { resolveProjectRef } from '../core/store-resolution.js';
6
6
  import { resolveCrossProjectLinks, resolveProjectCwd } from '../core/cross-project.js';
@@ -160,59 +160,70 @@ export function runSwitch(projectRef, options = {}) {
160
160
  }
161
161
  // --list: show available projects
162
162
  if (options.list) {
163
- listProjects(wsRoot, options.json ?? false);
163
+ listProjects(wsRoot, cwd, options.json ?? false);
164
164
  return;
165
165
  }
166
- // --clear: remove active project
166
+ // --clear: remove active project. Session-scoped by default (F3) — clearing
167
+ // the SHARED global pointer is an opt-in (--global) so one agent's clear no
168
+ // longer wipes every other agent's resolution.
167
169
  if (options.clear) {
168
- const session = loadCurrentSession(cwd);
169
- if (session?.active_project) {
170
- const { active_project: _removed, ...rest } = session;
171
- saveCurrentSession(rest, cwd);
170
+ let scope;
171
+ if (options.global) {
172
+ clearActiveProject(wsRoot);
173
+ scope = 'global';
174
+ }
175
+ else {
176
+ const session = loadCurrentSession(cwd);
177
+ if (session?.active_project) {
178
+ const { active_project: _removed, ...rest } = session;
179
+ saveCurrentSession(rest, cwd);
180
+ }
181
+ scope = 'session';
172
182
  }
173
- clearActiveProject(wsRoot);
174
183
  if (options.json) {
175
- console.log(JSON.stringify({ cleared: true }));
184
+ console.log(JSON.stringify({ cleared: true, scope }));
176
185
  }
177
186
  else {
178
- console.log('✔ Active project cleared. Commands will use current directory.');
187
+ const hint = scope === 'session' ? ' (session-scoped)' : ' (global)';
188
+ console.log(`✔ Active project cleared${hint}. Commands will use current directory.`);
179
189
  }
180
190
  return;
181
191
  }
182
192
  // No argument: show current active project
183
193
  if (!projectRef) {
184
- showCurrent(wsRoot, options.json ?? false);
194
+ showCurrent(wsRoot, cwd, options.json ?? false);
185
195
  return;
186
196
  }
187
197
  // Switch to project
188
- const resolved = resolveProjectRef(projectRef, cwd);
189
- if (!resolved) {
190
- console.error(`Error: cannot resolve project "${projectRef}".`);
191
- console.error('Use `brainclaw switch --list` to see available projects.');
192
- process.exit(1);
193
- }
194
- let projectName;
195
- try {
196
- const config = loadConfig(resolved);
197
- projectName = config.project_name;
198
- }
199
- catch {
200
- // name is optional
201
- }
202
198
  const now = new Date().toISOString();
203
- const session = loadCurrentSession(cwd);
204
- const scopedToSession = options.session ?? !!session;
205
199
  let scope;
206
- if (scopedToSession && session) {
207
- // Write to session state — only this agent sees this switch
208
- saveCurrentSession({
209
- ...session,
210
- active_project: { path: resolved, name: projectName, switched_at: now },
211
- }, cwd);
212
- scope = 'session';
213
- }
214
- else {
215
- // Fall back to global active-project.json
200
+ let switchedPath;
201
+ let switchedName;
202
+ if (options.global) {
203
+ // Opt-in, audited: set the SHARED workspace default for every agent on the
204
+ // host. Bypasses the session entirely (an operator setting a default).
205
+ // Resolve store-chain children AND cross-project links (mirror switchProject)
206
+ // so `switch <linked> --global` matches what --list shows and what the
207
+ // session path can target (Codex final review F3-F5 finding).
208
+ let resolved = resolveProjectRef(projectRef, cwd);
209
+ if (!resolved) {
210
+ try {
211
+ const linkResolved = resolveProjectCwd(projectRef, cwd);
212
+ if (linkResolved !== cwd)
213
+ resolved = linkResolved;
214
+ }
215
+ catch { /* falls through to the error below */ }
216
+ }
217
+ if (!resolved) {
218
+ console.error(`Error: cannot resolve project "${projectRef}".`);
219
+ console.error('Use `brainclaw switch --list` to see available projects.');
220
+ process.exit(1);
221
+ }
222
+ let projectName;
223
+ try {
224
+ projectName = loadConfig(resolved).project_name;
225
+ }
226
+ catch { /* name is optional */ }
216
227
  saveActiveProject(wsRoot, {
217
228
  path: resolved,
218
229
  name: projectName,
@@ -220,21 +231,47 @@ export function runSwitch(projectRef, options = {}) {
220
231
  switched_by: process.env.BRAINCLAW_AGENT_NAME ?? process.env.USER ?? 'unknown',
221
232
  });
222
233
  scope = 'global';
234
+ switchedPath = resolved;
235
+ switchedName = projectName;
236
+ }
237
+ else {
238
+ // F3 default: session-scoped + isolated. Delegate to switchProject — the
239
+ // safe model that auto-creates the session, honours an explicit
240
+ // BRAINCLAW_SESSION_ID (resolveCurrentSessionId returns it WITHOUT
241
+ // persisting, so the session file must be created), resolves cross-project
242
+ // links, and never touches the shared global pointer.
243
+ try {
244
+ const explicitSessionId = resolveCurrentSessionId(process.env, cwd) || undefined;
245
+ const result = switchProject(projectRef, { cwd, sessionOnly: true, sessionId: explicitSessionId });
246
+ scope = 'session';
247
+ switchedPath = result.path;
248
+ switchedName = result.name;
249
+ }
250
+ catch (err) {
251
+ console.error(`Error: ${err.message}`);
252
+ console.error('Use `brainclaw switch --list` to see available projects.');
253
+ process.exit(1);
254
+ }
223
255
  }
224
256
  if (options.json) {
225
- console.log(JSON.stringify({ switched: true, path: resolved, name: projectName, scope }));
257
+ console.log(JSON.stringify({ switched: true, path: switchedPath, name: switchedName, scope }));
226
258
  }
227
259
  else {
228
- const rel = path.relative(wsRoot, resolved) || '.';
229
- const scopeHint = scope === 'session' ? ' (session-scoped)' : '';
230
- console.log(`✔ Switched to ${projectName ? `"${projectName}" (${rel})` : rel}${scopeHint}`);
260
+ const rel = path.relative(wsRoot, switchedPath) || '.';
261
+ const scopeHint = scope === 'session' ? ' (session-scoped)' : ' (global — all agents)';
262
+ console.log(`✔ Switched to ${switchedName ? `"${switchedName}" (${rel})` : rel}${scopeHint}`);
231
263
  }
232
264
  }
233
- function showCurrent(wsRoot, json) {
234
- const active = loadActiveProject(wsRoot);
265
+ function showCurrent(wsRoot, cwd, json) {
266
+ // F5: prefer the session's own active project so an agent sees its own
267
+ // session-scoped switch, not just the shared global pointer.
268
+ const sessionActive = loadCurrentSession(cwd)?.active_project;
269
+ const globalActive = loadActiveProject(wsRoot);
270
+ const active = sessionActive ?? globalActive;
271
+ const source = sessionActive ? 'session' : globalActive ? 'global' : 'none';
235
272
  if (!active) {
236
273
  if (json) {
237
- console.log(JSON.stringify({ active: false }));
274
+ console.log(JSON.stringify({ active: false, scope: 'none' }));
238
275
  }
239
276
  else {
240
277
  console.log('No active project. Commands use current directory.');
@@ -243,67 +280,41 @@ function showCurrent(wsRoot, json) {
243
280
  return;
244
281
  }
245
282
  const rel = path.relative(wsRoot, active.path) || '.';
283
+ const switchedBy = 'switched_by' in active ? active.switched_by : undefined;
246
284
  if (json) {
247
- console.log(JSON.stringify({ active: true, ...active, relative_path: rel }));
285
+ console.log(JSON.stringify({ active: true, ...active, relative_path: rel, scope: source }));
248
286
  }
249
287
  else {
250
- console.log(`Active project: ${active.name ? `"${active.name}" (${rel})` : rel}`);
288
+ const scopeHint = source === 'session' ? ' (session-scoped)' : ' (global — all agents)';
289
+ console.log(`Active project: ${active.name ? `"${active.name}" (${rel})` : rel}${scopeHint}`);
251
290
  console.log(` switched at: ${active.switched_at}`);
252
- if (active.switched_by)
253
- console.log(` switched by: ${active.switched_by}`);
291
+ if (switchedBy)
292
+ console.log(` switched by: ${switchedBy}`);
254
293
  }
255
294
  }
256
- function listProjects(wsRoot, json) {
257
- const active = loadActiveProject(wsRoot);
258
- const projects = [];
259
- // Add workspace root itself
260
- if (memoryExists(wsRoot)) {
261
- try {
262
- const config = loadConfig(wsRoot);
263
- projects.push({
264
- name: config.project_name,
265
- path: wsRoot,
266
- relative_path: '.',
267
- active: active?.path === wsRoot,
268
- });
269
- }
270
- catch {
271
- projects.push({
272
- path: wsRoot,
273
- relative_path: '.',
274
- active: active?.path === wsRoot,
275
- });
276
- }
277
- }
278
- // Discover child projects (depth 7 covers deep workspace layouts like /srv/dev/repos/global/applications/*/...)
279
- const children = scanNestedBrainclawProjects(wsRoot, 7);
280
- for (const child of children) {
281
- const childPath = path.resolve(child.path);
282
- if (childPath === wsRoot)
283
- continue;
284
- const rel = path.relative(wsRoot, childPath) || '.';
285
- projects.push({
286
- name: child.project_name,
287
- path: childPath,
288
- relative_path: rel,
289
- active: active?.path === childPath,
290
- });
291
- }
295
+ function listProjects(wsRoot, cwd, json) {
296
+ // F5: delegate to the session-aware lister so the active marker reflects the
297
+ // agent's own session active project, falling back to the global pointer.
298
+ const result = listAvailableProjectsForSession(cwd);
292
299
  if (json) {
293
- console.log(JSON.stringify({ workspace: wsRoot, projects }, null, 2));
300
+ console.log(JSON.stringify({
301
+ workspace: result.workspace_root,
302
+ active_source: result.active_source,
303
+ projects: result.projects,
304
+ }, null, 2));
294
305
  return;
295
306
  }
296
- if (projects.length === 0) {
307
+ if (result.projects.length === 0) {
297
308
  console.log('No brainclaw projects found in this workspace.');
298
309
  return;
299
310
  }
300
- console.log(`Projects in ${wsRoot}:\n`);
301
- for (const p of projects) {
311
+ console.log(`Projects in ${result.workspace_root}:\n`);
312
+ for (const p of result.projects) {
302
313
  const marker = p.active ? '→ ' : ' ';
303
314
  const name = p.name ? `${p.name} (${p.relative_path})` : p.relative_path;
304
315
  console.log(`${marker}${name}`);
305
316
  }
306
- if (!active) {
317
+ if (result.active_source === 'none') {
307
318
  console.log('\nNo active project. Use `brainclaw switch <project>` to set one.');
308
319
  }
309
320
  }
@@ -24,6 +24,18 @@ This project uses brainclaw for shared coordination between humans and agents.
24
24
  2. Check **Your open work** for active claims and in-progress plans assigned to you
25
25
  3. Respect active claims from other agents — check \`brainclaw claim list\` before editing a claimed scope
26
26
 
27
+ ### Before editing unfamiliar code (Code Map)
28
+
29
+ Don't grep the repo blind. Orient with the Code Map first:
30
+
31
+ \`\`\`bash
32
+ brainclaw code-map brief <symbol-or-path> # ranked reading list + related decisions/traps (MCP: bclaw_code_brief)
33
+ brainclaw code-map find <name> # locate a symbol/class/component (MCP: bclaw_code_find)
34
+ brainclaw code-map status # freshness
35
+ brainclaw code-map refresh --all # when status is missing_index
36
+ brainclaw code-map refresh --changed # when status is stale_*
37
+ \`\`\`
38
+
27
39
  ### Before finishing (required)
28
40
 
29
41
  1. Release claims you opened: \`brainclaw claim release <id>\` — or \`brainclaw session-end --auto-release\`
@@ -0,0 +1,123 @@
1
+ /**
2
+ * CodeQueryBackend — the agent-facing query contract (spec §8).
3
+ *
4
+ * Introduced in P0 so a future Memgraph (or other) backend can be added without
5
+ * changing the agent-facing APIs. P0 ships exactly one implementation:
6
+ * `JsonlBackend`. In this sprint, `status()` and `refresh()` are minimally real
7
+ * (they read/init the durable store and report freshness); `find()`/`brief()`
8
+ * return not-yet-implemented placeholders that still carry a real
9
+ * `freshness_badge`, locking the response shape for later sprints.
10
+ */
11
+ import path from 'node:path';
12
+ import { readManifest, storeExists } from './store.js';
13
+ import { refresh as runRefresh } from './refresh.js';
14
+ import { brief as runBrief, find as runFind } from './query.js';
15
+ import { defaultMemoryReader } from './memory-reader.js';
16
+ /** spec §9 caps the brief reading list at 12 files. */
17
+ export const BRIEF_FILE_CAP = 12;
18
+ function badge(status, details = {}) {
19
+ return { status, details };
20
+ }
21
+ /**
22
+ * P0 JSONL-backed query backend. Reads the durable file store (manifest +
23
+ * shards + indexes); no graph DB. find()/brief() are stubbed for Sprint 1.
24
+ */
25
+ export class JsonlBackend {
26
+ /**
27
+ * Related-memory read seam (spec §11). Defaults to the canonical entity read
28
+ * path; tests inject an in-memory reader to assert attachment without a store.
29
+ */
30
+ memoryReader;
31
+ constructor(opts = {}) {
32
+ this.memoryReader = opts.memoryReader ?? defaultMemoryReader;
33
+ }
34
+ async status(input) {
35
+ const manifest = readManifest(input.cwd, input.preferredDirName);
36
+ if (!manifest) {
37
+ return {
38
+ store_exists: storeExists(input.cwd, input.preferredDirName),
39
+ freshness_badge: badge('missing_index'),
40
+ stats: null,
41
+ };
42
+ }
43
+ return {
44
+ store_exists: true,
45
+ freshness_badge: badge(manifest.freshness.status, {
46
+ stale_file_count: manifest.freshness.stale_file_count,
47
+ partial_reason: manifest.freshness.partial_reason,
48
+ }),
49
+ stats: {
50
+ files_indexed: manifest.stats.files_indexed,
51
+ nodes: manifest.stats.nodes,
52
+ edges: manifest.stats.edges,
53
+ },
54
+ };
55
+ }
56
+ /**
57
+ * Real refresh (spec §7): resolves project identity (input -> manifest ->
58
+ * cwd-derived default), then runs the Tree-sitter parse + index + materialize
59
+ * pipeline behind the project lock. A live competing lock fails fast with a
60
+ * clear status — refresh never blocks bclaw_work (rule 8).
61
+ */
62
+ async refresh(input) {
63
+ const scope = input.scope ?? 'changed';
64
+ const manifest = readManifest(input.cwd, input.preferredDirName);
65
+ const projectRoot = input.projectRoot ?? manifest?.project_root ?? input.cwd ?? process.cwd();
66
+ const projectId = input.projectId ?? manifest?.project_id ?? `prj_${path.basename(path.resolve(projectRoot))}`;
67
+ const result = await runRefresh({
68
+ projectId,
69
+ projectRoot,
70
+ scope,
71
+ cwd: input.cwd,
72
+ preferredDirName: input.preferredDirName,
73
+ ownerAgent: input.ownerAgent ?? null,
74
+ ownerAgentId: input.ownerAgentId ?? null,
75
+ });
76
+ return {
77
+ ran: result.ran,
78
+ scope,
79
+ lock_acquired: result.lock_acquired,
80
+ freshness_badge: badge(result.freshness.status, {
81
+ stale_file_count: result.freshness.stale_file_count,
82
+ partial_reason: result.freshness.partial_reason,
83
+ files_parsed: result.files_parsed,
84
+ files_compacted: result.files_compacted,
85
+ duration_ms: result.duration_ms,
86
+ }),
87
+ ...(result.lock_status ? { lock_status: result.lock_status } : {}),
88
+ };
89
+ }
90
+ /**
91
+ * Agent-facing symbol search (spec §12.1). Ranks symbols-index matches and
92
+ * lazily validates each backing shard against the live file before serving it
93
+ * as confident (§6.1); the response badge reflects any detected drift.
94
+ */
95
+ async find(input) {
96
+ const ctx = this.queryContext(input);
97
+ const out = runFind(input.query, input.limit, ctx);
98
+ return {
99
+ query: out.query,
100
+ matches: out.matches,
101
+ freshness_badge: { status: out.freshness_badge.status, details: out.freshness_badge.details },
102
+ };
103
+ }
104
+ /**
105
+ * Agent-facing reading list (spec §9, §11). Produces a ranked
106
+ * suggested_files_to_read (cap 12), attaches related brainclaw memory (cap 5),
107
+ * and carries a §6.1 lazy-validated freshness badge.
108
+ */
109
+ async brief(input) {
110
+ const ctx = this.queryContext(input);
111
+ const out = runBrief(input.target, input.limit, ctx, this.memoryReader);
112
+ return {
113
+ target: out.target,
114
+ suggested_files_to_read: out.suggested_files_to_read,
115
+ related_memory: out.related_memory,
116
+ freshness_badge: { status: out.freshness_badge.status, details: out.freshness_badge.details },
117
+ };
118
+ }
119
+ queryContext(input) {
120
+ return { cwd: input.cwd, preferredDirName: input.preferredDirName };
121
+ }
122
+ }
123
+ //# sourceMappingURL=backend.js.map
@@ -0,0 +1,81 @@
1
+ import { finalize } from './finalizer.js';
2
+ import { defaultRegistry } from './lang/providers.js';
3
+ /**
4
+ * The default registry is constructed + registered in `lang/providers.ts` (P1b
5
+ * §3.2) — the declared extension point for "which providers ship by default".
6
+ * Re-exported here so existing importers (`core.js`) keep working unchanged.
7
+ */
8
+ export { defaultRegistry };
9
+ const SERVICES = { version: '0.1.0' };
10
+ function fileOnlyResult(input, parseStatus) {
11
+ // Reuse the finalizer over an empty draft so the file node id matches exactly.
12
+ return finalize({
13
+ file: { path: input.path },
14
+ definitions: [],
15
+ imports: [],
16
+ exports: [],
17
+ tests: [],
18
+ facts: [{ code: 'skipped_unsupported', message: `no provider for ${input.path}` }],
19
+ attributes: { parseStatus },
20
+ }, input);
21
+ }
22
+ /** Delete the retained parse tree (best-effort). */
23
+ function releaseTree(draft) {
24
+ const tree = draft.attributes?.__tree;
25
+ if (tree) {
26
+ try {
27
+ tree.delete();
28
+ }
29
+ catch {
30
+ /* best effort */
31
+ }
32
+ }
33
+ }
34
+ /**
35
+ * Extract a single file via the provider pipeline. Signature-compatible with the
36
+ * legacy `extractor.ts:extractFile`. Resolves the provider by path; an unsupported
37
+ * extension yields a `skipped_unsupported` file-only result (never throws).
38
+ */
39
+ export async function extractFile(input, registry = defaultRegistry) {
40
+ const resolved = registry.providerForPath(input.path);
41
+ if (!resolved) {
42
+ return fileOnlyResult(input, 'skipped_unsupported');
43
+ }
44
+ const { provider, lang } = resolved;
45
+ // The caller's `input.lang` is authoritative for identity (it matches what the
46
+ // refresh pipeline resolved + what the oracle froze). We pass it through; the
47
+ // resolved `lang` is used only as a cross-check / for providers that re-resolve.
48
+ const providerInput = {
49
+ projectId: input.projectId,
50
+ path: input.path,
51
+ lang: input.lang,
52
+ source: input.source,
53
+ sizeBytes: input.sizeBytes,
54
+ maxParseFileBytes: input.maxParseFileBytes,
55
+ maxQueryWaitMs: input.maxQueryWaitMs,
56
+ };
57
+ void lang;
58
+ let draft = await provider.extractDraft(providerInput, SERVICES);
59
+ if (provider.refine) {
60
+ try {
61
+ draft = await provider.refine(draft, { input: providerInput, lang: input.lang });
62
+ }
63
+ catch (err) {
64
+ // Fall back to the pre-refine draft + a loud diagnostic (never drop the file).
65
+ draft = {
66
+ ...draft,
67
+ facts: [
68
+ ...draft.facts,
69
+ { code: 'refine_error', message: err instanceof Error ? err.message : String(err) },
70
+ ],
71
+ };
72
+ }
73
+ }
74
+ try {
75
+ return finalize(draft, input);
76
+ }
77
+ finally {
78
+ releaseTree(draft);
79
+ }
80
+ }
81
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=drafts.js.map
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Code Map extractor — THIN backward-compat surface (spec §3, §9 cutover).
3
+ *
4
+ * P1a cutover (Sprint 4): the legacy 540-line imperative extractor has been
5
+ * replaced by the query-driven provider pipeline. The real `extractFile` now
6
+ * lives on the CORE (`core.ts` → registry → provider.extractDraft → refine →
7
+ * finalize). This module keeps the historical import surface stable:
8
+ *
9
+ * - `extractFile` re-exported from `core.ts` (provider pipeline).
10
+ * - `ExtractInput` / `ExtractResult` the public extraction shapes (owned here so
11
+ * `core.ts`, `finalizer.ts`, and the oracle tests keep
12
+ * importing them from this module).
13
+ * - `hashContent` sha256 of file contents (used by refresh.ts + query.ts).
14
+ *
15
+ * The legacy imperative bodies (handleFunctionDeclaration / handleClassDeclaration
16
+ * / classifySubtype / returnsJsx / handleImport / markOrAddExport / …) are GONE.
17
+ * The oracle (`oracle.test.ts`) now exercises this re-export and so doubles as a
18
+ * provider-path regression guard against the frozen `oracle-golden.json`.
19
+ */
20
+ import crypto from 'node:crypto';
21
+ // The query-driven CORE entrypoint, re-exported under the historical name so all
22
+ // existing importers (refresh.ts, oracle.test.ts, …) keep resolving `extractFile`
23
+ // here. core.ts imports the types above (type-only → erased, so no runtime cycle).
24
+ export { extractFile } from './core.js';
25
+ /** sha256 of file contents (file_hash on the shard). */
26
+ export function hashContent(source) {
27
+ return `sha256:${crypto.createHash('sha256').update(source, 'utf-8').digest('hex')}`;
28
+ }
29
+ //# sourceMappingURL=extractor.js.map