brainclaw 0.29.2 → 1.5.4

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 (197) hide show
  1. package/LICENSE +21 -74
  2. package/README.md +199 -176
  3. package/dist/brainclaw-vscode.vsix +0 -0
  4. package/dist/cli.js +710 -25
  5. package/dist/commands/accept.js +3 -0
  6. package/dist/commands/add-step.js +11 -26
  7. package/dist/commands/agent-board.js +70 -3
  8. package/dist/commands/audit.js +19 -0
  9. package/dist/commands/check-policy.js +54 -0
  10. package/dist/commands/check-security-mcp.js +145 -0
  11. package/dist/commands/check-security.js +106 -0
  12. package/dist/commands/claim-resource.js +1 -0
  13. package/dist/commands/codev.js +672 -0
  14. package/dist/commands/compact.js +74 -0
  15. package/dist/commands/complete-step.js +16 -26
  16. package/dist/commands/constraint.js +8 -20
  17. package/dist/commands/decision.js +9 -20
  18. package/dist/commands/delete-plan.js +10 -12
  19. package/dist/commands/delete-step.js +16 -0
  20. package/dist/commands/dispatch.js +163 -0
  21. package/dist/commands/doctor.js +1122 -49
  22. package/dist/commands/enable-agent.js +1 -0
  23. package/dist/commands/export.js +280 -22
  24. package/dist/commands/handoff.js +33 -0
  25. package/dist/commands/harvest.js +189 -0
  26. package/dist/commands/hooks.js +82 -25
  27. package/dist/commands/inbox.js +169 -0
  28. package/dist/commands/init.js +38 -31
  29. package/dist/commands/install-hooks.js +71 -44
  30. package/dist/commands/link.js +89 -0
  31. package/dist/commands/list-claims.js +48 -3
  32. package/dist/commands/list-plans.js +129 -25
  33. package/dist/commands/loops-handlers.js +409 -0
  34. package/dist/commands/mcp-read-handlers.js +1628 -0
  35. package/dist/commands/mcp-schemas.generated.js +269 -0
  36. package/dist/commands/mcp.js +4224 -1501
  37. package/dist/commands/plan-resource.js +64 -0
  38. package/dist/commands/plan.js +12 -26
  39. package/dist/commands/prune.js +37 -2
  40. package/dist/commands/reflect.js +20 -7
  41. package/dist/commands/release-claim.js +11 -6
  42. package/dist/commands/release-notes.js +170 -0
  43. package/dist/commands/repair.js +210 -0
  44. package/dist/commands/run-profile.js +57 -0
  45. package/dist/commands/sequence.js +113 -0
  46. package/dist/commands/session-end.js +423 -14
  47. package/dist/commands/session-start.js +214 -41
  48. package/dist/commands/setup-security.js +103 -0
  49. package/dist/commands/setup.js +42 -4
  50. package/dist/commands/stale.js +109 -0
  51. package/dist/commands/switch.js +100 -2
  52. package/dist/commands/trap.js +14 -31
  53. package/dist/commands/update-handoff.js +63 -4
  54. package/dist/commands/update-plan.js +21 -28
  55. package/dist/commands/update-step.js +37 -0
  56. package/dist/commands/upgrade.js +313 -6
  57. package/dist/commands/usage.js +102 -0
  58. package/dist/commands/version.js +20 -0
  59. package/dist/commands/who.js +33 -5
  60. package/dist/commands/worktree.js +105 -0
  61. package/dist/core/actions.js +315 -0
  62. package/dist/core/agent-capability.js +610 -17
  63. package/dist/core/agent-context.js +7 -1
  64. package/dist/core/agent-files.js +1169 -85
  65. package/dist/core/agent-integrations.js +160 -5
  66. package/dist/core/agent-inventory.js +2 -0
  67. package/dist/core/agent-profiles.js +93 -0
  68. package/dist/core/agent-registry.js +162 -30
  69. package/dist/core/agentrun-reconciler.js +345 -0
  70. package/dist/core/agentruns.js +424 -0
  71. package/dist/core/ai-agent-detection.js +31 -10
  72. package/dist/core/archival.js +77 -0
  73. package/dist/core/assignment-sweeper.js +82 -0
  74. package/dist/core/assignments.js +367 -0
  75. package/dist/core/audit.js +30 -0
  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 +381 -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/events.js +106 -2
  98. package/dist/core/execution-adapters.js +154 -0
  99. package/dist/core/execution-context.js +63 -0
  100. package/dist/core/execution-profile.js +270 -0
  101. package/dist/core/execution.js +255 -0
  102. package/dist/core/facade-schema.js +81 -0
  103. package/dist/core/federation-cloud.js +99 -0
  104. package/dist/core/federation-message.js +52 -0
  105. package/dist/core/federation-transport.js +65 -0
  106. package/dist/core/gc-semantic.js +482 -0
  107. package/dist/core/governance.js +247 -0
  108. package/dist/core/guards.js +19 -0
  109. package/dist/core/ideation.js +72 -0
  110. package/dist/core/identity.js +110 -25
  111. package/dist/core/ids.js +6 -0
  112. package/dist/core/input-validation.js +2 -2
  113. package/dist/core/instruction-templates.js +344 -136
  114. package/dist/core/io.js +90 -11
  115. package/dist/core/lock.js +6 -2
  116. package/dist/core/loops/brief-assembly.js +213 -0
  117. package/dist/core/loops/facade-schema.js +148 -0
  118. package/dist/core/loops/index.js +7 -0
  119. package/dist/core/loops/iteration-engine.js +139 -0
  120. package/dist/core/loops/lock.js +385 -0
  121. package/dist/core/loops/store.js +201 -0
  122. package/dist/core/loops/types.js +403 -0
  123. package/dist/core/loops/verbs.js +534 -0
  124. package/dist/core/markdown.js +15 -3
  125. package/dist/core/memory-compactor.js +432 -0
  126. package/dist/core/memory-git.js +152 -8
  127. package/dist/core/messaging.js +278 -0
  128. package/dist/core/migration.js +32 -1
  129. package/dist/core/mutation-pipeline.js +4 -2
  130. package/dist/core/operations/memory-mutation.js +129 -0
  131. package/dist/core/operations/memory-write.js +78 -0
  132. package/dist/core/operations/plan.js +190 -0
  133. package/dist/core/policy.js +169 -0
  134. package/dist/core/reputation.js +9 -3
  135. package/dist/core/schema.js +491 -6
  136. package/dist/core/search.js +21 -2
  137. package/dist/core/security-cache.js +71 -0
  138. package/dist/core/security-guard.js +152 -0
  139. package/dist/core/security-scoring.js +86 -0
  140. package/dist/core/sequence.js +130 -0
  141. package/dist/core/socket-client.js +113 -0
  142. package/dist/core/staleness.js +246 -0
  143. package/dist/core/state.js +98 -22
  144. package/dist/core/store-resolution.js +43 -11
  145. package/dist/core/toml-writer.js +76 -0
  146. package/dist/core/upgrades/backup.js +232 -0
  147. package/dist/core/upgrades/health-check.js +169 -0
  148. package/dist/core/upgrades/patches/candidate-archive.js +145 -0
  149. package/dist/core/upgrades/patches/handoff-review-strip.js +128 -0
  150. package/dist/core/upgrades/patches/provenance-rollout.js +136 -0
  151. package/dist/core/upgrades/schema-version.js +97 -0
  152. package/dist/core/worktree.js +606 -0
  153. package/dist/facts.js +114 -0
  154. package/dist/facts.json +111 -0
  155. package/docs/architecture/project-refs.md +5 -1
  156. package/docs/cli.md +690 -43
  157. package/docs/concepts/ideation-loop.md +317 -0
  158. package/docs/concepts/loop-engine.md +456 -0
  159. package/docs/concepts/mcp-governance.md +268 -0
  160. package/docs/concepts/memory-staleness.md +122 -0
  161. package/docs/concepts/multi-agent-workflows.md +166 -0
  162. package/docs/concepts/plans-and-claims.md +31 -6
  163. package/docs/concepts/project-md-convention.md +35 -0
  164. package/docs/concepts/troubleshooting.md +220 -0
  165. package/docs/concepts/upgrade-cli.md +202 -0
  166. package/docs/concepts/upgrade-dogfood-procedure.md +114 -0
  167. package/docs/context-format-changelog.md +2 -2
  168. package/docs/context-format.md +2 -2
  169. package/docs/index.md +68 -0
  170. package/docs/integrations/agents.md +15 -16
  171. package/docs/integrations/cline.md +88 -0
  172. package/docs/integrations/codex.md +75 -23
  173. package/docs/integrations/continue.md +60 -0
  174. package/docs/integrations/copilot.md +67 -9
  175. package/docs/integrations/kilocode.md +72 -0
  176. package/docs/integrations/mcp.md +304 -21
  177. package/docs/integrations/mistral-vibe.md +122 -0
  178. package/docs/integrations/opencode.md +84 -0
  179. package/docs/integrations/overview.md +23 -8
  180. package/docs/integrations/roo.md +74 -0
  181. package/docs/integrations/windsurf.md +83 -0
  182. package/docs/mcp-schema-changelog.md +191 -1
  183. package/docs/playbooks/integration/index.md +121 -0
  184. package/docs/playbooks/productivity/index.md +102 -0
  185. package/docs/playbooks/team/index.md +122 -0
  186. package/docs/product/agent-first-model.md +184 -0
  187. package/docs/product/entity-model-audit.md +462 -0
  188. package/docs/product/positioning.md +10 -10
  189. package/docs/quickstart-existing-project.md +135 -0
  190. package/docs/quickstart.md +124 -37
  191. package/docs/release-maintenance.md +79 -0
  192. package/docs/review.md +2 -0
  193. package/docs/server-operations.md +118 -0
  194. package/package.json +21 -13
  195. package/dist/commands/claude-desktop-extension.js +0 -18
  196. package/dist/commands/diff.js +0 -99
  197. package/dist/core/claude-desktop-extension.js +0 -224
