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
package/README.md CHANGED
@@ -6,6 +6,15 @@
6
6
 
7
7
  <p align="center"><strong>Local-first coordination and shared memory for coding agents.</strong></p>
8
8
 
9
+ <p align="center">
10
+ <a href="https://github.com/jberdah/brainclaw/actions/workflows/ci.yml"><img src="https://github.com/jberdah/brainclaw/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
11
+ <a href="https://www.npmjs.com/package/brainclaw"><img src="https://img.shields.io/npm/v/brainclaw?logo=npm" alt="npm version"></a>
12
+ <a href="https://www.npmjs.com/package/brainclaw"><img src="https://img.shields.io/npm/dm/brainclaw" alt="npm downloads"></a>
13
+ <img src="https://img.shields.io/node/v/brainclaw" alt="node version">
14
+ <a href="./LICENSE"><img src="https://img.shields.io/npm/l/brainclaw" alt="MIT license"></a>
15
+ <img src="https://img.shields.io/badge/MCP-stdio-blue" alt="MCP">
16
+ </p>
17
+
9
18
  ---
10
19
 
11
20
  If you've ever:
@@ -35,6 +44,7 @@ It sits alongside your coding agents and gives them a shared state layer they ca
35
44
  | **Project memory** | constraints, decisions, traps, handoffs, and layered instructions agents can resume from |
36
45
  | **Coordination state** | shared plans, file claims (dispatched work isolated in Git Worktrees), runtime notes, and board views for active work |
37
46
  | **Agent-ready context** | compact, prompt-sized context built from real workspace state instead of stale instructions |
47
+ | **Code Map** | a Tree-sitter symbol + import index (JS/TS, Python, PHP, Java) so agents ask "where is X / what should I read first" before editing, with related decisions/traps attached — `bclaw_code_find` / `bclaw_code_brief`, see [code map](docs/code-map.md) |
38
48
  | **Native agent files** | auto-writes `CLAUDE.md`, `AGENTS.md`, `GEMINI.md`, `.cursor/rules/`, `.windsurfrules`, and similar local guidance |
39
49
  | **Multi-turn loops** | review and ideation loops with structured phases, iteration semantics, and per-phase memory filters — see[loop engine](docs/concepts/loop-engine.md) and [ideation loop](docs/concepts/ideation-loop.md) |
40
50
  | **Machine AI surface discovery** | detects local coding agents plus desktop AI work surfaces such as ChatGPT Desktop and Gemini CLI |
@@ -43,6 +53,19 @@ It sits alongside your coding agents and gives them a shared state layer they ca
43
53
 
44
54
  ---
45
55
 
