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
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
name: janitor
|
|
2
|
-
description: Clean up stale claims, archive old notes, check for orphaned files
|
|
3
|
-
trust_level: contributor
|
|
4
|
-
trigger: manual
|
|
5
|
-
prompt: >-
|
|
6
|
-
Clean up this project: prune stale claims, archive old runtime notes, check
|
|
7
|
-
for orphaned files. Use bclaw_doctor, prune, and release-claims.
|
|
8
|
-
invoke: codex exec --full-auto "{prompt}"
|
|
9
|
-
tags:
|
|
10
|
-
- maintenance
|
|
11
|
-
- built-in
|
|
1
|
+
name: janitor
|
|
2
|
+
description: Clean up stale claims, archive old notes, check for orphaned files
|
|
3
|
+
trust_level: contributor
|
|
4
|
+
trigger: manual
|
|
5
|
+
prompt: >-
|
|
6
|
+
Clean up this project: prune stale claims, archive old runtime notes, check
|
|
7
|
+
for orphaned files. Use bclaw_doctor, prune, and release-claims.
|
|
8
|
+
invoke: codex exec --full-auto "{prompt}"
|
|
9
|
+
tags:
|
|
10
|
+
- maintenance
|
|
11
|
+
- built-in
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
name: onboarder
|
|
2
|
-
description: Generate a project summary for new team members
|
|
3
|
-
trust_level: observer
|
|
4
|
-
trigger: manual
|
|
5
|
-
prompt: >-
|
|
6
|
-
Generate a project summary for a new team member. Read the board, plans,
|
|
7
|
-
decisions, constraints, and produce a concise onboarding document.
|
|
8
|
-
invoke: claude -p "{prompt}" --allowedTools "Edit,Write,Bash,Read,Glob,Grep"
|
|
9
|
-
tags:
|
|
10
|
-
- onboarding
|
|
11
|
-
- built-in
|
|
1
|
+
name: onboarder
|
|
2
|
+
description: Generate a project summary for new team members
|
|
3
|
+
trust_level: observer
|
|
4
|
+
trigger: manual
|
|
5
|
+
prompt: >-
|
|
6
|
+
Generate a project summary for a new team member. Read the board, plans,
|
|
7
|
+
decisions, constraints, and produce a concise onboarding document.
|
|
8
|
+
invoke: claude -p "{prompt}" --allowedTools "Edit,Write,Bash,Read,Glob,Grep"
|
|
9
|
+
tags:
|
|
10
|
+
- onboarding
|
|
11
|
+
- built-in
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
name: reviewer
|
|
2
|
-
description: Review pending candidates and give structured opinions
|
|
3
|
-
trust_level: trusted
|
|
4
|
-
trigger: manual
|
|
5
|
-
prompt: >-
|
|
6
|
-
Review pending candidates in this project. For each, give a structured
|
|
7
|
-
opinion (approve/reject with reasons). Use
|
|
8
|
-
bclaw_find(entity: "candidate", filter: {status: "pending"}) and
|
|
9
|
-
bclaw_transition(entity: "candidate", id, to: "accepted" | "rejected").
|
|
10
|
-
invoke: claude -p "{prompt}" --allowedTools "Edit,Write,Bash,Read,Glob,Grep"
|
|
11
|
-
tags:
|
|
12
|
-
- review
|
|
13
|
-
- built-in
|
|
1
|
+
name: reviewer
|
|
2
|
+
description: Review pending candidates and give structured opinions
|
|
3
|
+
trust_level: trusted
|
|
4
|
+
trigger: manual
|
|
5
|
+
prompt: >-
|
|
6
|
+
Review pending candidates in this project. For each, give a structured
|
|
7
|
+
opinion (approve/reject with reasons). Use
|
|
8
|
+
bclaw_find(entity: "candidate", filter: {status: "pending"}) and
|
|
9
|
+
bclaw_transition(entity: "candidate", id, to: "accepted" | "rejected").
|
|
10
|
+
invoke: claude -p "{prompt}" --allowedTools "Edit,Write,Bash,Read,Glob,Grep"
|
|
11
|
+
tags:
|
|
12
|
+
- review
|
|
13
|
+
- built-in
|
|
@@ -20,15 +20,44 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import fs from 'node:fs';
|
|
22
22
|
import path from 'node:path';
|
|
23
|
+
import { execFileSync } from 'node:child_process';
|
|
23
24
|
import { loadAssignment, listAssignments } from './assignments.js';
|
|
25
|
+
import { logger } from './logger.js';
|
|
24
26
|
import { loadAgentRun, listAgentRuns } from './agentruns.js';
|
|
25
27
|
import { loadClaim } from './claims.js';
|
|
26
28
|
import { getLoop, listLoops } from './loops/store.js';
|
|
27
29
|
import { isProcessAlive } from './agentrun-reconciler.js';
|
|
28
|
-
import {
|
|
30
|
+
import { findRuntimeNoteById } from './runtime.js';
|
|
31
|
+
import { latestActivityMs, decodeOemAwareBuffer } from './runtime-signals.js';
|
|
29
32
|
import { LaneResultSchema } from './schema.js';
|
|
30
33
|
const DEFAULT_TAIL = 20;
|
|
31
34
|
const DEFAULT_STALL_MS = 5 * 60_000;
|
|
35
|
+
const DEFAULT_BASE_REF = 'master';
|
|
36
|
+
/**
|
|
37
|
+
* pln#554 — worktree git evidence, the signal that beats process/administrative
|
|
38
|
+
* status: a worker that committed everything to its lane branch has DELIVERED,
|
|
39
|
+
* whatever its pid/heartbeat/assignment.status say. Shared by dispatch-status
|
|
40
|
+
* and `brainclaw dispatch watch`. Returns undefined when there is no worktree
|
|
41
|
+
* or git could not be queried (never conclude "no commits" from a failed read).
|
|
42
|
+
*/
|
|
43
|
+
export function gitEvidence(worktreePath, baseRef) {
|
|
44
|
+
if (!worktreePath)
|
|
45
|
+
return undefined;
|
|
46
|
+
try {
|
|
47
|
+
const ahead = execFileSync('git', ['-C', worktreePath, 'rev-list', '--count', `${baseRef}..HEAD`], {
|
|
48
|
+
encoding: 'utf-8', timeout: 15000,
|
|
49
|
+
}).trim();
|
|
50
|
+
const status = execFileSync('git', ['-C', worktreePath, 'status', '--short'], {
|
|
51
|
+
encoding: 'utf-8', timeout: 15000,
|
|
52
|
+
});
|
|
53
|
+
const dirty = status.split('\n').filter((l) => l.trim() && !l.startsWith('??')).length;
|
|
54
|
+
return { commitsAhead: Number.parseInt(ahead, 10) || 0, dirtyTracked: dirty };
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
logger.debug('dispatch status: git evidence unavailable:', err);
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
32
61
|
// ── Internal helpers ──────────────────────────────────────────────────────
|
|
33
62
|
function readLogTail(filePath, lines) {
|
|
34
63
|
try {
|
|
@@ -36,7 +65,9 @@ function readLogTail(filePath, lines) {
|
|
|
36
65
|
if (lines <= 0) {
|
|
37
66
|
return { path: filePath, exists: true, size_bytes: stat.size };
|
|
38
67
|
}
|
|
39
|
-
|
|
68
|
+
// can_c39f0961: Windows-native tools write OEM cp850 — decode-aware read
|
|
69
|
+
// instead of blind utf-8 so the tail is human-readable.
|
|
70
|
+
const content = decodeOemAwareBuffer(fs.readFileSync(filePath));
|
|
40
71
|
const all = content.split(/\r?\n/);
|
|
41
72
|
// Strip trailing empty line from final \n
|
|
42
73
|
if (all.length > 0 && all[all.length - 1] === '')
|
|
@@ -155,6 +186,18 @@ function computeDiagnosis(assignment, agentRun, runtime, options) {
|
|
|
155
186
|
: `Worker reported "${lr.status}". Read the LANE-RESULT summary + stderr; address the blocker or reroute.`,
|
|
156
187
|
};
|
|
157
188
|
}
|
|
189
|
+
// pln#554 — git evidence is the #2 signal, ABOVE process sentinels and
|
|
190
|
+
// administrative status: commits ahead of base with a clean tracked tree
|
|
191
|
+
// means the worker delivered everything to the branch, even if its pid is
|
|
192
|
+
// dead, its heartbeat stale, or the run was relabeled interrupted by a TTL
|
|
193
|
+
// sweep (can_948acfd6). The verdict is "harvest it" — never "kill and reroute".
|
|
194
|
+
if ((runtime.commits_ahead ?? 0) > 0 && runtime.dirty_tracked === 0) {
|
|
195
|
+
return {
|
|
196
|
+
health: 'terminal',
|
|
197
|
+
summary: `worker delivered: ${runtime.commits_ahead} commit(s) ahead of base with a clean tracked tree — everything is on the lane branch${agentRun && !TERMINAL_RUN_STATUSES.has(agentRun.status) ? ` (agent_run still ${agentRun.status}; exit formalities missing — harvest reconciles it)` : ''}`,
|
|
198
|
+
recommended_next_action: 'Worker delivered; harvest it: `brainclaw harvest <assignment_id>` to ingest and merge the lane branch. Do NOT kill or reroute.',
|
|
199
|
+
};
|
|
200
|
+
}
|
|
158
201
|
if (!agentRun) {
|
|
159
202
|
return {
|
|
160
203
|
health: 'not_dispatched',
|
|
@@ -234,7 +277,15 @@ export function getDispatchStatus(options) {
|
|
|
234
277
|
const resolved = resolveTarget(options.target_id, cwd);
|
|
235
278
|
const assignmentId = resolved.assignment_id;
|
|
236
279
|
const assignment = assignmentId ? loadAssignment(assignmentId, cwd) : undefined;
|
|
237
|
-
|
|
280
|
+
// loadClaim THROWS on a missing id — a GC'd/never-created claim must not
|
|
281
|
+
// crash the whole diagnostic (sprint 1.5).
|
|
282
|
+
let claim;
|
|
283
|
+
if (assignment?.claim_id) {
|
|
284
|
+
try {
|
|
285
|
+
claim = loadClaim(assignment.claim_id, cwd);
|
|
286
|
+
}
|
|
287
|
+
catch { /* claim gone — diagnose without it */ }
|
|
288
|
+
}
|
|
238
289
|
// Prefer the pre-resolved agent_run (when target_id was a run_…); otherwise
|
|
239
290
|
// look up by assignment_id and pick the most recent attempt.
|
|
240
291
|
let agentRun = resolved.agent_run;
|
|
@@ -260,7 +311,11 @@ export function getDispatchStatus(options) {
|
|
|
260
311
|
// pln#527 — filesystem-activity age: max mtime across the captured logs + the
|
|
261
312
|
// run's worktree files (skipping junctions). The truer liveness signal when
|
|
262
313
|
// the heartbeat / last_event_at is stale during a long single operation.
|
|
263
|
-
|
|
314
|
+
// can_948acfd6: also fall back to assignment.worktree_path — without it a
|
|
315
|
+
// LANE-RESULT.json sitting in the assignment's worktree was invisible when
|
|
316
|
+
// neither the run nor the claim carried the path, and the verdict degraded
|
|
317
|
+
// to 'read stderr for failure detail' despite a completed lane result.
|
|
318
|
+
const worktreeForFs = agentRun?.worktree_path ?? claim?.worktree_path ?? assignment?.worktree_path;
|
|
264
319
|
let lastFsActivityMs;
|
|
265
320
|
if (assignmentId) {
|
|
266
321
|
const lastFs = latestActivityMs(projectRoot, assignmentId, worktreeForFs);
|
|
@@ -277,6 +332,8 @@ export function getDispatchStatus(options) {
|
|
|
277
332
|
}
|
|
278
333
|
catch { /* no / invalid LANE-RESULT.json */ }
|
|
279
334
|
}
|
|
335
|
+
// pln#554 — worktree git evidence (commits ahead of base + dirty tracked files).
|
|
336
|
+
const evidence = gitEvidence(worktreeForFs, options.base_ref ?? DEFAULT_BASE_REF);
|
|
280
337
|
const runtime = {
|
|
281
338
|
pid: agentRun?.pid,
|
|
282
339
|
pid_alive: isProcessAlive(agentRun?.pid),
|
|
@@ -290,8 +347,25 @@ export function getDispatchStatus(options) {
|
|
|
290
347
|
},
|
|
291
348
|
last_fs_activity_ms: lastFsActivityMs,
|
|
292
349
|
lane_result: laneResult,
|
|
350
|
+
commits_ahead: evidence?.commitsAhead,
|
|
351
|
+
dirty_tracked: evidence?.dirtyTracked,
|
|
293
352
|
};
|
|
294
|
-
|
|
353
|
+
let diagnosis = computeDiagnosis(assignment, agentRun, runtime, { stallMs, nowMs });
|
|
354
|
+
// can_b8d53d18 — a `run_` target that resolves to nothing may be a LEGACY
|
|
355
|
+
// runtime_note id (pre-rtn_ prefix collision). Say so precisely instead of
|
|
356
|
+
// the generic "verify the target_id" message.
|
|
357
|
+
if (resolved.resolved_from === 'unresolved' && options.target_id.startsWith('run_')) {
|
|
358
|
+
try {
|
|
359
|
+
if (findRuntimeNoteById(options.target_id, {}, cwd)) {
|
|
360
|
+
diagnosis = {
|
|
361
|
+
health: 'unknown',
|
|
362
|
+
summary: `${options.target_id} is a runtime_note (legacy run_ id prefix), not an agent_run — nothing to dispatch-diagnose`,
|
|
363
|
+
recommended_next_action: 'Read it with bclaw_get(entity="runtime_note"). Run `brainclaw repair` to migrate legacy run_ note ids to rtn_.',
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
catch { /* diagnosis stays generic */ }
|
|
368
|
+
}
|
|
295
369
|
return {
|
|
296
370
|
target_id: options.target_id,
|
|
297
371
|
resolved_from: resolved.resolved_from,
|
package/dist/core/dispatcher.js
CHANGED
|
@@ -43,8 +43,8 @@ import { memoryDir } from './io.js';
|
|
|
43
43
|
import { loadVersionedJsonFile } from './migration.js';
|
|
44
44
|
import fs from 'node:fs';
|
|
45
45
|
import path from 'node:path';
|
|
46
|
-
import { buildInvokeCommand, resolveBriefMode, getCapabilityProfile, dispatchHasMcp, resolveConcurrencyLimit, resolveResourceKey, resolveModel, serializeConcurrencyLimit } from './agent-capability.js';
|
|
47
|
-
import { getRuntimeSignalPath } from './runtime-signals.js';
|
|
46
|
+
import { buildInvokeCommand, resolveBriefMode, getCapabilityProfile, dispatchHasMcp, dispatchCanCommit, isSandboxedSpawn, resolveConcurrencyLimit, resolveResourceKey, resolveModel, serializeConcurrencyLimit } from './agent-capability.js';
|
|
47
|
+
import { getRuntimeSignalPath, getWorktreeHeartbeatPath } from './runtime-signals.js';
|
|
48
48
|
import { attemptExecution } from './execution.js';
|
|
49
49
|
import { createAssignment, transitionAssignment, generateAssignmentId, patchAssignmentMessageId } from './assignments.js';
|
|
50
50
|
import { createAgentRun, transitionAgentRun } from './agentruns.js';
|
|
@@ -211,12 +211,28 @@ export function analyzeSequence(cwd) {
|
|
|
211
211
|
* heartbeat. This is the worker-side half of the liveness contract whose
|
|
212
212
|
* engine-side floor is the wrapper + reconciler (steps 4 + 1).
|
|
213
213
|
*/
|
|
214
|
-
export function buildLivenessSection(cwd, assignmentId) {
|
|
215
|
-
|
|
214
|
+
export function buildLivenessSection(cwd, assignmentId, worktreePath, opts) {
|
|
215
|
+
// sprint 1.5 (dogfooding): the project-root signal path is NOT writable from
|
|
216
|
+
// inside worker sandboxes (Claude Code restricts writes to its working dirs;
|
|
217
|
+
// codex workspace-write roots exclude the project root) — the brief was
|
|
218
|
+
// demanding a heartbeat the worker could not write. When the worker has a
|
|
219
|
+
// worktree, point step 0 at a worktree-local heartbeat instead; every reader
|
|
220
|
+
// (reconciler, sweeper, dispatch_status fs-activity) checks both locations.
|
|
221
|
+
//
|
|
222
|
+
// pln#554 step 4 — sandbox-aware: codex workspace-write refuses even absolute
|
|
223
|
+
// paths in some configurations (cnd_asgn_7336aa79_heartbeat_sandbox /
|
|
224
|
+
// can_asgn_b0169fd8_heartbeat). When the execution adapter KNOWS the worker is
|
|
225
|
+
// sandboxed, point the write command at a worktree-RELATIVE path (the sandbox
|
|
226
|
+
// cwd is the worktree root) — same file, sandbox-proof spelling.
|
|
227
|
+
const sandboxRelative = opts?.sandboxed === true && !!worktreePath;
|
|
228
|
+
const hbPath = worktreePath
|
|
229
|
+
? getWorktreeHeartbeatPath(worktreePath, assignmentId)
|
|
230
|
+
: getRuntimeSignalPath(cwd, assignmentId, 'heartbeat');
|
|
231
|
+
const targetPath = sandboxRelative ? `.brainclaw-heartbeat-${assignmentId}` : hbPath;
|
|
216
232
|
const isWin = process.platform === 'win32';
|
|
217
233
|
const writeCmd = isWin
|
|
218
|
-
? `echo work_loop_reached ${assignmentId} > "${
|
|
219
|
-
: `printf 'work_loop_reached ${assignmentId} %s' "$(date +%s)" > "${
|
|
234
|
+
? `echo work_loop_reached ${assignmentId} > "${targetPath}"`
|
|
235
|
+
: `printf 'work_loop_reached ${assignmentId} %s' "$(date +%s)" > "${targetPath}"`;
|
|
220
236
|
return [
|
|
221
237
|
'## Liveness — DO THIS FIRST (step 0)',
|
|
222
238
|
'Before ANY other action, prove you reached your work loop by writing a heartbeat,',
|
|
@@ -228,7 +244,28 @@ export function buildLivenessSection(cwd, assignmentId) {
|
|
|
228
244
|
'```sh',
|
|
229
245
|
writeCmd,
|
|
230
246
|
'```',
|
|
231
|
-
|
|
247
|
+
sandboxRelative
|
|
248
|
+
? `Heartbeat file (worktree-RELATIVE — run it from the worktree root, your sandbox cwd; sandboxes refuse the absolute coordination path): ${targetPath}`
|
|
249
|
+
: `Heartbeat file (absolute, writable from your sandbox): ${hbPath}`,
|
|
250
|
+
...(worktreePath ? ['If that write is denied, use any file edit in your worktree as your liveness signal and continue — do NOT stall on the heartbeat.'] : []),
|
|
251
|
+
'',
|
|
252
|
+
].join('\n');
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* pln#554 step 4 — working defaults baked into every generated brief, distilled
|
|
256
|
+
* from the 2026-06-10 session: (a) incremental commits so a worker death costs
|
|
257
|
+
* one step max (the orphaned recoveries that night lost zero work ONLY because
|
|
258
|
+
* the diff was still on disk); (b) a split validation bar so parallel workers
|
|
259
|
+
* don't pile full test suites onto a memory-pressured machine.
|
|
260
|
+
*/
|
|
261
|
+
export function buildWorkingDefaultsSection(opts) {
|
|
262
|
+
const commitRule = opts.canCommit
|
|
263
|
+
? '- **Incremental commits**: commit after EACH completed step (conventional message). Never hold more than one step uncommitted — a worker death then costs at most one step, and the coordinator can harvest everything already on the branch.'
|
|
264
|
+
: '- **Incremental delivery**: your sandbox cannot `git commit` — finish steps in order and keep every file saved as you complete each step; the coordinator commits the worktree on your behalf at harvest. Never leave a step half-edited.';
|
|
265
|
+
return [
|
|
266
|
+
'## Working defaults',
|
|
267
|
+
commitRule,
|
|
268
|
+
'- **Validation bar**: run `tsc --noEmit` (or the project typecheck) + the targeted unit tests for the files you touched ONLY. Do NOT run the full default test suite — the coordinator runs the full gate after harvest (prevents test-suite pileups when several workers run in parallel).',
|
|
232
269
|
'',
|
|
233
270
|
].join('\n');
|
|
234
271
|
}
|
|
@@ -364,12 +401,18 @@ export function generateBrief(plan, item, cwd, briefMode, options) {
|
|
|
364
401
|
if (plan.estimated_effort)
|
|
365
402
|
parts.push(`Estimated effort: ${plan.estimated_effort} minutes`);
|
|
366
403
|
parts.push('');
|
|
404
|
+
// Capability profile drives the sandbox-aware liveness path + the working
|
|
405
|
+
// defaults' commit rule (pln#554 step 4) and the transport addendum below.
|
|
406
|
+
const briefProfile = options?.agent ? getCapabilityProfile(options.agent) : undefined;
|
|
407
|
+
const briefSandboxed = briefProfile ? isSandboxedSpawn(briefProfile) : false;
|
|
367
408
|
// pln#520 step 5 — liveness heartbeat instruction, first actionable block so
|
|
368
409
|
// the worker writes work_loop_reached before anything else. Only when an
|
|
369
410
|
// assignment id is known (the heartbeat is keyed by it).
|
|
370
411
|
if (options?.assignmentId) {
|
|
371
|
-
parts.push(buildLivenessSection(cwd, options.assignmentId));
|
|
412
|
+
parts.push(buildLivenessSection(cwd, options.assignmentId, options.worktreePath, { sandboxed: briefSandboxed }));
|
|
372
413
|
}
|
|
414
|
+
// pln#554 step 4 — working defaults (incremental commits + validation bar).
|
|
415
|
+
parts.push(buildWorkingDefaultsSection({ canCommit: briefProfile ? dispatchCanCommit(briefProfile) : true }));
|
|
373
416
|
// Steps if any
|
|
374
417
|
if (plan.steps?.length) {
|
|
375
418
|
parts.push('## Steps');
|
|
@@ -426,7 +469,7 @@ export function generateBrief(plan, item, cwd, briefMode, options) {
|
|
|
426
469
|
if (mode === 'full') {
|
|
427
470
|
parts.push(buildProtocolSection(options));
|
|
428
471
|
}
|
|
429
|
-
// pln#528 — transport-aware addendum (debrief
|
|
472
|
+
// pln#528 — transport-aware addendum (field debrief P1#2). When the agent is
|
|
430
473
|
// spawned sandboxed (no MCP + no git commit — e.g. codex --sandbox
|
|
431
474
|
// workspace-write), the MCP lifecycle lines in the Protocol section do NOT
|
|
432
475
|
// apply. Say so explicitly and make the FILE protocol authoritative, so the
|
|
@@ -435,7 +478,6 @@ export function generateBrief(plan, item, cwd, briefMode, options) {
|
|
|
435
478
|
// so the reconciler-independent path is preserved; this addendum disambiguates
|
|
436
479
|
// the transport rather than stripping the section — the full compact reversal
|
|
437
480
|
// is a separate human-owned call on the May-vs-June MCP-availability conflict.)
|
|
438
|
-
const briefProfile = options?.agent ? getCapabilityProfile(options.agent) : undefined;
|
|
439
481
|
if (briefProfile && !dispatchHasMcp(briefProfile)) {
|
|
440
482
|
parts.push('## ⚠ Transport: sandboxed run (no MCP, no commit)');
|
|
441
483
|
parts.push('Your runtime is sandboxed — the brainclaw MCP server is NOT reachable and `git commit` is unavailable (.git is outside the sandbox root). Any `bclaw_*` MCP instruction above does NOT apply to you. Report your outcome via the FILE protocol only — it is authoritative for this run:');
|
|
@@ -468,6 +510,15 @@ export function generateDispatchBrief(options) {
|
|
|
468
510
|
if (options.worktreePath)
|
|
469
511
|
parts.push(`Worktree: ${options.worktreePath}`);
|
|
470
512
|
parts.push('');
|
|
513
|
+
const taskBriefProfile = options.agent ? getCapabilityProfile(options.agent) : undefined;
|
|
514
|
+
const taskSandboxed = taskBriefProfile ? isSandboxedSpawn(taskBriefProfile) : false;
|
|
515
|
+
// sprint 1.5 — task-based briefs get the same step-0 liveness contract as
|
|
516
|
+
// plan-based briefs (worktree-local heartbeat, writable from any sandbox).
|
|
517
|
+
if (options.assignmentId && options.worktreePath) {
|
|
518
|
+
parts.push(buildLivenessSection(options.worktreePath, options.assignmentId, options.worktreePath, { sandboxed: taskSandboxed }));
|
|
519
|
+
}
|
|
520
|
+
// pln#554 step 4 — working defaults (incremental commits + validation bar).
|
|
521
|
+
parts.push(buildWorkingDefaultsSection({ canCommit: taskBriefProfile ? dispatchCanCommit(taskBriefProfile) : true }));
|
|
471
522
|
if (briefMode === 'full') {
|
|
472
523
|
parts.push(buildProtocolSection({
|
|
473
524
|
claimId: options.claimId,
|
|
@@ -476,7 +527,6 @@ export function generateDispatchBrief(options) {
|
|
|
476
527
|
}));
|
|
477
528
|
}
|
|
478
529
|
// pln#528 — transport-aware addendum for sandboxed agents (see generateBrief).
|
|
479
|
-
const taskBriefProfile = options.agent ? getCapabilityProfile(options.agent) : undefined;
|
|
480
530
|
if (taskBriefProfile && !dispatchHasMcp(taskBriefProfile)) {
|
|
481
531
|
parts.push('## ⚠ Transport: sandboxed run (no MCP, no commit)');
|
|
482
532
|
parts.push('Your runtime is sandboxed — the brainclaw MCP server is NOT reachable and `git commit` is unavailable (.git is outside the sandbox root). Any `bclaw_*` MCP instruction above does NOT apply to you. Report your outcome via the FILE protocol only — it is authoritative for this run:');
|
|
@@ -679,7 +729,7 @@ export async function dispatch(options, cwd) {
|
|
|
679
729
|
const invokeCmd = buildInvokeCommand(targetAgent, brief, { model: resolveModel(targetAgent, { override: options.model }) });
|
|
680
730
|
if (invokeCmd) {
|
|
681
731
|
const cmdPrefix = buildEnvPrefix(claimId);
|
|
682
|
-
result.commands.push({ agent: targetAgent, lane: readyItem.lane, command: `${cmdPrefix}${invokeCmd.bashCommand}`, shell: process.platform === 'win32' ? 'cmd' : (invokeCmd.shell ? 'bash' : 'sh') });
|
|
732
|
+
result.commands.push({ agent: targetAgent, lane: readyItem.lane, plan_id: readyItem.plan.id, command: `${cmdPrefix}${invokeCmd.bashCommand}`, shell: process.platform === 'win32' ? 'cmd' : (invokeCmd.shell ? 'bash' : 'sh') });
|
|
683
733
|
}
|
|
684
734
|
const deliveryEntry = { agent: targetAgent, plan_id: readyItem.plan.id, message_id: '(dry-run)', lane: readyItem.lane, channel: 'inbox', claim_id: claimId };
|
|
685
735
|
result.delivery_plan.push(deliveryEntry);
|
|
@@ -738,6 +788,7 @@ export async function dispatch(options, cwd) {
|
|
|
738
788
|
result.commands.push({
|
|
739
789
|
agent: targetAgent,
|
|
740
790
|
lane: readyItem.lane,
|
|
791
|
+
plan_id: readyItem.plan.id,
|
|
741
792
|
command: `${cmdPrefix}${invokeCmd.bashCommand}`,
|
|
742
793
|
shell: process.platform === 'win32' ? 'cmd' : (invokeCmd.shell ? 'bash' : 'sh'),
|
|
743
794
|
});
|
|
@@ -881,6 +932,7 @@ export async function dispatch(options, cwd) {
|
|
|
881
932
|
status_reason: 'CLI spawn launched by dispatcher',
|
|
882
933
|
tags: ['dispatch-run', ...(entry.lane ? [`lane:${entry.lane}`] : [])],
|
|
883
934
|
}, cwd);
|
|
935
|
+
entry.run_id = run.id;
|
|
884
936
|
transitionAgentRun(run.id, 'failed', {
|
|
885
937
|
actor: options.dispatcherAgent,
|
|
886
938
|
actor_id: options.dispatcherAgentId,
|
|
@@ -928,6 +980,7 @@ export async function dispatch(options, cwd) {
|
|
|
928
980
|
status_reason: execResult.error,
|
|
929
981
|
tags: ['dispatch-run', ...(entry.lane ? [`lane:${entry.lane}`] : [])],
|
|
930
982
|
}, cwd);
|
|
983
|
+
entry.run_id = run.id;
|
|
931
984
|
if (execResult.execution_status === 'delivered_and_started') {
|
|
932
985
|
transitionAgentRun(run.id, 'launching', {
|
|
933
986
|
actor: options.dispatcherAgent,
|
|
@@ -12,14 +12,15 @@
|
|
|
12
12
|
* until later slices wire them in.
|
|
13
13
|
*/
|
|
14
14
|
import path from 'node:path';
|
|
15
|
-
import { loadState,
|
|
15
|
+
import { loadState, mutateState } from './state.js';
|
|
16
16
|
import { archiveCandidate, listCandidates, loadCandidate, saveCandidate, } from './candidates.js';
|
|
17
17
|
import { addCrossProjectLink, removeCrossProjectLink, resolveCrossProjectLinks, } from './cross-project.js';
|
|
18
|
-
import { listClaims } from './claims.js';
|
|
18
|
+
import { listClaims, loadClaim, saveClaim } from './claims.js';
|
|
19
19
|
import { listActionRequired } from './actions.js';
|
|
20
20
|
import { deleteAssignment, listAssignments, loadAssignment, saveAssignment, transitionAssignment } from './assignments.js';
|
|
21
21
|
import { listAgentRuns } from './agentruns.js';
|
|
22
22
|
import { reconcileAgentRun, reconcileDeadPidRunningAgentRunAtRead, TERMINAL_STATUSES } from './agentrun-reconciler.js';
|
|
23
|
+
import { isObserverMode } from './observer-mode.js';
|
|
23
24
|
import { deleteRuntimeNote, listRuntimeNotes, saveRuntimeNote, } from './runtime.js';
|
|
24
25
|
import { createSequence, deleteSequence, listSequences, updateSequence, } from './sequence.js';
|
|
25
26
|
import { createConstraint, createDecision, createTrap, } from './operations/memory-write.js';
|
|
@@ -61,6 +62,18 @@ function passesProvenanceFilter(item, filter) {
|
|
|
61
62
|
}
|
|
62
63
|
return true;
|
|
63
64
|
}
|
|
65
|
+
function isLegacyProvenance(item) {
|
|
66
|
+
return item.provenance?.kind === 'legacy';
|
|
67
|
+
}
|
|
68
|
+
function isLowConfidenceAutoReflect(item, filter) {
|
|
69
|
+
const provenance = item.provenance;
|
|
70
|
+
if (provenance?.kind !== 'auto_reflect')
|
|
71
|
+
return false;
|
|
72
|
+
const threshold = typeof filter.minAutoReflectConfidence === 'number'
|
|
73
|
+
? filter.minAutoReflectConfidence
|
|
74
|
+
: DEFAULT_MIN_AUTO_REFLECT_CONFIDENCE;
|
|
75
|
+
return (provenance.confidence ?? 0) < threshold;
|
|
76
|
+
}
|
|
64
77
|
/** Thrown when a verb is not yet wired for a given entity. */
|
|
65
78
|
export class EntityOperationUnsupportedError extends Error {
|
|
66
79
|
constructor(entity, verb, hint) {
|
|
@@ -102,6 +115,12 @@ export class InvalidTransitionError extends Error {
|
|
|
102
115
|
*/
|
|
103
116
|
function loadAgentRunsWithReconciliation(cwd) {
|
|
104
117
|
const runs = listAgentRuns(cwd);
|
|
118
|
+
// Observer mode (BRAINCLAW_OBSERVER=1) suppresses the lazy reconciliation
|
|
119
|
+
// pass. A dashboard reading agent_run records must never transition them —
|
|
120
|
+
// that loop drove the 2026-06-10 lock storm (every poll could mutate every
|
|
121
|
+
// non-terminal run, holding the mutation lock under each transition).
|
|
122
|
+
if (isObserverMode())
|
|
123
|
+
return runs;
|
|
105
124
|
for (const run of runs) {
|
|
106
125
|
if (run.status === 'running') {
|
|
107
126
|
try {
|
|
@@ -122,9 +141,21 @@ function loadAgentRunsWithReconciliation(cwd) {
|
|
|
122
141
|
}
|
|
123
142
|
export function listEntities(name, cwd, filter = {}) {
|
|
124
143
|
const all = loadAll(name, cwd);
|
|
125
|
-
const
|
|
144
|
+
const fieldFiltered = applyFieldFilter(all, filter);
|
|
145
|
+
const excludedLegacy = filter.includeLegacy === true
|
|
146
|
+
? 0
|
|
147
|
+
: fieldFiltered.filter((item) => isLegacyProvenance(item)).length;
|
|
148
|
+
const excludedLowConfidenceAutoReflect = fieldFiltered.filter((item) => isLowConfidenceAutoReflect(item, filter)).length;
|
|
149
|
+
const filtered = fieldFiltered.filter((item) => passesProvenanceFilter(item, filter));
|
|
126
150
|
const paged = applyPaging(filtered, filter);
|
|
127
|
-
return {
|
|
151
|
+
return {
|
|
152
|
+
entity: name,
|
|
153
|
+
total: filtered.length,
|
|
154
|
+
items: paged,
|
|
155
|
+
excluded_legacy: excludedLegacy,
|
|
156
|
+
excluded_low_confidence_auto_reflect: excludedLowConfidenceAutoReflect,
|
|
157
|
+
total_before_provenance_filter: fieldFiltered.length,
|
|
158
|
+
};
|
|
128
159
|
}
|
|
129
160
|
/** Default serialized-items budget (chars) — keeps a bclaw_find payload well under the ~25k-token MCP cap (trp#449). */
|
|
130
161
|
export const DEFAULT_FIND_CHAR_BUDGET = 40000;
|
|
@@ -182,8 +213,10 @@ function loadAll(name, cwd) {
|
|
|
182
213
|
}
|
|
183
214
|
}
|
|
184
215
|
function applyFilter(items, filter) {
|
|
216
|
+
return applyFieldFilter(items, filter).filter((item) => passesProvenanceFilter(item, filter));
|
|
217
|
+
}
|
|
218
|
+
function applyFieldFilter(items, filter) {
|
|
185
219
|
let result = items;
|
|
186
|
-
result = result.filter((item) => passesProvenanceFilter(item, filter));
|
|
187
220
|
if (filter.status) {
|
|
188
221
|
result = result.filter((item) => item.status === filter.status);
|
|
189
222
|
}
|
|
@@ -441,6 +474,20 @@ export function updateEntity(name, id, patch, cwd) {
|
|
|
441
474
|
saveAssignment(patched, cwd);
|
|
442
475
|
return { entity: name, id };
|
|
443
476
|
}
|
|
477
|
+
case 'claim': {
|
|
478
|
+
// sprint 1.5 — description + worktree_path (manual-worktree registration).
|
|
479
|
+
// Status changes still go through bclaw_transition / release flows.
|
|
480
|
+
let claim;
|
|
481
|
+
try {
|
|
482
|
+
claim = loadClaim(id, cwd);
|
|
483
|
+
}
|
|
484
|
+
catch {
|
|
485
|
+
throw new EntityNotFoundError(name, id);
|
|
486
|
+
}
|
|
487
|
+
const patched = { ...claim, ...patch };
|
|
488
|
+
saveClaim(patched, cwd);
|
|
489
|
+
return { entity: name, id };
|
|
490
|
+
}
|
|
444
491
|
case 'candidate': {
|
|
445
492
|
// Note: `candidate.type` is intentionally create-only (not in
|
|
446
493
|
// candidate.updatable at entity-registry.ts) — no validation needed.
|
|
@@ -561,8 +608,9 @@ export function transitionEntity(name, id, to, cwd, _reason) {
|
|
|
561
608
|
if (!spec.statusField) {
|
|
562
609
|
throw new Error(`${name} has no lifecycle (statusField is undefined)`);
|
|
563
610
|
}
|
|
611
|
+
const statusField = spec.statusField;
|
|
564
612
|
const current = getEntity(name, id, cwd);
|
|
565
|
-
const from = current[
|
|
613
|
+
const from = current[statusField];
|
|
566
614
|
if (!from) {
|
|
567
615
|
throw new Error(`${name} '${id}' has no '${spec.statusField}' field set`);
|
|
568
616
|
}
|
|
@@ -579,15 +627,15 @@ export function transitionEntity(name, id, to, cwd, _reason) {
|
|
|
579
627
|
case 'decision':
|
|
580
628
|
case 'constraint':
|
|
581
629
|
case 'trap': {
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
630
|
+
mutateState((state) => {
|
|
631
|
+
const bucket = name === 'decision' ? state.recent_decisions
|
|
632
|
+
: name === 'constraint' ? state.active_constraints
|
|
633
|
+
: state.known_traps;
|
|
634
|
+
const item = bucket.find((x) => x.id === id);
|
|
635
|
+
if (!item)
|
|
636
|
+
throw new EntityNotFoundError(name, id);
|
|
637
|
+
item[statusField] = to;
|
|
638
|
+
}, cwd);
|
|
591
639
|
return { entity: name, id, from, to, side_effects: sideEffects };
|
|
592
640
|
}
|
|
593
641
|
case 'candidate': {
|
|
@@ -618,20 +666,19 @@ export function transitionEntity(name, id, to, cwd, _reason) {
|
|
|
618
666
|
// ─── Helpers ──────────────────────────────────────────────────────────
|
|
619
667
|
/**
|
|
620
668
|
* Stamp provenance on a state-resident record (plan, decision, constraint, trap)
|
|
621
|
-
* immediately after create.
|
|
622
|
-
* v1 since create is infrequent compared to reads.
|
|
669
|
+
* immediately after create.
|
|
623
670
|
*/
|
|
624
671
|
function stampProvenanceOnStateItem(name, id, provenance, cwd) {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
672
|
+
mutateState((state) => {
|
|
673
|
+
const bucket = name === 'plan' ? state.plan_items
|
|
674
|
+
: name === 'decision' ? state.recent_decisions
|
|
675
|
+
: name === 'constraint' ? state.active_constraints
|
|
676
|
+
: state.known_traps;
|
|
677
|
+
const item = bucket.find((x) => x.id === id);
|
|
678
|
+
if (!item)
|
|
679
|
+
return;
|
|
680
|
+
item.provenance = provenance;
|
|
681
|
+
}, cwd);
|
|
635
682
|
}
|
|
636
683
|
function requireString(data, field) {
|
|
637
684
|
const value = data[field];
|
|
@@ -44,7 +44,7 @@ const step = {
|
|
|
44
44
|
name: 'step',
|
|
45
45
|
shortLabelPrefix: 'stp',
|
|
46
46
|
schema: PlanStepSchema,
|
|
47
|
-
updatable: ['text', 'assignee'],
|
|
47
|
+
updatable: ['text', 'assignee', 'estimated_effort', 'actual_effort'],
|
|
48
48
|
statusField: 'status',
|
|
49
49
|
transitions: {
|
|
50
50
|
todo: ['in_progress', 'blocked', 'done'],
|
|
@@ -63,7 +63,9 @@ const claim = {
|
|
|
63
63
|
name: 'claim',
|
|
64
64
|
shortLabelPrefix: 'clm',
|
|
65
65
|
schema: ClaimSchema,
|
|
66
|
-
|
|
66
|
+
// worktree_path: sprint 1.5 — coordinators register manual worktrees (or fix
|
|
67
|
+
// stale paths) so harvest/dispatch_status can resolve LANE-RESULT locations.
|
|
68
|
+
updatable: ['description', 'worktree_path'],
|
|
67
69
|
statusField: 'status',
|
|
68
70
|
transitions: {
|
|
69
71
|
active: ['released', 'stale'],
|
|
@@ -107,7 +109,16 @@ const decision = {
|
|
|
107
109
|
name: 'decision',
|
|
108
110
|
shortLabelPrefix: 'dec',
|
|
109
111
|
schema: DecisionSchema,
|
|
110
|
-
updatable: [
|
|
112
|
+
updatable: [
|
|
113
|
+
'text', 'tags', 'outcome', 'scope', 'related_paths', 'verified_at', 'verify_cmd',
|
|
114
|
+
// pln#544 lifecycle (touched via memory-lifecycle.ts recordMemoryEvent;
|
|
115
|
+
// exposing them here keeps bclaw_update straight-through for tests and
|
|
116
|
+
// operator backfills).
|
|
117
|
+
'last_confirmed_at', 'last_infirmed_at',
|
|
118
|
+
'confirmation_count', 'infirmation_count',
|
|
119
|
+
'saved_me_count', 'misled_me_count',
|
|
120
|
+
'confirmations',
|
|
121
|
+
],
|
|
111
122
|
statusField: 'outcome',
|
|
112
123
|
transitions: {
|
|
113
124
|
pending: ['approved', 'rejected', 'deferred'],
|
|
@@ -121,7 +132,14 @@ const constraint = {
|
|
|
121
132
|
name: 'constraint',
|
|
122
133
|
shortLabelPrefix: 'cst',
|
|
123
134
|
schema: ConstraintSchema,
|
|
124
|
-
updatable: [
|
|
135
|
+
updatable: [
|
|
136
|
+
'text', 'tags', 'category', 'scope', 'related_paths', 'expires_at',
|
|
137
|
+
// pln#544 lifecycle — see decision.updatable.
|
|
138
|
+
'last_confirmed_at', 'last_infirmed_at',
|
|
139
|
+
'confirmation_count', 'infirmation_count',
|
|
140
|
+
'saved_me_count', 'misled_me_count',
|
|
141
|
+
'confirmations',
|
|
142
|
+
],
|
|
125
143
|
statusField: 'status',
|
|
126
144
|
transitions: {
|
|
127
145
|
active: ['resolved', 'expired'],
|
|
@@ -137,7 +155,15 @@ const trap = {
|
|
|
137
155
|
name: 'trap',
|
|
138
156
|
shortLabelPrefix: 'trp',
|
|
139
157
|
schema: TrapSchema,
|
|
140
|
-
updatable: [
|
|
158
|
+
updatable: [
|
|
159
|
+
'text', 'tags', 'severity', 'scope', 'related_paths', 'expires_at', 'platform_scope',
|
|
160
|
+
'verified_at', 'verify_cmd',
|
|
161
|
+
// pln#544 lifecycle — see decision.updatable.
|
|
162
|
+
'last_confirmed_at', 'last_infirmed_at',
|
|
163
|
+
'confirmation_count', 'infirmation_count',
|
|
164
|
+
'saved_me_count', 'misled_me_count',
|
|
165
|
+
'confirmations',
|
|
166
|
+
],
|
|
141
167
|
statusField: 'status',
|
|
142
168
|
transitions: {
|
|
143
169
|
active: ['resolved', 'expired'],
|