@@ -0,0 +1,344 @@
1
+ /**
2
+ * Entity Registry — single source of truth for the canonical CRUD surface.
3
+ *
4
+ * Phase 3 slice 3a (pln_c6472192). Consumed by bclaw_find / get / create /
5
+ * update / remove / transition (slice 3b). Grammar-consistency tests
6
+ * live in tests/unit/entity-registry.test.ts.
7
+ *
8
+ * Design principles (per entity-model-audit §6 P6.2):
9
+ * - Declarative: transition matrix + side-effect tags as data.
10
+ * - Imperative side effects (the code that actually releases claims,
11
+ * promotes candidates, emits events) stays in `src/core/operations/*`
12
+ * and `src/core/<entity>.ts` — the registry references them by name
13
+ * but does not dispatch.
14
+ * - Short-label prefixes match the authoritative map in `src/core/ids.ts`
15
+ * (a consistency test pins this).
16
+ * - `updatable` is the subset of persisted fields `bclaw_update` may
17
+ * patch. Non-listed fields are set at create time or via a
18
+ * transition — never by plain update.
19
+ */
20
+ import { CandidateSchema, ClaimSchema, ConstraintSchema, CrossProjectLinkSchema, CurrentSessionStateSchema, DecisionSchema, HandoffSchema, InboxMessageSchema, InstructionEntrySchema, PlanItemSchema, PlanStepSchema, RuntimeNoteSchema, SequenceSchema, TrapSchema, AssignmentSchema, AgentRunSchema, ActionRequiredSchema, } from './schema.js';
21
+ /** Canonical plan lifecycle. */
22
+ const plan = {
23
+ name: 'plan',
24
+ shortLabelPrefix: 'pln',
25
+ schema: PlanItemSchema,
26
+ updatable: ['text', 'priority', 'tags', 'assignee', 'estimated_effort', 'actual_effort', 'depends_on'],
27
+ statusField: 'status',
28
+ transitions: {
29
+ todo: ['in_progress', 'blocked', 'done', 'dropped'],
30
+ in_progress: ['blocked', 'done', 'dropped'],
31
+ blocked: ['in_progress', 'dropped'],
32
+ },
33
+ terminal: ['done', 'dropped'],
34
+ sideEffects: {
35
+ 'in_progress->done': ['audit:plan_done', 'cascade:release_linked_claims_if_last'],
36
+ 'todo->done': ['audit:plan_done', 'cascade:release_linked_claims_if_last'],
37
+ 'todo->dropped': ['audit:plan_dropped'],
38
+ 'in_progress->dropped': ['audit:plan_dropped'],
39
+ 'blocked->dropped': ['audit:plan_dropped'],
40
+ },
41
+ };
42
+ /** Plan steps — finer-grained lifecycle with a testing stage. */
43
+ const step = {
44
+ name: 'step',
45
+ shortLabelPrefix: 'stp',
46
+ schema: PlanStepSchema,
47
+ updatable: ['text', 'assignee'],
48
+ statusField: 'status',
49
+ transitions: {
50
+ todo: ['in_progress', 'blocked', 'done'],
51
+ in_progress: ['testing', 'done', 'blocked'],
52
+ testing: ['done', 'blocked', 'in_progress'],
53
+ blocked: ['in_progress', 'todo'],
54
+ },
55
+ terminal: ['done'],
56
+ sideEffects: {
57
+ 'testing->done': ['audit:step_done', 'cascade:auto_complete_plan_if_last_step'],
58
+ 'in_progress->done': ['audit:step_done', 'cascade:auto_complete_plan_if_last_step'],
59
+ },
60
+ };
61
+ /** Claim — file-level advisory lock. */
62
+ const claim = {
63
+ name: 'claim',
64
+ shortLabelPrefix: 'clm',
65
+ schema: ClaimSchema,
66
+ updatable: ['description'],
67
+ statusField: 'status',
68
+ transitions: {
69
+ active: ['released', 'stale'],
70
+ },
71
+ terminal: ['released', 'stale'],
72
+ sideEffects: {
73
+ 'active->released': ['audit:claim_released', 'cascade:plan_status_if_last_claim'],
74
+ 'active->stale': ['audit:claim_stale', 'heartbeat:expired'],
75
+ },
76
+ };
77
+ /** Session — execution envelope. Status tracked via timestamps, not an enum. */
78
+ const session = {
79
+ name: 'session',
80
+ shortLabelPrefix: 'sess',
81
+ schema: CurrentSessionStateSchema,
82
+ updatable: ['last_seen_at'],
83
+ transitions: {},
84
+ terminal: [],
85
+ sideEffects: {},
86
+ };
87
+ /** Handoff — session-end artefact. Downscale in 3e: no more review sub-flow. */
88
+ const handoff = {
89
+ name: 'handoff',
90
+ shortLabelPrefix: 'hnd',
91
+ schema: HandoffSchema,
92
+ updatable: ['narrative', 'tags'],
93
+ statusField: 'status',
94
+ transitions: {
95
+ open: ['accepted', 'closed'],
96
+ accepted: ['closed'],
97
+ },
98
+ terminal: ['closed'],
99
+ sideEffects: {
100
+ 'open->closed': ['audit:handoff_closed'],
101
+ 'open->accepted': ['audit:handoff_accepted'],
102
+ 'accepted->closed': ['audit:handoff_closed'],
103
+ },
104
+ };
105
+ /** Decision — memory record with a retrospective outcome field (not a lifecycle status). */
106
+ const decision = {
107
+ name: 'decision',
108
+ shortLabelPrefix: 'dec',
109
+ schema: DecisionSchema,
110
+ updatable: ['text', 'tags', 'outcome', 'scope', 'related_paths'],
111
+ statusField: 'outcome',
112
+ transitions: {
113
+ pending: ['approved', 'rejected', 'deferred'],
114
+ deferred: ['approved', 'rejected'],
115
+ },
116
+ terminal: ['approved', 'rejected'],
117
+ sideEffects: {},
118
+ };
119
+ /** Constraint — enforced rule. */
120
+ const constraint = {
121
+ name: 'constraint',
122
+ shortLabelPrefix: 'cst',
123
+ schema: ConstraintSchema,
124
+ updatable: ['text', 'tags', 'category', 'scope', 'related_paths', 'expires_at'],
125
+ statusField: 'status',
126
+ transitions: {
127
+ active: ['resolved', 'expired'],
128
+ },
129
+ terminal: ['resolved', 'expired'],
130
+ sideEffects: {
131
+ 'active->resolved': ['audit:constraint_resolved'],
132
+ 'active->expired': ['audit:constraint_expired'],
133
+ },
134
+ };
135
+ /** Trap — environmental pitfall. */
136
+ const trap = {
137
+ name: 'trap',
138
+ shortLabelPrefix: 'trp',
139
+ schema: TrapSchema,
140
+ updatable: ['text', 'tags', 'severity', 'scope', 'related_paths', 'expires_at', 'platform_scope'],
141
+ statusField: 'status',
142
+ transitions: {
143
+ active: ['resolved', 'expired'],
144
+ },
145
+ terminal: ['resolved', 'expired'],
146
+ sideEffects: {
147
+ 'active->resolved': ['audit:trap_resolved'],
148
+ 'active->expired': ['audit:trap_expired'],
149
+ },
150
+ };
151
+ /**
152
+ * Candidate — review queue entity. Slated for removal in v1.0: new code
153
+ * should prefer direct memory writes + read-time confidence filtering.
154
+ * Kept in the registry so that legacy accept/reject flows still work.
155
+ */
156
+ const candidate = {
157
+ name: 'candidate',
158
+ shortLabelPrefix: 'cnd',
159
+ schema: CandidateSchema,
160
+ updatable: ['text', 'tags', 'severity', 'narrative', 'related_paths', 'plan_id'],
161
+ statusField: 'status',
162
+ transitions: {
163
+ pending: ['accepted', 'rejected'],
164
+ },
165
+ terminal: ['accepted', 'rejected'],
166
+ sideEffects: {
167
+ 'pending->accepted': ['audit:candidate_accepted', 'promote:to_decision_or_constraint_or_trap_or_handoff'],
168
+ 'pending->rejected': ['audit:candidate_rejected'],
169
+ },
170
+ };
171
+ /** Runtime note — observation record, no lifecycle. */
172
+ const runtime_note = {
173
+ name: 'runtime_note',
174
+ shortLabelPrefix: 'rtn',
175
+ schema: RuntimeNoteSchema,
176
+ updatable: ['text', 'tags', 'visibility', 'expires_at'],
177
+ transitions: {},
178
+ terminal: [],
179
+ sideEffects: {},
180
+ };
181
+ /** Sequence — lane-based execution coordinator across plans/steps. */
182
+ const sequence = {
183
+ name: 'sequence',
184
+ shortLabelPrefix: 'seq',
185
+ schema: SequenceSchema,
186
+ updatable: ['name', 'description', 'tags', 'items', 'owner'],
187
+ statusField: 'status',
188
+ transitions: {
189
+ draft: ['active', 'archived'],
190
+ active: ['archived'],
191
+ },
192
+ terminal: ['archived'],
193
+ sideEffects: {
194
+ 'draft->active': ['audit:sequence_activated'],
195
+ 'active->archived': ['audit:sequence_archived'],
196
+ },
197
+ };
198
+ /** Inbox message — dispatch brief, review request, or signal. */
199
+ const inbox_message = {
200
+ name: 'inbox_message',
201
+ shortLabelPrefix: 'msg',
202
+ schema: InboxMessageSchema,
203
+ updatable: [],
204
+ statusField: 'status',
205
+ transitions: {
206
+ pending: ['read', 'acknowledged', 'archived'],
207
+ read: ['acknowledged', 'archived'],
208
+ acknowledged: ['archived'],
209
+ },
210
+ terminal: ['archived'],
211
+ sideEffects: {
212
+ 'pending->read': ['timestamp:read_at'],
213
+ 'pending->acknowledged': ['timestamp:read_at', 'timestamp:ack_at'],
214
+ 'read->acknowledged': ['timestamp:ack_at'],
215
+ },
216
+ };
217
+ /** Instruction — rule layer (global/project/agent). No lifecycle status. */
218
+ const instruction = {
219
+ name: 'instruction',
220
+ shortLabelPrefix: 'ins',
221
+ schema: InstructionEntrySchema,
222
+ updatable: ['text', 'tags', 'active'],
223
+ transitions: {},
224
+ terminal: [],
225
+ sideEffects: {},
226
+ };
227
+ /**
228
+ * Assignment — work delegation record. Its transition matrix is rich and
229
+ * owned by `src/core/assignments.ts` (transitionAssignment + VALID). The
230
+ * registry mirrors that matrix so the canonical CRUD verbs see a unified
231
+ * surface; the imperative transition code stays where it is.
232
+ */
233
+ const assignment = {
234
+ name: 'assignment',
235
+ shortLabelPrefix: 'asgn',
236
+ schema: AssignmentSchema,
237
+ updatable: ['brief', 'tags'],
238
+ statusField: 'status',
239
+ transitions: {
240
+ created: ['offered', 'expired'],
241
+ offered: ['accepted', 'expired', 'rerouted'],
242
+ accepted: ['started', 'failed', 'timed_out'],
243
+ started: ['completed', 'failed', 'blocked', 'timed_out'],
244
+ failed: ['retrying'],
245
+ timed_out: ['retrying'],
246
+ retrying: ['offered'],
247
+ blocked: ['started', 'rerouted', 'failed'],
248
+ },
249
+ terminal: ['completed', 'expired', 'rerouted'],
250
+ sideEffects: {
251
+ 'created->offered': ['timestamp:offered_at', 'event:assignment_offered', 'audit:assignment_offered'],
252
+ 'offered->accepted': ['timestamp:accepted_at', 'event:assignment_accepted', 'sync:agent_run'],
253
+ 'accepted->started': ['timestamp:started_at', 'event:assignment_started', 'sync:agent_run'],
254
+ 'started->completed': ['timestamp:completed_at', 'event:assignment_completed', 'sync:agent_run'],
255
+ 'started->failed': ['timestamp:failed_at', 'event:assignment_failed', 'sync:agent_run'],
256
+ },
257
+ };
258
+ /** Agent run — per-attempt execution record paired with an assignment. */
259
+ const agent_run = {
260
+ name: 'agent_run',
261
+ shortLabelPrefix: 'run',
262
+ schema: AgentRunSchema,
263
+ updatable: ['output_summary', 'error_summary'],
264
+ statusField: 'status',
265
+ transitions: {
266
+ created: ['launching', 'waiting_input', 'running', 'cancelled', 'interrupted'],
267
+ launching: ['waiting_input', 'running', 'failed', 'cancelled', 'timed_out', 'interrupted'],
268
+ waiting_input: ['running', 'blocked', 'failed', 'cancelled', 'timed_out', 'interrupted'],
269
+ running: ['waiting_input', 'blocked', 'completed', 'failed', 'cancelled', 'timed_out', 'interrupted'],
270
+ blocked: ['waiting_input', 'running', 'failed', 'cancelled', 'timed_out', 'interrupted'],
271
+ },
272
+ terminal: ['completed', 'failed', 'cancelled', 'timed_out', 'interrupted'],
273
+ sideEffects: {
274
+ 'running->completed': ['timestamp:completed_at', 'event:agent_run_completed'],
275
+ 'running->failed': ['timestamp:completed_at', 'event:agent_run_failed'],
276
+ },
277
+ };
278
+ /** Action — required follow-up item. */
279
+ const action = {
280
+ name: 'action',
281
+ shortLabelPrefix: 'act',
282
+ schema: ActionRequiredSchema,
283
+ updatable: ['description', 'priority'],
284
+ statusField: 'status',
285
+ transitions: {
286
+ open: ['in_progress', 'completed', 'dismissed'],
287
+ in_progress: ['completed', 'dismissed'],
288
+ },
289
+ terminal: ['completed', 'dismissed'],
290
+ sideEffects: {
291
+ 'open->completed': ['audit:action_completed'],
292
+ 'in_progress->completed': ['audit:action_completed'],
293
+ },
294
+ };
295
+ /**
296
+ * Cross-project link — federation peer entry stored in config.yaml
297
+ * (cross_project_links). Stateless: identified by `name`, no lifecycle.
298
+ * Storage is YAML-in-config rather than a per-entity directory, so the
299
+ * `bclaw_create/find/remove` ops route through `src/core/cross-project.ts`
300
+ * helpers (addCrossProjectLink / resolveCrossProjectLinks /
301
+ * removeCrossProjectLink).
302
+ */
303
+ const cross_project_link = {
304
+ name: 'cross_project_link',
305
+ shortLabelPrefix: 'xpl',
306
+ schema: CrossProjectLinkSchema,
307
+ updatable: ['name', 'role', 'channels'],
308
+ transitions: {},
309
+ terminal: [],
310
+ sideEffects: {},
311
+ };
312
+ export const ENTITY_REGISTRY = {
313
+ plan, step, claim, session, handoff,
314
+ decision, constraint, trap, candidate, runtime_note, sequence,
315
+ inbox_message, instruction,
316
+ assignment, agent_run, action,
317
+ cross_project_link,
318
+ };
319
+ export const ENTITY_NAMES = Object.keys(ENTITY_REGISTRY);
320
+ /** Look up an entity spec by name. Throws if the name is unknown. */
321
+ export function getEntitySpec(name) {
322
+ const spec = ENTITY_REGISTRY[name];
323
+ if (!spec)
324
+ throw new Error(`Unknown entity: ${name}`);
325
+ return spec;
326
+ }
327
+ /**
328
+ * Check if a transition is valid for the given entity.
329
+ * Stateless entities (no statusField) always reject transitions.
330
+ */
331
+ export function isValidTransition(name, from, to) {
332
+ const spec = getEntitySpec(name);
333
+ if (!spec.statusField)
334
+ return false;
335
+ if (spec.terminal.includes(from))
336
+ return false;
337
+ const allowed = spec.transitions[from];
338
+ return allowed ? allowed.includes(to) : false;
339
+ }
340
+ /** Canonical transition key for sideEffects lookup: `from->to`. */
341
+ export function transitionKey(from, to) {
342
+ return `${from}->${to}`;
343
+ }
344
+ //# sourceMappingURL=entity-registry.js.map
@@ -2,11 +2,22 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { resolveEventSessionId } from './identity.js';
4
4
  import { RuntimeEventSchema } from './schema.js';