56
+ ## Code Map
57
+
58
+ When an agent (or you) is about to edit unfamiliar code, the first question is *"where is this, and what should I read first?"* Code Map answers it without grepping: a per-project [Tree-sitter](https://tree-sitter.github.io/) index of the symbols each file defines (functions, classes, types, React components/hooks), what it imports/exports, and how files relate — across **JS / TS / TSX, Python, PHP, and Java**.
59
+
60
+ ```bash
61
+ brainclaw code-map find useAuth # locate a symbol / component / hook by name
62
+ brainclaw code-map brief src/App.tsx # ranked "read these first" list + related decisions/traps
63
+ ```
64
+
65
+ Capable agents use the MCP equivalents `bclaw_code_find` / `bclaw_code_brief`, each carrying a freshness badge. Code Map is a **discovery aid, not a build artifact**: it never changes your code, never blocks `bclaw_work`, and degrades gracefully — a stale or missing index says so instead of answering wrong. Pull-based (no daemon) and monorepo-aware. Full guide: **[Code Map](docs/code-map.md)**.
66
+
67
+ ---
68
+
46
69
  ## Agent Surfaces
47
70
 
48
71
  brainclaw exposes the same collaboration state through three surfaces, but they do not have the same role in an agent-first workflow.
@@ -97,7 +120,7 @@ brainclaw is most effective today when one agent works at a time in a given chec
97
120
 
98
121
  ## Platform Support
99
122
 
100
- brainclaw declares support for Node.js 20+ in `package.json` (`engines.node = ">=20.0.0"`). CI actively exercises Node 22 (Active LTS) and Node 24 (current LTS) on Linux; Windows runs on Node 24. Node 20 still works as a minimum runtime but is no longer CI-verifiedit reached EOL in April 2026 and was removed from GitHub-hosted runners. The recommended runtime is Node 22 LTS or Node 24 LTS. Real-world support is still not perfectly even yet.
123
+ brainclaw requires Node.js 22.12+ (`engines.node = ">=22.12.0"`). CI exercises Node 22 (Active LTS) and Node 24 (current LTS) on Linux; Windows runs on Node 24. Node 20 reached EOL in April 2026 and is no longer supportedthe commander 15 upgrade requires Node ≥22.12. The recommended runtime is Node 22 LTS or Node 24 LTS.
101
124
 
102
125
  | Logo | Platform | Status today | Notes |
103
126
  | ----------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
@@ -359,6 +382,7 @@ If you are integrating Brainclaw into an agent workflow, start with the agent-fa
359
382
  | `docs/quickstart-existing-project.md` | Joining a project that already has `.brainclaw/` |
360
383
  | `docs/server-operations.md` | Operator and remote-server workflow guide |
361
384
  | `docs/cli.md` | CLI reference for operators, scripts, and fallback use |
385
+ | `docs/code-map.md` | Code Map — symbol/import index, freshness model, monorepo behavior |
362
386
  | `docs/concepts/memory.md` | What "memory" means in brainclaw |
363
387
  | `docs/concepts/plans-and-claims.md` | Coordination layer |
364
388
  | `docs/concepts/runtime-notes.md` | Ephemeral observations |
@@ -393,6 +417,28 @@ npm run test:coverage # with coverage report
393
417
 
394
418
  For older releases (v0.x and the early v1.0 launch series), `git log` on `master` is the source of truth — every release commit follows the `chore(release): bump version to <semver>` convention, and the matching feature/fix commits reference their plan id (e.g. `feat(mcp): self-heal ... (pln#478)`).
395
419
 
420
+ ### v1.10.0
421
+
422
+ - **Code Map** — a new per-project [Tree-sitter](docs/code-map.md) symbol + import
423
+ index for **JS / TS / TSX, Python, PHP, and Java**. Ask *"where is X / what should I
424
+ read first"* before editing: `bclaw_code_find`, `bclaw_code_brief`,
425
+ `bclaw_code_status`, `bclaw_code_refresh` (CLI: `brainclaw code-map …`). Pull-based
426
+ freshness with a per-response badge; never blocks `bclaw_work`; monorepo-aware. See
427
+ [code map](docs/code-map.md).
428
+ - **Monorepo multi-project safety** — agents working in different child projects of a
429
+ monorepo are now genuinely independent:
430
+ - an anchored agent working *inside* a child project resolves **that** child, not the
431
+ repo root — no more "plans / index target the repo root";
432
+ - CLI `brainclaw switch` is **session-scoped by default** (two agents no longer clobber
433
+ a shared pointer); the new `--global` flag is the only path that sets the shared
434
+ workspace default; `switch --list` / show are session-aware;
435
+ - a session-less agent physically inside a child project beats a stale shared global
436
+ pointer (resolves the child it is in);
437
+ - dispatched / CoDev workers spawn with a **clean identity** — the coordinator's
438
+ session / project / agent env no longer leaks into a worker.
439
+ - Internal — MCP public surface fingerprint re-pinned for the Code Map tools (additive;
440
+ no tool removed or renamed).
441
+
396
442
  ### v1.9.1
397
443
 
398
444
  - **Multi-project scoping fixes for monorepos** — MCP reads/writes resolve the
Binary file
package/dist/cli.js CHANGED
@@ -19,6 +19,7 @@ import { runListPlans } from './commands/list-plans.js';
19
19
  import { runUpdatePlan } from './commands/update-plan.js';
20
20
  import { runDeletePlan } from './commands/delete-plan.js';
21
21
  import { runPlanResource } from './commands/plan-resource.js';
22
+ import { runCodeMap } from './commands/code-map.js';
22
23
  import { runSequenceResource } from './commands/sequence.js';
23
24
  import { runAddStep } from './commands/add-step.js';
24
25
  import { runDeleteStep } from './commands/delete-step.js';
@@ -598,6 +599,20 @@ program
598
599
  .action((subcommand, args, options) => {
599
600
  runPlanResource(subcommand, args, { ...options, actualEffort: options.actualEffort, localOnly: options.localOnly });
600
601
  });
602
+ // --- code-map ---
603
+ program
604
+ .command('code-map <subcommand> [args...]')
605
+ .description('Query the per-project Code Map (status, refresh, find, brief)')
606
+ .option('--json', 'Output as JSON')
607
+ .option('--all', 'For refresh: enumerate all supported files (full refresh)')
608
+ .option('--changed', 'For refresh: only changed files (default)')
609
+ .option('--limit <n>', 'Max results for find/brief', (v) => parseInt(v, 10))
610
+ .action((subcommand, args, options) => {
611
+ void runCodeMap(subcommand, args, options).catch((err) => {
612
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
613
+ process.exit(1);
614
+ });
615
+ });
601
616
  // --- list-plans ---
602
617
  program
603
618
  .command('list-plans')
@@ -1806,15 +1821,17 @@ program
1806
1821
  });
