brainclaw 0.28.0 → 1.5.3

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 (198) hide show
  1. package/README.md +193 -170
  2. package/dist/brainclaw-vscode.vsix +0 -0
  3. package/dist/cli.js +683 -23
  4. package/dist/commands/accept.js +3 -0
  5. package/dist/commands/add-step.js +11 -26
  6. package/dist/commands/agent-board.js +70 -3
  7. package/dist/commands/audit.js +19 -0
  8. package/dist/commands/check-policy.js +54 -0
  9. package/dist/commands/check-security-mcp.js +145 -0
  10. package/dist/commands/check-security.js +106 -0
  11. package/dist/commands/claim-resource.js +1 -0
  12. package/dist/commands/codev.js +672 -0
  13. package/dist/commands/compact.js +74 -0
  14. package/dist/commands/complete-step.js +16 -26
  15. package/dist/commands/constraint.js +8 -20
  16. package/dist/commands/decision.js +9 -20
  17. package/dist/commands/delete-plan.js +10 -12
  18. package/dist/commands/delete-step.js +16 -0
  19. package/dist/commands/dispatch.js +163 -0
  20. package/dist/commands/doctor.js +1122 -49
  21. package/dist/commands/enable-agent.js +1 -0
  22. package/dist/commands/export.js +280 -22
  23. package/dist/commands/handoff.js +33 -0
  24. package/dist/commands/harvest.js +189 -0
  25. package/dist/commands/hooks.js +82 -25
  26. package/dist/commands/inbox.js +169 -0
  27. package/dist/commands/init.js +38 -31
  28. package/dist/commands/install-hooks.js +71 -44
  29. package/dist/commands/link.js +89 -0
  30. package/dist/commands/list-claims.js +48 -3
  31. package/dist/commands/list-plans.js +129 -25
  32. package/dist/commands/loops-handlers.js +409 -0
  33. package/dist/commands/mcp-read-handlers.js +1628 -0
  34. package/dist/commands/mcp-schemas.generated.js +74 -0
  35. package/dist/commands/mcp.js +4244 -1475
  36. package/dist/commands/plan-resource.js +64 -0
  37. package/dist/commands/plan.js +12 -26
  38. package/dist/commands/prune.js +37 -2
  39. package/dist/commands/reflect.js +20 -7
  40. package/dist/commands/release-claim.js +11 -6
  41. package/dist/commands/release-notes.js +170 -0
  42. package/dist/commands/repair.js +210 -0
  43. package/dist/commands/run-profile.js +57 -0
  44. package/dist/commands/sequence.js +113 -0
  45. package/dist/commands/session-end.js +423 -14
  46. package/dist/commands/session-start.js +214 -41
  47. package/dist/commands/setup-security.js +103 -0
  48. package/dist/commands/setup.js +42 -4
  49. package/dist/commands/stale.js +109 -0
  50. package/dist/commands/switch.js +131 -10
  51. package/dist/commands/trap.js +14 -31
  52. package/dist/commands/update-handoff.js +63 -4
  53. package/dist/commands/update-plan.js +21 -28
  54. package/dist/commands/update-step.js +37 -0
  55. package/dist/commands/upgrade.js +313 -6
  56. package/dist/commands/usage.js +102 -0
  57. package/dist/commands/version.js +20 -0
  58. package/dist/commands/who.js +124 -0
  59. package/dist/commands/worktree.js +105 -0
  60. package/dist/core/actions.js +315 -0
  61. package/dist/core/agent-capability.js +610 -17
  62. package/dist/core/agent-context.js +7 -1
  63. package/dist/core/agent-files.js +1169 -85
  64. package/dist/core/agent-integrations.js +160 -5
  65. package/dist/core/agent-inventory.js +2 -0
  66. package/dist/core/agent-profiles.js +93 -0
  67. package/dist/core/agent-registry.js +162 -30
  68. package/dist/core/agentrun-reconciler.js +345 -0
  69. package/dist/core/agentruns.js +424 -0
  70. package/dist/core/ai-agent-detection.js +31 -10
  71. package/dist/core/archival.js +77 -0
  72. package/dist/core/assignment-sweeper.js +82 -0
  73. package/dist/core/assignments.js +367 -0
  74. package/dist/core/audit.js +30 -0
  75. package/dist/core/bootstrap.js +61 -10
  76. package/dist/core/brainclaw-version.js +94 -2
  77. package/dist/core/candidates.js +93 -2
  78. package/dist/core/claims.js +419 -0
  79. package/dist/core/codev-metrics.js +77 -0
  80. package/dist/core/codev-personas.js +31 -0
  81. package/dist/core/codev-plan-gen.js +35 -0
  82. package/dist/core/codev-prompts.js +74 -0
  83. package/dist/core/codev-responses.js +62 -0
  84. package/dist/core/codev-rounds.js +218 -0
  85. package/dist/core/config.js +4 -0
  86. package/dist/core/context.js +454 -34
  87. package/dist/core/coordination.js +201 -6
  88. package/dist/core/cross-project.js +230 -16
  89. package/dist/core/default-profiles/doctor.yaml +11 -0
  90. package/dist/core/default-profiles/janitor.yaml +11 -0
  91. package/dist/core/default-profiles/onboarder.yaml +11 -0
  92. package/dist/core/default-profiles/reviewer.yaml +13 -0
  93. package/dist/core/dispatcher.js +1189 -0
  94. package/dist/core/duplicates.js +2 -2
  95. package/dist/core/entity-operations.js +450 -0
  96. package/dist/core/entity-registry.js +344 -0
  97. package/dist/core/event-log.js +1 -0
  98. package/dist/core/events.js +106 -2
  99. package/dist/core/execution-adapters.js +154 -0
  100. package/dist/core/execution-context.js +63 -0
  101. package/dist/core/execution-profile.js +270 -0
  102. package/dist/core/execution.js +255 -0
  103. package/dist/core/facade-schema.js +81 -0
  104. package/dist/core/federation-cloud.js +99 -0
  105. package/dist/core/federation-message.js +52 -0
  106. package/dist/core/federation-transport.js +65 -0
  107. package/dist/core/gc-semantic.js +482 -0
  108. package/dist/core/governance.js +247 -0
  109. package/dist/core/guards.js +19 -0
  110. package/dist/core/ideation.js +72 -0
  111. package/dist/core/identity.js +252 -28
  112. package/dist/core/ids.js +6 -0
  113. package/dist/core/input-validation.js +2 -2
  114. package/dist/core/instruction-templates.js +344 -136
  115. package/dist/core/io.js +90 -11
  116. package/dist/core/lock.js +6 -2
  117. package/dist/core/loops/brief-assembly.js +213 -0
  118. package/dist/core/loops/facade-schema.js +148 -0
  119. package/dist/core/loops/index.js +7 -0
  120. package/dist/core/loops/iteration-engine.js +139 -0
  121. package/dist/core/loops/lock.js +385 -0
  122. package/dist/core/loops/store.js +201 -0
  123. package/dist/core/loops/types.js +403 -0
  124. package/dist/core/loops/verbs.js +534 -0
  125. package/dist/core/markdown.js +15 -3
  126. package/dist/core/memory-compactor.js +432 -0
  127. package/dist/core/memory-git.js +152 -8
  128. package/dist/core/messaging.js +278 -0
  129. package/dist/core/migration.js +32 -1
  130. package/dist/core/mutation-pipeline.js +4 -2
  131. package/dist/core/operations/memory-mutation.js +129 -0
  132. package/dist/core/operations/memory-write.js +78 -0
  133. package/dist/core/operations/plan.js +190 -0
  134. package/dist/core/policy.js +169 -0
  135. package/dist/core/repo-analysis.js +67 -0
  136. package/dist/core/reputation.js +9 -3
  137. package/dist/core/schema.js +546 -21
  138. package/dist/core/search.js +21 -2
  139. package/dist/core/security-cache.js +71 -0
  140. package/dist/core/security-guard.js +152 -0
  141. package/dist/core/security-scoring.js +86 -0
  142. package/dist/core/sequence.js +130 -0
  143. package/dist/core/socket-client.js +113 -0
  144. package/dist/core/staleness.js +246 -0
  145. package/dist/core/state.js +98 -22
  146. package/dist/core/store-resolution.js +54 -12
  147. package/dist/core/toml-writer.js +76 -0
  148. package/dist/core/upgrades/backup.js +232 -0
  149. package/dist/core/upgrades/health-check.js +169 -0
  150. package/dist/core/upgrades/patches/candidate-archive.js +145 -0
  151. package/dist/core/upgrades/patches/handoff-review-strip.js +128 -0
  152. package/dist/core/upgrades/patches/provenance-rollout.js +136 -0
  153. package/dist/core/upgrades/schema-version.js +97 -0
  154. package/dist/core/worktree.js +606 -0
  155. package/dist/facts.js +114 -0
  156. package/dist/facts.json +111 -0
  157. package/docs/architecture/project-refs.md +5 -1
  158. package/docs/cli.md +690 -43
  159. package/docs/concepts/ideation-loop.md +317 -0
  160. package/docs/concepts/loop-engine.md +456 -0
  161. package/docs/concepts/mcp-governance.md +268 -0
  162. package/docs/concepts/memory-staleness.md +122 -0
  163. package/docs/concepts/multi-agent-workflows.md +166 -0
  164. package/docs/concepts/plans-and-claims.md +31 -6
  165. package/docs/concepts/project-md-convention.md +35 -0
  166. package/docs/concepts/troubleshooting.md +220 -0
  167. package/docs/concepts/upgrade-cli.md +202 -0
  168. package/docs/concepts/upgrade-dogfood-procedure.md +114 -0
  169. package/docs/context-format-changelog.md +2 -2
  170. package/docs/context-format.md +2 -2
  171. package/docs/index.md +68 -0
  172. package/docs/integrations/agents.md +15 -16
  173. package/docs/integrations/cline.md +88 -0
  174. package/docs/integrations/codex.md +75 -23
  175. package/docs/integrations/continue.md +60 -0
  176. package/docs/integrations/copilot.md +67 -9
  177. package/docs/integrations/kilocode.md +72 -0
  178. package/docs/integrations/mcp.md +304 -21
  179. package/docs/integrations/mistral-vibe.md +122 -0
  180. package/docs/integrations/opencode.md +84 -0
  181. package/docs/integrations/overview.md +23 -8
  182. package/docs/integrations/roo.md +74 -0
  183. package/docs/integrations/windsurf.md +83 -0
  184. package/docs/mcp-schema-changelog.md +191 -1
  185. package/docs/playbooks/integration/index.md +121 -0
  186. package/docs/playbooks/productivity/index.md +102 -0
  187. package/docs/playbooks/team/index.md +122 -0
  188. package/docs/product/agent-first-model.md +184 -0
  189. package/docs/product/entity-model-audit.md +462 -0
  190. package/docs/quickstart-existing-project.md +135 -0
  191. package/docs/quickstart.md +124 -37
  192. package/docs/release-maintenance.md +79 -0
  193. package/docs/review.md +2 -0
  194. package/docs/server-operations.md +118 -0
  195. package/package.json +20 -12
  196. package/dist/commands/claude-desktop-extension.js +0 -18
  197. package/dist/commands/diff.js +0 -99
  198. package/dist/core/claude-desktop-extension.js +0 -224