5
- import { readFileSync, resolveEntityDir } from './io.js';
5
+ import { readFileSync, resolveEntityDir, writeFileAtomic } from './io.js';
6
6
  import { logger } from './logger.js';
7
+ import { mutate } from './mutation-pipeline.js';
8
+ import { generateId, nowISO } from './ids.js';
7
9
  function runtimeDir(cwd) {
8
10
  return resolveEntityDir('runtime', cwd ?? process.cwd(), 'read');
9
11
  }
12
+ function runtimeEventsDir(cwd, mode = 'read') {
13
+ return path.join(resolveEntityDir('runtime', cwd ?? process.cwd(), mode), 'agent-runtime');
14
+ }
15
+ function ensureRuntimeEventsDir(cwd) {
16
+ const dir = runtimeEventsDir(cwd, 'write');
17
+ if (!fs.existsSync(dir)) {
18
+ fs.mkdirSync(dir, { recursive: true });
19
+ }
20
+ }
10
21
  function collectJsonFiles(dir) {
11
22
  const files = [];
12
23
  if (!fs.existsSync(dir))
@@ -50,7 +61,100 @@ export function listRuntimeEvents(cwd) {
50
61
  }
51
62
  return events.sort((a, b) => a.created_at.localeCompare(b.created_at));
52
63
  }