1807
1822
  program
1808
1823
  .command('switch [project]')
1809
- .description('Set the active project for subsequent commands')
1824
+ .description('Set the active project for subsequent commands (session-scoped by default)')
1810
1825
  .option('--list', 'List available projects in the workspace')
1811
1826
  .option('--clear', 'Clear the active project (revert to cwd)')
1827
+ .option('--global', 'Set/clear the SHARED workspace default for ALL agents (writes active-project.json). Without it, switch is session-scoped and isolated.')
1812
1828
  .option('--json', 'Output as JSON')
1813
1829
  .action((project, options) => {
1814
1830
  const globalOpts = options.parent?.parent ? program.opts() : {};
1815
1831
  runSwitch(project, {
1816
1832
  list: options.list,
1817
1833
  clear: options.clear,
1834
+ global: options.global,
1818
1835
  json: options.json,
1819
1836
  cwd: globalOpts.cwd,
1820
1837
  });
@@ -0,0 +1,129 @@
1
+ /**
2
+ * `brainclaw code-map <subcommand>` — CLI surface over the Code Map backend
3
+ * (spec §9). Mirrors plan-resource.ts: a switch over the subcommand delegating
4
+ * to a JsonlBackend (status | refresh | find | brief). The backend owns all
5
+ * query logic; this file only adapts it to argv + stdout (text or --json), and
6
+ * every output carries the freshness_badge.
7
+ */
8
+ import { JsonlBackend } from '../core/code-map/backend.js';
9
+ const KNOWN_SUBCOMMANDS = new Set(['status', 'refresh', 'find', 'brief']);
10
+ function backend() {
11
+ return new JsonlBackend();
12
+ }
13
+ function badgeLine(badge) {
14
+ const detailKeys = Object.keys(badge.details ?? {}).filter((k) => badge.details[k] !== null && badge.details[k] !== undefined);
15
+ const detail = detailKeys.length
16
+ ? ` (${detailKeys.map((k) => `${k}=${JSON.stringify(badge.details[k])}`).join(', ')})`
17
+ : '';
18
+ return `Freshness: ${badge.status}${detail}`;
19
+ }
20
+ export async function runCodeMap(subcommand, args, options = {}) {
21
+ const normalized = (subcommand ?? '').trim().toLowerCase();
22
+ const be = backend();
23
+ const cwd = options.cwd;
24
+ if (normalized === 'status') {
25
+ const status = await be.status({ cwd });
26
+ printStatus(status, options);
27
+ return;
28
+ }
29
+ if (normalized === 'refresh') {
30
+ const scope = options.all ? 'all' : 'changed';
31
+ const result = await be.refresh({ scope, cwd });
32
+ printRefresh(result, options);
33
+ return;
34
+ }
35
+ if (normalized === 'find') {
36
+ const query = args.join(' ').trim();
37
+ if (!query) {
38
+ console.error('Error: code-map find requires <query>.');
39
+ console.error(' Usage: brainclaw code-map find <query>');
40
+ process.exit(1);
41
+ }
42
+ const result = await be.find({ query, limit: options.limit, cwd });
43
+ printFind(result, options);
44
+ return;
45
+ }
46
+ if (normalized === 'brief') {
47
+ const target = args.join(' ').trim();
48
+ if (!target) {
49
+ console.error('Error: code-map brief requires <symbol-or-path>.');
50
+ console.error(' Usage: brainclaw code-map brief <symbol-or-path>');
51
+ process.exit(1);
52
+ }
53
+ const result = await be.brief({ target, limit: options.limit, cwd });
54
+ printBrief(result, options);
55
+ return;
56
+ }
57
+ console.error(`Error: unknown code-map subcommand "${subcommand}".`);
58
+ console.error(` Available: ${[...KNOWN_SUBCOMMANDS].join(', ')}`);
59
+ process.exit(1);
60
+ }
61
+ function printStatus(status, options) {
62
+ if (options.json) {
63
+ console.log(JSON.stringify(status, null, 2));
64
+ return;
65
+ }
66
+ console.log('Code Map status');
67
+ console.log(` Store: ${status.store_exists ? 'present' : 'absent'}`);
68
+ console.log(` ${badgeLine(status.freshness_badge)}`);
69
+ if (status.stats) {
70
+ console.log(` Files: ${status.stats.files_indexed}`);
71
+ console.log(` Nodes: ${status.stats.nodes}`);
72
+ console.log(` Edges: ${status.stats.edges}`);
73
+ }
74
+ else {
75
+ console.log(' Stats: (none — index not built)');
76
+ }
77
+ }
78
+ function printRefresh(result, options) {
79
+ if (options.json) {
80
+ console.log(JSON.stringify(result, null, 2));
81
+ return;
82
+ }
83
+ console.log(`Code Map refresh [${result.scope}]`);
84
+ console.log(` Ran: ${result.ran}`);
85
+ console.log(` Lock: ${result.lock_acquired ? 'acquired' : 'not acquired'}`);
86
+ if (result.lock_status)
87
+ console.log(` Status: ${result.lock_status}`);
88
+ console.log(` ${badgeLine(result.freshness_badge)}`);
89
+ }
90
+ function printFind(result, options) {
91
+ if (options.json) {
92
+ console.log(JSON.stringify(result, null, 2));
93
+ return;
94
+ }
95
+ console.log(`Code Map find: "${result.query}"`);
96
+ console.log(` ${badgeLine(result.freshness_badge)}`);
97
+ if (result.matches.length === 0) {
98
+ console.log(' (no matches)');
99
+ return;
100
+ }
101
+ for (const m of result.matches) {
102
+ const sub = m.subtype ? ` ${m.subtype}` : '';
103
+ console.log(` [${m.score.toFixed(1)}] ${m.name}${sub} — ${m.path}`);
104
+ }
105
+ }
106
+ function printBrief(result, options) {
107
+ if (options.json) {
108
+ console.log(JSON.stringify(result, null, 2));
109
+ return;
110
+ }
111
+ console.log(`Code Map brief: "${result.target}"`);
112
+ console.log(` ${badgeLine(result.freshness_badge)}`);
113
+ if (result.suggested_files_to_read.length === 0) {
114
+ console.log(' Suggested files: (none)');
115
+ }
116
+ else {
117
+ console.log(' Suggested files to read:');
118
+ for (const f of result.suggested_files_to_read) {
119
+ console.log(` [${f.score.toFixed(1)}] ${f.path} — ${f.reason}`);
120
+ }
121
+ }
122
+ if (result.related_memory.length > 0) {
123
+ console.log(' Related memory:');
124
+ for (const mem of result.related_memory) {
125
+ console.log(` ${mem.id} (${mem.kind}): ${mem.text.slice(0, 80)}`);
126
+ }
127
+ }
128
+ }
129
+ //# sourceMappingURL=code-map.js.map
@@ -26,6 +26,7 @@ import { buildContext } from '../core/context.js';
26
26
  import { buildCoordinationSnapshot } from '../core/coordination.js';
27
27
  import { getDefaultInvokeTemplate, getSpawnableAgents } from '../core/agent-capability.js';
28
28
  import { executeRound } from '../core/codev-rounds.js';
29
+ import { buildWorkerIdentityEnv } from '../core/execution-profile.js';
29
30
  import { loadIdeationRound } from '../core/ideation.js';
30
31
  import { summarizeMetrics, summarizeMetricsByRound } from '../core/codev-metrics.js';
31
32
  import { generatePlansFromConvergence, generateSummaryNote } from '../core/codev-plan-gen.js';
@@ -422,12 +423,16 @@ function spawnConsultant(brief, threadId, personaName, cwd, agent) {
422
423
  console.warn(` ⚠ Spawn error for ${agentName}/${personaName}: ${err.message}`);
423
424
  });
424
425
  };
