brainclaw 1.8.0 → 1.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +592 -505
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/cli.js +138 -13
- package/dist/commands/add-step.js +1 -1
- package/dist/commands/bootstrap.js +2 -26
- package/dist/commands/check-security-mcp.js +50 -33
- package/dist/commands/check-security.js +86 -43
- package/dist/commands/claim.js +22 -21
- package/dist/commands/confirm.js +26 -0
- package/dist/commands/context-diff.js +1 -1
- package/dist/commands/dispatch-watch.js +142 -0
- package/dist/commands/doctor.js +113 -2
- package/dist/commands/estimation-report.js +115 -16
- package/dist/commands/harvest.js +286 -23
- package/dist/commands/hooks.js +73 -73
- package/dist/commands/init.js +124 -22
- package/dist/commands/install-hooks.js +78 -78
- package/dist/commands/loops-handlers.js +4 -0
- package/dist/commands/mcp-read-handlers.js +253 -41
- package/dist/commands/mcp.js +664 -102
- package/dist/commands/memory.js +21 -17
- package/dist/commands/migrate.js +81 -17
- package/dist/commands/prune.js +78 -4
- package/dist/commands/reflect.js +26 -20
- package/dist/commands/register-agent.js +57 -1
- package/dist/commands/repair.js +20 -0
- package/dist/commands/session-end.js +15 -6
- package/dist/commands/session-start.js +18 -1
- package/dist/commands/setup-security.js +39 -18
- package/dist/commands/setup.js +26 -27
- package/dist/commands/stale.js +16 -2
- package/dist/commands/switch.js +26 -5
- package/dist/commands/uninstall.js +126 -34
- package/dist/commands/update-step.js +6 -0
- package/dist/commands/version.js +1 -1
- package/dist/commands/worktree.js +60 -0
- package/dist/core/actions.js +12 -3
- package/dist/core/agent-capability.js +30 -17
- package/dist/core/agent-files.js +963 -666
- package/dist/core/agent-integrations.js +0 -3
- package/dist/core/agent-inventory.js +67 -0
- package/dist/core/agent-registry.js +163 -29
- package/dist/core/agentrun-reconciler.js +33 -2
- package/dist/core/agentruns.js +7 -1
- package/dist/core/ai-agent-detection.js +31 -44
- package/dist/core/archival.js +15 -9
- package/dist/core/assignment-reconciler.js +56 -0
- package/dist/core/assignment-sweeper.js +127 -4
- package/dist/core/assignments.js +69 -11
- package/dist/core/bootstrap.js +233 -67
- package/dist/core/brainclaw-version.js +22 -0
- package/dist/core/candidates.js +21 -1
- package/dist/core/claims.js +313 -150
- package/dist/core/codev-prompts.js +38 -38
- package/dist/core/config.js +6 -1
- package/dist/core/context-diff.js +148 -20
- package/dist/core/context.js +129 -8
- package/dist/core/coordination.js +22 -3
- package/dist/core/default-profiles/doctor.yaml +11 -11
- package/dist/core/default-profiles/janitor.yaml +11 -11
- package/dist/core/default-profiles/onboarder.yaml +11 -11
- package/dist/core/default-profiles/reviewer.yaml +13 -13
- package/dist/core/dispatch-status.js +79 -5
- package/dist/core/dispatcher.js +65 -12
- package/dist/core/entity-operations.js +74 -27
- package/dist/core/entity-registry.js +31 -5
- package/dist/core/event-log.js +138 -21
- package/dist/core/events/checkpoint.js +258 -0
- package/dist/core/events/genesis.js +220 -0
- package/dist/core/events/journal.js +507 -0
- package/dist/core/events/materialize.js +126 -0
- package/dist/core/events/registry-post-image.js +110 -0
- package/dist/core/events/verify.js +109 -0
- package/dist/core/execution-adapters.js +23 -0
- package/dist/core/execution.js +1 -1
- package/dist/core/facade-schema.js +38 -0
- package/dist/core/gc-semantic.js +130 -5
- package/dist/core/handoff-snapshot.js +68 -0
- package/dist/core/ids.js +19 -8
- package/dist/core/instruction-templates.js +34 -115
- package/dist/core/io.js +39 -3
- package/dist/core/json-store.js +10 -1
- package/dist/core/lock.js +153 -28
- package/dist/core/loops/bootstrap-acquire.js +25 -1
- package/dist/core/loops/facade-schema.js +2 -0
- package/dist/core/loops/hooks/survey-signals-baseline.js +36 -0
- package/dist/core/loops/index.js +1 -0
- package/dist/core/loops/presets/bootstrap.js +7 -0
- package/dist/core/loops/store.js +17 -0
- package/dist/core/loops/verbs.js +24 -2
- package/dist/core/markdown.js +8 -76
- package/dist/core/mcp-command-resolution.js +245 -0
- package/dist/core/memory-compactor.js +5 -3
- package/dist/core/memory-lifecycle.js +282 -0
- package/dist/core/merge-risk.js +150 -0
- package/dist/core/messaging.js +10 -3
- package/dist/core/migration.js +11 -1
- package/dist/core/observer-mode.js +26 -0
- package/dist/core/operations/memory-mutation.js +90 -65
- package/dist/core/operations/plan.js +27 -1
- package/dist/core/protocol-skills.js +210 -0
- package/dist/core/reflection-safety.js +6 -7
- package/dist/core/reputation.js +84 -2
- package/dist/core/runtime-signals.js +72 -10
- package/dist/core/runtime.js +84 -1
- package/dist/core/schema.js +114 -0
- package/dist/core/search.js +19 -2
- package/dist/core/security-detectors.js +125 -0
- package/dist/core/security-extract.js +189 -0
- package/dist/core/security-guard.js +217 -139
- package/dist/core/security-packages.js +121 -0
- package/dist/core/security-scoring.js +76 -9
- package/dist/core/security.js +34 -2
- package/dist/core/sequence.js +11 -2
- package/dist/core/setup-flow.js +141 -13
- package/dist/core/spawn-check.js +16 -2
- package/dist/core/staleness.js +73 -2
- package/dist/core/state.js +250 -54
- package/dist/core/store-resolution.js +45 -12
- package/dist/core/worktree.js +90 -26
- package/dist/facts.js +8 -8
- package/dist/facts.json +7 -7
- package/docs/PROTOCOL.md +223 -0
- package/docs/adapters/openclaw.md +43 -43
- package/docs/architecture/project-refs.md +328 -328
- package/docs/cli.md +2097 -2096
- package/docs/concepts/coordination.md +52 -52
- package/docs/concepts/coordinator-runbook.md +129 -0
- package/docs/concepts/dispatch-lifecycle.md +245 -245
- package/docs/concepts/event-log-store.md +928 -0
- package/docs/concepts/ideation-loop.md +317 -317
- package/docs/concepts/loop-engine.md +520 -511
- package/docs/concepts/mcp-governance.md +268 -268
- package/docs/concepts/memory.md +89 -88
- package/docs/concepts/multi-agent-workflows.md +167 -167
- package/docs/concepts/observer-protocol.md +361 -0
- package/docs/concepts/parallel-merge-protocol.md +71 -0
- package/docs/concepts/plans-and-claims.md +217 -174
- package/docs/concepts/project-md-convention.md +35 -35
- package/docs/concepts/runtime-notes.md +38 -38
- package/docs/concepts/skills.md +78 -0
- package/docs/concepts/troubleshooting.md +254 -254
- package/docs/concepts/workspace-bootstrapping.md +142 -81
- package/docs/context-format-changelog.md +35 -35
- package/docs/context-format.md +48 -48
- package/docs/index.md +65 -65
- package/docs/integrations/agents.md +162 -162
- package/docs/integrations/claude-code.md +23 -23
- package/docs/integrations/cline.md +87 -88
- package/docs/integrations/codex.md +2 -2
- package/docs/integrations/continue.md +60 -60
- package/docs/integrations/copilot.md +82 -80
- package/docs/integrations/cursor.md +23 -23
- package/docs/integrations/kilocode.md +72 -72
- package/docs/integrations/mcp.md +377 -377
- package/docs/integrations/mistral-vibe.md +122 -122
- package/docs/integrations/openclaw.md +99 -98
- package/docs/integrations/opencode.md +84 -84
- package/docs/integrations/overview.md +122 -122
- package/docs/integrations/roo.md +74 -74
- package/docs/integrations/windsurf.md +83 -83
- package/docs/mcp-schema-changelog.md +360 -329
- package/docs/playbooks/integration/index.md +121 -121
- package/docs/playbooks/orchestration.md +37 -0
- package/docs/playbooks/productivity/index.md +99 -99
- package/docs/playbooks/team/index.md +117 -117
- package/docs/product/agent-first-model.md +184 -184
- package/docs/product/entity-model-audit.md +462 -462
- package/docs/product/positioning.md +86 -86
- package/docs/quickstart-existing-project.md +107 -107
- package/docs/quickstart.md +148 -147
- package/docs/release-maintenance.md +79 -79
- package/docs/reputation.md +52 -52
- package/docs/review.md +45 -45
- package/docs/security.md +212 -53
- package/docs/server-operations.md +118 -118
- package/docs/storage.md +110 -108
- package/package.json +86 -69
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
* LIVE (gitignored, frequent refresh) — plans, claims, traps, decisions, sequences
|
|
8
8
|
*
|
|
9
9
|
* Tier delivery:
|
|
10
|
-
* Tier A (MCP
|
|
10
|
+
* Tier A (managed MCP/native surface): stable file only — live context via MCP or native runtime surfaces
|
|
11
11
|
* Tier B (MCP, no hooks): stable file + live companion file
|
|
12
12
|
* Tier C (no MCP): stable file + live companion file (richer, only source)
|
|
13
13
|
*/
|
|
14
|
+
import { resolveExportTarget } from './agent-files.js';
|
|
14
15
|
/**
|
|
15
16
|
* Render the STABLE brainclaw section content for an instruction file.
|
|
16
17
|
* This is the versioned file that changes rarely (on upgrade, convention change).
|
|
@@ -212,7 +213,7 @@ function renderVisionSection(input) {
|
|
|
212
213
|
}
|
|
213
214
|
function renderHeader(input) {
|
|
214
215
|
return [
|
|
215
|
-
`> Managed by brainclaw
|
|
216
|
+
`> Managed by brainclaw — do not edit manually.`,
|
|
216
217
|
`> Regenerate: brainclaw export --format ${formatForAgent(input.profile.name)} --write`,
|
|
217
218
|
].join('\n');
|
|
218
219
|
}
|
|
@@ -222,50 +223,30 @@ function renderLiveHeader(_input) {
|
|
|
222
223
|
`> Last updated: ${new Date().toISOString().slice(0, 19)}`,
|
|
223
224
|
].join('\n');
|
|
224
225
|
}
|
|
226
|
+
// Kept deliberately small (pln#542): entry point + grammar + escalation
|
|
227
|
+
// pointer. Everything else is discoverable via the `next_actions` carried by
|
|
228
|
+
// every MCP response — protocol teaching moved out of this file.
|
|
225
229
|
function renderSessionProtocol() {
|
|
226
230
|
return [
|
|
227
231
|
'## brainclaw — session protocol',
|
|
228
232
|
'',
|
|
229
|
-
'1. Call `bclaw_work(intent)`
|
|
230
|
-
'2.
|
|
233
|
+
'1. Call `bclaw_work(intent)` (consult|execute|resume|review) — one call handles session, context, and claim (execute). Every response carries `next_actions` with the exact follow-up calls: follow those instead of memorizing the API.',
|
|
234
|
+
'2. Canonical grammar for memory objects: `bclaw_find` / `bclaw_get` / `bclaw_create` / `bclaw_update` / `bclaw_remove` / `bclaw_transition` (entity, …).',
|
|
231
235
|
'3. Do not assume project state without reading brainclaw context first.',
|
|
232
236
|
'',
|
|
233
|
-
'
|
|
234
|
-
'- Start a code review / consult an agent / assign a scope → `bclaw_coordinate(intent=review|consult|assign)`',
|
|
235
|
-
'- Parallelize execute across a sequence\'s lanes → `bclaw_dispatch(intent=execute)`',
|
|
236
|
-
'- Drive a turn in a loop already assigned to you → `bclaw_loop(intent=turn|complete_turn|advance|close)`',
|
|
237
|
-
'',
|
|
238
|
-
'Do NOT call `bclaw_loop(intent=open)` directly — it creates a loop structure without dispatch, so the reviewer/participant never gets the work. Use the goal entries above.',
|
|
239
|
-
'',
|
|
240
|
-
'_How to verify a dispatch actually worked:_ `execution_status="delivered_and_started"` only means the brief-ack sentinel was touched — it does NOT mean the worker is doing useful work. (1) Call `bclaw_dispatch_status(target_id=<asgn_…|clm_…|lop_…|run_…>)` — the purpose-built facade: it resolves the linked entities, reads the runtime sentinels (`ack` / `heartbeat` / `completed` / `failed`) and the captured stdout/stderr tails, checks pid liveness, and returns a single health verdict plus a recommended next action. This is the `verify_with` target named in the coordinate/dispatch response — prefer it over assembling the picture by hand. (2) Do NOT diagnose liveness from the tracked pid yourself: on Windows an ack-wrapped spawn runs under a `cmd.exe` shell, so `agent_run.pid` is the wrapper (which exits early by design), NOT the real worker — `Get-Process -Id <pid>` reads it dead while the worker is alive and committing. Trust the sentinel-derived verdict instead; the reconciler already infers `completed` from a post-start commit on the worktree branch even when the worker never called `bclaw_assignment_update`. (3) Fallback only if the facade is unavailable: `bclaw_find(entity="agent_run", filter={assignment_id})` plus the captured streams at `.brainclaw/coordination/runtime/log/<assignment_id>.{stdout,stderr}.log` — note that `claude -p` buffers stdout until exit, so an empty log mid-run is expected; use the `heartbeat` sentinel as the live progress signal, not stdout. Full FSM tables + diagnostic decision tree in `docs/concepts/dispatch-lifecycle.md`.',
|
|
237
|
+
'Escalation (only when orchestrating other agents): `bclaw_coordinate(intent=review|consult|assign)`. Verify any dispatch with `bclaw_dispatch_status(target_id)` — trust its sentinel-based verdict, not the tracked pid. Details: `docs/concepts/dispatch-lifecycle.md`.',
|
|
241
238
|
].join('\n');
|
|
242
239
|
}
|
|
243
240
|
function renderUserWorkflow() {
|
|
244
241
|
return [
|
|
245
242
|
'## brainclaw — user workflow',
|
|
246
243
|
'',
|
|
247
|
-
'The intended end-to-end flow, executable by a single agent:',
|
|
248
|
-
'',
|
|
249
244
|
' ideation → plan (+ steps) → claim → implement → release claim → review → close step/plan → merge',
|
|
250
245
|
'',
|
|
251
|
-
'
|
|
252
|
-
'`sequence` is optional: add it between plan and claim when you want parallelized lanes across agents.',
|
|
253
|
-
'',
|
|
254
|
-
'**Entity → role in the flow:**',
|
|
255
|
-
'- `plan` — intended outcome. Create with `bclaw_create(plan, …)`, decompose with `bclaw_add_step`.',
|
|
256
|
-
'- `step` — incremental unit inside a plan; mark done with `bclaw_complete_step` as you implement.',
|
|
257
|
-
'- `sequence` — ordered lanes when work can be parallelized across claims/agents (optional).',
|
|
258
|
-
'- `claim` — advisory reservation of a scope before editing; release once implementation is ready for review.',
|
|
259
|
-
'- `handoff` — immutable snapshot of what moved to the next stage (review, merge).',
|
|
260
|
-
'- `candidate` — proposed decision / constraint / trap awaiting review before entering durable memory.',
|
|
261
|
-
'- `decision` / `constraint` / `trap` / `runtime_note` — captured along the way to preserve context for future sessions.',
|
|
246
|
+
'Entities: `plan` (intended outcome) · `step` (unit inside a plan) · `sequence` (optional parallel lanes) · `claim` (advisory scope reservation) · `handoff` (stage snapshot) · `candidate` (proposed memory awaiting review) · `decision`/`constraint`/`trap`/`runtime_note` (context captured along the way).',
|
|
262
247
|
'',
|
|
263
|
-
'
|
|
264
|
-
'
|
|
265
|
-
'- Drive: `bclaw_loop(intent=turn|complete_turn|advance|close)` for turns assigned to your slot.',
|
|
266
|
-
'- Anti-pattern: `bclaw_loop(intent=open)` alone — creates the loop structure without any dispatch, so nothing actually runs.',
|
|
267
|
-
'',
|
|
268
|
-
'Ideation / Debug / Research / Planning loops — *planned*. See `docs/product/agent-first-model.md` §3.',
|
|
248
|
+
'Review & Fix Loop: start with `bclaw_coordinate(intent=review, open_loop=true, review_mode=symmetric|asymmetric, targetAgents=[reviewer])` — opens the loop AND dispatches the first turn. Drive your turns with `bclaw_loop(intent=turn|complete_turn|advance|close)`. Parallelize a sequence\'s lanes with `bclaw_dispatch(intent=execute)`.',
|
|
249
|
+
'Ideation / Debug / Research loops — *planned* (`docs/product/agent-first-model.md` §3).',
|
|
269
250
|
].join('\n');
|
|
270
251
|
}
|
|
271
252
|
/**
|
|
@@ -288,51 +269,16 @@ function renderAutonomyContract() {
|
|
|
288
269
|
return [
|
|
289
270
|
'## brainclaw — autonomous workflow contract',
|
|
290
271
|
'',
|
|
291
|
-
'When a brainclaw protocol prescribes the next action
|
|
292
|
-
'completed, **execute it. Do not ask for permission.** brainclaw\'s coordination',
|
|
293
|
-
'promise depends on this — every avoidable pause forces the human back into the',
|
|
294
|
-
'synchronization loop, which is exactly what brainclaw is built to eliminate.',
|
|
295
|
-
'',
|
|
296
|
-
'**Protocol-defined transitions you MUST execute autonomously:**',
|
|
297
|
-
'',
|
|
298
|
-
'- IF you completed a review (assigned via `bclaw_coordinate(intent=review)` or',
|
|
299
|
-
' inbox `review_request` message) → produce findings → **send the reply with',
|
|
300
|
-
' your verdict via `bclaw_send_message(type="reply", thread_id=…)`**. Do not',
|
|
301
|
-
' ask whether to send.',
|
|
302
|
-
'- IF you receive a reply with an `LGTM` verdict AND you are the requester →',
|
|
303
|
-
' **close the loop and merge the branch yourself** (when you own the merge).',
|
|
304
|
-
' Do not pass the merge to the human.',
|
|
305
|
-
'- IF the reviewer reply contains findings → **apply the symmetric fixes you',
|
|
306
|
-
' can make**, commit on the same branch, then reroute via',
|
|
307
|
-
' `bclaw_send_message(type="reply", …)` for re-review. Do not stop after the',
|
|
308
|
-
' fixes.',
|
|
309
|
-
'- IF you have an active claim and your work is complete → call',
|
|
310
|
-
' `bclaw_release_claim(id=…, planStatus="done")`. Do not leave the claim',
|
|
311
|
-
' active for someone else to clean up.',
|
|
312
|
-
'- IF you have an `assignment_id` (you were dispatched via `bclaw_coordinate` /',
|
|
313
|
-
' `bclaw_dispatch`) → at end of work, call',
|
|
314
|
-
' `bclaw_assignment_update(assignment_id=…, status="completed", artifacts=[…])`.',
|
|
315
|
-
' The coordinator depends on this lifecycle event; without it, the loop never',
|
|
316
|
-
' converges and the supervisor cannot tell success from silent failure.',
|
|
317
|
-
'',
|
|
318
|
-
'**Legitimate reasons to pause and ask the human:**',
|
|
272
|
+
'When a brainclaw protocol prescribes the next action, **execute it. Do not ask for permission.** (Empirical: May 2026 multi-agent review threads stalled at "should I send this reply? / merge?", forcing the human to carry context between agents.)',
|
|
319
273
|
'',
|
|
320
|
-
'
|
|
321
|
-
'
|
|
322
|
-
'
|
|
323
|
-
'
|
|
324
|
-
'
|
|
325
|
-
'
|
|
326
|
-
' scoping judgement only the human owns).',
|
|
327
|
-
'3. The user has **explicitly told you to confirm** before specific actions',
|
|
328
|
-
' in this session.',
|
|
274
|
+
'MUST execute autonomously:',
|
|
275
|
+
'- review done → send the verdict: `bclaw_send_message(type="reply", thread_id=…)`',
|
|
276
|
+
'- LGTM received and you own the merge → close the loop and merge yourself',
|
|
277
|
+
'- findings received → apply fixes, commit, reply for re-review',
|
|
278
|
+
'- work complete → `bclaw_release_claim(id=…, planStatus="done")`',
|
|
279
|
+
'- dispatched work done → `bclaw_assignment_update(assignment_id=…, status="completed", artifacts=[…])`',
|
|
329
280
|
'',
|
|
330
|
-
'
|
|
331
|
-
'not safety. If the next step appears in the workflow above — do it. The',
|
|
332
|
-
'empirical cost of skipping the contract: in May 2026, multi-agent review',
|
|
333
|
-
'threads systematically stalled at "should I send this reply?" / "should I',
|
|
334
|
-
'merge?", forcing the human supervisor to manually carry context between',
|
|
335
|
-
'agents. Stop reproducing that pattern.',
|
|
281
|
+
'Pause for the human ONLY when the action is destructive AND irreversible AND outside the protocol, when the protocol does not specify the next step, or when the user explicitly asked for confirmation.',
|
|
336
282
|
].join('\n');
|
|
337
283
|
}
|
|
338
284
|
// ─── Constraint sections (split by category) ────────────────────────────────
|
|
@@ -371,33 +317,13 @@ function renderAvailableTools() {
|
|
|
371
317
|
return [
|
|
372
318
|
'## brainclaw — available tools',
|
|
373
319
|
'',
|
|
374
|
-
'
|
|
320
|
+
'**Entry:** `bclaw_work(intent, compact?, budget_tokens?)` · `bclaw_context(kind=memory|execution|board|board_summary|delta)`',
|
|
321
|
+
'**Canonical grammar** (entities: plan, decision, constraint, trap, handoff, runtime_note, candidate, sequence, claim, action, assignment, agent_run): `bclaw_find`, `bclaw_get`, `bclaw_create`, `bclaw_update`, `bclaw_remove`, `bclaw_transition`. Reads accept `budget_tokens` and `project` (cross-project routing — unknown names throw).',
|
|
322
|
+
'**Session/claims:** `bclaw_session_start`, `bclaw_session_end`, `bclaw_claim`, `bclaw_release_claim` · **steps:** `bclaw_add_step`, `bclaw_complete_step`, `bclaw_update_step`, `bclaw_delete_step` · **sequences:** `bclaw_list_sequences`, `bclaw_create_sequence`, `bclaw_update_sequence`, `bclaw_delete_sequence`',
|
|
323
|
+
'**Inbox:** `bclaw_read_inbox`, `bclaw_ack_message`, `bclaw_send_message`, `bclaw_correct_handoff` · **capture:** `bclaw_write_note`, `bclaw_quick_capture(text, type?)` · **search:** `bclaw_search` · **setup:** `bclaw_setup`, `bclaw_bootstrap`, `bclaw_switch`, `bclaw_release_notes`',
|
|
324
|
+
'**Escalation (orchestrators):** `bclaw_coordinate(intent=review|consult|assign|ideate)` · `bclaw_dispatch(intent=execute)` on an active sequence · `bclaw_loop(intent=turn|complete_turn|advance|close)` to drive turns · `bclaw_dispatch_status(target_id)` to verify',
|
|
375
325
|
'',
|
|
376
|
-
'
|
|
377
|
-
'**Canonical grammar (standard tier) — your main tool for working with memory:**',
|
|
378
|
-
'- `bclaw_find(entity, filter?)` — list by type',
|
|
379
|
-
'- `bclaw_get(entity, id)` — read one',
|
|
380
|
-
'- `bclaw_create(entity, data)` — add a new plan / decision / constraint / trap / handoff / candidate / runtime_note',
|
|
381
|
-
'- `bclaw_update(entity, id, patch)` — edit in place',
|
|
382
|
-
'- `bclaw_remove(entity, id, purge?)` — soft-delete (or purge)',
|
|
383
|
-
'- `bclaw_transition(entity, id, to)` — change status (e.g. plan todo→in_progress→done)',
|
|
384
|
-
'',
|
|
385
|
-
'Entities supported by the grammar: plan, decision, constraint, trap, handoff, runtime_note, candidate, sequence, claim, action, assignment, agent_run.',
|
|
386
|
-
'',
|
|
387
|
-
'**Cross-project access (pln#359):** every canonical-grammar call, `bclaw_context`, and `bclaw_coordinate` accept an optional `project: <name>` argument that routes the operation to a linked project (cross_project_links from `brainclaw link list` OR a workspace store-chain child). Identity is sourced from the caller; writes + audit land in the target. Unknown project names throw — no silent fallback. The CLI exposes the same as `--project <name>` (mutually exclusive with `--cwd`). Example: `bclaw_get(entity="trap", id="trp#36", project="brainclaw-site")`. Cross-project `bclaw_coordinate` is inbox-only — auto-spawn is force-disabled because the spawn cwd / worktree are tied to the target repo; the target agent picks the brief up async via its own `bclaw_work`.',
|
|
388
|
-
'',
|
|
389
|
-
'**Session + claims:** `bclaw_session_start`, `bclaw_session_end`, `bclaw_claim`, `bclaw_release_claim`',
|
|
390
|
-
'**Plan steps:** `bclaw_add_step`, `bclaw_complete_step`, `bclaw_update_step`, `bclaw_delete_step`',
|
|
391
|
-
'**Sequences:** `bclaw_list_sequences`, `bclaw_create_sequence`, `bclaw_update_sequence`, `bclaw_delete_sequence` — create/activate ordered lanes for parallel dispatch. Item shape: `{ planId, stepId?, rank, hard_after?, soft_after?, lane?, scope_hint?, rationale? }`.',
|
|
392
|
-
'**Inbox + handoffs:** `bclaw_read_inbox`, `bclaw_ack_message`, `bclaw_send_message`, `bclaw_correct_handoff`',
|
|
393
|
-
'**Notes + search:** `bclaw_write_note`, `bclaw_quick_capture`, `bclaw_search`',
|
|
394
|
-
'**Escalation (orchestrator path):**',
|
|
395
|
-
'- Review / consult / assign another agent → `bclaw_coordinate(intent=review|consult|assign)` (use `open_loop=true` on review to also dispatch the reviewer turn)',
|
|
396
|
-
'- Parallel execute across a sequence\'s lanes → create/update an active sequence, then `bclaw_dispatch(intent=analysis)` and `bclaw_dispatch(intent=execute)`',
|
|
397
|
-
'- Drive your turn in an already-opened loop → `bclaw_loop(intent=turn|complete_turn|advance|close)`',
|
|
398
|
-
'**Setup + navigation:** `bclaw_setup`, `bclaw_bootstrap`, `bclaw_switch`, `bclaw_release_notes`',
|
|
399
|
-
'',
|
|
400
|
-
'Legacy per-entity tools (`bclaw_list_plans`, `bclaw_accept`, `bclaw_get_context`, `bclaw_dispatch_review`, …) were removed from the catalog at v1.0 — direct calls still succeed as a migration escape hatch but emit a redirect warning. See `docs/integrations/mcp.md` + `docs/concepts/mcp-governance.md` for the full catalog and stability contract.',
|
|
326
|
+
'Responses are self-teaching — follow their `next_actions`. Full catalog + stability contract: `docs/integrations/mcp.md`, `docs/concepts/mcp-governance.md`.',
|
|
401
327
|
].join('\n');
|
|
402
328
|
}
|
|
403
329
|
// ─── Live section renderers ─────────────────────────────────────────────────
|
|
@@ -464,7 +390,7 @@ function renderOpenHandoffs(state, limit) {
|
|
|
464
390
|
if (handoffs.length === 0)
|
|
465
391
|
return undefined;
|
|
466
392
|
return [
|
|
467
|
-
'## brainclaw
|
|
393
|
+
'## brainclaw — open handoffs',
|
|
468
394
|
'',
|
|
469
395
|
...handoffs.map((h) => {
|
|
470
396
|
const plan = h.plan_id ? ` (${h.plan_id})` : '';
|
|
@@ -500,20 +426,13 @@ function priorityOrder(priority) {
|
|
|
500
426
|
default: return 0;
|
|
501
427
|
}
|
|
502
428
|
}
|
|
429
|
+
/**
|
|
430
|
+
* Resolve an agent name to its export format by reading AGENT_EXPORT_REGISTRY.
|
|
431
|
+
* Was a hand-maintained switch that drifted with every new agent — now derived
|
|
432
|
+
* from the same registry the export command iterates, so adding an agent in one
|
|
433
|
+
* place updates every consumer (pln#546 step 2).
|
|
434
|
+
*/
|
|
503
435
|
function formatForAgent(agentName) {
|
|
504
|
-
|
|
505
|
-
case 'claude-code': return 'claude-md';
|
|
506
|
-
case 'cursor': return 'cursor-rules';
|
|
507
|
-
case 'github-copilot': return 'copilot-instructions';
|
|
508
|
-
case 'opencode':
|
|
509
|
-
case 'codex': return 'agents-md';
|
|
510
|
-
case 'antigravity': return 'gemini-md';
|
|
511
|
-
case 'windsurf': return 'windsurf';
|
|
512
|
-
case 'cline': return 'cline';
|
|
513
|
-
case 'roo': return 'roo';
|
|
514
|
-
case 'kilocode': return 'kilocode';
|
|
515
|
-
case 'continue': return 'continue';
|
|
516
|
-
default: return 'agents-md';
|
|
517
|
-
}
|
|
436
|
+
return resolveExportTarget(agentName).format;
|
|
518
437
|
}
|
|
519
438
|
//# sourceMappingURL=instruction-templates.js.map
|
package/dist/core/io.js
CHANGED
|
@@ -7,6 +7,7 @@ const STORE_LOCK_BASENAME = '.store-mutation';
|
|
|
7
7
|
const RETRYABLE_RENAME_ERROR_CODES = new Set(['EPERM', 'EBUSY', 'EACCES']);
|
|
8
8
|
const DEFAULT_RENAME_RETRY_ATTEMPTS = 6;
|
|
9
9
|
const DEFAULT_RENAME_RETRY_DELAY_MS = 25;
|
|
10
|
+
const TMP_ORPHAN_MIN_AGE_MS = 60_000;
|
|
10
11
|
/**
|
|
11
12
|
* Entity-aligned directory mapping.
|
|
12
13
|
* Maps legacy flat directory names to their entity-partitioned paths.
|
|
@@ -83,7 +84,9 @@ export function memoryPath(filename, cwd, preferredDirName) {
|
|
|
83
84
|
return path.join(memoryDir(cwd, preferredDirName), filename);
|
|
84
85
|
}
|
|
85
86
|
export function storeLockPath(cwd, preferredDirName) {
|
|
86
|
-
|
|
87
|
+
// O3 (lop_e2d566765b8b4ce3): canonicalize so two spellings of the same store
|
|
88
|
+
// (relative vs absolute) produce one lock target / re-entrancy key.
|
|
89
|
+
const root = path.resolve(cwd ?? process.cwd());
|
|
87
90
|
const dirName = preferredDirName ?? MEMORY_DIR;
|
|
88
91
|
// Keep the store-wide lock alongside the store root so it survives
|
|
89
92
|
// upgrade park/swap renames. Writers and upgrade/rollback all share
|
|
@@ -214,6 +217,32 @@ function makeTempPath(filepath) {
|
|
|
214
217
|
const unique = `${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 10)}`;
|
|
215
218
|
return path.join(dir, `.${base}.${unique}.tmp`);
|
|
216
219
|
}
|
|
220
|
+
function isProcessAlive(pid) {
|
|
221
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
222
|
+
return false;
|
|
223
|
+
try {
|
|
224
|
+
process.kill(pid, 0);
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function tempOwnerPid(entry) {
|
|
232
|
+
if (!entry.endsWith('.tmp'))
|
|
233
|
+
return undefined;
|
|
234
|
+
const parts = entry.split('.');
|
|
235
|
+
const pid = Number(parts.at(-4));
|
|
236
|
+
return Number.isInteger(pid) && pid > 0 ? pid : undefined;
|
|
237
|
+
}
|
|
238
|
+
function shouldRemoveTmp(entry, stat) {
|
|
239
|
+
const pid = tempOwnerPid(entry);
|
|
240
|
+
if (!pid)
|
|
241
|
+
return false;
|
|
242
|
+
if (Date.now() - stat.mtimeMs < TMP_ORPHAN_MIN_AGE_MS)
|
|
243
|
+
return false;
|
|
244
|
+
return !isProcessAlive(pid);
|
|
245
|
+
}
|
|
217
246
|
function isRetryableRenameError(error) {
|
|
218
247
|
if (!(error instanceof Error) || !('code' in error))
|
|
219
248
|
return false;
|
|
@@ -261,7 +290,14 @@ export function cleanOrphanFiles(dirPath) {
|
|
|
261
290
|
try {
|
|
262
291
|
for (const entry of fs.readdirSync(dirPath)) {
|
|
263
292
|
const full = path.join(dirPath, entry);
|
|
264
|
-
|
|
293
|
+
let stat;
|
|
294
|
+
try {
|
|
295
|
+
stat = fs.statSync(full);
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (entry.endsWith('.tmp') && stat.isFile() && shouldRemoveTmp(entry, stat)) {
|
|
265
301
|
try {
|
|
266
302
|
fs.unlinkSync(full);
|
|
267
303
|
removed++;
|
|
@@ -269,7 +305,7 @@ export function cleanOrphanFiles(dirPath) {
|
|
|
269
305
|
catch { /* already gone */ }
|
|
270
306
|
}
|
|
271
307
|
// Recurse into subdirectories
|
|
272
|
-
if (
|
|
308
|
+
if (stat.isDirectory()) {
|
|
273
309
|
removed += cleanOrphanFiles(full);
|
|
274
310
|
}
|
|
275
311
|
}
|
package/dist/core/json-store.js
CHANGED
|
@@ -57,7 +57,16 @@ export class JsonStore {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
pathFor(id) {
|
|
60
|
-
|
|
60
|
+
if (!/^[A-Za-z0-9_-]+$/.test(id)) {
|
|
61
|
+
throw new Error(`Invalid ${this.documentType} id '${id}'`);
|
|
62
|
+
}
|
|
63
|
+
const root = path.resolve(this.dirPath);
|
|
64
|
+
const filepath = path.resolve(root, `${id}.json`);
|
|
65
|
+
const relative = path.relative(root, filepath);
|
|
66
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
67
|
+
throw new Error(`Invalid ${this.documentType} id '${id}'`);
|
|
68
|
+
}
|
|
69
|
+
return filepath;
|
|
61
70
|
}
|
|
62
71
|
}
|
|
63
72
|
//# sourceMappingURL=json-store.js.map
|
package/dist/core/lock.js
CHANGED
|
@@ -3,16 +3,30 @@ import path from 'node:path';
|
|
|
3
3
|
const DEFAULT_TIMEOUT_MS = 5000;
|
|
4
4
|
const LOCK_RETRY_INTERVAL_MS = 50;
|
|
5
5
|
const LOCK_EXPIRY_MS = 10000;
|
|
6
|
+
const LOCK_REFRESH_INTERVAL_MS = Math.max(1000, Math.floor(LOCK_EXPIRY_MS / 3));
|
|
6
7
|
const heldLocks = new Map();
|
|
7
8
|
function lockFilePath(targetPath) {
|
|
8
9
|
return targetPath + '.lock';
|
|
9
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Review follow-up O3 (lop_e2d566765b8b4ce3): the re-entrancy map key must be
|
|
13
|
+
* canonical, or two spellings of the same store path (relative vs absolute,
|
|
14
|
+
* Windows drive-letter case) miss each other in `heldLocks` and the nested
|
|
15
|
+
* acquire self-deadlocks for the full timeout. Resolve + case-fold (win32) for
|
|
16
|
+
* the KEY only — the filesystem path used for I/O keeps its original spelling.
|
|
17
|
+
*/
|
|
18
|
+
function lockKey(lockPath) {
|
|
19
|
+
const resolved = path.resolve(lockPath);
|
|
20
|
+
return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
|
|
21
|
+
}
|
|
10
22
|
function syncSleep(ms) {
|
|
11
23
|
if (ms <= 0)
|
|
12
24
|
return;
|
|
13
25
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
14
26
|
}
|
|
15
27
|
function isProcessAlive(pid) {
|
|
28
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
29
|
+
return false;
|
|
16
30
|
try {
|
|
17
31
|
process.kill(pid, 0);
|
|
18
32
|
return true;
|
|
@@ -30,43 +44,154 @@ function readLockData(lockPath) {
|
|
|
30
44
|
return null;
|
|
31
45
|
}
|
|
32
46
|
}
|
|
47
|
+
function randomToken() {
|
|
48
|
+
return `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
49
|
+
}
|
|
50
|
+
function sameLockData(left, right) {
|
|
51
|
+
if (!left || !right)
|
|
52
|
+
return false;
|
|
53
|
+
return left.pid === right.pid
|
|
54
|
+
&& left.timestamp === right.timestamp
|
|
55
|
+
&& (left.token ?? '') === (right.token ?? '');
|
|
56
|
+
}
|
|
57
|
+
function lockIsOwnedByCurrentProcess(data, token) {
|
|
58
|
+
return Boolean(data && data.pid === process.pid && data.token === token);
|
|
59
|
+
}
|
|
60
|
+
function writeLockData(lockPath, data, flag) {
|
|
61
|
+
fs.writeFileSync(lockPath, JSON.stringify(data), { encoding: 'utf-8', flag });
|
|
62
|
+
}
|
|
33
63
|
function tryCreateLock(lockPath) {
|
|
34
|
-
const
|
|
64
|
+
const token = randomToken();
|
|
65
|
+
const data = { pid: process.pid, timestamp: Date.now(), token };
|
|
35
66
|
try {
|
|
36
|
-
|
|
37
|
-
return
|
|
67
|
+
writeLockData(lockPath, data, 'wx');
|
|
68
|
+
return token;
|
|
38
69
|
}
|
|
39
70
|
catch (err) {
|
|
40
71
|
if (err instanceof Error && 'code' in err) {
|
|
41
72
|
const code = err.code;
|
|
42
73
|
if (code === 'EEXIST' || code === 'EPERM' || code === 'EACCES') {
|
|
43
|
-
return
|
|
74
|
+
return null;
|
|
44
75
|
}
|
|
45
76
|
}
|
|
46
77
|
throw err;
|
|
47
78
|
}
|
|
48
79
|
}
|
|
49
|
-
function
|
|
50
|
-
|
|
80
|
+
function lockFileIsOld(lockPath) {
|
|
81
|
+
try {
|
|
82
|
+
const stat = fs.statSync(lockPath);
|
|
83
|
+
return Date.now() - stat.mtimeMs > LOCK_EXPIRY_MS;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function canBreakLock(lockPath, data) {
|
|
51
90
|
if (!data)
|
|
91
|
+
return lockFileIsOld(lockPath);
|
|
92
|
+
if (data.pid === process.pid)
|
|
52
93
|
return false;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
94
|
+
if (isProcessAlive(data.pid))
|
|
95
|
+
return false;
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Review follow-up O2 (lop_e2d566765b8b4ce3) — accepted residual risk: a
|
|
100
|
+
* microsecond TOCTOU remains between the second read and the rename, where a
|
|
101
|
+
* third process can break+reacquire and the renamer steals a live lock. The
|
|
102
|
+
* restore branch covers it unless a FOURTH process acquires inside that gap.
|
|
103
|
+
* Decision (sprint 1.5): not closing it. The full fix is an O_EXCL token
|
|
104
|
+
* handshake protocol — a coordinated change to every lock consumer — and the
|
|
105
|
+
* residual window requires 4 racing processes inside microseconds on a lock
|
|
106
|
+
* whose owner is already dead. Strictly better than the old unlink-based break.
|
|
107
|
+
*/
|
|
108
|
+
function tryBreakLock(lockPath) {
|
|
109
|
+
const observed = readLockData(lockPath);
|
|
110
|
+
if (!canBreakLock(lockPath, observed))
|
|
111
|
+
return false;
|
|
112
|
+
const current = readLockData(lockPath);
|
|
113
|
+
if (!sameLockData(observed, current) && (observed || current))
|
|
114
|
+
return false;
|
|
115
|
+
if (!canBreakLock(lockPath, current))
|
|
116
|
+
return false;
|
|
117
|
+
// Dot-separated suffix with pid 4th-from-end so cleanOrphanFiles/tempOwnerPid
|
|
118
|
+
// can reclaim tombstones from crashed breakers (dash-separated names were
|
|
119
|
+
// unparseable and accumulated forever).
|
|
120
|
+
const tombstone = `${lockPath}.stale.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
121
|
+
try {
|
|
122
|
+
fs.renameSync(lockPath, tombstone);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
56
125
|
return false;
|
|
126
|
+
}
|
|
57
127
|
try {
|
|
58
|
-
|
|
128
|
+
const moved = readLockData(tombstone);
|
|
129
|
+
if ((observed || moved) && !sameLockData(observed, moved)) {
|
|
130
|
+
try {
|
|
131
|
+
if (!fs.existsSync(lockPath))
|
|
132
|
+
fs.renameSync(tombstone, lockPath);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// If another process already acquired the lock path, leave the
|
|
136
|
+
// mismatched tombstone for orphan cleanup instead of deleting live data.
|
|
137
|
+
}
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
const token = tryCreateLock(lockPath);
|
|
141
|
+
try {
|
|
142
|
+
fs.unlinkSync(tombstone);
|
|
143
|
+
}
|
|
144
|
+
catch { /* best effort */ }
|
|
145
|
+
if (token) {
|
|
146
|
+
startHeldLock(lockPath, token);
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
return false;
|
|
59
150
|
}
|
|
60
151
|
catch {
|
|
152
|
+
try {
|
|
153
|
+
if (!fs.existsSync(lockPath))
|
|
154
|
+
fs.renameSync(tombstone, lockPath);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// Best effort recovery; acquisition will retry.
|
|
158
|
+
}
|
|
61
159
|
return false;
|
|
62
160
|
}
|
|
63
|
-
|
|
161
|
+
}
|
|
162
|
+
function refreshLock(lockPath, token) {
|
|
163
|
+
const current = readLockData(lockPath);
|
|
164
|
+
if (!lockIsOwnedByCurrentProcess(current, token))
|
|
165
|
+
return;
|
|
166
|
+
try {
|
|
167
|
+
// 'r+' writes in place without truncating. Pad with trailing spaces (valid
|
|
168
|
+
// JSON whitespace) so a payload shorter than the on-disk file can never
|
|
169
|
+
// leave trailing garbage that would corrupt the lock for readers.
|
|
170
|
+
let payload = JSON.stringify({ ...current, timestamp: Date.now() });
|
|
171
|
+
try {
|
|
172
|
+
const existingSize = fs.statSync(lockPath).size;
|
|
173
|
+
if (Buffer.byteLength(payload, 'utf-8') < existingSize) {
|
|
174
|
+
payload = payload.padEnd(payload.length + (existingSize - Buffer.byteLength(payload, 'utf-8')), ' ');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch { /* stat failed — write unpadded */ }
|
|
178
|
+
fs.writeFileSync(lockPath, payload, { encoding: 'utf-8', flag: 'r+' });
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
// A failed refresh is not fatal; contenders still respect pid liveness.
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function startHeldLock(lockPath, token) {
|
|
185
|
+
const refreshTimer = setInterval(() => refreshLock(lockPath, token), LOCK_REFRESH_INTERVAL_MS);
|
|
186
|
+
refreshTimer.unref?.();
|
|
187
|
+
heldLocks.set(lockKey(lockPath), { count: 1, token, refreshTimer });
|
|
64
188
|
}
|
|
65
189
|
export function acquireLock(targetPath, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
66
190
|
const lockPath = lockFilePath(targetPath);
|
|
67
|
-
const
|
|
68
|
-
if (
|
|
69
|
-
|
|
191
|
+
const held = heldLocks.get(lockKey(lockPath));
|
|
192
|
+
if (held) {
|
|
193
|
+
held.count += 1;
|
|
194
|
+
refreshLock(lockPath, held.token);
|
|
70
195
|
return true;
|
|
71
196
|
}
|
|
72
197
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -75,12 +200,12 @@ export function acquireLock(targetPath, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
|
75
200
|
fs.mkdirSync(dir, { recursive: true });
|
|
76
201
|
}
|
|
77
202
|
while (Date.now() < deadline) {
|
|
78
|
-
|
|
79
|
-
|
|
203
|
+
const token = tryCreateLock(lockPath);
|
|
204
|
+
if (token) {
|
|
205
|
+
startHeldLock(lockPath, token);
|
|
80
206
|
return true;
|
|
81
207
|
}
|
|
82
208
|
if (tryBreakLock(lockPath)) {
|
|
83
|
-
heldLocks.set(lockPath, 1);
|
|
84
209
|
return true;
|
|
85
210
|
}
|
|
86
211
|
syncSleep(Math.min(LOCK_RETRY_INTERVAL_MS, deadline - Date.now()));
|
|
@@ -89,14 +214,16 @@ export function acquireLock(targetPath, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
|
89
214
|
}
|
|
90
215
|
export function releaseLock(targetPath) {
|
|
91
216
|
const lockPath = lockFilePath(targetPath);
|
|
92
|
-
const
|
|
93
|
-
if (
|
|
94
|
-
|
|
217
|
+
const held = heldLocks.get(lockKey(lockPath));
|
|
218
|
+
if (held && held.count > 1) {
|
|
219
|
+
held.count -= 1;
|
|
95
220
|
return;
|
|
96
221
|
}
|
|
97
|
-
heldLocks.delete(lockPath);
|
|
222
|
+
heldLocks.delete(lockKey(lockPath));
|
|
223
|
+
if (held?.refreshTimer)
|
|
224
|
+
clearInterval(held.refreshTimer);
|
|
98
225
|
try {
|
|
99
|
-
if (
|
|
226
|
+
if (lockIsOwnedByCurrentProcess(readLockData(lockPath), held?.token ?? '')) {
|
|
100
227
|
fs.unlinkSync(lockPath);
|
|
101
228
|
}
|
|
102
229
|
}
|
|
@@ -130,13 +257,11 @@ export function cleanStaleLocks(dirPath) {
|
|
|
130
257
|
continue;
|
|
131
258
|
const lockPath = path.join(dirPath, entry);
|
|
132
259
|
const data = readLockData(lockPath);
|
|
133
|
-
if (
|
|
134
|
-
|
|
135
|
-
const expired = Date.now() - data.timestamp > LOCK_EXPIRY_MS;
|
|
136
|
-
const ownerDead = !isProcessAlive(data.pid);
|
|
137
|
-
if (expired || ownerDead) {
|
|
260
|
+
if (canBreakLock(lockPath, data)) {
|
|
261
|
+
const tombstone = `${lockPath}.clean.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
138
262
|
try {
|
|
139
|
-
fs.
|
|
263
|
+
fs.renameSync(lockPath, tombstone);
|
|
264
|
+
fs.unlinkSync(tombstone);
|
|
140
265
|
removed++;
|
|
141
266
|
}
|
|
142
267
|
catch {
|
|
@@ -18,8 +18,10 @@
|
|
|
18
18
|
import fs from 'node:fs';
|
|
19
19
|
import path from 'node:path';
|
|
20
20
|
import { acquireClaimScope, listClaims, releaseClaim } from '../claims.js';
|
|
21
|
+
import { buildSurveySignalsBaseline } from './hooks/survey-signals-baseline.js';
|
|
21
22
|
import { BOOTSTRAP_PRESET } from './presets/bootstrap.js';
|
|
22
23
|
import { listLoops, openLoop } from './store.js';
|
|
24
|
+
import { add_artifact } from './verbs.js';
|
|
23
25
|
export class BootstrapCoordinationInProgressError extends Error {
|
|
24
26
|
blockingClaimId;
|
|
25
27
|
constructor(blockingClaimId) {
|
|
@@ -165,7 +167,7 @@ export function acquireBootstrapLoop(opts, cwd) {
|
|
|
165
167
|
// Step 3 — open the loop, release the lock.
|
|
166
168
|
const lockClaimId = acquireResult.claim.id;
|
|
167
169
|
try {
|
|
168
|
-
|
|
170
|
+
let loop = openLoop({
|
|
169
171
|
kind: 'ideation',
|
|
170
172
|
title: opts.title ?? 'Bootstrap PROJECT.md',
|
|
171
173
|
goal: opts.goal,
|
|
@@ -181,6 +183,28 @@ export function acquireBootstrapLoop(opts, cwd) {
|
|
|
181
183
|
stop_condition: BOOTSTRAP_PRESET.stop_condition,
|
|
182
184
|
protocol: BOOTSTRAP_PRESET.protocol,
|
|
183
185
|
}, cwd);
|
|
186
|
+
// pln#557 step 4 — seed the survey phase with the deterministic-scanner
|
|
187
|
+
// baseline so survey quality is reproducible across champions instead of
|
|
188
|
+
// depending on per-agent re-discovery (TranslaVox miss, can_0160d6c4).
|
|
189
|
+
// Attached as `signals_baseline` (freeform body), NOT `signals_report`,
|
|
190
|
+
// so the survey advance-gate is not auto-traversed: the champion enriches
|
|
191
|
+
// the baseline into its own signals_report. Best-effort — a scanner
|
|
192
|
+
// failure must never block opening the loop.
|
|
193
|
+
try {
|
|
194
|
+
const baseline = buildSurveySignalsBaseline(cwd ?? process.cwd());
|
|
195
|
+
loop = add_artifact({
|
|
196
|
+
id: loop.id,
|
|
197
|
+
actor: opts.created_by ?? opts.agent_id ?? opts.actor,
|
|
198
|
+
artifact: {
|
|
199
|
+
phase: 'survey',
|
|
200
|
+
type: 'signals_baseline',
|
|
201
|
+
body: JSON.stringify(baseline),
|
|
202
|
+
produced_by: 'brainclaw-scanner',
|
|
203
|
+
},
|
|
204
|
+
}, cwd);
|
|
205
|
+
warnings.push('survey baseline attached (artifact type signals_baseline, produced by the deterministic scanner) — enrich it into your signals_report instead of re-scanning the repo from scratch.');
|
|
206
|
+
}
|
|
207
|
+
catch { /* best-effort */ }
|
|
184
208
|
return { action: 'opened', loop, warnings };
|
|
185
209
|
}
|
|
186
210
|
finally {
|
|
@@ -58,6 +58,8 @@ export const BclawLoopTurnSchema = z.object({
|
|
|
58
58
|
role: z.string().optional(),
|
|
59
59
|
input: z.string().optional(),
|
|
60
60
|
assignment_id: z.string().optional(),
|
|
61
|
+
/** pln#562 step 4 — claim binding the turn's slot to a dispatched instance. */
|
|
62
|
+
claim_id: z.string().optional(),
|
|
61
63
|
dispatch: z.boolean().optional(),
|
|
62
64
|
expected_version: z.number().int().nonnegative().optional(),
|
|
63
65
|
...CallerEnvelopeFields,
|