64
+ export function queryRuntimeEvents(filter = {}, cwd) {
65
+ let events = listRuntimeEvents(cwd);
66
+ if (filter.id) {
67
+ events = events.filter((event) => event.id === filter.id);
68
+ }
69
+ if (filter.agent) {
70
+ events = events.filter((event) => event.agent === filter.agent);
71
+ }
72
+ if (filter.session_id) {
73
+ events = events.filter((event) => resolveEventSessionId(event) === filter.session_id);
74
+ }
75
+ if (filter.event_type) {
76
+ events = events.filter((event) => event.event_type === filter.event_type);
77
+ }
78
+ if (filter.assignment_id) {
79
+ events = events.filter((event) => event.assignment_id === filter.assignment_id);
80
+ }
81
+ if (filter.run_id) {
82
+ events = events.filter((event) => event.run_id === filter.run_id);
83
+ }
84
+ if (filter.claim_id) {
85
+ events = events.filter((event) => event.claim_id === filter.claim_id);
86
+ }
87
+ if (filter.plan_id) {
88
+ events = events.filter((event) => event.plan_id === filter.plan_id);
89
+ }
90
+ if (filter.sequence_id) {
91
+ events = events.filter((event) => event.sequence_id === filter.sequence_id);
92
+ }
93
+ if (filter.reflectableOnly) {
94
+ events = events.filter((event) => isReflectableRuntimeEvent(event));
95
+ }
96
+ return events;
97
+ }
53
98
  export function listRuntimeEventsBySession(session, cwd) {
54
- return listRuntimeEvents(cwd).filter((event) => resolveEventSessionId(event) === session);
99
+ return queryRuntimeEvents({ session_id: session }, cwd);
100
+ }
101
+ export function saveRuntimeEvent(event, cwd) {
102
+ const parsed = RuntimeEventSchema.parse(event);
103
+ mutate({ cwd }, () => {
104
+ ensureRuntimeEventsDir(cwd);
105
+ const filepath = path.join(runtimeEventsDir(cwd, 'write'), `${parsed.id}.json`);
106
+ writeFileAtomic(filepath, JSON.stringify(parsed, null, 2) + '\n');
107
+ });
108
+ return parsed;
109
+ }
110
+ export function createRuntimeEvent(options, cwd) {
111
+ const event = RuntimeEventSchema.parse({
112
+ id: options.id ?? generateId('runtime_events'),
113
+ agent: options.agent,
114
+ agent_id: options.agent_id,
115
+ project_id: options.project_id,
116
+ host_id: options.host_id,
117
+ session_id: options.session_id,
118
+ event_type: options.event_type,
119
+ created_at: options.created_at ?? nowISO(),
120
+ text: options.text,
121
+ tags: options.tags ?? [],
122
+ assignment_id: options.assignment_id,
123
+ run_id: options.run_id,
124
+ claim_id: options.claim_id,
125
+ message_id: options.message_id,
126
+ plan_id: options.plan_id,
127
+ sequence_id: options.sequence_id,
128
+ correlation_id: options.correlation_id,
129
+ scope: options.scope,
130
+ transport: options.transport,
131
+ status: options.status,
132
+ status_reason: options.status_reason,
133
+ candidate_type: options.candidate_type,
134
+ severity: options.severity,
135
+ from: options.from,
136
+ to: options.to,
137
+ related_paths: options.related_paths,
138
+ metadata: options.metadata,
139
+ model: options.model,
140
+ });
141
+ return saveRuntimeEvent(event, cwd);
142
+ }
143
+ const REFLECTABLE_RUNTIME_EVENT_TYPES = new Set([
144
+ 'task_started',
145
+ 'observation',
146
+ 'risk_detected',
147
+ 'handoff_requested',
148
+ 'task_finished',
149
+ 'session_start',
150
+ 'session_end',
151
+ ]);
152
+ export function isReflectableRuntimeEvent(event) {
153
+ if (event.candidate_type)
154
+ return true;
155
+ return REFLECTABLE_RUNTIME_EVENT_TYPES.has(event.event_type);
156
+ }
157
+ export function isTaskLifecycleRuntimeEvent(event) {
158
+ return event.event_type === 'task_started' || event.event_type === 'task_finished';
55
159
  }