426
+ // F7 (trp_0e5150d3): scrub coordinator identity so a consultant worker is an
427
+ // independent agent — these spawns previously inherited the full parent env.
428
+ const workerEnv = buildWorkerIdentityEnv(process.env, { agent: agentName });
425
429
  if (agentName === 'codex') {
426
430
  // Codex: use temp file via shell to avoid Windows .cmd ENOENT issues
427
431
  const child = spawn('sh', ['-c', `cat "${briefFile}" | "${binaryPath}" exec --full-auto - ; rm -f "${briefFile}"`], {
428
432
  detached: true,
429
433
  stdio: 'ignore',
430
434
  cwd,
435
+ env: workerEnv,
431
436
  });
432
437
  attachErrorHandler(child);
433
438
  child.unref();
@@ -438,6 +443,7 @@ function spawnConsultant(brief, threadId, personaName, cwd, agent) {
438
443
  detached: true,
439
444
  stdio: 'ignore',
440
445
  cwd,
446
+ env: workerEnv,
441
447
  });
442
448
  attachErrorHandler(child);
443
449
  child.unref();
@@ -448,6 +454,7 @@ function spawnConsultant(brief, threadId, personaName, cwd, agent) {
448
454
  detached: true,
449
455
  stdio: 'ignore',
450
456
  cwd,
457
+ env: workerEnv,
451
458
  });
