brainclaw 1.9.0 → 1.9.1

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 (91) hide show
  1. package/README.md +585 -499
  2. package/dist/brainclaw-vscode.vsix +0 -0
  3. package/dist/commands/harvest.js +1 -1
  4. package/dist/commands/hooks.js +73 -73
  5. package/dist/commands/init.js +1 -1
  6. package/dist/commands/install-hooks.js +78 -78
  7. package/dist/commands/mcp-read-handlers.js +57 -14
  8. package/dist/commands/mcp.js +79 -13
  9. package/dist/commands/switch.js +26 -5
  10. package/dist/commands/version.js +1 -1
  11. package/dist/core/agent-capability.js +19 -4
  12. package/dist/core/agent-files.js +119 -119
  13. package/dist/core/codev-prompts.js +38 -38
  14. package/dist/core/default-profiles/doctor.yaml +11 -11
  15. package/dist/core/default-profiles/janitor.yaml +11 -11
  16. package/dist/core/default-profiles/onboarder.yaml +11 -11
  17. package/dist/core/default-profiles/reviewer.yaml +13 -13
  18. package/dist/core/dispatcher.js +1 -1
  19. package/dist/core/entity-operations.js +29 -3
  20. package/dist/core/execution.js +1 -1
  21. package/dist/core/loops/verbs.js +0 -1
  22. package/dist/core/messaging.js +2 -2
  23. package/dist/core/protocol-skills.js +164 -164
  24. package/dist/core/runtime-signals.js +1 -1
  25. package/dist/core/search.js +19 -2
  26. package/dist/core/security-guard.js +207 -207
  27. package/dist/core/spawn-check.js +16 -2
  28. package/dist/core/staleness.js +1 -1
  29. package/dist/core/store-resolution.js +26 -7
  30. package/dist/core/worktree.js +18 -18
  31. package/dist/facts.js +3 -3
  32. package/dist/facts.json +2 -2
  33. package/docs/PROTOCOL.md +1 -1
  34. package/docs/adapters/openclaw.md +43 -43
  35. package/docs/architecture/project-refs.md +328 -328
  36. package/docs/cli.md +2093 -2093
  37. package/docs/concepts/coordination.md +52 -52
  38. package/docs/concepts/coordinator-runbook.md +129 -129
  39. package/docs/concepts/dispatch-lifecycle.md +245 -245
  40. package/docs/concepts/event-log-store.md +928 -928
  41. package/docs/concepts/ideation-loop.md +317 -317
  42. package/docs/concepts/loop-engine.md +520 -511
  43. package/docs/concepts/mcp-governance.md +268 -268
  44. package/docs/concepts/memory.md +84 -84
  45. package/docs/concepts/multi-agent-workflows.md +167 -167
  46. package/docs/concepts/observer-protocol.md +361 -361
  47. package/docs/concepts/plans-and-claims.md +217 -217
  48. package/docs/concepts/project-md-convention.md +35 -35
  49. package/docs/concepts/runtime-notes.md +38 -38
  50. package/docs/concepts/troubleshooting.md +254 -254
  51. package/docs/concepts/workspace-bootstrapping.md +142 -142
  52. package/docs/context-format-changelog.md +35 -35
  53. package/docs/context-format.md +48 -48
  54. package/docs/index.md +65 -65
  55. package/docs/integrations/agents.md +158 -158
  56. package/docs/integrations/claude-code.md +23 -23
  57. package/docs/integrations/cline.md +77 -77
  58. package/docs/integrations/continue.md +55 -55
  59. package/docs/integrations/copilot.md +68 -68
  60. package/docs/integrations/cursor.md +23 -23
  61. package/docs/integrations/kilocode.md +72 -72
  62. package/docs/integrations/mcp.md +377 -377
  63. package/docs/integrations/mistral-vibe.md +122 -122
  64. package/docs/integrations/openclaw.md +92 -92
  65. package/docs/integrations/opencode.md +84 -84
  66. package/docs/integrations/overview.md +115 -115
  67. package/docs/integrations/roo.md +71 -71
  68. package/docs/integrations/windsurf.md +77 -77
  69. package/docs/mcp-schema-changelog.md +360 -356
  70. package/docs/playbooks/integration/index.md +121 -121
  71. package/docs/playbooks/orchestration.md +37 -0
  72. package/docs/playbooks/productivity/index.md +99 -99
  73. package/docs/playbooks/team/index.md +117 -117
  74. package/docs/product/agent-first-model.md +184 -184
  75. package/docs/product/entity-model-audit.md +462 -462
  76. package/docs/product/positioning.md +86 -86
  77. package/docs/quickstart-existing-project.md +107 -107
  78. package/docs/quickstart.md +183 -183
  79. package/docs/release-maintenance.md +79 -79
  80. package/docs/reputation.md +52 -52
  81. package/docs/review.md +45 -45
  82. package/docs/security.md +212 -212
  83. package/docs/server-operations.md +118 -118
  84. package/docs/storage.md +106 -106
  85. package/package.json +80 -65
  86. package/docs/concepts/event-log-store-critique-A.md +0 -333
  87. package/docs/concepts/event-log-store-critique-B.md +0 -353
  88. package/docs/concepts/event-log-store-phase0-measurements.md +0 -58
  89. package/docs/concepts/event-log-store-proposal-A.md +0 -365
  90. package/docs/concepts/event-log-store-proposal-B.md +0 -404
  91. package/docs/concepts/identity-model-proposal.md +0 -371
