brainclaw 0.29.2 → 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 (195) hide show
  1. package/README.md +193 -170
  2. package/dist/brainclaw-vscode.vsix +0 -0
  3. package/dist/cli.js +673 -24
  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 +4221 -1501
  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 +100 -2
  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 +33 -5
  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/brainclaw-version.js +94 -2
  76. package/dist/core/candidates.js +93 -2
  77. package/dist/core/claims.js +419 -0
  78. package/dist/core/codev-metrics.js +77 -0
  79. package/dist/core/codev-personas.js +31 -0
  80. package/dist/core/codev-plan-gen.js +35 -0
  81. package/dist/core/codev-prompts.js +74 -0
  82. package/dist/core/codev-responses.js +62 -0
  83. package/dist/core/codev-rounds.js +218 -0
  84. package/dist/core/config.js +4 -0
  85. package/dist/core/context.js +381 -34
  86. package/dist/core/coordination.js +201 -6
  87. package/dist/core/cross-project.js +230 -16
  88. package/dist/core/default-profiles/doctor.yaml +11 -0
  89. package/dist/core/default-profiles/janitor.yaml +11 -0
  90. package/dist/core/default-profiles/onboarder.yaml +11 -0
  91. package/dist/core/default-profiles/reviewer.yaml +13 -0
  92. package/dist/core/dispatcher.js +1189 -0
  93. package/dist/core/duplicates.js +2 -2
  94. package/dist/core/entity-operations.js +450 -0
  95. package/dist/core/entity-registry.js +344 -0
  96. package/dist/core/events.js +106 -2
  97. package/dist/core/execution-adapters.js +154 -0
  98. package/dist/core/execution-context.js +63 -0
  99. package/dist/core/execution-profile.js +270 -0
  100. package/dist/core/execution.js +255 -0
  101. package/dist/core/facade-schema.js +81 -0
  102. package/dist/core/federation-cloud.js +99 -0
  103. package/dist/core/federation-message.js +52 -0
  104. package/dist/core/federation-transport.js +65 -0
  105. package/dist/core/gc-semantic.js +482 -0
  106. package/dist/core/governance.js +247 -0
  107. package/dist/core/guards.js +19 -0
  108. package/dist/core/ideation.js +72 -0
  109. package/dist/core/identity.js +110 -25
  110. package/dist/core/ids.js +6 -0
  111. package/dist/core/input-validation.js +2 -2
  112. package/dist/core/instruction-templates.js +344 -136
  113. package/dist/core/io.js +90 -11
  114. package/dist/core/lock.js +6 -2
  115. package/dist/core/loops/brief-assembly.js +213 -0
  116. package/dist/core/loops/facade-schema.js +148 -0
  117. package/dist/core/loops/index.js +7 -0
  118. package/dist/core/loops/iteration-engine.js +139 -0
  119. package/dist/core/loops/lock.js +385 -0
  120. package/dist/core/loops/store.js +201 -0
  121. package/dist/core/loops/types.js +403 -0
  122. package/dist/core/loops/verbs.js +534 -0
  123. package/dist/core/markdown.js +15 -3
  124. package/dist/core/memory-compactor.js +432 -0
  125. package/dist/core/memory-git.js +152 -8
  126. package/dist/core/messaging.js +278 -0
  127. package/dist/core/migration.js +32 -1
  128. package/dist/core/mutation-pipeline.js +4 -2
  129. package/dist/core/operations/memory-mutation.js +129 -0
  130. package/dist/core/operations/memory-write.js +78 -0
  131. package/dist/core/operations/plan.js +190 -0
  132. package/dist/core/policy.js +169 -0
  133. package/dist/core/reputation.js +9 -3
  134. package/dist/core/schema.js +491 -6
  135. package/dist/core/search.js +21 -2
  136. package/dist/core/security-cache.js +71 -0
  137. package/dist/core/security-guard.js +152 -0
  138. package/dist/core/security-scoring.js +86 -0
  139. package/dist/core/sequence.js +130 -0
  140. package/dist/core/socket-client.js +113 -0
  141. package/dist/core/staleness.js +246 -0
  142. package/dist/core/state.js +98 -22
  143. package/dist/core/store-resolution.js +43 -11
  144. package/dist/core/toml-writer.js +76 -0
  145. package/dist/core/upgrades/backup.js +232 -0
  146. package/dist/core/upgrades/health-check.js +169 -0
  147. package/dist/core/upgrades/patches/candidate-archive.js +145 -0
  148. package/dist/core/upgrades/patches/handoff-review-strip.js +128 -0
  149. package/dist/core/upgrades/patches/provenance-rollout.js +136 -0
  150. package/dist/core/upgrades/schema-version.js +97 -0
  151. package/dist/core/worktree.js +606 -0
  152. package/dist/facts.js +114 -0
  153. package/dist/facts.json +111 -0
  154. package/docs/architecture/project-refs.md +5 -1
  155. package/docs/cli.md +690 -43
  156. package/docs/concepts/ideation-loop.md +317 -0
  157. package/docs/concepts/loop-engine.md +456 -0
  158. package/docs/concepts/mcp-governance.md +268 -0
  159. package/docs/concepts/memory-staleness.md +122 -0
  160. package/docs/concepts/multi-agent-workflows.md +166 -0
  161. package/docs/concepts/plans-and-claims.md +31 -6
  162. package/docs/concepts/project-md-convention.md +35 -0
  163. package/docs/concepts/troubleshooting.md +220 -0
  164. package/docs/concepts/upgrade-cli.md +202 -0
  165. package/docs/concepts/upgrade-dogfood-procedure.md +114 -0
  166. package/docs/context-format-changelog.md +2 -2
  167. package/docs/context-format.md +2 -2
  168. package/docs/index.md +68 -0
  169. package/docs/integrations/agents.md +15 -16
  170. package/docs/integrations/cline.md +88 -0
  171. package/docs/integrations/codex.md +75 -23
  172. package/docs/integrations/continue.md +60 -0
  173. package/docs/integrations/copilot.md +67 -9
  174. package/docs/integrations/kilocode.md +72 -0
  175. package/docs/integrations/mcp.md +304 -21
  176. package/docs/integrations/mistral-vibe.md +122 -0
  177. package/docs/integrations/opencode.md +84 -0
  178. package/docs/integrations/overview.md +23 -8
  179. package/docs/integrations/roo.md +74 -0
  180. package/docs/integrations/windsurf.md +83 -0
  181. package/docs/mcp-schema-changelog.md +191 -1
  182. package/docs/playbooks/integration/index.md +121 -0
  183. package/docs/playbooks/productivity/index.md +102 -0
  184. package/docs/playbooks/team/index.md +122 -0
  185. package/docs/product/agent-first-model.md +184 -0
  186. package/docs/product/entity-model-audit.md +462 -0
  187. package/docs/quickstart-existing-project.md +135 -0
  188. package/docs/quickstart.md +124 -37
  189. package/docs/release-maintenance.md +79 -0
  190. package/docs/review.md +2 -0
  191. package/docs/server-operations.md +118 -0
  192. package/package.json +20 -12
  193. package/dist/commands/claude-desktop-extension.js +0 -18
  194. package/dist/commands/diff.js +0 -99
  195. package/dist/core/claude-desktop-extension.js +0 -224