@@ -1,6 +1,9 @@
1
1
  import fs from 'node:fs';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
+ import { spawnSync } from 'node:child_process';
5
+ import { getInstalledBrainclawVersion } from './brainclaw-version.js';
6
+ import { getAgentCapabilityProfile } from './agent-capability.js';
4
7
  const SUPPORTED_AGENT_INTEGRATION_NAMES = new Set([
5
8
  'github-copilot',
6
9
  'claude-code',
@@ -12,6 +15,7 @@ const SUPPORTED_AGENT_INTEGRATION_NAMES = new Set([
12
15
  'antigravity',
13
16
  'continue',
14
17
  'roo',
18
+ 'kilocode',
15
19
  'openclaw',
16
20
  'nanoclaw',
17
21
  'nemoclaw',
@@ -21,49 +25,69 @@ const SUPPORTED_AGENT_INTEGRATION_NAMES = new Set([
21
25
  const DEFAULT_SURFACES = {
22
26
  'github-copilot': [
23
27
  { kind: 'instructions', location: 'workspace', path: '.github/copilot-instructions.md' },
28
+ { kind: 'hook', location: 'workspace', path: '.github/copilot/hooks.json' },
29
+ { kind: 'mcp', location: 'workspace', path: '.vscode/settings.json' },
24
30
  { kind: 'skill', location: 'workspace', path: '.github/skills/brainclaw-context/SKILL.md' },
31
+ { kind: 'skill', location: 'workspace', path: '.agents/skills/brainclaw/SKILL.md' },
25
32
  ],
26
33
  'claude-code': [
27
34
  { kind: 'instructions', location: 'workspace', path: 'CLAUDE.md' },
35
+ { kind: 'hook', location: 'machine', path: '.claude/settings.local.json' },
28
36
  { kind: 'mcp', location: 'workspace', path: '.mcp.json' },
29
37
  { kind: 'skill', location: 'workspace', path: '.claude/commands/brainclaw.md' },
30
38
  ],
31
39
  'cursor': [
32
40
  { kind: 'instructions', location: 'workspace', path: '.cursor/rules/brainclaw.md' },
33
41
  { kind: 'rule', location: 'workspace', path: '.cursor/rules/brainclaw-mcp-shim.mdc' },
42
+ { kind: 'hook', location: 'workspace', path: '.cursor/hooks.json' },
34
43
  { kind: 'mcp', location: 'machine', path: '.cursor/mcp.json' },
44
+ { kind: 'skill', location: 'workspace', path: '.agents/skills/brainclaw/SKILL.md' },
35
45
  ],
36
46
  'windsurf': [
37
47
  { kind: 'instructions', location: 'workspace', path: '.windsurfrules' },
38
48
  { kind: 'hook', location: 'workspace', path: '.windsurfrules' },
39
49
  { kind: 'mcp', location: 'machine', path: '.codeium/windsurf/mcp_config.json' },
50
+ { kind: 'rule', location: 'workspace', path: '.windsurf/rules/brainclaw.md' },
40
51
  ],
41
52
  'cline': [
42
53
  { kind: 'instructions', location: 'workspace', path: '.clinerules/brainclaw.md' },
54
+ { kind: 'hook', location: 'workspace', path: '.clinerules/brainclaw.md' },
43
55
  { kind: 'mcp', location: 'workspace', path: '.vscode/cline_mcp_settings.json' },
44
56
  ],
45
57
  'codex': [
46
58
  { kind: 'instructions', location: 'workspace', path: 'AGENTS.md' },
59
+ { kind: 'hook', location: 'workspace', path: 'AGENTS.md' },
47
60
  { kind: 'mcp', location: 'machine', path: '.codex/config.toml' },
61
+ { kind: 'skill', location: 'workspace', path: '.agents/skills/brainclaw/SKILL.md' },
48
62
  ],
49
63
  'opencode': [
50
64
  { kind: 'instructions', location: 'workspace', path: 'AGENTS.md' },
51
65
  { kind: 'mcp', location: 'workspace', path: 'opencode.json' },
66
+ { kind: 'skill', location: 'workspace', path: '.agents/skills/brainclaw/SKILL.md' },
52
67
  ],
53
68
  'antigravity': [
54
69
  { kind: 'instructions', location: 'workspace', path: 'GEMINI.md' },
55
70
  { kind: 'mcp', location: 'machine', path: '.gemini/antigravity/mcp_config.json' },
71
+ { kind: 'hook', location: 'machine', path: '.gemini/antigravity/hooks.json' },
56
72
  ],
57
73
  'continue': [
58
74
  { kind: 'instructions', location: 'workspace', path: '.continue/rules/brainclaw.md' },
59
75
  { kind: 'mcp', location: 'workspace', path: '.continue/config.json' },
76
+ { kind: 'permissions', location: 'machine', path: '.continue/permissions.yaml' },
60
77
  ],
61
78
  'roo': [
62
79
  { kind: 'instructions', location: 'workspace', path: '.roo/rules/brainclaw.md' },
63
80
  { kind: 'mcp', location: 'workspace', path: '.roo/mcp.json' },
81
+ { kind: 'skill', location: 'workspace', path: '.agents/skills/brainclaw/SKILL.md' },
82
+ ],
83
+ 'kilocode': [
84
+ { kind: 'instructions', location: 'workspace', path: '.kilo/rules/brainclaw.md' },
85
+ { kind: 'mcp', location: 'workspace', path: '.kilo/mcp.json' },
86
+ { kind: 'skill', location: 'workspace', path: '.agents/skills/brainclaw/SKILL.md' },
64
87
  ],
65
88
  'openclaw': [
66
89
  { kind: 'skill', location: 'machine', path: '.openclaw/workspace/skills/brainclaw/SKILL.md' },
90
+ { kind: 'mcp', location: 'machine', path: '.openclaw/mcp.json' },
67
91
  ],
68
92
  'nanoclaw': [
69
93
  { kind: 'skill', location: 'workspace', path: 'skills/nanoclaw/SKILL.md' },
@@ -100,6 +124,10 @@ export function buildAgentIntegrationDeclaration(agentName, declarationSource =
100
124
  export function isAgentIntegrationName(value) {
101
125
  return SUPPORTED_AGENT_INTEGRATION_NAMES.has(value);
102
126
  }
127
+ let commandVersionProbeForTests;
128
+ export function setCommandVersionProbeForTests(probe) {
129
+ commandVersionProbeForTests = probe;
130
+ }
103
131
  function resolveDeclaredSurfacePath(surface, cwd, env) {
104
132
  if (!surface.path) {
105
133
  return undefined;
@@ -110,24 +138,151 @@ function resolveDeclaredSurfacePath(surface, cwd, env) {
110
138
  const homeDir = env.HOME?.trim() || env.USERPROFILE?.trim() || os.homedir();
111
139
  return path.join(homeDir, surface.path);
112
140
  }
113
- function surfaceExists(surface, cwd, env) {
141
+ export function extractMcpCommandVal(agentName, expectedPath) {
142
+ let content;
143
+ try {
144
+ content = fs.readFileSync(expectedPath, 'utf-8');
145
+ }
146
+ catch {
147
+ return { is_valid: false };
148
+ }
149
+ if (expectedPath.endsWith('.toml')) {
150
+ const cmdMatch = content.match(/\[mcp_servers\.brainclaw\](?:[^\[]*)command\s*=\s*(["'])(.+?)\1/is);
151
+ const argsMatch = content.match(/\[mcp_servers\.brainclaw\](?:[^\[]*)args\s*=\s*\[(.+?)\]/is);
152
+ let args;
153
+ if (argsMatch) {
154
+ args = argsMatch[1]
155
+ .split(',')
156
+ .map(s => s.trim().replace(/^["']|["']$/g, '').replace(/\\\\/g, '\\'));
157
+ }
158
+ return {
159
+ command: cmdMatch ? cmdMatch[2].replace(/\\\\/g, '\\') : undefined,
160
+ args,
161
+ is_valid: true,
162
+ };
163
+ }
164
+ try {
165
+ const j = JSON.parse(content);
166
+ let cmd;
167
+ let args;
168
+ if (agentName === 'github-copilot') {
169
+ const mcpServers = j['github.copilot.chat.mcpServers'];
170
+ cmd = mcpServers?.brainclaw?.command;
171
+ args = mcpServers?.brainclaw?.args;
172
+ }
173
+ else if (agentName === 'continue') {
174
+ const servers = Array.isArray(j.mcpServers) ? j.mcpServers : [];
175
+ const bc = servers.find((s) => s && s.name === 'brainclaw');
176
+ cmd = bc?.command;
177
+ args = bc?.args;
178
+ }
179
+ else {
180
+ cmd = j.mcpServers?.brainclaw?.command;
181
+ args = j.mcpServers?.brainclaw?.args;
182
+ }
183
+ return {
184
+ command: typeof cmd === 'string' ? cmd : undefined,
185
+ args: Array.isArray(args) ? args.filter(a => typeof a === 'string') : undefined,
186
+ is_valid: true
187
+ };
188
+ }
189
+ catch {
190
+ return { is_valid: false };
191
+ }
192
+ }
193
+ function getCommandVersion(cmdPath, args) {
194
+ if (commandVersionProbeForTests) {
195
+ return commandVersionProbeForTests(cmdPath, args);
196
+ }
197
+ if (cmdPath === 'npx')
198
+ return null; // dynamic
199
+ try {
200
+ const isNode = cmdPath.endsWith('node') || cmdPath.endsWith('node.exe');
201
+ const spawnArgs = isNode && args && args[0] ? [args[0], '--version'] : ['--version'];
202
+ const res = spawnSync(cmdPath, spawnArgs, { encoding: 'utf-8', timeout: 2000 });
203
+ if (res.status === 0 && res.stdout) {
204
+ return res.stdout.trim();
205
+ }
206
+ }
207
+ catch { }
208
+ return null;
209
+ }
210
+ function surfaceExists(surface, cwd, env, agentName) {
114
211
  const expectedPath = resolveDeclaredSurfacePath(surface, cwd, env);
115
- return {
212
+ const exists = expectedPath ? fs.existsSync(expectedPath) : false;
213
+ const result = {
116
214
  ...surface,
117
215
  expected_path: expectedPath,
118
- exists: expectedPath ? fs.existsSync(expectedPath) : false,
216
+ exists,
119
217
  };
218
+ if (surface.kind === 'mcp' && exists && agentName && expectedPath) {
219
+ const { command, args, is_valid } = extractMcpCommandVal(agentName, expectedPath);
220
+ if (!is_valid) {
221
+ result.drift_message = `MCP config file is invalid JSON/TOML`;
222
+ }
223
+ else if (!command) {
224
+ result.drift_message = `MCP config file is missing 'brainclaw' command`;
225
+ }
226
+ else {
227
+ if (command !== 'npx' && !fs.existsSync(command) && !['brainclaw', 'node'].includes(path.basename(command).replace(/\.exe$/, ''))) {
228
+ result.drift_message = `MCP command points to a non-existent file: ${command}`;
229
+ }
230
+ else {
231
+ const expectedVersion = getInstalledBrainclawVersion();
232
+ const cmdVersion = getCommandVersion(command, args);
233
+ if (cmdVersion && cmdVersion !== expectedVersion) {
234
+ result.drift_message = `MCP command version drift (found ${cmdVersion}, expected ${expectedVersion})`;
235
+ }
236
+ }
237
+ }
238
+ }
239
+ return result;
120
240
  }
121
241
  export function assessAgentIntegrationReadiness(config, cwd, env = process.env) {
122
242
  return (config.agent_integrations?.declarations ?? []).map((declaration) => {
123
- const surfaces = declaration.surfaces.map((surface) => surfaceExists(surface, cwd, env));
243
+ const surfaces = declaration.surfaces.map((surface) => surfaceExists(surface, cwd, env, declaration.agent_name));
124
244
  const missingSurfaces = surfaces.filter((surface) => !surface.exists);
245
+ const driftingSurfaces = surfaces.filter((surface) => surface.drift_message != null);
246
+ let effectiveTier = 'tier-b';
247
+ const selfHealingGuidance = [];
248
+ const hasMissingMcpOrHook = missingSurfaces.some((s) => s.kind === 'mcp' || s.kind === 'hook');
249
+ const hasDriftingMcp = driftingSurfaces.some((s) => s.kind === 'mcp');
250
+ const profile = getAgentCapabilityProfile(declaration.agent_name);
251
+ if (!profile) {
252
+ effectiveTier = 'tier-c';
253
+ selfHealingGuidance.push(`Agent ${declaration.agent_name} lacks a known capability profile. Defaulting to Tier C.`);
254
+ }
255
+ else {
256
+ const isPrimaryTierA = profile.templateTier === 'A';
257
+ if (isPrimaryTierA) {
258
+ if (hasMissingMcpOrHook || hasDriftingMcp) {
259
+ effectiveTier = 'tier-b';
260
+ selfHealingGuidance.push(`Agent ${declaration.agent_name} is degraded to Tier B because MCP or hooks are missing/drifting. Run 'brainclaw doctor --fix' or check integrations.`);
261
+ }
262
+ else {
263
+ effectiveTier = 'tier-a';
264
+ }
265
+ }
266
+ else {
267
+ effectiveTier = 'tier-b'; // Inherently Tier B because context relies on native rules
268
+ if (hasMissingMcpOrHook || hasDriftingMcp) {
269
+ selfHealingGuidance.push(`Agent ${declaration.agent_name} is missing or drifting MCP or hook configurations. Run 'brainclaw doctor --fix'.`);
270
+ }
271
+ }
272
+ }
273
+ if (missingSurfaces.length === surfaces.length && surfaces.length > 0) {
274
+ effectiveTier = 'tier-c';
275
+ selfHealingGuidance.push(`Agent ${declaration.agent_name} has no configured surfaces and is falling back to compact Tier C behavior.`);
276
+ }
125
277
  return {
126
278
  agent_name: declaration.agent_name,
127
279
  declaration_source: declaration.declaration_source,
128
- ready: missingSurfaces.length === 0,
280
+ ready: missingSurfaces.length === 0 && driftingSurfaces.length === 0,
129
281
  missing_surfaces: missingSurfaces,
282
+ drifting_surfaces: driftingSurfaces,
130
283
  surfaces,
284
+ effective_tier: effectiveTier,
285
+ self_healing_guidance: selfHealingGuidance,
131
286
  };
132
287
  });
133
288
  }
@@ -4,6 +4,7 @@ import path from 'node:path';
4
4
  import { spawnSync } from 'node:child_process';
5
5
  import yaml from 'yaml';
6
6
  import { MEMORY_DIR } from './io.js';
7
+ import { detectHostExecutionProfile, } from './execution-profile.js';
7
8
  function tryCommand(command, args, timeout = 5000) {
8
9
  try {
9
10
  const r = spawnSync(command, args, { encoding: 'utf-8', timeout, windowsHide: true });
@@ -327,6 +328,7 @@ export function buildAgentInventory(homeDir = os.homedir(), env = process.env) {
327
328
  schema_version: 1,
328
329
  generated_at: new Date().toISOString(),
329
330
  agents,
331
+ host_execution_profile: detectHostExecutionProfile({ env, platform: process.platform }),
330
332
  };
331
333
  }
332
334
  /**
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Agent profile system — load, list, and save reusable agent profiles.
3
+ *
4
+ * Profiles are YAML files in .brainclaw/agents/profiles/{name}.yaml
5
+ * that define a reusable agent invocation: prompt, invoke template,
6
+ * trust level, trigger mode, and optional scope.
7
+ *
8
+ * Built-in default profiles ship with the package in default-profiles/
9
+ * and are merged with user profiles (user overrides defaults).
10
+ *
11
+ * @module
12
+ */
13
+ import fs from 'node:fs';
14
+ import path from 'node:path';
15
+ import { fileURLToPath } from 'node:url';
16
+ import yaml from 'yaml';
17
+ import { AgentProfileSchema } from './schema.js';
18
+ import { memoryDir } from './io.js';
19
+ const PROFILES_DIR = path.join('agents', 'profiles');
20
+ /** Directory containing built-in default profiles shipped with the package. */
21
+ const DEFAULT_PROFILES_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), 'default-profiles');
22
+ /** Resolve the user profiles directory for a given cwd. */
23
+ function profilesDir(cwd) {
24
+ return path.join(memoryDir(cwd), PROFILES_DIR);
25
+ }
26
+ /** Resolve the path to a specific user profile YAML file. */
27
+ function profilePath(name, cwd) {
28
+ return path.join(profilesDir(cwd), `${name}.yaml`);
29
+ }
30
+ /** Read and validate all .yaml profiles from a directory. */
31
+ function readProfilesFromDir(dir) {
32
+ if (!fs.existsSync(dir))
33
+ return [];
34
+ return fs.readdirSync(dir)
35
+ .filter(f => f.endsWith('.yaml'))
36
+ .sort()
37
+ .map(f => {
38
+ const raw = fs.readFileSync(path.join(dir, f), 'utf-8');
39
+ const parsed = yaml.parse(raw);
40
+ return AgentProfileSchema.safeParse(parsed);
41
+ })
42
+ .filter(r => r.success)
43
+ .map(r => r.data);
44
+ }
45
+ /**
46
+ * Load a single agent profile by name.
47
+ * Checks user profiles first, then built-in defaults.
48
+ * Throws if the profile does not exist or fails validation.
49
+ */
50
+ export function loadProfile(name, cwd = process.cwd()) {
51
+ // User profile takes priority
52
+ const userPath = profilePath(name, cwd);
53
+ if (fs.existsSync(userPath)) {
54
+ const raw = fs.readFileSync(userPath, 'utf-8');
55
+ return AgentProfileSchema.parse(yaml.parse(raw));
56
+ }
57
+ // Fall back to built-in default
58
+ const defaultPath = path.join(DEFAULT_PROFILES_DIR, `${name}.yaml`);
59
+ if (fs.existsSync(defaultPath)) {
60
+ const raw = fs.readFileSync(defaultPath, 'utf-8');
61
+ return AgentProfileSchema.parse(yaml.parse(raw));
62
+ }
63
+ throw new Error(`Profile not found: ${name} (checked ${userPath} and built-in defaults)`);
64
+ }
65
+ /**
66
+ * List all available agent profiles.
67
+ * Merges built-in defaults with user profiles. User profiles override defaults
68
+ * with the same name.
69
+ */
70
+ export function listProfiles(cwd = process.cwd()) {
71
+ const defaults = readProfilesFromDir(DEFAULT_PROFILES_DIR);
72
+ const user = readProfilesFromDir(profilesDir(cwd));
73
+ // Index by name — user profiles override defaults
74
+ const merged = new Map();
75
+ for (const p of defaults)
76
+ merged.set(p.name, p);
77
+ for (const p of user)
78
+ merged.set(p.name, p);
79
+ return [...merged.values()].sort((a, b) => a.name.localeCompare(b.name));
80
+ }
81
+ /**
82
+ * Save an agent profile to disk.
83
+ * Creates the profiles directory if it doesn't exist.
84
+ */
85
+ export function saveProfile(profile, cwd = process.cwd()) {
86
+ const validated = AgentProfileSchema.parse(profile);
87
+ const dir = profilesDir(cwd);
88
+ fs.mkdirSync(dir, { recursive: true });
89
+ const filepath = profilePath(validated.name, cwd);
90
+ fs.writeFileSync(filepath, yaml.stringify(validated), 'utf-8');
91
+ return filepath;
92
+ }
93
+ //# sourceMappingURL=agent-profiles.js.map
@@ -2,10 +2,11 @@ import crypto from 'node:crypto';
2
2
  import fs from 'node:fs';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
+ import { isKnownAgent, getCapabilityProfile } from './agent-capability.js';
5
6
  import { detectAiAgent } from './ai-agent-detection.js';
6
7
  import { loadConfig, saveConfig } from './config.js';
7
8
  import { nowISO } from './ids.js';
8
- import { MEMORY_DIR, resolveEntityDir } from './io.js';
9
+ import { MEMORY_DIR, memoryExists, resolveEntityDir } from './io.js';
9
10
  import { JsonStore } from './json-store.js';
10
11
  import { AgentIdentityDocumentSchema, } from './schema.js';
11
12
  import { logger } from './logger.js';
@@ -54,7 +55,7 @@ function agentStore(cwd, preferredDirName) {
54
55
  sort: (a, b) => a.created_at.localeCompare(b.created_at) || a.agent_name.localeCompare(b.agent_name),
55
56
  });
56
57
  }
57
- function normalizeAgentName(agentName) {
58
+ export function normalizeAgentName(agentName) {
58
59
  return agentName.trim().toLowerCase();
59
60
  }
60
61
  function normalizeCapability(capability) {
@@ -168,6 +169,9 @@ export function registerAgentIdentity(input) {
168
169
  else if (normalizedCapabilities.length > 0) {
169
170
  updated = { ...updated, capabilities: mergeCapabilities(existing.capabilities ?? [], normalizedCapabilities) };
170
171
  }
172
+ if (input.contextProfile && existing.context_profile !== input.contextProfile) {
173
+ updated = { ...updated, context_profile: input.contextProfile };
174
+ }
171
175
  if (input.generateFingerprint) {
172
176
  updated = withIdentityKey(updated, input.env, true);
173
177
  }
@@ -180,11 +184,12 @@ export function registerAgentIdentity(input) {
180
184
  schema_version: 2,
181
185
  version: 1,
182
186
  agent_id: generateAgentId(),
183
- agent_name: input.agentName.trim(),
187
+ agent_name: normalizeAgentName(input.agentName),
184
188
  created_at: nowISO(),
185
189
  kind: input.kind ?? 'unknown',
186
190
  trust_level: input.trustLevel ?? 'contributor',
187
191
  capabilities: normalizedCapabilities,
192
+ ...(input.contextProfile ? { context_profile: input.contextProfile } : {}),
188
193
  };
189
194
  if (input.generateFingerprint) {
190
195
  created = withIdentityKey(created, input.env, true);
@@ -192,7 +197,7 @@ export function registerAgentIdentity(input) {
192
197
  saveAgentIdentity(created, input.cwd, input.preferredDirName);
193
198
  return created;
194
199
  }
195
- export function resolveCurrentAgentIdentity(cwd, preferredDirName) {
200
+ export function resolveCurrentAgentIdentity(cwd, preferredDirName, homeDir) {
196
201
  // env var takes priority over config — allows AI agent to self-identify
197
202
  const envAgentId = (process.env.BRAINCLAW_AGENT_ID ?? '').trim();
198
203
  const envAgentName = (process.env.BRAINCLAW_AGENT_NAME ?? process.env.BRAINCLAW_AGENT ?? '').trim();
@@ -206,25 +211,41 @@ export function resolveCurrentAgentIdentity(cwd, preferredDirName) {
206
211
  if (byEnvName)
207
212
  return byEnvName;
208
213
  }
209
- // Auto-detect from native agent env vars (e.g. CLAUDECODE, CURSOR_TRACE_ID).
210
- // Falls through to config if the detected agent is not registered.
211
- const detected = detectAiAgent();
214
+ // Auto-detect from native agent env vars (e.g. CLAUDECODE, CURSOR_TRACE_ID, CODEX_THREAD_ID).
215
+ // If detected agent is not registered, auto-register it as trusted agent.
216
+ // This is the primary identification path for MCP servers and CLI hooks.
217
+ const detected = detectAiAgent(process.env, homeDir);
212
218
  if (detected) {
219
+ // If the detected name matches an explicit env var that was already tried
220
+ // and not found, don't auto-register — the caller expects a "not registered" error.
221
+ if (normalizeAgentName(detected.name) === normalizeAgentName(envAgentName)) {
222
+ return undefined;
223
+ }
213
224
  const byDetected = findAgentIdentityByName(detected.name, cwd, preferredDirName);
214
225
  if (byDetected)
215
226
  return byDetected;
216
- }
217
- // Config fallback (last resort may not reflect the actual calling agent)
218
- const config = loadConfig(cwd, preferredDirName);
219
- if (config.current_agent_id) {
220
- const byId = findAgentIdentityById(config.current_agent_id, cwd, preferredDirName);
221
- if (byId) {
222
- return byId;
227
+ // Auto-register detected agent so it's immediately usable.
228
+ // This avoids the "not registered" error for agents detected for the first time.
229
+ try {
230
+ const autoRegistered = registerAgentIdentity({
231
+ agentName: normalizeAgentName(detected.name),
232
+ kind: detected.kind,
233
+ trustLevel: detected.trust_level,
234
+ cwd,
235
+ preferredDirName,
236
+ });
237
+ logger.debug(`Auto-registered detected agent: ${detected.name} (${autoRegistered.agent_id})`);
238
+ return autoRegistered;
239
+ }
240
+ catch {
241
+ // Non-fatal: registration may fail if store is read-only
223
242
  }
224
243
  }
225
- if (config.current_agent) {
226
- return findAgentIdentityByName(config.current_agent, cwd, preferredDirName);
227
- }
244
+ // config.current_agent is NOT used for identity resolution — it's a singleton global
245
+ // that gets overwritten by whichever agent last ran register-agent --set-current.
246
+ // In multi-agent setups this always resolves to the wrong agent.
247
+ // The field remains in config for display (status, doctor) and for resolveExistingCurrentAgent
248
+ // which is used during setup/init only.
228
249
  return undefined;
229
250
  }
230
251
  export function resolveRegisteredAgentIdentity(options = {}) {
@@ -285,13 +306,24 @@ export function requireRegisteredAgentIdentity(options = {}) {
285
306
  }
286
307
  if (agentName) {
287
308
  const resolved = findAgentIdentityByName(agentName, cwd, preferredDirName);
288
- if (!resolved) {
289
- throw new AgentIdentityResolutionError(`Agent '${agentName}' is not registered. Run \`brainclaw register-agent ${agentName}\`.`, { agent_name: agentName });
309
+ if (resolved)
310
+ return resolved;
311
+ // Auto-register if the agent is a known brainclaw-supported agent or declared in agent_integrations
312
+ const normalizedName = normalizeAgentName(agentName);
313
+ if (isKnownAgent(normalizedName) || isAgentDeclaredInIntegrations(normalizedName, cwd)) {
314
+ const autoRegistered = registerAgentIdentity({
315
+ agentName: normalizedName,
316
+ kind: 'agent',
317
+ trustLevel: 'contributor',
318
+ cwd,
319
+ preferredDirName,
320
+ });
321
+ return autoRegistered;
290
322
  }
291
- return resolved;
323
+ throw new AgentIdentityResolutionError(`Agent '${normalizedName}' is not registered. Run \`brainclaw register-agent ${normalizedName}\`.`, { agent_name: normalizedName });
292
324
  }
293
325
  const current = options.allowCurrent !== false
294
- ? resolveCurrentAgentIdentity(cwd, preferredDirName)
326
+ ? resolveCurrentAgentIdentity(cwd, preferredDirName, options.homeDir)
295
327
  : undefined;
296
328
  if (current) {
297
329
  return current;
@@ -300,14 +332,111 @@ export function requireRegisteredAgentIdentity(options = {}) {
300
332
  const envAgent = resolveEnvAgentName(env);
301
333
  if (envAgent) {
302
334
  const resolved = findAgentIdentityByName(envAgent, cwd, preferredDirName);
303
- if (!resolved) {
304
- throw new AgentIdentityResolutionError(`Environment agent '${envAgent}' is not registered.`, { agent_name: envAgent });
335
+ if (resolved)
336
+ return resolved;
337
+ // Auto-register env-declared agent if known or declared in agent_integrations
338
+ const normalizedEnv = normalizeAgentName(envAgent);
339
+ if (isKnownAgent(normalizedEnv) || isAgentDeclaredInIntegrations(normalizedEnv, cwd)) {
340
+ return registerAgentIdentity({
341
+ agentName: normalizedEnv,
342
+ kind: 'agent',
343
+ trustLevel: 'contributor',
344
+ cwd,
345
+ preferredDirName,
346
+ });
305
347
  }
306
- return resolved;
348
+ throw new AgentIdentityResolutionError(`Environment agent '${normalizedEnv}' is not registered.`, { agent_name: normalizedEnv });
307
349
  }
308
350
  }
309
351
  throw new AgentIdentityResolutionError('No registered agent identity resolved. Use --agent/--agent-id or configure a current agent with `brainclaw register-agent <name> --set-current`.');
310
352
  }
353
+ /**
354
+ * Resolve agent identity for session start, returning both the resolved identity and whether
355
+ * it was auto-registered (did not exist before this call).
356
+ *
357
+ * Unlike `requireRegisteredAgentIdentity`, this never throws for unknown agents — it will
358
+ * auto-register any resolvable name (from args or env) with contributor trust level.
359
+ * This implements the "separate known agent from current agent" principle: starting a session
360
+ * never requires prior registration.
361
+ *
362
+ * Throws only when no agent name can be derived at all.
363
+ */
364
+ export function resolveOrAutoRegisterAgentIdentity(options = {}) {
365
+ const existingBefore = resolveRegisteredAgentIdentity(options);
366
+ try {
367
+ const identity = requireRegisteredAgentIdentity(options);
368
+ return { identity, auto_registered: !existingBefore };
369
+ }
370
+ catch (err) {
371
+ if (!(err instanceof AgentIdentityResolutionError))
372
+ throw err;
373
+ // Last-resort: derive a name from explicit arg or env and auto-register.
374
+ // This allows session_start to succeed even for agents not yet registered.
375
+ const candidateName = options.agentName?.trim()
376
+ || (options.allowEnv !== false ? resolveEnvAgentName(options.env ?? process.env) : undefined);
377
+ if (!candidateName)
378
+ throw err;
379
+ const normalizedName = normalizeAgentName(candidateName);
380
+ const registered = registerAgentIdentity({
381
+ agentName: normalizedName,
382
+ kind: 'agent',
383
+ trustLevel: 'contributor',
384
+ cwd: options.cwd,
385
+ preferredDirName: options.preferredDirName,
386
+ });
387
+ return { identity: registered, auto_registered: true };
388
+ }
389
+ }
390
+ /**
391
+ * Ensure that a target agent is registered in the current project before dispatch.
392
+ *
393
+ * If the agent is already registered, returns the existing identity.
394
+ * If not registered but has a known capability profile with canBeSpawnedCli=true,
395
+ * auto-registers it as a contributor agent with source='dispatch-auto-register'.
396
+ *
397
+ * Returns the identity, or undefined if the agent is unknown/not spawnable.
398
+ */
399
+ export function ensureAgentRegisteredForDispatch(agentName, cwd) {
400
+ const normalized = normalizeAgentName(agentName);
401
+ // Already registered? Return as-is.
402
+ const existing = findAgentIdentityByName(normalized, cwd);
403
+ if (existing)
404
+ return existing;
405
+ // Check capability profile — only auto-register agents we know about
406
+ const profile = getCapabilityProfile(normalized);
407
+ if (!profile || !profile.runtime.canBeSpawnedCli)
408
+ return undefined;
409
+ // Auto-register with contributor trust
410
+ try {
411
+ const registered = registerAgentIdentity({
412
+ agentName: normalized,
413
+ kind: 'agent',
414
+ trustLevel: 'contributor',
415
+ capabilities: profile.role_capabilities ?? [],
416
+ cwd,
417
+ });
418
+ logger.debug(`Auto-registered agent for dispatch: ${normalized} (${registered.agent_id})`);
419
+ return registered;
420
+ }
421
+ catch {
422
+ // Non-fatal: store may be read-only
423
+ return undefined;
424
+ }
425
+ }
426
+ /**
427
+ * Check whether an agent name is declared in the project's agent_integrations config.
428
+ */
429
+ function isAgentDeclaredInIntegrations(normalizedName, cwd) {
430
+ if (!memoryExists(cwd))
431
+ return false;
432
+ try {
433
+ const cfg = loadConfig(cwd);
434
+ return (cfg.agent_integrations?.declarations ?? []).some((d) => normalizeAgentName(d.agent_name) === normalizedName);
435
+ }
436
+ catch {
437
+ return false;
438
+ }
439
+ }
311
440
  export function resolveAgentScope(agentName, cwd, preferredDirName) {
312
441
  const explicit = agentName?.trim();
313
442
  if (explicit) {
@@ -331,17 +460,20 @@ export function resolveCurrentModel(cwd) {
331
460
  /**
332
461
  * Returns the name of the current agent, with priority:
333
462
  * 1. $BRAINCLAW_AGENT_NAME env var (AI agent self-declaration)
334
- * 2. $BRAINCLAW_AGENT env var (legacy alias)
335
- * 3. config.current_agent (project owner / human default)
463
+ * 2. $BRAINCLAW_AGENT env var (legacy alias / relay model)
464
+ * 3. detectAiAgent() (auto-detection from process env vars)
336
465
  * 4. OS user (last-resort fallback)
466
+ *
467
+ * Note: config.current_agent is intentionally NOT used here — it's a singleton
468
+ * global that causes cross-agent confusion in multi-agent setups.
337
469
  */
338
- export function resolveCurrentAgentName(cwd) {
470
+ export function resolveCurrentAgentName(cwd, homeDir) {
339
471
  const fromEnv = (process.env.BRAINCLAW_AGENT_NAME ?? process.env.BRAINCLAW_AGENT)?.trim();
340
472
  if (fromEnv)
341
473
  return fromEnv;
342
- const fromConfig = loadConfig(cwd).current_agent?.trim();
343
- if (fromConfig)
344
- return fromConfig;
474
+ const detected = detectAiAgent(process.env, homeDir);
475
+ if (detected)
476
+ return detected.name;
345
477
  return process.env.USER ?? process.env.USERNAME ?? 'unknown';
346
478
  }
347
479
  export function requireOperationalAgentIdentity(agentName, cwd, preferredDirName) {