Binary file
@@ -299,7 +299,7 @@ export function harvestLaneResults(options = {}) {
299
299
  // ─────────────────────────────────────────────────────────────────────────────
300
300
  // pln#534 — worktree-as-contract: integrate a worker's lane on its behalf.
301
301
  //
302
- // LEVER #1 from the LeaseUp frontier (can_100f1e8c). The worker's contract is
302
+ // LEVER #1 from a cross-project field session. The worker's contract is
303
303
  // reduced to "edit files in this worktree + drop LANE-RESULT.json". brainclaw
304
304
  // carries the rest for a worker that cannot (a sandboxed agent whose root
305
305
  // excludes `.git`, i.e. dispatchCanCommit=false): it COMMITS the worktree diff
@@ -9,33 +9,33 @@ import { BRAINCLAW_SECTION_START, BRAINCLAW_SECTION_END, upsertBrainclawSection,
9
9
  * deterministically into every agent conversation.
10
10
  */
11
11
  export function generateCursorHook(projectName) {
12
- return `---
13
- description: brainclaw session bootstrap for ${projectName}
14
- alwaysApply: true
15
- ---
16
-
17
- # Brainclaw session bootstrap
18
-
19
- Brainclaw is the shared coordination layer for this project. Use its MCP facades first — the CLI is only a fallback when MCP is unavailable.
20
-
21
- ## At the start of every session
22
-
23
- Call \`bclaw_work(intent)\` — it handles session setup, context load, and scope claim in a single call.
24
-
25
- - \`bclaw_work(intent: "resume")\` when continuing an existing task.
26
- - \`bclaw_work(intent: "execute", scope: "<path>", task: "<text>")\` when starting new work on a specific scope.
27
- - \`bclaw_work(intent: "consult")\` to read context without claiming.
28
-
29
- ## To coordinate with other agents
30
-
31
- \`bclaw_coordinate(intent)\` — \`assign\`, \`consult\`, \`review\`, or \`reroute\`.
32
-
33
- ## Before finishing
34
-
35
- - Release your claims: \`bclaw_release_claim(id)\`.
36
- - Close out the session: \`bclaw_session_end\` (or let it auto-release via the session hook).
37
-
38
- CLI fallback only: \`brainclaw context\` / \`brainclaw session-end --auto-release\` if MCP is not reachable.
12
+ return `---
13
+ description: brainclaw session bootstrap for ${projectName}
14
+ alwaysApply: true
15
+ ---
16
+
17
+ # Brainclaw session bootstrap
18
+
19
+ Brainclaw is the shared coordination layer for this project. Use its MCP facades first — the CLI is only a fallback when MCP is unavailable.
20
+
21
+ ## At the start of every session
22
+
23
+ Call \`bclaw_work(intent)\` — it handles session setup, context load, and scope claim in a single call.
24
+
25
+ - \`bclaw_work(intent: "resume")\` when continuing an existing task.
26
+ - \`bclaw_work(intent: "execute", scope: "<path>", task: "<text>")\` when starting new work on a specific scope.
27
+ - \`bclaw_work(intent: "consult")\` to read context without claiming.
28
+
29
+ ## To coordinate with other agents
30
+
31
+ \`bclaw_coordinate(intent)\` — \`assign\`, \`consult\`, \`review\`, or \`reroute\`.
32
+
33
+ ## Before finishing
34
+
35
+ - Release your claims: \`bclaw_release_claim(id)\`.
36
+ - Close out the session: \`bclaw_session_end\` (or let it auto-release via the session hook).
37
+
38
+ CLI fallback only: \`brainclaw context\` / \`brainclaw session-end --auto-release\` if MCP is not reachable.
39
39
  `;
40
40
  }
41
41
  /**
@@ -44,58 +44,58 @@ CLI fallback only: \`brainclaw context\` / \`brainclaw session-end --auto-releas
44
44
  * delimited "SESSION START" block acts as a deterministic trigger.
45
45
  */
46
46
  export function generateWindsurfHook(projectName) {
47
- return `# Brainclaw session trigger — ${projectName}
48
- # This block is always active. Windsurf Cascade reads .windsurfrules on every activation.
49
-
50
- ## SESSION START (always run first)
51
-
52
- Call the Brainclaw MCP facade before responding to any request:
53
-
54
- - \`bclaw_work(intent: "resume")\` when continuing an existing task.
55
- - \`bclaw_work(intent: "execute", scope: "<path>", task: "<text>")\` when starting new work on a specific scope.
56
- - \`bclaw_work(intent: "consult")\` to read context without claiming.
57
-
58
- This returns active constraints, recent decisions, known traps, open plans, active claims, and the last handoff. Do not skip.
59
-
60
- ## COORDINATION
61
-
62
- \`bclaw_coordinate(intent)\` to assign, consult, review, or reroute other agents.
63
-
64
- ## SESSION END (before finishing)
65
-
66
- - \`bclaw_release_claim(id)\` for each claim you hold.
67
- - \`bclaw_session_end\` to close the session (auto-releases remaining claims).
68
-
69
- CLI fallback only when MCP is unavailable: \`brainclaw context\` / \`brainclaw session-end --auto-release\`.
47
+ return `# Brainclaw session trigger — ${projectName}
48
+ # This block is always active. Windsurf Cascade reads .windsurfrules on every activation.
49
+
50
+ ## SESSION START (always run first)
51
+
52
+ Call the Brainclaw MCP facade before responding to any request:
53
+
54
+ - \`bclaw_work(intent: "resume")\` when continuing an existing task.
55
+ - \`bclaw_work(intent: "execute", scope: "<path>", task: "<text>")\` when starting new work on a specific scope.
56
+ - \`bclaw_work(intent: "consult")\` to read context without claiming.
57
+
58
+ This returns active constraints, recent decisions, known traps, open plans, active claims, and the last handoff. Do not skip.
59
+
60
+ ## COORDINATION
61
+
62
+ \`bclaw_coordinate(intent)\` to assign, consult, review, or reroute other agents.
63
+
64
+ ## SESSION END (before finishing)
65
+
66
+ - \`bclaw_release_claim(id)\` for each claim you hold.
67
+ - \`bclaw_session_end\` to close the session (auto-releases remaining claims).
68
+
69
+ CLI fallback only when MCP is unavailable: \`brainclaw context\` / \`brainclaw session-end --auto-release\`.
70
70
  `;
71
71
  }
72
72
  /**
73
73
  * Generate a generic markdown session-trigger section.
74
74
  */
75
75
  export function generateMarkdownHook(agentName, projectName) {
76
- return `# Brainclaw session trigger — ${projectName}
77
- # This block is always active. ${agentName} reads this file on every activation.
78
-
79
- ## SESSION START (always run first)
80
-
81
- Call the Brainclaw MCP facade before responding to any request:
82
-
83
- - \`bclaw_work(intent: "resume")\` when continuing an existing task.
84
- - \`bclaw_work(intent: "execute", scope: "<path>", task: "<text>")\` when starting new work on a specific scope.
85
- - \`bclaw_work(intent: "consult")\` to read context without claiming.
86
-
87
- This returns active constraints, recent decisions, known traps, open plans, active claims, and the last handoff. Do not skip.
88
-
89
- ## COORDINATION
90
-
91
- \`bclaw_coordinate(intent)\` to assign, consult, review, or reroute other agents.
92
-
93
- ## SESSION END (before finishing)
94
-
95
- - \`bclaw_release_claim(id)\` for each claim you hold.
96
- - \`bclaw_session_end\` to close the session (auto-releases remaining claims).
97
-
98
- CLI fallback only when MCP is unavailable: \`brainclaw context\` / \`brainclaw session-end --auto-release\`.
76
+ return `# Brainclaw session trigger — ${projectName}
77
+ # This block is always active. ${agentName} reads this file on every activation.
78
+
79
+ ## SESSION START (always run first)
80
+
81
+ Call the Brainclaw MCP facade before responding to any request:
82
+
83
+ - \`bclaw_work(intent: "resume")\` when continuing an existing task.
84
+ - \`bclaw_work(intent: "execute", scope: "<path>", task: "<text>")\` when starting new work on a specific scope.
85
+ - \`bclaw_work(intent: "consult")\` to read context without claiming.
86
+
87
+ This returns active constraints, recent decisions, known traps, open plans, active claims, and the last handoff. Do not skip.
88
+
89
+ ## COORDINATION
90
+
91
+ \`bclaw_coordinate(intent)\` to assign, consult, review, or reroute other agents.
92
+
93
+ ## SESSION END (before finishing)
94
+
95
+ - \`bclaw_release_claim(id)\` for each claim you hold.
96
+ - \`bclaw_session_end\` to close the session (auto-releases remaining claims).
97
+
98
+ CLI fallback only when MCP is unavailable: \`brainclaw context\` / \`brainclaw session-end --auto-release\`.
99
99
  `;
100
100
  }
101
101
  export function writeHook(content, relativePath, cwd) {
@@ -117,7 +117,7 @@ export async function runInit(options = {}) {
117
117
  });
118
118
  // Auto-detect and register the AI coding agent running in this environment
119
119
  const detectedAi = skipAgentBootstrap ? undefined : detectAiAgent();
120
- let registeredAiAgent = detectedAi
120
+ const registeredAiAgent = detectedAi
121
121
  ? registerAgentIdentity({
122
122
  agentName: detectedAi.name,
123
123
  kind: detectedAi.kind,
@@ -40,63 +40,63 @@ export function runInstallHooks(options = {}) {
40
40
  }
41
41
  }
42
42
  function generateClaudePreToolScript() {
43
- return `#!/bin/sh
44
- # brainclaw Claude Code preToolUse hook
45
- # Generated by: brainclaw install-hooks
46
- exec node -e "
47
- const fs = require('fs');
48
- const path = require('path');
49
- const { execSync } = require('child_process');
50
-
51
- const toolName = process.env.CLAUDE_TOOL_NAME || process.argv[2] || process.env.TOOL_NAME || '';
52
- const isWrite = /edit|write|replace|bash|str_replace/i.test(toolName);
53
-
54
- if (!isWrite || !toolName) process.exit(0);
55
-
56
- try {
57
- const BCLAW_CMD = fs.existsSync(path.join(process.cwd(), 'node_modules', '.bin', 'brainclaw'))
58
- ? 'npx brainclaw' : 'brainclaw';
59
- const out = execSync(BCLAW_CMD + ' claim list --json', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] });
60
- const claims = JSON.parse(out);
61
- if (claims && claims.length > 0) process.exit(0);
62
- } catch (e) {
63
- // ignore if brainclaw not found or fails
64
- process.exit(0);
65
- }
66
-
67
- const sessionMark = path.join(process.cwd(), '.brainclaw', 'tmp', 'claude_warned');
68
- if (!fs.existsSync(path.dirname(sessionMark))) fs.mkdirSync(path.dirname(sessionMark), { recursive: true });
69
-
70
- if (fs.existsSync(sessionMark)) {
71
- const mtime = fs.statSync(sessionMark).mtimeMs;
72
- if (Date.now() - mtime < 2 * 60 * 60 * 1000) {
73
- process.exit(0); // already warned this session
74
- }
75
- }
76
- fs.writeFileSync(sessionMark, Date.now().toString());
77
-
78
- process.stderr.write('\\n[Brainclaw] ⚠️ WARNING: You are about to use an Edit/Write tool (' + toolName + ') but you have NO active claim.\\n');
79
- process.stderr.write('[Brainclaw] Consider running \\\`brainclaw claim create <scope>\\\` to lock your work and avoid conflicts.\\n\\n');
80
- " 2>&1 || exit 0
43
+ return `#!/bin/sh
44
+ # brainclaw Claude Code preToolUse hook
45
+ # Generated by: brainclaw install-hooks
46
+ exec node -e "
47
+ const fs = require('fs');
48
+ const path = require('path');
49
+ const { execSync } = require('child_process');
50
+
51
+ const toolName = process.env.CLAUDE_TOOL_NAME || process.argv[2] || process.env.TOOL_NAME || '';
52
+ const isWrite = /edit|write|replace|bash|str_replace/i.test(toolName);
53
+
54
+ if (!isWrite || !toolName) process.exit(0);
55
+
56
+ try {
57
+ const BCLAW_CMD = fs.existsSync(path.join(process.cwd(), 'node_modules', '.bin', 'brainclaw'))
58
+ ? 'npx brainclaw' : 'brainclaw';
59
+ const out = execSync(BCLAW_CMD + ' claim list --json', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] });
60
+ const claims = JSON.parse(out);
61
+ if (claims && claims.length > 0) process.exit(0);
62
+ } catch (e) {
63
+ // ignore if brainclaw not found or fails
64
+ process.exit(0);
65
+ }
66
+
67
+ const sessionMark = path.join(process.cwd(), '.brainclaw', 'tmp', 'claude_warned');
68
+ if (!fs.existsSync(path.dirname(sessionMark))) fs.mkdirSync(path.dirname(sessionMark), { recursive: true });
69
+
70
+ if (fs.existsSync(sessionMark)) {
71
+ const mtime = fs.statSync(sessionMark).mtimeMs;
72
+ if (Date.now() - mtime < 2 * 60 * 60 * 1000) {
73
+ process.exit(0); // already warned this session
74
+ }
75
+ }
76
+ fs.writeFileSync(sessionMark, Date.now().toString());
77
+
78
+ process.stderr.write('\\n[Brainclaw] ⚠️ WARNING: You are about to use an Edit/Write tool (' + toolName + ') but you have NO active claim.\\n');
79
+ process.stderr.write('[Brainclaw] Consider running \\\`brainclaw claim create <scope>\\\` to lock your work and avoid conflicts.\\n\\n');
80
+ " 2>&1 || exit 0
81
81
  `;
82
82
  }
83
83
  function generatePostMergeScript() {
84
- return `#!/bin/sh
85
- # brainclaw post-merge hook
86
- # Auto-releases claims whose scope overlaps with files changed by the merge
87
- # Generated by: brainclaw install-hooks
88
-
89
- BCLAW_CMD=""
90
- if command -v brainclaw >/dev/null 2>&1; then
91
- BCLAW_CMD="brainclaw"
92
- elif command -v bclaw >/dev/null 2>&1; then
93
- BCLAW_CMD="bclaw"
94
- else
95
- BCLAW_CMD="npx --no brainclaw"
96
- fi
97
-
98
- $BCLAW_CMD release-claims --from-git-diff 2>/dev/null || true
99
- $BCLAW_CMD worktree clean 2>/dev/null || true
84
+ return `#!/bin/sh
85
+ # brainclaw post-merge hook
86
+ # Auto-releases claims whose scope overlaps with files changed by the merge
87
+ # Generated by: brainclaw install-hooks
88
+
89
+ BCLAW_CMD=""
90
+ if command -v brainclaw >/dev/null 2>&1; then
91
+ BCLAW_CMD="brainclaw"
92
+ elif command -v bclaw >/dev/null 2>&1; then
93
+ BCLAW_CMD="bclaw"
94
+ else
95
+ BCLAW_CMD="npx --no brainclaw"
96
+ fi
97
+
98
+ $BCLAW_CMD release-claims --from-git-diff 2>/dev/null || true
99
+ $BCLAW_CMD worktree clean 2>/dev/null || true
100
100
  `;
101
101
  }
102
102
  function findGitRoot(cwd) {
@@ -113,30 +113,30 @@ function findGitRoot(cwd) {
113
113
  function generateHookScript() {
114
114
  // Use node directly to avoid sh.exe pipe issues on Windows (SIGPIPE).
115
115
  // The script is a self-contained node -e that runs both checks.
116
- return `#!/bin/sh
117
- # brainclaw pre-commit hook — generated by brainclaw install-hooks
118
- # Runs via node to avoid sh.exe SIGPIPE issues on Windows.
119
- exec node -e "
120
- const { execSync } = require('child_process');
121
- const staged = execSync('git diff --cached --name-only', { encoding: 'utf-8' }).trim();
122
- if (!staged) process.exit(0);
123
-
124
- // Check 1: reject staged .brainclaw/ files
125
- const brainclawFiles = staged.split('\\\\n').filter(f => f.startsWith('.brainclaw/'));
126
- if (brainclawFiles.length > 0) {
127
- process.stderr.write('\\\\nbrainclaw: .brainclaw/ files are staged — blocked.\\\\n');
128
- brainclawFiles.forEach(f => process.stderr.write(' ' + f + '\\\\n'));
129
- process.stderr.write(' Fix: git reset HEAD .brainclaw/\\\\n\\\\n');
130
- process.exit(1);
131
- }
132
-
133
- // Check 2: active constraint violations
134
- try {
135
- execSync('brainclaw check-constraints --staged', { stdio: 'inherit' });
136
- } catch (e) {
137
- if (e.status) process.exit(e.status);
138
- }
139
- " 2>&1 || exit $?
116
+ return `#!/bin/sh
117
+ # brainclaw pre-commit hook — generated by brainclaw install-hooks
118
+ # Runs via node to avoid sh.exe SIGPIPE issues on Windows.
119
+ exec node -e "
120
+ const { execSync } = require('child_process');
121
+ const staged = execSync('git diff --cached --name-only', { encoding: 'utf-8' }).trim();
122
+ if (!staged) process.exit(0);
123
+
124
+ // Check 1: reject staged .brainclaw/ files
125
+ const brainclawFiles = staged.split('\\\\n').filter(f => f.startsWith('.brainclaw/'));
126
+ if (brainclawFiles.length > 0) {
127
+ process.stderr.write('\\\\nbrainclaw: .brainclaw/ files are staged — blocked.\\\\n');
128
+ brainclawFiles.forEach(f => process.stderr.write(' ' + f + '\\\\n'));
129
+ process.stderr.write(' Fix: git reset HEAD .brainclaw/\\\\n\\\\n');
130
+ process.exit(1);
131
+ }
132
+
133
+ // Check 2: active constraint violations
134
+ try {
135
+ execSync('brainclaw check-constraints --staged', { stdio: 'inherit' });
136
+ } catch (e) {
137
+ if (e.status) process.exit(e.status);
138
+ }
139
+ " 2>&1 || exit $?
140
140
  `;
141
141
  }
142
142
  //# sourceMappingURL=install-hooks.js.map
@@ -6,7 +6,7 @@ import { buildContext } from '../core/context.js';
6
6
  import { buildExecutionContext, renderExecutionContextSummary } from '../core/execution-context.js';
7
7
  import { checkBrainclawInstallableUpdate, renderBrainclawInstallableUpdateNotice } from '../core/brainclaw-version.js';
8
8
  import { loadConfig } from '../core/config.js';
9
- import { loadAllSessions, loadCurrentSession, saveCurrentSession, gcStaleSessions } from '../core/identity.js';
9
+ import { loadAllSessions, loadCurrentSession, loadSessionById, saveCurrentSession, gcStaleSessions } from '../core/identity.js';
10
10
  import { loadState } from '../core/state.js';
11
11
  import { listArchivedCandidates, listCandidates, resolvedSource } from '../core/candidates.js';
12
12
  import { listClaims, assessClaimLiveness } from '../core/claims.js';
@@ -27,13 +27,13 @@ import { checkPolicy } from '../core/policy.js';
27
27
  import { buildGovernanceReport, renderGovernanceMarkdown } from '../core/governance.js';
28
28
  import { inferProjectFromTarget, loadInstructions, resolveInstructions } from '../core/instructions.js';
29
29
  import { buildReputationSnapshot, toPublicReputationSummary } from '../core/reputation.js';
30
- import { search } from '../core/search.js';
30
+ import { countLegacySearchMatches, search } from '../core/search.js';
31
31
  import { buildEstimationReport } from './estimation-report.js';
32
32
  import { runDoctor } from './doctor.js';
33
33
  import { buildProjectDiscovery, saveDiscoveryProfile, loadDiscoveryProfile, renderDiscoverySummary } from '../core/project-discovery.js';
34
34
  import { listCapabilities, listTools as listRegistryTools } from '../core/registries.js';
35
- import { listAvailableProjects, switchProject } from './switch.js';
36
- import { resolveEffectiveCwd } from '../core/store-resolution.js';
35
+ import { listAvailableProjectsForSession, switchProject } from './switch.js';
36
+ import { resolveEffectiveCwdInfo } from '../core/store-resolution.js';
37
37
  import { resolveProjectCwd } from '../core/cross-project.js';
38
38
  import { readUnseenEvents, buildNotificationSummary } from '../core/event-log.js';
39
39
  import { boundListResult, DEFAULT_FIND_CHAR_BUDGET } from '../core/entity-operations.js';
@@ -100,9 +100,12 @@ function getReviewAssignee(tags) {
100
100
  }
101
101
  export function handleMcpReadToolCall(name, args = {}, context = {}) {
102
102
  const baseCwd = context.cwd ?? process.cwd();
103
- let cwd = name === 'bclaw_switch'
104
- ? baseCwd
105
- : resolveEffectiveCwd({ baseCwd });
103
+ const effective = name === 'bclaw_switch'
104
+ ? { cwd: baseCwd, active_source: 'cwd', resolved_project: undefined }
105
+ : context.effectiveScope ?? resolveEffectiveCwdInfo({ baseCwd, sessionId: context.connectionSessionId });
106
+ let cwd = effective.cwd;
107
+ let activeSource = effective.active_source;
108
+ let resolvedProject = effective.resolved_project;
106
109
  // If a project param is provided, resolve it to an actual cwd override.
107
110
  // resolveProjectCwd unifies cross_project_links (siblings/peers) AND
108
111
  // workspace store-chain children. Throws on unknown project — surfaces
@@ -115,6 +118,14 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
115
118
  let projectRoutingApplied = false;
116
119
  if (targetProjectArg) {
117
120
  cwd = resolveProjectCwd(targetProjectArg, cwd);
121
+ activeSource = 'explicit';
122
+ try {
123
+ const config = loadConfig(cwd);
124
+ resolvedProject = { path: cwd, name: config.project_name };
125
+ }
126
+ catch {
127
+ resolvedProject = { path: cwd };
128
+ }
118
129
  projectRoutingApplied = true;
119
130
  }
120
131
  if (name === 'bclaw_get_context') {
@@ -631,13 +642,25 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
631
642
  }
632
643
  const offset = Math.max(0, Number(args.offset) || 0);
633
644
  const limit = typeof args.limit === 'number' ? args.limit : 10;
645
+ const includeLegacy = args.includeLegacy === true || (typeof args.filter === 'object' && args.filter?.includeLegacy === true);
634
646
  const allResults = search({
635
647
  query,
636
648
  section: (args.section ?? args.type),
637
649
  since: args.since,
638
- maxResults: offset + limit,
650
+ maxResults: Number.MAX_SAFE_INTEGER,
651
+ includeLegacy,
639
652
  cwd,
640
653
  });
654
+ const excludedLegacy = includeLegacy
655
+ ? 0
656
+ : countLegacySearchMatches({
657
+ query,
658
+ section: (args.section ?? args.type),
659
+ since: args.since,
660
+ maxResults: offset + limit,
661
+ includeLegacy: true,
662
+ cwd,
663
+ });
641
664
  const total = allResults.length;
642
665
  const page = allResults.slice(offset, offset + limit);
643
666
  // trp#449 class — bound the page by size (pln#542). budget_tokens tightens
@@ -647,16 +670,34 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
647
670
  const bounded = boundListResult({ entity: 'search_result', total, items: page }, offset, charBudget);
648
671
  const lines = bounded.items.map((result) => `[${result.id}] (${result.section}) score=${result.score.toFixed(2)}: ${result.text.slice(0, 120)}`);
649
672
  const nextActions = bounded.has_more
650
- ? [{ tool: 'bclaw_search', args: { query, offset: bounded.next_offset, limit }, when: 'to fetch the next page' }]
673
+ ? [{
674
+ tool: 'bclaw_search',
675
+ args: {
676
+ query,
677
+ offset: bounded.next_offset,
678
+ limit,
679
+ ...(args.project ? { project: args.project } : {}),
680
+ ...(args.section ? { section: args.section } : {}),
681
+ ...(args.type ? { type: args.type } : {}),
682
+ ...(args.since ? { since: args.since } : {}),
683
+ ...(args.budget_tokens ? { budget_tokens: args.budget_tokens } : {}),
684
+ ...(includeLegacy ? { includeLegacy: true } : {}),
685
+ },
686
+ when: 'to fetch the next page',
687
+ }]
651
688
  : [];
689
+ const legacyNote = excludedLegacy > 0 ? ` (${excludedLegacy} legacy result(s) excluded; pass includeLegacy=true to include them)` : '';
652
690
  return {
653
- content: [{ type: 'text', text: bounded.items.length > 0 ? lines.join('\n') : 'No results found.' }],
691
+ content: [{ type: 'text', text: bounded.items.length > 0 ? `${lines.join('\n')}${legacyNote}` : `No results found.${legacyNote}` }],
654
692
  structuredContent: {
655
693
  total,
656
694
  offset,
657
695
  limit,
658
696
  results: bounded.items,
659
697
  returned: bounded.returned,
698
+ excluded_legacy: excludedLegacy,
699
+ resolved_project: resolvedProject ?? { path: cwd },
700
+ active_source: activeSource,
660
701
  has_more: bounded.has_more,
661
702
  ...(bounded.next_offset !== undefined ? { next_offset: bounded.next_offset } : {}),
662
703
  ...(bounded.omitted_for_size ? { omitted_for_size: bounded.omitted_for_size } : {}),
@@ -1034,7 +1075,7 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
1034
1075
  }
1035
1076
  catch { /* defensive: never block events query on reconcile failure */ }
1036
1077
  }
1037
- let events = queryRuntimeEvents({
1078
+ const events = queryRuntimeEvents({
1038
1079
  ...(id ? { id } : {}),
1039
1080
  ...(assignmentId ? { assignment_id: assignmentId } : {}),
1040
1081
  ...(runId ? { run_id: runId } : {}),
@@ -1422,7 +1463,7 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
1422
1463
  if (name === 'bclaw_switch') {
1423
1464
  if (args.list === true) {
1424
1465
  try {
1425
- const result = listAvailableProjects(cwd);
1466
+ const result = listAvailableProjectsForSession(cwd, context.connectionSessionId);
1426
1467
  const lines = result.projects.map(p => {
1427
1468
  const marker = p.active ? '→' : ' ';
1428
1469
  const label = p.name ? `${p.name} (${p.relative_path})` : p.relative_path;
@@ -1439,7 +1480,9 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
1439
1480
  }
1440
1481
  if (args.clear === true) {
1441
1482
  try {
1442
- const session = loadCurrentSession(cwd);
1483
+ const session = context.connectionSessionId
1484
+ ? loadSessionById(context.connectionSessionId, cwd)
1485
+ : loadCurrentSession(cwd);
1443
1486
  if (session?.active_project) {
1444
1487
  const { active_project: _removed, ...rest } = session;
1445
1488
  saveCurrentSession(rest, cwd);
@@ -1458,7 +1501,7 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
1458
1501
  return createToolErrorResponse('validation_error', 'Missing required argument: project (or use list=true / clear=true)');
1459
1502
  }
1460
1503
  try {
1461
- const result = switchProject(projectRef, { cwd, sessionOnly: true });
1504
+ const result = switchProject(projectRef, { cwd, sessionOnly: true, sessionId: context.connectionSessionId });
1462
1505
  const text = `✔ Switched to ${result.name ? `"${result.name}"` : result.path} (${result.scope}-scoped)`;
1463
1506
  return {
1464
1507
  content: [{ type: 'text', text }],