@@ -1,35 +1,66 @@
1
1
  import fs from 'node:fs';
2
+ import os from 'node:os';
2
3
  import path from 'node:path';
3
4
  import { execSync } from 'node:child_process';
4
5
  import { memoryExists, resolveEntityDir } from '../core/io.js';
5
6
  import { loadVersionedJsonFile, saveVersionedJsonFile } from '../core/migration.js';
6
- import { buildOperationalIdentity, saveCurrentSession } from '../core/identity.js';
7
- import { requireMinimumTrustLevel, requireRegisteredAgentIdentity, resolveCurrentModel } from '../core/agent-registry.js';
8
- import { buildContext } from '../core/context.js';
7
+ import { buildOperationalIdentity, loadAllSessions, saveCurrentSession } from '../core/identity.js';
8
+ import { requireMinimumTrustLevel, resolveCurrentModel, resolveOrAutoRegisterAgentIdentity } from '../core/agent-registry.js';
9
+ import { buildContext, renderContextPromptTemplate } from '../core/context.js';
10
+ import { writeContextMarker } from '../core/freshness.js';
9
11
  import { saveRuntimeNote, generateRuntimeNoteId } from '../core/runtime.js';
10
- import { nowISO, generateId } from '../core/ids.js';
12
+ import { nowISO, generateId, generateIdWithLabel } from '../core/ids.js';
11
13
  import { appendAuditEntry } from '../core/audit.js';
12
- import { SessionSnapshotSchema } from '../core/schema.js';
14
+ import { releaseStaleClaimsFromOtherAgents } from '../core/claims.js';
15
+ import { SessionSnapshotSchema, CandidateSchema, HandoffSchema, RuntimeNoteSchema } from '../core/schema.js';
13
16
  import { auditLocalAgentWorkspaceFiles } from '../core/agent-files.js';
14
17
  import { buildAgentInventory, loadAgentInventory, saveAgentInventory, diffInventory } from '../core/agent-inventory.js';
18
+ import { checkMemoryPressure } from '../core/gc-semantic.js';
19
+ import { pullSignalsFromLinkedProjects, markSignalProcessed } from '../core/federation-transport.js';
20
+ import { saveCandidate, generateCandidateIdWithLabel } from '../core/candidates.js';
21
+ import { mutateState } from '../core/state.js';
15
22
  function sessionsDir(cwd) {
16
23
  return resolveEntityDir('sessions', cwd ?? process.cwd(), 'read');
17
24
  }
18
25
  function sessionSnapshotPath(sessionId, cwd) {
19
26
  return path.join(sessionsDir(cwd), `${sessionId}.json`);
20
27
  }