56
160
  //# sourceMappingURL=events.js.map
@@ -0,0 +1,154 @@
1
+ import { spawn, execFileSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { buildClaimEnvPrefix } from './execution-profile.js';
5
+ import { getCapabilityProfile } from './agent-capability.js';
6
+ import { nowISO } from './ids.js';
7
+ /**
8
+ * Check if a binary is resolvable on the system PATH.
9
+ * On Windows, `spawn({shell:true})` always succeeds (launches cmd.exe),
10
+ * masking ENOENT for missing binaries. This pre-check catches that.
11
+ */
12
+ function resolveBinaryOnPath(binary) {
13
+ // Absolute or relative path — check directly
14
+ if (binary.includes('/') || binary.includes('\\')) {
15
+ return fs.existsSync(binary) ? binary : undefined;
16
+ }
17
+ try {
18
+ if (process.platform === 'win32') {
19
+ const output = execFileSync('where', [binary], { encoding: 'utf-8', timeout: 5000 });
20
+ const matches = output
21
+ .split(/\r?\n/)
22
+ .map((line) => line.trim())
23
+ .filter(Boolean);
24
+ if (matches.length === 0)
25
+ return undefined;
26
+ return (matches.find((candidate) => /\.(exe|com)$/i.test(candidate)) ??
27
+ matches.find((candidate) => /\.(cmd|bat)$/i.test(candidate)) ??
28
+ matches[0]);
29
+ }
30
+ else {
31
+ const output = execFileSync('which', [binary], { encoding: 'utf-8', timeout: 5000 }).trim();
32
+ return output || undefined;
33
+ }
34
+ }
35
+ catch {
36
+ return undefined;
37
+ }
38
+ }
39
+ function buildManualEnvPrefix(claimId) {
40
+ // pln#496 step stp_a9afe59d: the cross-platform / cross-shell logic
41
+ // now lives in execution-profile.ts:buildClaimEnvPrefix. Keep this
42
+ // wrapper for symmetry with the dispatcher's buildEnvPrefix.
43
+ return buildClaimEnvPrefix(claimId);
44
+ }
45
+ export class CliExecutionAdapter {
46
+ id = 'cli';
47
+ canSpawn(agentName) {
48
+ const profile = getCapabilityProfile(agentName);
49
+ if (!profile) {
50
+ return { canSpawn: false, reason: `unknown agent profile: ${agentName}` };
51
+ }
52
+ if (!profile.runtime.canBeSpawnedCli) {
53
+ return { canSpawn: false, reason: `agent ${agentName} is not CLI-spawnable` };
54
+ }
55
+ if (!profile.invoke_template || !profile.invoke_binary) {
56
+ return { canSpawn: false, reason: `agent ${agentName} has no invoke template` };
57
+ }
58
+ if (process.env.BRAINCLAW_NO_SPAWN === '1') {
59
+ return { canSpawn: false, reason: 'BRAINCLAW_NO_SPAWN is set' };
60
+ }
61
+ return { canSpawn: true, reason: 'agent has spawnable profile' };
62
+ }
63
+ prepareManualCommand(invoke, options) {
64
+ const envPrefix = buildManualEnvPrefix(options.claimId);
65
+ return {
66
+ command: `${envPrefix}${invoke.bashCommand}`,
67
+ shell: process.platform === 'win32' ? 'cmd' : (invoke.shell ? 'bash' : 'sh'),
68
+ };
69
+ }
70
+ start(invoke, options) {
71
+ const isWin32 = process.platform === 'win32';
72
+ const env = {
73
+ ...process.env,
74
+ ...(invoke.env ?? {}),
75
+ ...(options.claimId ? { BRAINCLAW_CLAIM_ID: options.claimId } : {}),
76
+ };
77
+ if (invoke.promptDelivery === 'temp_file' && invoke.tempFilePath && invoke.promptText) {
78
+ const dir = path.dirname(invoke.tempFilePath);
79
+ if (!fs.existsSync(dir))
80
+ fs.mkdirSync(dir, { recursive: true });
81
+ fs.writeFileSync(invoke.tempFilePath, invoke.promptText, 'utf-8');
82
+ }
83
+ // Pre-check: on Windows shell:true masks ENOENT (cmd.exe spawns OK, exits 1 silently)
84
+ const resolvedExecutable = isWin32
85
+ ? resolveBinaryOnPath(invoke.executable)
86
+ : invoke.executable;
87
+ if (isWin32 && !resolvedExecutable) {
88
+ throw new Error(`Cannot spawn agent ${options.agent}: binary '${invoke.executable}' not found on PATH`);
89
+ }
90
+ const spawnExecutable = resolvedExecutable ?? invoke.executable;
91
+ const useShell = isWin32 && /\.(cmd|bat)$/i.test(spawnExecutable);
92
+ const needsStdin = invoke.promptDelivery === 'stdin_pipe' && invoke.promptText;
93
+ const stdio = needsStdin ? ['pipe', 'ignore', 'ignore'] : 'ignore';
94
+ // pln#476: wrap the spawn command with a brief-ack step so the worker
95
+ // shell touches a sentinel file BEFORE the agent binary runs.
96
+ // waitForAssignmentHandshake checks that file as evidence the spawn
97
+ // executed — needed for codex (which lacks the brainclaw MCP context
98
+ // to call bclaw_assignment_update). When ackRoot/assignmentId are
99
+ // omitted, we keep the original direct-binary spawn.
100
+ const useAckWrap = !!(options.assignmentId && (options.ackRoot ?? options.worktreePath));
101
+ let child;
102
+ if (useAckWrap) {
103
+ const ackRoot = options.ackRoot ?? options.worktreePath;
104
+ const ackDir = path.join(ackRoot, '.brainclaw', 'coordination', 'runtime', 'ack');
105
+ const ackPath = path.join(ackDir, `${options.assignmentId}.ack`);
106
+ fs.mkdirSync(ackDir, { recursive: true });
107
+ const ackStep = isWin32
108
+ ? `type nul > "${ackPath}"`
109
+ : `touch "${ackPath}"`;
110
+ const wrappedCmd = `${ackStep} && ${invoke.bashCommand}`;
111
+ child = spawn(wrappedCmd, [], {
112
+ detached: !isWin32,
113
+ shell: true,
114
+ stdio,
115
+ cwd: options.worktreePath,
116
+ env,
117
+ windowsHide: true,
118
+ });
119
+ }
120
+ else {
121
+ child = spawn(spawnExecutable, invoke.args, {
122
+ // Windows: detached is unreliable with shell:true — child stays in parent's process group.
123
+ // POSIX: detached lets the child survive parent exit.
124
+ detached: !isWin32,
125
+ shell: useShell,
126
+ stdio,
127
+ cwd: options.worktreePath,
128
+ env,
129
+ windowsHide: true,
130
+ });
131
+ }
132
+ // Swallowed to prevent unhandled 'error' event crash.
133
+ // On POSIX ENOENT: pid is undefined → the throw below handles it.
134
+ // On Windows shell:true: this never fires for ENOENT (cmd.exe succeeds);
135
+ // the isBinaryOnPath pre-check above catches that case instead.
136
+ child.on('error', () => { });
137
+ if (needsStdin && child.stdin) {
138
+ child.stdin.write(invoke.promptText);
139
+ child.stdin.end();
140
+ }
141
+ child.unref();
142
+ const pid = child.pid;
143
+ if (!pid) {
144
+ throw new Error(`Failed to spawn agent ${options.agent}: no PID returned`);
145
+ }
146
+ return {
147
+ pid,
148
+ started_at: nowISO(),
149
+ status: 'started',
150
+ };
151
+ }
152
+ }
153
+ export const defaultExecutionAdapter = new CliExecutionAdapter();
154
+ //# sourceMappingURL=execution-adapters.js.map