452
459
  attachErrorHandler(child);
453
460
  child.unref();
@@ -48,6 +48,7 @@ import { dispatch, dispatchReview, generateDispatchBrief } from '../core/dispatc
48
48
  import { deleteMemoryItem, updateMemoryItem } from '../core/operations/memory-mutation.js';
49
49
  import { assessMemoryPressure, buildCompactionTemplate, applyCompaction } from '../core/gc-semantic.js';
50
50
  import { WorkRequestSchema, CoordinateRequestSchema } from '../core/facade-schema.js';
51
+ import { codeMapWorkSection, codeMapRefreshNextActions } from '../core/code-map/work-section.js';
51
52
  import { getSpawnableAgents, getCapabilityProfile, buildInvokeCommand, validateAgentForDispatch } from '../core/agent-capability.js';
52
53
  import { attemptExecution } from '../core/execution.js';
53
54
  import { createAgentRun, transitionAgentRun } from '../core/agentruns.js';
@@ -449,8 +450,54 @@ export const MCP_READ_TOOLS = [
449
450
  required: ['target_id'],
450
451
  },
451
452
  },
453
+ {
454
+ name: 'bclaw_code_status',
455
+ description: 'Code Map status for this project: store presence, freshness badge (fresh / stale_changed_files / stale_extractor / stale_grammar / partial / missing_index), and index stats (files, nodes, edges). Read-only; never refreshes. Pair with bclaw_code_refresh when freshness is missing_index or stale.',
456
+ annotations: { tier: 'standard', category: 'discovery', headlessApproval: 'auto' },
457
+ inputSchema: {
458
+ type: 'object',
459
+ properties: {},
460
+ },
461
+ },
462
+ {
463
+ name: 'bclaw_code_find',
464
+ description: 'Search the Code Map symbol index for a query (function/class/component/hook/type names). Returns ranked matches with path + score, plus a freshness_badge from the lazy read-path check. Read-only; never refreshes — a missing_index badge means run bclaw_code_refresh first.',
465
+ annotations: { tier: 'standard', category: 'discovery', headlessApproval: 'auto' },
466
+ inputSchema: {
467
+ type: 'object',
468
+ properties: {
469
+ query: { type: 'string', description: 'Symbol or token to search for (e.g. "App", "useAuth", "dispatch").' },
470
+ limit: { type: 'number', description: 'Max matches to return.' },
471
+ },
472
+ required: ['query'],
473
+ },
474
+ },
475
+ {
476
+ name: 'bclaw_code_brief',
477
+ description: 'Before editing, ask Code Map what to read: returns a ranked suggested_files_to_read list (cap 12) for a symbol or path, related brainclaw memory (cap 5), and a freshness_badge. Read-only; never refreshes.',
478
+ annotations: { tier: 'standard', category: 'discovery', headlessApproval: 'auto' },
479
+ inputSchema: {
480
+ type: 'object',
481
+ properties: {
482
+ target: { type: 'string', description: 'Symbol name or file path to build a reading brief for.' },
483
+ limit: { type: 'number', description: 'Max suggested files (hard-capped at 12 by the spec).' },
484
+ },
485
+ required: ['target'],
486
+ },
487
+ },
452
488
  ];
