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
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Governance posture report.
3
+ *
4
+ * Aggregates claims, constraints, traps, instructions, and audit entries
5
+ * into a structured governance snapshot. No scores, no synthetic metrics —
6
+ * only verifiable facts.
7
+ *
8
+ * @module
9
+ */
10
+ import { listClaims, isClaimExpired } from './claims.js';
11
+ import { loadState } from './state.js';
12
+ import { loadInstructions, resolveInstructions } from './instructions.js';
13
+ import { readAuditLog } from './audit.js';
14
+ // ---------------------------------------------------------------------------
15
+ // Report builder
16
+ // ---------------------------------------------------------------------------
17
+ export function buildGovernanceReport(options = {}) {
18
+ const cwd = options.cwd;
19
+ const now = new Date();
20
+ const last24h = new Date(now.getTime() - 24 * 60 * 60 * 1000).toISOString();
21
+ // --- Constitution (global instructions) ---
22
+ const allInstructions = loadInstructions(cwd);
23
+ const activeInstructions = resolveInstructions(allInstructions, {});
24
+ const globalInstructions = activeInstructions.filter(i => i.layer === 'global');
25
+ // --- Red Lines (constraints) ---
26
+ const state = loadState(cwd);
27
+ const activeConstraints = state.active_constraints.filter(c => c.status === 'active');
28
+ const constraintsByCategory = {};
29
+ for (const c of activeConstraints) {
30
+ const cat = c.category ?? 'other';
31
+ (constraintsByCategory[cat] ??= []).push(c);
32
+ }
33
+ const highSeverityCount = activeConstraints.filter(c => c.tags?.includes('high') || c.category === 'security').length;
34
+ // --- Claims ---
35
+ const allClaims = listClaims(cwd);
36
+ const activeClaims = allClaims.filter(c => c.status === 'active' && !isClaimExpired(c));
37
+ const expiredUnreleased = allClaims.filter(c => c.status === 'active' && isClaimExpired(c));
38
+ const claimsByAgent = {};
39
+ for (const c of activeClaims) {
40
+ claimsByAgent[c.agent] = (claimsByAgent[c.agent] ?? 0) + 1;
41
+ }
42
+ // Apply filters
43
+ let filteredActiveClaims = activeClaims;
44
+ let filteredExpired = expiredUnreleased;
45
+ if (options.agent) {
46
+ const agentLower = options.agent.toLowerCase();
47
+ filteredActiveClaims = activeClaims.filter(c => c.agent.toLowerCase() === agentLower || c.agent_id?.toLowerCase() === agentLower);
48
+ filteredExpired = expiredUnreleased.filter(c => c.agent.toLowerCase() === agentLower || c.agent_id?.toLowerCase() === agentLower);
49
+ }
50
+ // --- Traps (shared visibility only — machine/private traps are environment-specific) ---
51
+ const openTraps = state.known_traps.filter(t => t.status === 'active' && (t.visibility === 'shared' || !t.visibility));
52
+ const trapsBySeverity = {};
53
+ for (const t of openTraps) {
54
+ trapsBySeverity[t.severity] = (trapsBySeverity[t.severity] ?? 0) + 1;
55
+ }
56
+ // --- Recent activity ---
57
+ const recentEntries = readAuditLog({ since: last24h }, cwd);
58
+ const claimsLast24h = recentEntries.filter(e => e.action === 'claim').length;
59
+ const releasesLast24h = recentEntries.filter(e => e.action === 'release_claim').length;
60
+ // Detect mutations without claim — check creates/updates that aren't claim/release/session actions
61
+ const mutationActions = new Set(['create', 'update', 'delete', 'promote_direct']);
62
+ const sessionTypes = new Set(['session', 'claim']);
63
+ const actionsWithoutClaim = recentEntries.filter(e => {
64
+ if (!mutationActions.has(e.action))
65
+ return false;
66
+ if (sessionTypes.has(e.item_type ?? ''))
67
+ return false;
68
+ // Check if the actor had any active claim at that time
69
+ const actorClaims = activeClaims.filter(c => c.agent === e.actor);
70
+ return actorClaims.length === 0;
71
+ });
72
+ // --- Recommendations ---
73
+ const recommendations = [];
74
+ if (expiredUnreleased.length > 0) {
75
+ recommendations.push(`${expiredUnreleased.length} expired claim(s) need release. Run: bclaw release-claims --expired`);
76
+ }
77
+ if (openTraps.length > 0) {
78
+ const highTraps = openTraps.filter(t => t.severity === 'high');
79
+ if (highTraps.length > 0) {
80
+ recommendations.push(`${highTraps.length} high-severity trap(s) open. Review before editing related files.`);
81
+ }
82
+ }
83
+ if (actionsWithoutClaim.length > 0) {
84
+ recommendations.push(`${actionsWithoutClaim.length} mutation(s) detected without active claim in last 24h.`);
85
+ }
86
+ if (globalInstructions.length === 0) {
87
+ recommendations.push('No global instructions set. Consider adding governance rules via: bclaw instruction add --layer global');
88
+ }
89
+ return {
90
+ generated_at: now.toISOString(),
91
+ scope_filter: options.scope,
92
+ agent_filter: options.agent,
93
+ constitution: {
94
+ global_instructions: globalInstructions,
95
+ total: globalInstructions.length,
96
+ },
97
+ red_lines: {
98
+ constraints_by_category: constraintsByCategory,
99
+ high_severity_count: highSeverityCount,
100
+ total: activeConstraints.length,
101
+ },
102
+ claims: {
103
+ active: filteredActiveClaims.map(toClaimSummary),
104
+ expired_unreleased: filteredExpired.map(toClaimSummary),
105
+ by_agent: claimsByAgent,
106
+ total_active: filteredActiveClaims.length,
107
+ total_expired_unreleased: filteredExpired.length,
108
+ },
109
+ traps: {
110
+ open: openTraps.map(toTrapSummary),
111
+ by_severity: trapsBySeverity,
112
+ total_open: openTraps.length,
113
+ },
114
+ recent_activity: {
115
+ claims_last_24h: claimsLast24h,
116
+ releases_last_24h: releasesLast24h,
117
+ actions_without_claim: actionsWithoutClaim,
118
+ },
119
+ recommendations,
120
+ };
121
+ }
122
+ // ---------------------------------------------------------------------------
123
+ // Markdown renderer
124
+ // ---------------------------------------------------------------------------
125
+ export function renderGovernanceMarkdown(report) {
126
+ const lines = [];
127
+ const ts = report.generated_at.slice(0, 16).replace('T', ' ');
128
+ lines.push(`# Governance Posture Report`);
129
+ lines.push(`Generated: ${ts} UTC`);
130
+ if (report.scope_filter)
131
+ lines.push(`Scope: ${report.scope_filter}`);
132
+ if (report.agent_filter)
133
+ lines.push(`Agent: ${report.agent_filter}`);
134
+ lines.push('');
135
+ // --- Summary ---
136
+ lines.push('## Summary');
137
+ lines.push(`- Active claims: ${report.claims.total_active}`);
138
+ lines.push(`- Expired (unreleased): ${report.claims.total_expired_unreleased}`);
139
+ lines.push(`- Active constraints: ${report.red_lines.total}`);
140
+ lines.push(`- Open traps: ${report.traps.total_open}`);
141
+ lines.push(`- Global instructions: ${report.constitution.total}`);
142
+ lines.push(`- Claims (24h): ${report.recent_activity.claims_last_24h} created, ${report.recent_activity.releases_last_24h} released`);
143
+ if (report.recent_activity.actions_without_claim.length > 0) {
144
+ lines.push(`- **Mutations without claim (24h): ${report.recent_activity.actions_without_claim.length}**`);
145
+ }
146
+ lines.push('');
147
+ // --- Constitution ---
148
+ if (report.constitution.total > 0) {
149
+ lines.push('## Constitution (Global Instructions)');
150
+ for (const ins of report.constitution.global_instructions) {
151
+ lines.push(`- ${ins.text}`);
152
+ }
153
+ lines.push('');
154
+ }
155
+ // --- Red Lines ---
156
+ if (report.red_lines.total > 0) {
157
+ lines.push('## Red Lines (Active Constraints)');
158
+ for (const [category, constraints] of Object.entries(report.red_lines.constraints_by_category)) {
159
+ lines.push(`### ${category} (${constraints.length})`);
160
+ for (const c of constraints) {
161
+ const paths = c.related_paths?.length ? ` — ${c.related_paths.join(', ')}` : '';
162
+ lines.push(`- [${c.id}] ${c.text}${paths}`);
163
+ }
164
+ }
165
+ lines.push('');
166
+ }
167
+ // --- Claims ---
168
+ if (report.claims.total_active > 0) {
169
+ lines.push('## Active Claims');
170
+ const agentEntries = Object.entries(report.claims.by_agent).sort((a, b) => b[1] - a[1]);
171
+ if (agentEntries.length > 1) {
172
+ lines.push(`By agent: ${agentEntries.map(([a, n]) => `${a} (${n})`).join(', ')}`);
173
+ lines.push('');
174
+ }
175
+ for (const c of report.claims.active) {
176
+ const planNote = c.plan_id ? ` [${c.plan_id}]` : '';
177
+ const expiryNote = c.expires_at ? ` (expires ${c.expires_at.slice(0, 16).replace('T', ' ')})` : '';
178
+ lines.push(`- [${c.id}] ${c.agent} → ${c.scope}: ${c.description}${planNote}${expiryNote}`);
179
+ }
180
+ lines.push('');
181
+ }
182
+ if (report.claims.total_expired_unreleased > 0) {
183
+ lines.push('## Expired Claims (Need Release)');
184
+ for (const c of report.claims.expired_unreleased) {
185
+ lines.push(`- [${c.id}] ${c.agent} → ${c.scope}: ${c.description} (expired ${c.expires_at?.slice(0, 16).replace('T', ' ') ?? '?'})`);
186
+ }
187
+ lines.push('');
188
+ }
189
+ // --- Traps ---
190
+ if (report.traps.total_open > 0) {
191
+ lines.push('## Open Traps');
192
+ const sevEntries = Object.entries(report.traps.by_severity);
193
+ if (sevEntries.length > 0) {
194
+ lines.push(`By severity: ${sevEntries.map(([s, n]) => `${s} (${n})`).join(', ')}`);
195
+ lines.push('');
196
+ }
197
+ for (const t of report.traps.open) {
198
+ const paths = t.related_paths?.length ? ` — ${t.related_paths.join(', ')}` : '';
199
+ lines.push(`- [${t.id}] [${t.severity}] ${t.text}${paths}`);
200
+ }
201
+ lines.push('');
202
+ }
203
+ // --- Mutations without claim ---
204
+ if (report.recent_activity.actions_without_claim.length > 0) {
205
+ lines.push('## Mutations Without Claim (Last 24h)');
206
+ for (const e of report.recent_activity.actions_without_claim.slice(0, 20)) {
207
+ const scope = e.scope ? ` → ${e.scope}` : '';
208
+ lines.push(`- ${e.timestamp.slice(0, 16).replace('T', ' ')} [${e.actor}] ${e.action} ${e.item_type ?? ''}${scope}`);
209
+ }
210
+ if (report.recent_activity.actions_without_claim.length > 20) {
211
+ lines.push(` ... and ${report.recent_activity.actions_without_claim.length - 20} more`);
212
+ }
213
+ lines.push('');
214
+ }
215
+ // --- Recommendations ---
216
+ if (report.recommendations.length > 0) {
217
+ lines.push('## Recommendations');
218
+ for (const r of report.recommendations) {
219
+ lines.push(`- ${r}`);
220
+ }
221
+ lines.push('');
222
+ }
223
+ return lines.join('\n');
224
+ }
225
+ // ---------------------------------------------------------------------------
226
+ // Helpers
227
+ // ---------------------------------------------------------------------------
228
+ function toClaimSummary(c) {
229
+ return {
230
+ id: c.id,
231
+ agent: c.agent,
232
+ scope: c.scope,
233
+ description: c.description,
234
+ created_at: c.created_at,
235
+ expires_at: c.expires_at,
236
+ plan_id: c.plan_id,
237
+ };
238
+ }
239
+ function toTrapSummary(t) {
240
+ return {
241
+ id: t.id,
242
+ text: t.text,
243
+ severity: t.severity,
244
+ related_paths: t.related_paths,
245
+ };
246
+ }
247
+ //# sourceMappingURL=governance.js.map
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Shared guard utilities for CLI commands.
3
+ *
4
+ * These replace the duplicated memoryExists + process.exit(1) pattern
5
+ * found across 70+ command files.
6
+ *
7
+ * @module
8
+ */
9
+ import { memoryExists } from './io.js';
10
+ /**
11
+ * Abort the CLI process if .brainclaw/ is not found at the given path.
12
+ */
13
+ export function requireInitialized(cwd) {
14
+ if (!memoryExists(cwd)) {
15
+ console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
16
+ process.exit(1);
17
+ }
18
+ }
19
+ //# sourceMappingURL=guards.js.map
@@ -0,0 +1,72 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { z } from 'zod';
4
+ import { memoryDir } from './io.js';
5
+ export const IdeationRoundSchema = z.object({
6
+ schema_version: z.literal(1),
7
+ thread_id: z.string(),
8
+ round_number: z.number().int().min(0),
9
+ round_type: z.enum(['position', 'reaction', 'convergence']),
10
+ positions: z.array(z.object({
11
+ persona: z.string(),
12
+ agent: z.string(),
13
+ text: z.string(),
14
+ duration_ms: z.number().optional(),
15
+ })),
16
+ tensions: z.array(z.string()).default([]),
17
+ convergences: z.array(z.string()).default([]),
18
+ created_at: z.string(),
19
+ });
20
+ function sanitizeForPath(slug) {
21
+ return slug.replace(/[<>:"/\\|?*]/g, '_');
22
+ }
23
+ export function ideationDir(threadSlug, cwd) {
24
+ return path.join(memoryDir(cwd), 'coordination', 'ideation', sanitizeForPath(threadSlug));
25
+ }
26
+ export function saveIdeationRound(threadSlug, round, cwd) {
27
+ const dir = ideationDir(threadSlug, cwd);
28
+ fs.mkdirSync(dir, { recursive: true });
29
+ const filePath = path.join(dir, `round_${round.round_number}.json`);
30
+ fs.writeFileSync(filePath, JSON.stringify(round, null, 2), 'utf8');
31
+ }
32
+ export function loadIdeationRound(threadSlug, roundNumber, cwd) {
33
+ const filePath = path.join(ideationDir(threadSlug, cwd), `round_${roundNumber}.json`);
34
+ if (!fs.existsSync(filePath)) {
35
+ return undefined;
36
+ }
37
+ try {
38
+ const raw = fs.readFileSync(filePath, 'utf8');
39
+ return IdeationRoundSchema.parse(JSON.parse(raw));
40
+ }
41
+ catch {
42
+ return undefined;
43
+ }
44
+ }
45
+ export function listIdeationThreads(cwd) {
46
+ const base = path.join(memoryDir(cwd), 'coordination', 'ideation');
47
+ if (!fs.existsSync(base))
48
+ return [];
49
+ return fs.readdirSync(base, { withFileTypes: true })
50
+ .filter(e => e.isDirectory())
51
+ .map(e => e.name)
52
+ .sort();
53
+ }
54
+ export function listIdeationRounds(threadSlug, cwd) {
55
+ const dir = ideationDir(threadSlug, cwd);
56
+ if (!fs.existsSync(dir)) {
57
+ return [];
58
+ }
59
+ const rounds = [];
60
+ for (const entry of fs.readdirSync(dir)) {
61
+ const match = /^round_(\d+)\.json$/.exec(entry);
62
+ if (!match) {
63
+ continue;
64
+ }
65
+ const round = loadIdeationRound(threadSlug, Number(match[1]), cwd);
66
+ if (round) {
67
+ rounds.push(round);
68
+ }
69
+ }
70
+ return rounds.sort((a, b) => a.round_number - b.round_number);
71
+ }
72
+ //# sourceMappingURL=ideation.js.map
@@ -76,18 +76,23 @@ export function loadCurrentSession(cwd) {
76
76
  const currentUser = resolveCurrentUser();
77
77
  const currentAgent = resolveCurrentAgentName();
78
78
  // 1. Look in sessions/ directory for a matching session
79
- if (fs.existsSync(dir)) {
79
+ if (fs.existsSync(dir) && currentAgent) {
80
80
  const files = fs.readdirSync(dir).filter(f => f.endsWith('.json'));
81
81
  const ttlMs = parseDurationToMs(loadConfigSafe(cwd)?.implicit_session_ttl ?? '4h');
82
82
  const now = Date.now();
83
83
  for (const file of files) {
84
84
  try {
85
- const session = CurrentSessionStateSchema.parse(loadVersionedJsonFile('current_session', path.join(dir, file)).document);
86
- // Match by agent+user (or agent only if user not set in old sessions)
85
+ const migration = loadVersionedJsonFile('current_session', path.join(dir, file));
86
+ const session = {
87
+ ...CurrentSessionStateSchema.parse(migration.document),
88
+ schema_version: migration.metadata.currentVersion,
89
+ };
90
+ // Strict match: agent name must match, user must match (when both are known)
91
+ if (session.agent !== currentAgent)
92
+ continue;
87
93
  const userMatch = !session.user || !currentUser || session.user === currentUser;
88
- const agentMatch = !currentAgent || session.agent === currentAgent;
89
94
  const alive = (now - Date.parse(session.last_seen_at)) <= ttlMs;
90
- if (userMatch && agentMatch && alive) {
95
+ if (userMatch && alive) {
91
96
  return session;
92
97
  }
93
98
  }
@@ -100,7 +105,11 @@ export function loadCurrentSession(cwd) {
100
105
  const legacyPath = path.join(memoryDir(cwd), LEGACY_SESSION_FILE);
101
106
  if (fs.existsSync(legacyPath)) {
102
107
  try {
103
- return CurrentSessionStateSchema.parse(loadVersionedJsonFile('current_session', legacyPath).document);
108
+ const migration = loadVersionedJsonFile('current_session', legacyPath);
109
+ return {
110
+ ...CurrentSessionStateSchema.parse(migration.document),
111
+ schema_version: migration.metadata.currentVersion,
112
+ };
104
113
  }
105
114
  catch {
106
115
  return undefined;
@@ -116,7 +125,11 @@ export function loadSessionById(sessionId, cwd) {
116
125
  if (!fs.existsSync(filepath))
117
126
  return undefined;
118
127
  try {
119
- return CurrentSessionStateSchema.parse(loadVersionedJsonFile('current_session', filepath).document);
128
+ const migration = loadVersionedJsonFile('current_session', filepath);
129
+ return {
130
+ ...CurrentSessionStateSchema.parse(migration.document),
131
+ schema_version: migration.metadata.currentVersion,
132
+ };
120
133
  }
121
134
  catch {
122
135
  return undefined;
@@ -133,7 +146,11 @@ export function loadAllSessions(cwd) {
133
146
  const sessions = [];
134
147
  for (const file of files) {
135
148
  try {
136
- sessions.push(CurrentSessionStateSchema.parse(loadVersionedJsonFile('current_session', path.join(dir, file)).document));
149
+ const migration = loadVersionedJsonFile('current_session', path.join(dir, file));
150
+ sessions.push({
151
+ ...CurrentSessionStateSchema.parse(migration.document),
152
+ schema_version: migration.metadata.currentVersion,
153
+ });
137
154
  }
138
155
  catch {
139
156
  // skip invalid
@@ -195,7 +212,11 @@ export function gcStaleSessions(cwd, ttlOverride) {
195
212
  const files = fs.readdirSync(dir).filter(f => f.endsWith('.json'));
196
213
  for (const file of files) {
197
214
  try {
198
- const session = CurrentSessionStateSchema.parse(loadVersionedJsonFile('current_session', path.join(dir, file)).document);
215
+ const migration = loadVersionedJsonFile('current_session', path.join(dir, file));
216
+ const session = {
217
+ ...CurrentSessionStateSchema.parse(migration.document),
218
+ schema_version: migration.metadata.currentVersion,
219
+ };
199
220
  if (now - Date.parse(session.last_seen_at) > ttlMs) {
200
221
  fs.unlinkSync(path.join(dir, file));
201
222
  removed++;
@@ -223,7 +244,11 @@ function resolveCurrentUser() {
223
244
  return process.env.USER || process.env.USERNAME || os.userInfo().username || undefined;
224
245
  }
225
246
  function resolveCurrentAgentName() {
226
- return process.env.BRAINCLAW_AGENT_NAME || process.env.CLAUDE_CODE_VERSION ? 'claude-code' : undefined;
247
+ if (process.env.BRAINCLAW_AGENT_NAME)
248
+ return process.env.BRAINCLAW_AGENT_NAME;
249
+ if (process.env.CLAUDE_CODE_VERSION)
250
+ return 'claude-code';
251
+ return undefined;
227
252
  }
228
253
  function loadConfigSafe(cwd) {
229
254
  try {
@@ -233,29 +258,90 @@ function loadConfigSafe(cwd) {
233
258
  return undefined;
234
259
  }
235
260
  }
261
+ function isPidAlive(pid) {
262
+ try {
263
+ process.kill(pid, 0);
264
+ return true;
265
+ }
266
+ catch {
267
+ return false;
268
+ }
269
+ }
270
+ /**
271
+ * Find the session matching the current process among all active sessions.
272
+ *
273
+ * Resolution order:
274
+ * 1. Preferred session ID (explicit env var / parameter) → exact match
275
+ * 2. Same agent + user + host + same PID → refresh (same process reconnecting)
276
+ * 3. Same agent + user + host + dead PID → reclaim stale session
277
+ * 4. No match → create new session
278
+ *
279
+ * Crucially, if another session exists for the same agent+user+host but with
280
+ * a LIVE different PID, it is left untouched — that's a parallel instance.
281
+ */
236
282
  function resolveImplicitSession(cwd, options) {
237
- const current = loadCurrentSession(cwd);
238
283
  const persistImplicit = options.persistImplicit ?? true;
239
284
  const ttlMs = parseDurationToMs(loadConfigSafe(cwd)?.implicit_session_ttl ?? '4h');
240
285
  const now = new Date();
241
286
  const currentUser = resolveCurrentUser();
242
- if (current
243
- && current.agent === options.agentName
244
- && current.agent_id === options.agentId
245
- && current.host_id === options.hostId
246
- && (!current.user || !currentUser || current.user === currentUser)
247
- && now.getTime() - Date.parse(current.last_seen_at) <= ttlMs) {
287
+ const currentPid = process.pid;
288
+ // 1. If a preferred session ID is given, try exact match first
289
+ if (options.preferredSessionId) {
290
+ const exact = loadSessionById(options.preferredSessionId, cwd);
291
+ if (exact && now.getTime() - Date.parse(exact.last_seen_at) <= ttlMs) {
292
+ const refreshed = {
293
+ ...exact,
294
+ last_seen_at: now.toISOString(),
295
+ user: exact.user || currentUser,
296
+ pid: currentPid,
297
+ };
298
+ if (persistImplicit)
299
+ saveCurrentSession(refreshed, cwd);
300
+ return refreshed;
301
+ }
302
+ }
303
+ // 2. Scan all sessions for PID-aware matching
304
+ const allSessions = loadAllSessions(cwd);
305
+ let samePidSession;
306
+ let deadPidSession;
307
+ for (const session of allSessions) {
308
+ if (session.agent !== options.agentName)
309
+ continue;
310
+ if (session.agent_id !== options.agentId)
311
+ continue;
312
+ if (session.host_id !== options.hostId)
313
+ continue;
314
+ if (currentUser && session.user && session.user !== currentUser)
315
+ continue;
316
+ if (now.getTime() - Date.parse(session.last_seen_at) > ttlMs)
317
+ continue;
318
+ // Same PID = same process reconnecting (e.g. MCP server refreshing)
319
+ if (session.pid === currentPid) {
320
+ samePidSession = session;
321
+ break;
322
+ }
323
+ // Different PID but alive = parallel instance, do NOT reclaim
324
+ if (session.pid && isPidAlive(session.pid)) {
325
+ continue;
326
+ }
327
+ // Dead PID = stale session, candidate for reclaim
328
+ if (!deadPidSession) {
329
+ deadPidSession = session;
330
+ }
331
+ }
332
+ const toRefresh = samePidSession ?? deadPidSession;
333
+ if (toRefresh) {
248
334
  const refreshed = {
249
- ...current,
335
+ ...toRefresh,
250
336
  last_seen_at: now.toISOString(),
251
- user: current.user || currentUser,
252
- pid: process.pid,
337
+ user: toRefresh.user || currentUser,
338
+ pid: currentPid,
253
339
  };
254
- if (persistImplicit) {
340
+ if (persistImplicit)
255
341
  saveCurrentSession(refreshed, cwd);
256
- }
257
342
  return refreshed;
258
343
  }
344
+ // 3. No match — create new session
259
345
  const created = {
260
346
  session_id: options.preferredSessionId ?? generateImplicitSessionId(),
261
347
  started_at: now.toISOString(),
@@ -264,11 +350,10 @@ function resolveImplicitSession(cwd, options) {
264
350
  agent_id: options.agentId,
265
351
  host_id: options.hostId,
266
352
  user: currentUser,
267
- pid: process.pid,
353
+ pid: currentPid,
268
354
  };
269
- if (persistImplicit) {
355
+ if (persistImplicit)
270
356
  saveCurrentSession(created, cwd);
271
- }
272
357
  return created;
273
358
  }
274
359
  function parseDurationToMs(value) {
package/dist/core/ids.js CHANGED
@@ -10,8 +10,14 @@ const PREFIXES = {
10
10
  open_handoffs: 'hnd',
11
11
  plan_items: 'pln',
12
12
  plan_steps: 'stp',
13
+ sequences: 'seq',
13
14
  instruction_entries: 'ins',
14
15
  ai_surface_tasks: 'ast',
16
+ inbox_messages: 'msg',
17
+ assignments: 'asgn',
18
+ runs: 'run',
19
+ actions: 'act',
20
+ runtime_events: 'evt',
15
21
  };
16
22
  const ID_COUNTER_FILE = '.id-counter.json';
17
23
  function counterPath(cwd) {
@@ -11,7 +11,7 @@ export const TtlSchema = z
11
11
  .string()
12
12
  .regex(TTL_PATTERN, 'Invalid TTL format. Use <number><unit> where unit is m (minutes), h (hours), or d (days). Example: 30m, 2h, 7d');
13
13
  function formatZodError(error, field) {
14
- return error.errors.map((e) => ({ field, message: e.message }));
14
+ return error.issues.map((e) => ({ field, message: e.message }));
15
15
  }
16
16
  /**
17
17
  * Validate CLI mutation inputs. On failure: prints error and calls process.exit(1).
@@ -41,7 +41,7 @@ export function validateCliInput(text, tags) {
41
41
  export function validateCliTtl(ttl) {
42
42
  const result = TtlSchema.safeParse(ttl);
43
43
  if (!result.success) {
44
- console.error(`Error: ${result.error.errors[0]?.message ?? 'Invalid TTL'}`);
44
+ console.error(`Error: ${result.error.issues[0]?.message ?? 'Invalid TTL'}`);
45
45
  process.exit(1);
46
46
  }
47
47
  }