21
- function createHash(data) {
22
- let hash = 0;
23
- for (let i = 0; i < data.length; i++) {
24
- const chr = data.charCodeAt(i);
25
- hash = ((hash << 5) - hash) + chr;
26
- hash |= 0;
27
- }
28
- return (hash >>> 0).toString(16).padStart(8, '0');
29
- }
30
28
  export function runSessionStart(options = {}) {
31
29
  try {
32
- const snapshot = startSession(options);
30
+ const snapshot = startSession({
31
+ ...options,
32
+ maintenanceMode: options.maintenanceMode ?? 'full',
33
+ });
34
+ // --include-context: output full project context (replaces separate `brainclaw context` call)
35
+ if (options.includeContext) {
36
+ try {
37
+ const cwd = options.cwd ?? process.cwd();
38
+ // Find the previous session for the same agent to auto-surface a context diff on resume.
39
+ // We exclude the session just created so we always point at the prior one.
40
+ const previousSession = loadAllSessions(cwd)
41
+ .find((s) => s.agent === snapshot.agent && s.session_id !== snapshot.session_id);
42
+ const contextResult = buildContext({
43
+ target: options.context,
44
+ agent: snapshot.agent,
45
+ cwd,
46
+ sinceSession: previousSession?.session_id,
47
+ });
48
+ console.log(renderContextPromptTemplate(contextResult, false));
49
+ writeContextMarker({
50
+ read_at: nowISO(),
51
+ memory_version: contextResult.memory_version,
52
+ host_id: contextResult.current_host,
53
+ target: options.context,
54
+ project: contextResult.project,
55
+ all_hosts: false,
56
+ }, cwd);
57
+ }
58
+ catch (ctxErr) {
59
+ // Context build failure should not block session start output
60
+ console.error(`⚠ Context build failed: ${ctxErr instanceof Error ? ctxErr.message : String(ctxErr)}`);
61
+ }
62
+ return;
63
+ }
33
64
  if (options.json) {
34
65
  console.log(JSON.stringify(snapshot, null, 2));
35
66
  return;
@@ -53,6 +84,12 @@ export function runSessionStart(options = {}) {
53
84
  console.warn(`⚠ ${line}`);
54
85
  }
55
86
  }
87
+ if (snapshot.stale_claims_released) {
88
+ console.warn(`⚠ Auto-released ${snapshot.stale_claims_released.length} stale claim(s):`);
89
+ for (const c of snapshot.stale_claims_released) {
90
+ console.warn(` ${c.agent} → ${c.scope}`);
91
+ }
92
+ }
56
93
  }
57
94
  catch (e) {
58
95
  console.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
@@ -63,7 +100,7 @@ export function startSession(options = {}) {
63
100
  if (!memoryExists(options.cwd)) {
64
101
  throw new Error('.brainclaw/ not found. Run `brainclaw init` first.');
65
102
  }
66
- const registered = requireRegisteredAgentIdentity({
103
+ const { identity: registered, auto_registered: autoRegistered } = resolveOrAutoRegisterAgentIdentity({
67
104
  agentName: options.agent,
68
105
  agentId: options.agentId,
69
106
  cwd: options.cwd,
@@ -72,13 +109,7 @@ export function startSession(options = {}) {
72
109
  });
73
110
  requireMinimumTrustLevel(registered, 'contributor');
74
111
  const actor = buildOperationalIdentity(registered.agent_name, options.cwd, { agentId: registered.agent_id });
75
- // Capture initial context snapshot
76
- let initialContextHash;
77
- try {
78
- const ctx = buildContext({ target: options.context, agent: actor.agent, cwd: options.cwd });
79
- initialContextHash = createHash(JSON.stringify(ctx.selected));
80
- }
81
- catch { /* non-fatal */ }
112
+ const maintenanceMode = options.maintenanceMode ?? 'fast';
82
113
  // Capture git HEAD SHA for later handoff generation
83
114
  let gitSha;
84
115
  try {
@@ -93,7 +124,6 @@ export function startSession(options = {}) {
93
124
  agent_id: actor.agent_id,
94
125
  started_at: nowISO(),
95
126
  context_target: options.context,
96
- initial_context_hash: initialContextHash,
97
127
  git_sha: gitSha,
98
128
  ...(model ? { model } : {}),
99
129
  };
@@ -102,6 +132,14 @@ export function startSession(options = {}) {
102
132
  if (!fs.existsSync(dir))
103
133
  fs.mkdirSync(dir, { recursive: true });
104
134
  saveVersionedJsonFile('session_snapshot', sessionSnapshotPath(snapshot.session_id, options.cwd), SessionSnapshotSchema.parse(snapshot));
135
+ // Resolve git branch and worktree for session tracking
136
+ let currentBranch;
137
+ let currentWorktreePath;
138
+ try {
139
+ currentBranch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8', cwd: options.cwd ?? process.cwd() }).trim();
140
+ currentWorktreePath = execSync('git rev-parse --show-toplevel', { encoding: 'utf-8', cwd: options.cwd ?? process.cwd() }).trim();
141
+ }
142
+ catch { /* non-fatal — not a git repo */ }
105
143
  saveCurrentSession({
106
144
  schema_version: 2,
107
145
  session_id: snapshot.session_id,
@@ -110,6 +148,12 @@ export function startSession(options = {}) {
110
148
  agent: actor.agent,
111
149
  agent_id: actor.agent_id,
112
150
  host_id: actor.host_id,
151
+ user: process.env.USER || process.env.USERNAME || os.userInfo().username || undefined,
152
+ pid: process.pid,
153
+ model: model ?? undefined,
154
+ branch: currentBranch,
155
+ worktree_path: currentWorktreePath,
156
+ isolation_mode: 'shared-checkout',
113
157
  }, options.cwd);
114
158
  // Write session_start runtime note
115
159
  const noteId = generateRuntimeNoteId();
@@ -125,27 +169,143 @@ export function startSession(options = {}) {
125
169
  visibility: 'shared',
126
170
  note_type: 'session_start',
127
171
  }, options.cwd);
128
- appendAuditEntry({ action: 'session_start', actor: actor.agent, actor_id: actor.agent_id, item_id: snapshot.session_id, item_type: 'session' }, options.cwd);
172
+ appendAuditEntry({ action: 'session_start', actor: actor.agent, actor_id: actor.agent_id, item_id: snapshot.session_id, item_type: 'session', session_id: snapshot.session_id, host_id: actor.host_id }, options.cwd);
129
173
  const agentGitHygiene = auditLocalAgentWorkspaceFiles(options.cwd ?? process.cwd());
130
- // Inventory reconciliation detect new/disappeared agents on this machine
174
+ // Non-critical maintenance work lives behind the full mode only.
131
175
  let inventoryAdvisory;
132
- try {
133
- const previousInventory = loadAgentInventory();
134
- const currentInventory = buildAgentInventory();
135
- const diff = diffInventory(previousInventory, currentInventory);
136
- saveAgentInventory(currentInventory);
137
- const lines = [];
138
- if (diff.appeared.length > 0)
139
- lines.push(`New agents detected: ${diff.appeared.join(', ')}`);
140
- if (diff.disappeared.length > 0)
141
- lines.push(`Agents no longer detected: ${diff.disappeared.join(', ')}`);
142
- for (const vc of diff.version_changed) {
143
- lines.push(`${vc.name} version changed: ${vc.from ?? '?'} → ${vc.to ?? '?'}`);
176
+ if (maintenanceMode === 'full') {
177
+ try {
178
+ const previousInventory = loadAgentInventory();
179
+ const currentInventory = buildAgentInventory();
180
+ const diff = diffInventory(previousInventory, currentInventory);
181
+ saveAgentInventory(currentInventory);
182
+ const lines = [];
183
+ if (diff.appeared.length > 0)
184
+ lines.push(`New agents detected: ${diff.appeared.join(', ')}`);
185
+ if (diff.disappeared.length > 0)
186
+ lines.push(`Agents no longer detected: ${diff.disappeared.join(', ')}`);
187
+ for (const vc of diff.version_changed) {
188
+ lines.push(`${vc.name} version changed: ${vc.from ?? '?'} → ${vc.to ?? '?'}`);
189
+ }
190
+ if (lines.length > 0)
191
+ inventoryAdvisory = lines;
144
192
  }
145
- if (lines.length > 0)
146
- inventoryAdvisory = lines;
193
+ catch { /* non-fatal — inventory scan failure should not block session start */ }
194
+ }
195
+ // Shared checkout detection: warn if other active sessions share the same worktree
196
+ let sharedCheckoutWarning;
197
+ if (currentWorktreePath) {
198
+ try {
199
+ const allSessions = loadAllSessions(options.cwd);
200
+ const ttlMs = 4 * 60 * 60 * 1000; // 4h
201
+ const now = Date.now();
202
+ const otherSessions = allSessions.filter(s => s.session_id !== snapshot.session_id
203
+ && s.worktree_path === currentWorktreePath
204
+ && (now - Date.parse(s.last_seen_at)) <= ttlMs
205
+ && (!s.pid || isPidAlive(s.pid)));
206
+ if (otherSessions.length > 0) {
207
+ sharedCheckoutWarning = {
208
+ worktree_path: currentWorktreePath,
209
+ other_sessions: otherSessions.map(s => ({
210
+ session_id: s.session_id,
211
+ agent: s.agent,
212
+ user: s.user,
213
+ branch: s.branch,
214
+ pid: s.pid,
215
+ })),
216
+ };
217
+ }
218
+ }
219
+ catch { /* non-fatal */ }
220
+ }
221
+ // Stale claim auto-release. Phase 4 slice pln#388 stp_e2b10ab4: pass
222
+ // the new session_id so same-agent prior-session claims can be swept
223
+ // too — without it, a crash-recovered agent would keep its own orphaned
224
+ // claims hanging until 24h.
225
+ let staleClaimsReleased;
226
+ if (maintenanceMode === 'full') {
227
+ try {
228
+ const staleResult = releaseStaleClaimsFromOtherAgents(actor.agent, options.cwd, snapshot.session_id);
229
+ if (staleResult.released.length > 0) {
230
+ staleClaimsReleased = staleResult.released.map(c => ({ id: c.id, agent: c.agent, scope: c.scope }));
231
+ }
232
+ }
233
+ catch { /* non-fatal */ }
234
+ }
235
+ // Memory pressure check: hint agent to run bclaw_compact if store is large
236
+ let memoryPressure;
237
+ if (maintenanceMode === 'full') {
238
+ try {
239
+ const pressure = checkMemoryPressure(options.cwd);
240
+ if (pressure.memory_pressure) {
241
+ memoryPressure = pressure;
242
+ }
243
+ }
244
+ catch { /* non-fatal */ }
245
+ }
246
+ // Materialize incoming federation signals from linked projects
247
+ if (maintenanceMode === 'full') {
248
+ try {
249
+ const federationSignals = pullSignalsFromLinkedProjects(options.cwd);
250
+ let materialized = 0;
251
+ for (const signal of federationSignals) {
252
+ try {
253
+ const origin = `remote:${signal.from.project_name}:${signal.from.agent_name}`;
254
+ if (signal.type === 'candidate') {
255
+ const parsed = CandidateSchema.safeParse(signal.payload);
256
+ if (parsed.success) {
257
+ const { id, short_label } = generateCandidateIdWithLabel(options.cwd);
258
+ saveCandidate({
259
+ ...parsed.data,
260
+ id,
261
+ short_label,
262
+ created_at: nowISO(),
263
+ source: undefined, // remote federation signal — treated as 'human' (legacy default)
264
+ star_count: 0,
265
+ starred_by: [],
266
+ usage_count: 0,
267
+ usage_events: [],
268
+ status: 'pending',
269
+ }, options.cwd);
270
+ }
271
+ }
272
+ else if (signal.type === 'handoff') {
273
+ const parsed = HandoffSchema.safeParse(signal.payload);
274
+ if (parsed.success) {
275
+ const { id, short_label } = generateIdWithLabel('open_handoffs', options.cwd);
276
+ mutateState((state) => {
277
+ state.open_handoffs.push({
278
+ ...parsed.data,
279
+ id,
280
+ short_label,
281
+ created_at: nowISO(),
282
+ tags: [...(parsed.data.tags ?? []), origin],
283
+ });
284
+ }, options.cwd);
285
+ }
286
+ }
287
+ else if (signal.type === 'runtime_note') {
288
+ const parsed = RuntimeNoteSchema.safeParse(signal.payload);
289
+ if (parsed.success) {
290
+ saveRuntimeNote({
291
+ ...parsed.data,
292
+ id: generateRuntimeNoteId(),
293
+ created_at: nowISO(),
294
+ tags: [...(parsed.data.tags ?? []), origin],
295
+ }, options.cwd);
296
+ }
297
+ }
298
+ markSignalProcessed(signal.from.project_path, signal.id);
299
+ materialized++;
300
+ }
301
+ catch { /* skip this signal — do not block session start */ }
302
+ }
303
+ if (materialized > 0) {
304
+ console.log(`✔ Materialized ${materialized} federation signal(s) from linked projects`);
305
+ }
306
+ }
307
+ catch { /* Non-fatal — federation pull failure should not block session start */ }
147
308
  }
148
- catch { /* non-fatal — inventory scan failure should not block session start */ }
149
309
  return {
150
310
  ...snapshot,
151
311
  ...(agentGitHygiene.isGitRepo && (agentGitHygiene.missingGitignorePaths.length > 0 || agentGitHygiene.trackedPaths.length > 0)
@@ -157,8 +317,21 @@ export function startSession(options = {}) {
157
317
  }
158
318
  : {}),
159
319
  ...(inventoryAdvisory ? { inventory_advisory: inventoryAdvisory } : {}),
320
+ ...(sharedCheckoutWarning ? { shared_checkout_warning: sharedCheckoutWarning } : {}),
321
+ ...(staleClaimsReleased ? { stale_claims_released: staleClaimsReleased } : {}),
322
+ ...(memoryPressure ? { memory_pressure: memoryPressure } : {}),
323
+ ...(autoRegistered ? { auto_registered: true } : {}),
160
324
  };
161
325
  }
326
+ function isPidAlive(pid) {
327
+ try {
328
+ process.kill(pid, 0);
329
+ return true;
330
+ }
331
+ catch {
332
+ return false;
333
+ }
334
+ }
162
335
  export function loadSessionSnapshot(sessionId, cwd) {
163
336
  const p = sessionSnapshotPath(sessionId, cwd);
164
337
  if (!fs.existsSync(p))
@@ -0,0 +1,103 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { memoryExists, memoryPath, ensureMemoryDir } from '../core/io.js';
4
+ import { loadConfig, saveConfig } from '../core/config.js';
5
+ import { generateBashGuard, generatePowerShellGuard, generatePipBashGuard } from '../core/security-guard.js';
6
+ export function runSetupSecurity(options = {}) {
7
+ const cwd = options.cwd;
8
+ if (!memoryExists(cwd)) {
9
+ console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
10
+ process.exit(1);
11
+ }
12
+ const config = loadConfig(cwd);
13
+ const mode = options.mode ?? 'advisory';
14
+ // Enable preinstall in config
15
+ if (!config.security) {
16
+ config.security = { mode: 'warn', strict_redaction: false, block_sensitive_paths: true };
17
+ }
18
+ config.security.preinstall = {
19
+ enabled: true,
20
+ mode,
21
+ thresholds: {
22
+ composite_pass: 70,
23
+ composite_warn: 50,
24
+ supply_chain_block: 30,
25
+ vulnerability_block: 20,
26
+ },
27
+ weights: {
28
+ supply_chain: 0.35,
29
+ vulnerability: 0.30,
30
+ quality: 0.15,
31
+ maintenance: 0.15,
32
+ license: 0.05,
33
+ },
34
+ cache_ttl_hours: 24,
35
+ fallback_on_error: 'warn',
36
+ allowlist: [],
37
+ denylist: [],
38
+ socket_endpoint: 'https://mcp.socket.dev/',
39
+ };
40
+ saveConfig(config, cwd);
41
+ // Resolve brainclaw binary path
42
+ const brainclawBin = resolveBrainclawBin(cwd);
43
+ // Generate wrapper scripts
44
+ const guardDir = memoryPath('security/bin', cwd);
45
+ ensureMemoryDir(cwd);
46
+ if (!fs.existsSync(guardDir)) {
47
+ fs.mkdirSync(guardDir, { recursive: true });
48
+ }
49
+ // npm guard (bash)
50
+ const npmBashPath = path.join(guardDir, 'npm');
51
+ fs.writeFileSync(npmBashPath, generateBashGuard(brainclawBin), { mode: 0o755 });
52
+ // npm guard (PowerShell)
53
+ const npmPs1Path = path.join(guardDir, 'npm.ps1');
54
+ fs.writeFileSync(npmPs1Path, generatePowerShellGuard(brainclawBin));
55
+ // pip guard (bash)
56
+ const pipBashPath = path.join(guardDir, 'pip');
57
+ fs.writeFileSync(pipBashPath, generatePipBashGuard(brainclawBin), { mode: 0o755 });
58
+ // pip guard (PowerShell)
59
+ const pipPs1Path = path.join(guardDir, 'pip.ps1');
60
+ fs.writeFileSync(pipPs1Path, generatePowerShellGuard(brainclawBin).replace('} else { "npm" }', '} else { "pip" }'));
61
+ console.log(`\u2705 Security gate enabled (mode: ${mode})`);
62
+ console.log('');
63
+ console.log('Generated wrapper scripts:');
64
+ console.log(` ${npmBashPath}`);
65
+ console.log(` ${npmPs1Path}`);
66
+ console.log(` ${pipBashPath}`);
67
+ console.log(` ${pipPs1Path}`);
68
+ console.log('');
69
+ console.log('To activate, prepend the guard directory to your PATH:');
70
+ console.log(` export PATH="${guardDir}:$PATH" # bash/zsh`);
71
+ console.log(` $env:PATH = "${guardDir};$env:PATH" # PowerShell`);
72
+ console.log('');
73
+ console.log('Or add it to your shell profile for persistent activation.');
74
+ console.log('');
75
+ console.log(`Mode: ${mode}`);
76
+ if (mode === 'advisory') {
77
+ console.log(' Advisory mode: warnings and traps are created but installs are not blocked.');
78
+ console.log(' Switch to enforced mode: brainclaw setup --security --mode enforced');
79
+ }
80
+ else {
81
+ console.log(' Enforced mode: risky installs will be blocked (exit code 1).');
82
+ }
83
+ }
84
+ function resolveBrainclawBin(cwd) {
85
+ // Try to find brainclaw in common locations
86
+ const candidates = [
87
+ 'brainclaw',
88
+ path.resolve(cwd ?? process.cwd(), 'node_modules/.bin/brainclaw'),
89
+ path.resolve(cwd ?? process.cwd(), 'dist/cli.js'),
90
+ ];
91
+ for (const c of candidates) {
92
+ try {
93
+ if (fs.existsSync(c))
94
+ return c;
95
+ }
96
+ catch {
97
+ // continue
98
+ }
99
+ }
100
+ // Default to just 'brainclaw' and hope it's in PATH
101
+ return 'brainclaw';
102
+ }
103
+ //# sourceMappingURL=setup-security.js.map
@@ -7,7 +7,7 @@ import { detectAiAgent } from '../core/ai-agent-detection.js';
7
7
  import { buildAiSurfaceInventory, renderAiSurfaceUsageHints } from '../core/ai-surface-inventory.js';
8
8
  import { buildMachineProfile, saveMachineProfile, loadMachineProfile } from '../core/machine-profile.js';
9
9
  import { buildAgentInventory, saveAgentInventory, loadAgentInventory } from '../core/agent-inventory.js';
10
- import { ensureClaudeCodeUserSettings, ensureClaudeCodeUserCommand, ensureCursorMcpConfig, ensureWindsurfMcpConfig, ensureAntigravityMcpConfig, ensureContinueUserMcpConfig, ensureCodexMcpConfig, writeDetectedAgentAutoConfig, describeAutoConfigWrite, ensureGitignoreEntries, collectWorkspaceGitignoreEntries, } from '../core/agent-files.js';
10
+ import { ensureClaudeCodeUserSettings, ensureClaudeCodeUserCommand, ensureCursorMcpConfig, ensureWindsurfMcpConfig, ensureAntigravityMcpConfig, ensureContinueUserMcpConfig, ensureContinueUserPermissions, ensureCodexMcpConfig, writeDetectedAgentAutoConfig, describeAutoConfigWrite, ensureGitignoreEntries, collectWorkspaceGitignoreEntries, BRAINCLAW_EXCLUSIVE_DIRECTORIES, } from '../core/agent-files.js';
11
11
  import { MEMORY_DIR, memoryExists, ensureMemoryDir } from '../core/io.js';
12
12
  import { saveConfig, defaultConfig } from '../core/config.js';
13
13
  import { readSetupState, resolveHomeDir, writeSetupState } from '../core/setup-state.js';
@@ -26,6 +26,10 @@ export const ALL_KNOWN_AGENTS = [
26
26
  'continue',
27
27
  'roo',
28
28
  'openclaw',
29
+ 'nanoclaw',
30
+ 'nemoclaw',
31
+ 'picoclaw',
32
+ 'zeroclaw',
29
33
  ];
30
34
  const BRAINCLAW_ASCII = [
31
35
  '',
@@ -224,6 +228,9 @@ export function runGlobalInstall(selectedAgents, env = process.env) {
224
228
  const r = ensureContinueUserMcpConfig(home);
225
229
  if (r && (r.created || r.updated))
226
230
  written.push(r.filePath);
231
+ const perms = ensureContinueUserPermissions(home);
232
+ if (perms && (perms.created || perms.updated))
233
+ written.push(perms.filePath);
227
234
  }
228
235
  if (selectedAgents.includes('codex')) {
229
236
  const r = ensureCodexMcpConfig(home, env);
@@ -272,7 +279,7 @@ export async function initReposAndConfigureAgents(selectedRepos, selectedAgents,
272
279
  }
273
280
  }
274
281
  if (gitignoreEntries.size > 0) {
275
- ensureGitignoreEntries(repo.path, [...gitignoreEntries]);
282
+ ensureGitignoreEntries(repo.path, [...gitignoreEntries, ...BRAINCLAW_EXCLUSIVE_DIRECTORIES]);
276
283
  console.log(` ✔ Updated .gitignore with generated agent files (${[...gitignoreEntries].join(', ')})`);
277
284
  }
278
285
  }
@@ -390,7 +397,8 @@ export async function runSetup(options = {}) {
390
397
  // Step 4: Agent detection & selection
391
398
  const detectedAi = detectAiAgent(env);
392
399
  const detectedName = detectedAi?.name;
393
- const detectedSurfaces = buildAiSurfaceInventory();
400
+ const testMode = process.env.BRAINCLAW_TEST_MODE === '1';
401
+ const detectedSurfaces = testMode ? [] : buildAiSurfaceInventory();
394
402
  console.log('');
395
403
  if (detectedName) {
396
404
  console.log(`Detected AI agent: ${detectedName}`);
@@ -452,6 +460,8 @@ export async function runSetup(options = {}) {
452
460
  // Step 6: Init repos + configure agents
453
461
  console.log('\n→ Initialising repositories and configuring agents...');
454
462
  const { initialisedRepos, configActions } = await initReposAndConfigureAgents(selectedRepos, selectedAgents, env);
463
+ // Step 7: VS Code extension
464
+ installVscodeExtension();
455
465
  // Save state
456
466
  writeSetupState({
457
467
  completed_at: new Date().toISOString(),
@@ -459,8 +469,36 @@ export async function runSetup(options = {}) {
459
469
  initialised_repos: initialisedRepos,
460
470
  global_configs_written: selectedAgents,
461
471
  }, env);
462
- // Step 7: Reload reminder
472
+ // Step 8: Reload reminder
463
473
  printReloadReminder(detectedName);
464
474
  void configActions;
465
475
  }
476
+ // ─── VS Code extension auto-install ──────────────────────────────────────────
477
+ function installVscodeExtension() {
478
+ // Find the bundled vsix
479
+ const vsixPath = resolveVsixPath();
480
+ if (!vsixPath)
481
+ return;
482
+ // Check if `code` CLI is available
483
+ const codeAvailable = spawnSync('code', ['--version'], { stdio: 'ignore', timeout: 5000 });
484
+ if (codeAvailable.status !== 0)
485
+ return; // VS Code not installed or not in PATH — skip silently
486
+ // Install the extension
487
+ const result = spawnSync('code', ['--install-extension', vsixPath, '--force'], { stdio: 'pipe', timeout: 30000 });
488
+ if (result.status === 0) {
489
+ console.log(' ✔ Installed Brainclaw VS Code extension');
490
+ }
491
+ }
492
+ function resolveVsixPath() {
493
+ const thisDir = path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/i, '$1'));
494
+ // 1. Bundled in dist/ (npm install) — dist/commands/setup.js → dist/brainclaw-vscode.vsix
495
+ const distVsix = path.join(thisDir, '..', 'brainclaw-vscode.vsix');
496
+ if (fs.existsSync(distVsix))
497
+ return distVsix;
498
+ // 2. Dev mode: vscode-extension/ directory
499
+ const devVsix = path.join(thisDir, '..', '..', 'vscode-extension', 'brainclaw-vscode-0.1.0.vsix');
500
+ if (fs.existsSync(devVsix))
501
+ return devVsix;
502
+ return undefined;
503
+ }
466
504
  //# sourceMappingURL=setup.js.map
@@ -0,0 +1,109 @@
1
+ /**
2
+ * `brainclaw stale` — minimal operator flow over the staleness detector.
3
+ *
4
+ * Phase 4 Sprint 1 Lane A step 4 (pln#390 / stp_18d6bc03). Lets operators
5
+ * list stale items and apply the canonical v1.0 action without having to
6
+ * remember per-entity syntax.
7
+ *
8
+ * Subcommands:
9
+ * list — print the stale report (same as doctor + board)
10
+ * resolve <id> — apply the canonical action for the entity type:
11
+ * plan → transition status 'dropped'
12
+ * handoff → transition status 'closed'
13
+ * candidate → transition status 'rejected'
14
+ * trap → transition status 'resolved'
15
+ * runtime_note → remove (archive via entity-operations)
16
+ */
17
+ import { listCandidates } from '../core/candidates.js';
18
+ import { removeEntity, transitionEntity, } from '../core/entity-operations.js';
19
+ import { listRuntimeNotes } from '../core/runtime.js';
20
+ import { loadState } from '../core/state.js';
21
+ import { detectStaleness, staleSummary, } from '../core/staleness.js';
22
+ /** Resolve the canonical transition target (or removal) per entity kind. */
23
+ const RESOLVE_ACTIONS = {
24
+ plan: { kind: 'transition', entity: 'plan', to: 'dropped' },
25
+ handoff: { kind: 'transition', entity: 'handoff', to: 'closed' },
26
+ candidate: { kind: 'transition', entity: 'candidate', to: 'rejected' },
27
+ trap: { kind: 'transition', entity: 'trap', to: 'resolved' },
28
+ runtime_note: { kind: 'remove', entity: 'runtime_note' },
29
+ };
30
+ function buildReport(cwd) {
31
+ const state = loadState(cwd);
32
+ const pending = listCandidates('pending', cwd);
33
+ const notes = listRuntimeNotes(undefined, cwd);
34
+ return detectStaleness(state.plan_items, state.known_traps, state.open_handoffs, pending, Date.now(), notes);
35
+ }
36
+ export function runStaleList(options = {}) {
37
+ const report = buildReport(options.cwd);
38
+ if (options.json) {
39
+ console.log(JSON.stringify(report, null, 2));
40
+ return;
41
+ }
42
+ if (report.warnings.length === 0) {
43
+ console.log('✔ No stale items detected.');
44
+ return;
45
+ }
46
+ console.log(`⚠ ${staleSummary(report)}`);
47
+ console.log('');
48
+ for (const w of report.warnings) {
49
+ console.log(` [${w.entity} ${w.id}] ${w.reason}`);
50
+ console.log(` → brainclaw stale resolve ${w.id} # or: ${w.suggested_action}`);
51
+ }
52
+ }
53
+ export function runStaleResolve(id, options = {}) {
54
+ const cwd = options.cwd ?? process.cwd();
55
+ const report = buildReport(cwd);
56
+ const warning = report.warnings.find((w) => w.id === id);
57
+ if (!warning) {
58
+ console.error(`Error: ${id} is not currently flagged stale.`);
59
+ console.error('Run `brainclaw stale list` to see the current warnings.');
60
+ process.exit(1);
61
+ }
62
+ const action = RESOLVE_ACTIONS[warning.entity];
63
+ try {
64
+ if (action.kind === 'transition') {
65
+ const result = transitionEntity(action.entity, id, action.to, cwd, 'resolved via brainclaw stale resolve');
66
+ if (options.json) {
67
+ console.log(JSON.stringify({ resolved: id, ...result }, null, 2));
68
+ }
69
+ else {
70
+ console.log(`✔ ${warning.entity} ${id}: ${result.from} → ${action.to}`);
71
+ }
72
+ }
73
+ else {
74
+ const result = removeEntity(action.entity, id, cwd);
75
+ if (options.json) {
76
+ console.log(JSON.stringify({ resolved: id, ...result }, null, 2));
77
+ }
78
+ else {
79
+ const verb = result.purged ? 'purged' : 'archived';
80
+ console.log(`✔ ${warning.entity} ${id}: ${verb}`);
81
+ }
82
+ }
83
+ }
84
+ catch (error) {
85
+ const msg = error instanceof Error ? error.message : String(error);
86
+ console.error(`Error: could not resolve ${id} — ${msg}`);
87
+ process.exit(1);
88
+ }
89
+ }
90
+ export function runStale(subcommand, arg, options = {}) {
91
+ const effective = subcommand ?? 'list';
92
+ switch (effective) {
93
+ case 'list':
94
+ runStaleList(options);
95
+ return;
96
+ case 'resolve': {
97
+ if (!arg) {
98
+ console.error('Error: `brainclaw stale resolve <id>` requires an entity id.');
99
+ process.exit(1);
100
+ }
101
+ runStaleResolve(arg, options);
102
+ return;
103
+ }
104
+ default:
105
+ console.error(`Error: unknown subcommand '${effective}'. Use: list | resolve <id>.`);
106
+ process.exit(1);
107
+ }
108
+ }
109
+ //# sourceMappingURL=stale.js.map