453
489
  const MCP_WRITE_TOOLS = [
490
+ {
491
+ name: 'bclaw_code_refresh',
492
+ description: 'Rebuild the Code Map index for this project (Tree-sitter parse + shards + indexes, behind the per-project lock). scope="changed" (default) reparses changed files; scope="all" does a full refresh + compaction. A live competing lock fails fast with a clear status — refresh never blocks. Returns the resulting freshness_badge.',
493
+ annotations: { tier: 'standard', category: 'discovery', headlessApproval: 'prompt' },
494
+ inputSchema: {
495
+ type: 'object',
496
+ properties: {
497
+ scope: { type: 'string', enum: ['changed', 'all'], description: 'changed (default) reparses changed files only; all does a full refresh with orphan compaction.' },
498
+ },
499
+ },
500
+ },
454
501
  {
455
502
  name: 'bclaw_dispatch',
456
503
  description: 'Unified dispatch entry for sequence-lane parallelization (parallelize plans across lanes). To open a NEW review of a commit/branch, use `bclaw_coordinate(intent="review", open_loop=true, targetAgents=[…])` instead — bclaw_dispatch is for sequence-driven execution, NOT for opening new reviews. `intent` discriminator: analysis (sequence lane status, read-only), execute (default — analyze + generate briefs + send to agents), review (routes an EXISTING already-reflected handoff to a reviewer — only for handoffs produced by `session-end --reflect-handoff` or similar). Consolidates the legacy bclaw_dispatch_analysis / bclaw_dispatch / bclaw_dispatch_review. Returns FacadeResponse; for verification semantics see the same response-validation guidance documented on `bclaw_coordinate`.',
@@ -2599,6 +2646,59 @@ async function _executeMcpToolCallInner(payload) {
2599
2646
  const { handleCheckSecurity } = await import('./check-security-mcp.js');
2600
2647
  return { response: toolResponse(await handleCheckSecurity(args, cwd)) };
2601
2648
  }
2649
+ // Code Map tools (spec §9). These delegate to the async JsonlBackend, so
2650
+ // they are handled here rather than via the synchronous read-tool path.
2651
+ // status/find/brief are reads; refresh is a write (prompt approval).
2652
+ if (name === 'bclaw_code_status' || name === 'bclaw_code_find' || name === 'bclaw_code_brief' || name === 'bclaw_code_refresh') {
2653
+ const { JsonlBackend } = await import('../core/code-map/backend.js');
2654
+ const be = new JsonlBackend();
2655
+ if (name === 'bclaw_code_status') {
2656
+ const status = await be.status({ cwd });
2657
+ return {
2658
+ response: toolResponse({
2659
+ content: [{ type: 'text', text: `Code Map: ${status.store_exists ? 'store present' : 'no store'} — freshness=${status.freshness_badge.status}` }],
2660
+ structuredContent: { ...status, freshness_badge: status.freshness_badge },
2661
+ }),
2662
+ };
2663
+ }
2664
+ if (name === 'bclaw_code_refresh') {
2665
+ const scope = args.scope === 'all' ? 'all' : 'changed';
2666
+ const result = await be.refresh({ scope, cwd });
2667
+ return {
2668
+ response: toolResponse({
2669
+ content: [{ type: 'text', text: `Code Map refresh [${result.scope}]: ran=${result.ran} freshness=${result.freshness_badge.status}${result.lock_status ? ` (${result.lock_status})` : ''}` }],
2670
+ structuredContent: { ...result, freshness_badge: result.freshness_badge },
2671
+ }),
2672
+ };
2673
+ }
2674
+ if (name === 'bclaw_code_find') {
2675
+ const query = typeof args.query === 'string' ? args.query : '';
2676
+ if (!query.trim()) {
2677
+ return { response: createToolErrorResponse('validation_error', 'bclaw_code_find requires a non-empty query.') };
2678
+ }
2679
+ const limit = typeof args.limit === 'number' ? args.limit : undefined;
2680
+ const result = await be.find({ query, limit, cwd });
2681
+ return {
2682
+ response: toolResponse({
2683
+ content: [{ type: 'text', text: `Code Map find "${result.query}": ${result.matches.length} match(es), freshness=${result.freshness_badge.status}` }],
2684
+ structuredContent: { ...result, freshness_badge: result.freshness_badge },
2685
+ }),
2686
+ };
2687
+ }
2688
+ // bclaw_code_brief
2689
+ const target = typeof args.target === 'string' ? args.target : '';
2690
+ if (!target.trim()) {
2691
+ return { response: createToolErrorResponse('validation_error', 'bclaw_code_brief requires a non-empty target.') };
2692
+ }
2693
+ const limit = typeof args.limit === 'number' ? args.limit : undefined;
2694
+ const result = await be.brief({ target, limit, cwd });
2695
+ return {
2696
+ response: toolResponse({
2697
+ content: [{ type: 'text', text: `Code Map brief "${result.target}": ${result.suggested_files_to_read.length} file(s) to read, freshness=${result.freshness_badge.status}` }],
2698
+ structuredContent: { ...result, freshness_badge: result.freshness_badge },
2699
+ }),
2700
+ };
2701
+ }
2602
2702
  if (MCP_READ_TOOLS.some((tool) => tool.name === name) || LEGACY_READ_TOOL_HANDLERS.has(name)) {
2603
2703
  return {
2604
2704
  response: appendLegacyMcpToolWarning(toolResponse(handleMcpReadToolCall(name, args, { cwd, connectionSessionId, effectiveScope: scopeInfo })), name),
@@ -4854,6 +4954,26 @@ async function _executeMcpToolCallInner(payload) {
4854
4954
  nextActions.push({ tool: 'bclaw_context', args: { kind: 'memory' }, when: 'to read the full changed items behind context_diff' });
4855
4955
  }
4856
4956
  nextActions.push({ tool: 'bclaw_quick_capture', args: { text: '<finding>', type: '<decision|trap|constraint|note>' }, when: 'capture decisions/traps as you work' });
4957
+ // Code Map opt-in section (spec §10). Single live seam: returns null
4958
+ // (and does no work) unless the project's Code Map manifest carries
4959
+ // code_map_enabled:true. Never refreshes; bounded ≤2500ms on an active
4960
+ // lock; surfaces a missing_index hint or stale results with the badge.
4961
+ let codeMapSection = null;
4962
+ try {
4963
+ codeMapSection = await codeMapWorkSection(targetCwd, {
4964
+ query: workReq.scope ?? workReq.contextTarget,
4965
+ });
4966
+ }
4967
+ catch {
4968
+ // Best-effort: Code Map must never break or block bclaw_work.
4969
+ }
4970
+ // Code Map onboarding/freshness nudge (pln#588): promote the actionable
4971
+ // refresh to a first-class next_action so a fresh or stale project's first
4972
+ // bclaw_work TELLS the agent to build/update the index — the passive
4973
+ // missing_index hint alone was easy to skip. Still never refreshes here.
4974
+ for (const action of codeMapRefreshNextActions(codeMapSection)) {
4975
+ nextActions.push(action);
4976
+ }
4857
4977
  const facadeResponse = {
4858
4978
  status: 'ok',
4859
4979
  intent: workReq.intent,
@@ -4868,6 +4988,7 @@ async function _executeMcpToolCallInner(payload) {
4868
4988
  bootstrap_verdict: bootstrapVerdict,
4869
4989
  next_action: nextAction,
4870
4990
  next_actions: nextActions,
4991
+ ...(codeMapSection ? { code_map: codeMapSection } : {}),
4871
4992
  };
4872
4993
  const summaryParts = [`✔ bclaw_work [${workReq.intent}] session=${sessionResult.session_id}`];
4873
4994
  if (claimId)
@@ -37,8 +37,9 @@ export function runRunProfile(profileName, options = {}) {
37
37
  console.error(`Unknown agent: ${options.agent}. Using profile invoke template.`);
38
38
  }
39
39
  }
40
- // Replace {prompt} placeholder with the profile prompt
41
- const command = invoke.replace(/\{prompt\}/g, profile.prompt.replace(/"/g, '\\"'));
40
+ // Replace {prompt} placeholder with the profile prompt. Escape backslashes
41
+ // before quotes so a backslash in the prompt can't break out of the quoting.
42
+ const command = invoke.replace(/\{prompt\}/g, profile.prompt.replace(/\\/g, '\\\\').replace(/"/g, '\\"'));
42
43
  if (options.dry) {
43
44
  console.log(`[dry-run] Profile: ${profile.name}`);
44
45
  console.log(`[dry-run] Command: ${command}`);