brainclaw 1.12.0 → 1.14.0
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 +40 -0
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/cli.js +11 -3
- package/dist/commands/claim-resource.js +1 -0
- package/dist/commands/doctor.js +98 -0
- package/dist/commands/estimation-report.js +1 -1
- package/dist/commands/harvest.js +38 -24
- package/dist/commands/mcp.js +328 -46
- package/dist/commands/release-claim.js +21 -1
- package/dist/commands/session-start.js +16 -1
- package/dist/core/agent-capability.js +15 -4
- package/dist/core/agent-registry.js +7 -1
- package/dist/core/assignment-sweeper.js +92 -11
- package/dist/core/claims.js +160 -1
- package/dist/core/context.js +11 -4
- package/dist/core/dispatch-status.js +113 -7
- package/dist/core/entity-operations.js +51 -3
- package/dist/core/gc-semantic.js +79 -0
- package/dist/core/hint-aging.js +188 -0
- package/dist/core/hygiene-policy.js +77 -0
- package/dist/core/loops/store.js +33 -0
- package/dist/core/reputation.js +18 -0
- package/dist/core/schema.js +29 -0
- package/dist/core/worktree.js +198 -22
- package/dist/facts.js +36 -3
- package/dist/facts.json +35 -2
- package/docs/concepts/dispatch-supervisor.md +393 -0
- package/docs/mcp-schema-changelog.md +7 -2
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -66,6 +66,20 @@ Capable agents use the MCP equivalents `bclaw_code_find` / `bclaw_code_brief`, e
|
|
|
66
66
|
|
|
67
67
|
---
|
|
68
68
|
|
|
69
|
+
## Measured agent experience
|
|
70
|
+
|
|
71
|
+
The onboarding path a fresh agent takes — init, first `bclaw_work`, first `code_find` — is exercised on every CI run by a reproducible bench (`scripts/bench.mjs`) against a synthetic store calibrated on the shape of a real production project (~200 plans, ~500 handoffs, ~450 claims). Budgets (`bench-budgets.json`) live in the repo alongside the coverage gate; a regression beyond a per-scenario tolerance fails the build. The latest report ships as `dist/facts.json` under `facts.bench` so the site can render current numbers.
|
|
72
|
+
|
|
73
|
+
The bench covers three scenarios:
|
|
74
|
+
|
|
75
|
+
- **Cold onboard** — fresh machine → init → first useful context. Measures the baseline time-to-first-value.
|
|
76
|
+
- **Warm work** — `bclaw_work` consult over a real-shaped store. Tracks the cost of building context when the store is non-empty (the surface `pln#578`/`pln#566` optimise).
|
|
77
|
+
- **First edit** — `code_find` + `code_brief` on the fresh-agent path. Tracks payload compactness (`pln#598`) and match relevance (`pln#601`).
|
|
78
|
+
|
|
79
|
+
Reproduce locally with `npm run bench` (writes `dist/bench-report.json`); check budgets with `npm run bench:check`. The bench runs in-process against synthetic fixtures, so it is deterministic per seed and safe to run on any machine.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
69
83
|
## Agent Surfaces
|
|
70
84
|
|
|
71
85
|
brainclaw exposes the same collaboration state through three surfaces, but they do not have the same role in an agent-first workflow.
|
|
@@ -417,6 +431,32 @@ npm run test:coverage # with coverage report
|
|
|
417
431
|
|
|
418
432
|
For older releases (v0.x and the early v1.0 launch series), `git log` on `master` is the source of truth — every release commit follows the `chore(release): bump version to <semver>` convention, and the matching feature/fix commits reference their plan id (e.g. `feat(mcp): self-heal ... (pln#478)`).
|
|
419
433
|
|
|
434
|
+
### v1.14.0
|
|
435
|
+
|
|
436
|
+
Coordination hygiene, a dispatch-supervisor spec, and a monorepo worktree fix — from continued cross-machine dogfooding, each Codex-reviewed before merge:
|
|
437
|
+
|
|
438
|
+
- **Coordination hygiene v1** (pln#602) — family-level TTL sweep (park-don't-delete, `config.hygiene`-overridable), lazy at the `bclaw_work` read path (zero extra reads on a healthy store) + full at session-start, K-times aging of stale warnings/hints into one actionable aggregate, and `brainclaw doctor --hygiene`.
|
|
439
|
+
- **Worktree creation for in-tree projects** (pln#614) — `bclaw_coordinate(assign|review)` on a project dir that isn't the git root (app inside a monorepo) now resolves the real toplevel (`resolveGitToplevel` with a parent-walk past an invalid nested `.git`), so it spawns instead of failing with "not a git repository". Standalone projects unchanged; validated E2E on the reporting monorepo.
|
|
440
|
+
- **Dispatch-supervisor round-3 spec** (pln#545, docs) — `docs/concepts/dispatch-supervisor.md`: honest worker-liveness attribution (Node supervisor + `run_id`-keyed sentinels), A0→B hard dependency, Windows Job Object FAIL-CLOSED, complete behavior matrix. Implementation lands later as A0-first increments.
|
|
441
|
+
|
|
442
|
+
### v1.13.0
|
|
443
|
+
|
|
444
|
+
Operator-maturity batch from two days of heavy multi-agent dogfooding — dispatch/worktree lifecycle, claim parity, write-path auto-repair, model routing, benchmark gate, 2× faster context reads:
|
|
445
|
+
|
|
446
|
+
- **Model selection for codex and copilot spawns** (pln#606) — `dispatch run --model <m>` now reaches codex (`exec -m`) and copilot argv, verified on the installed binaries. The codex `--add-dir` writable-roots spike closed negative on Windows: file-first stays the codex transport.
|
|
447
|
+
- **Auto-repair identity & session on canonical writes** (pln#608) — `bclaw_create`/`update`/`remove`/`transition` auto-register + auto-session instead of throwing "Start a session first", with an explicit `session auto-created` warning; the trust boundary for unknown identities is unchanged.
|
|
448
|
+
- **Time-to-first-value benchmark with blocking CI budgets** (pln#604) — seeded synthetic stores, three scenarios (cold_onboard / warm_work / first_edit), budgets versioned in `bench-budgets.json`.
|
|
449
|
+
- **Claim lifecycle parity** (trp#928) — `bclaw_transition(entity='claim')` wired, `coordinator_override` (trusted+) on release/transition, cascade release with per-claim logging on plan-done / loop close / assignment-completed / harvest, entity-scoped `bclaw_find` filter rejections.
|
|
450
|
+
- **Squash-aware worktree GC + junction-safe removal** (trp#926) — squash-merged lanes are finally collected (content-based detection), and the Windows `node_modules`-junction wipe class is closed for good; `dispatch_status` compares against the worktree's creation ref.
|
|
451
|
+
- **VS Code extension** — Backlog pagination (recent plans were invisible, trp#925) and probe/spawn parity with classified, actionable resolver failures (trp#927).
|
|
452
|
+
- **Context read 2× faster on large stores** (pln#578) — 3 of the 4 full-store read passes per context build eliminated (disabled-reputation sweep ×2, estimation reload, triple candidate scan): 23.9 s → 11.9 s, 196 MB → 49 MB parsed, byte-identical output.
|
|
453
|
+
|
|
454
|
+
### v1.12.0
|
|
455
|
+
|
|
456
|
+
Auto-localized execution writes for multi-project workspaces (pln#597), from DGX-Spark dogfooding:
|
|
457
|
+
|
|
458
|
+
- **Execution writes auto-localize into a workspace sibling named by `project=X`** — `bclaw_create`/`bclaw_transition` (plan & claim), `bclaw_claim`, the step tools and `bclaw_delete_plan` open a session + sticky switch into a workspace store-chain child instead of rejecting with "limited to signaling entities", echoing `auto_switched`. The signaling-vs-execution boundary is re-scoped to federation (`cross_project_links`): federated links and unknown names stay blocked.
|
|
459
|
+
|
|
420
460
|
### v1.11.1
|
|
421
461
|
|
|
422
462
|
Agent-identity & session-hook resilience, from a fresh-CLI dogfood on a monorepo:
|
|
Binary file
|
package/dist/cli.js
CHANGED
|
@@ -782,12 +782,13 @@ program
|
|
|
782
782
|
.option('--verify-journal', 'Phase-2 cutover gate (pln#565): rebuild state from the event journal and diff vs live projections; exits non-zero on any drift')
|
|
783
783
|
.option('--spawn-check', 'Real spawn round-trip per installed agent before dispatch (pln#520 step 2): validates delivery + handshake on this host, exits non-zero on any installed-agent failure')
|
|
784
784
|
.option('--spawn-check-timeout <ms>', 'Per-agent timeout for --spawn-check (default 15000)', parseInt)
|
|
785
|
+
.option('--hygiene', 'Coordination-hygiene snapshot (pln#602): counts per family, park candidates, serve-count aging stats. Read-only.')
|
|
785
786
|
.action(async (options) => {
|
|
786
787
|
if (options.spawnCheck) {
|
|
787
788
|
await runDoctorSpawnCheck({ cwd: options.cwd, json: options.json, timeoutMs: options.spawnCheckTimeout });
|
|
788
789
|
return;
|
|
789
790
|
}
|
|
790
|
-
runDoctor({ ...options, afterMigration: options.afterMigration, dispatch: options.dispatch, verifyJournal: options.verifyJournal });
|
|
791
|
+
runDoctor({ ...options, afterMigration: options.afterMigration, dispatch: options.dispatch, verifyJournal: options.verifyJournal, hygiene: options.hygiene });
|
|
791
792
|
});
|
|
792
793
|
// --- repair (Phase 4 Sprint 2 Lane C / pln#397) ---
|
|
793
794
|
program
|
|
@@ -1179,10 +1180,16 @@ program
|
|
|
1179
1180
|
.option('--all', 'Include released claims in list')
|
|
1180
1181
|
.option('--json', 'Output as JSON for list')
|
|
1181
1182
|
.option('--plan-status <status>', 'Optional linked plan status when releasing: todo, in_progress, blocked, done, dropped')
|
|
1183
|
+
.option('--coordinator-override', 'Trusted+ only: release a claim owned by another agent')
|
|
1182
1184
|
.option('--store <target>', 'Target store level: local (default), repo, workspace')
|
|
1183
1185
|
.option('--local-only', 'Read from local store only for list (skip parent stores in chain)')
|
|
1184
1186
|
.action((subcommand, args, options) => {
|
|
1185
|
-
runClaimResource(subcommand, args, {
|
|
1187
|
+
runClaimResource(subcommand, args, {
|
|
1188
|
+
...options,
|
|
1189
|
+
planStatus: options.planStatus,
|
|
1190
|
+
coordinatorOverride: options.coordinatorOverride,
|
|
1191
|
+
localOnly: options.localOnly,
|
|
1192
|
+
});
|
|
1186
1193
|
});
|
|
1187
1194
|
// --- assignment ---
|
|
1188
1195
|
program
|
|
@@ -1222,8 +1229,9 @@ program
|
|
|
1222
1229
|
.command('release-claim <id>')
|
|
1223
1230
|
.description('Release a work claim')
|
|
1224
1231
|
.option('--plan-status <status>', 'Optional linked plan status: todo, in_progress, blocked, done, dropped')
|
|
1232
|
+
.option('--coordinator-override', 'Trusted+ only: release a claim owned by another agent')
|
|
1225
1233
|
.action((id, options) => {
|
|
1226
|
-
runReleaseClaim(id, options);
|
|
1234
|
+
runReleaseClaim(id, { ...options, coordinatorOverride: options.coordinatorOverride });
|
|
1227
1235
|
});
|
|
1228
1236
|
// --- release-claims ---
|
|
1229
1237
|
program
|
package/dist/commands/doctor.js
CHANGED
|
@@ -4,6 +4,10 @@ import os from 'node:os';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import * as childProcess from 'node:child_process';
|
|
6
6
|
import { reconcileAllOpenRuns } from '../core/agentrun-reconciler.js';
|
|
7
|
+
import { loadHygienePolicy } from '../core/hygiene-policy.js';
|
|
8
|
+
import { computeServeStats, loadServeRegistry } from '../core/hint-aging.js';
|
|
9
|
+
import { listAssignments } from '../core/assignments.js';
|
|
10
|
+
import { parkClosedAutoHandoffs } from '../core/gc-semantic.js';
|
|
7
11
|
import { runSpawnCheck, renderSpawnCheckReport } from '../core/spawn-check.js';
|
|
8
12
|
import { loadAgentRun } from '../core/agentruns.js';
|
|
9
13
|
import { listAgentIdentities, listDebrisAgentIdentities, resolveCurrentAgentIdentity } from '../core/agent-registry.js';
|
|
@@ -638,7 +642,101 @@ function runJournalVerification(options) {
|
|
|
638
642
|
if (drift.length > 0)
|
|
639
643
|
process.exit(1);
|
|
640
644
|
}
|
|
645
|
+
function medianAgeDays(items, nowMs) {
|
|
646
|
+
if (items.length === 0)
|
|
647
|
+
return 0;
|
|
648
|
+
const ages = items
|
|
649
|
+
.map((a) => Math.floor((nowMs - new Date(a.created_at).getTime()) / 86_400_000))
|
|
650
|
+
.sort((a, b) => a - b);
|
|
651
|
+
const mid = Math.floor(ages.length / 2);
|
|
652
|
+
return ages.length % 2 === 1 ? ages[mid] : (ages[mid - 1] + ages[mid]) / 2;
|
|
653
|
+
}
|
|
654
|
+
export function runHygieneReport(options = {}) {
|
|
655
|
+
const cwd = options.cwd;
|
|
656
|
+
const policy = loadHygienePolicy(cwd);
|
|
657
|
+
const nowMs = Date.now();
|
|
658
|
+
const allAssignments = listAssignments(cwd);
|
|
659
|
+
const open = allAssignments.filter((a) => a.status === 'offered' || a.status === 'accepted' || a.status === 'started');
|
|
660
|
+
const offered = open.filter((a) => a.status === 'offered');
|
|
661
|
+
const accepted = open.filter((a) => a.status === 'accepted');
|
|
662
|
+
const started = open.filter((a) => a.status === 'started');
|
|
663
|
+
const heartbeatAgeMs = (a) => {
|
|
664
|
+
const anchor = a.last_heartbeat_at ?? a.offered_at ?? a.created_at;
|
|
665
|
+
return nowMs - new Date(anchor).getTime();
|
|
666
|
+
};
|
|
667
|
+
const offered_park_candidates = offered.filter((a) => heartbeatAgeMs(a) > policy.assignment_offered_ttl_ms).length;
|
|
668
|
+
const accepted_park_candidates = accepted.filter((a) => heartbeatAgeMs(a) > policy.assignment_accepted_ttl_ms).length;
|
|
669
|
+
const handoffParkDry = parkClosedAutoHandoffs(cwd ?? process.cwd(), Math.floor(policy.handoff_closed_ttl_ms / 86_400_000), true);
|
|
670
|
+
const registry = loadServeRegistry(cwd);
|
|
671
|
+
const staleStats = computeServeStats(registry.warnings, policy.stale_warning_serve_k);
|
|
672
|
+
const hintsStats = computeServeStats(registry.hints, policy.workflow_hint_serve_k);
|
|
673
|
+
return {
|
|
674
|
+
generated_at: new Date(nowMs).toISOString(),
|
|
675
|
+
disabled: policy.disabled,
|
|
676
|
+
policy,
|
|
677
|
+
families: {
|
|
678
|
+
assignments: {
|
|
679
|
+
total_open: open.length,
|
|
680
|
+
offered: offered.length,
|
|
681
|
+
accepted: accepted.length,
|
|
682
|
+
started: started.length,
|
|
683
|
+
offered_park_candidates,
|
|
684
|
+
accepted_park_candidates,
|
|
685
|
+
median_open_age_days: medianAgeDays(open, nowMs),
|
|
686
|
+
},
|
|
687
|
+
handoffs: {
|
|
688
|
+
closed_park_candidates: handoffParkDry.candidates,
|
|
689
|
+
},
|
|
690
|
+
stale_warnings: {
|
|
691
|
+
total_tracked: staleStats.total,
|
|
692
|
+
over_threshold: staleStats.over_threshold,
|
|
693
|
+
median_count: staleStats.median_count,
|
|
694
|
+
oldest_first_at: staleStats.oldest_first_at,
|
|
695
|
+
},
|
|
696
|
+
workflow_hints: {
|
|
697
|
+
total_tracked: hintsStats.total,
|
|
698
|
+
over_threshold: hintsStats.over_threshold,
|
|
699
|
+
median_count: hintsStats.median_count,
|
|
700
|
+
oldest_first_at: hintsStats.oldest_first_at,
|
|
701
|
+
},
|
|
702
|
+
},
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
function renderHygieneReport(report) {
|
|
706
|
+
const lines = [];
|
|
707
|
+
lines.push(`Coordination hygiene — snapshot ${report.generated_at}`);
|
|
708
|
+
if (report.disabled) {
|
|
709
|
+
lines.push(' ✗ policy.disabled=true — hygiene sweep + aging are opted out via config.hygiene.disabled.');
|
|
710
|
+
}
|
|
711
|
+
lines.push('');
|
|
712
|
+
const a = report.families.assignments;
|
|
713
|
+
lines.push(`Assignments (open ${a.total_open}: offered ${a.offered} / accepted ${a.accepted} / started ${a.started}, median age ${a.median_open_age_days}d)`);
|
|
714
|
+
lines.push(` Park candidates: offered=${a.offered_park_candidates}, accepted=${a.accepted_park_candidates} — next sweep at session-start or bclaw_work will converge.`);
|
|
715
|
+
const h = report.families.handoffs;
|
|
716
|
+
lines.push(`Handoffs closed park candidates: ${h.closed_park_candidates} (auto-generated, older than ${Math.floor(report.policy.handoff_closed_ttl_ms / 86_400_000)}d)`);
|
|
717
|
+
const sw = report.families.stale_warnings;
|
|
718
|
+
lines.push(`Stale warnings tracked: ${sw.total_tracked} (over serve-K=${report.policy.stale_warning_serve_k}: ${sw.over_threshold}, median count ${sw.median_count}${sw.oldest_first_at ? `, oldest first-served ${sw.oldest_first_at.slice(0, 10)}` : ''})`);
|
|
719
|
+
const wh = report.families.workflow_hints;
|
|
720
|
+
lines.push(`Workflow hints tracked: ${wh.total_tracked} (over serve-K=${report.policy.workflow_hint_serve_k}: ${wh.over_threshold}, median count ${wh.median_count}${wh.oldest_first_at ? `, oldest first-served ${wh.oldest_first_at.slice(0, 10)}` : ''})`);
|
|
721
|
+
lines.push('');
|
|
722
|
+
lines.push('Read-only: no state was mutated. Session-start and bclaw_work drive the actual sweep/park; the counters age at bclaw_work read paths.');
|
|
723
|
+
return lines.join('\n');
|
|
724
|
+
}
|
|
641
725
|
export function runDoctor(options = {}) {
|
|
726
|
+
if (options.hygiene) {
|
|
727
|
+
if (!memoryExists(options.cwd)) {
|
|
728
|
+
console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
|
|
729
|
+
process.exit(1);
|
|
730
|
+
}
|
|
731
|
+
const report = runHygieneReport(options);
|
|
732
|
+
if (options.json) {
|
|
733
|
+
console.log(JSON.stringify(report, null, 2));
|
|
734
|
+
}
|
|
735
|
+
else {
|
|
736
|
+
console.log(renderHygieneReport(report));
|
|
737
|
+
}
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
642
740
|
if (options.verifyJournal) {
|
|
643
741
|
runJournalVerification(options);
|
|
644
742
|
return;
|
|
@@ -104,7 +104,7 @@ export function renderRatioBar(ratio, width = 40) {
|
|
|
104
104
|
return bar;
|
|
105
105
|
}
|
|
106
106
|
export function buildEstimationReport(options = {}) {
|
|
107
|
-
const state = loadState(options.cwd);
|
|
107
|
+
const state = options.state ?? loadState(options.cwd);
|
|
108
108
|
const done = state.plan_items.filter((p) => p.status === 'done' && (!options.agent || p.author === options.agent));
|
|
109
109
|
const entries = done.map((p) => {
|
|
110
110
|
// Estimate: prefer the sum of per-step estimates when ALL steps carry one,
|
package/dist/commands/harvest.js
CHANGED
|
@@ -19,15 +19,21 @@ import { listCandidates, listArchivedCandidates, saveCandidate } from '../core/c
|
|
|
19
19
|
import { createRuntimeEvent } from '../core/events.js';
|
|
20
20
|
import { memoryExists } from '../core/io.js';
|
|
21
21
|
import { loadAssignment, transitionAssignment } from '../core/assignments.js';
|
|
22
|
-
import {
|
|
22
|
+
import { loadClaim, releaseClaimsCascade, logCascadeReleaseResult } from '../core/claims.js';
|
|
23
23
|
import { getCapabilityProfile, dispatchCanCommit } from '../core/agent-capability.js';
|
|
24
|
-
import { commitWorktreeOnBehalf, worktreesBaseDir } from '../core/worktree.js';
|
|
24
|
+
import { commitWorktreeOnBehalf, worktreesBaseDir, resolveGitToplevel } from '../core/worktree.js';
|
|
25
25
|
/**
|
|
26
26
|
* Auto-detect all worktree directories under the brainclaw-managed base dir.
|
|
27
27
|
* Returns subdirectories that exist on disk (may or may not have an inbox).
|
|
28
28
|
*/
|
|
29
29
|
function autoDetectWorktreePaths(cwd) {
|
|
30
|
-
|
|
30
|
+
// Codex review of PR #49 (MED): createWorktree now writes in-tree worktrees
|
|
31
|
+
// under the git-TOPLEVEL hash (pln#614), so the scan base must resolve the
|
|
32
|
+
// toplevel too — otherwise `harvest --all` / candidates from an in-tree
|
|
33
|
+
// project subdir scan the stale subdir hash and miss every lane result. Only
|
|
34
|
+
// the scan base is toplevel-resolved; .brainclaw store reads/writes elsewhere
|
|
35
|
+
// keep the original project cwd.
|
|
36
|
+
const base = worktreesBaseDir(resolveGitToplevel(cwd));
|
|
31
37
|
if (!fs.existsSync(base))
|
|
32
38
|
return [];
|
|
33
39
|
return fs.readdirSync(base, { withFileTypes: true })
|
|
@@ -438,12 +444,16 @@ export function integrateLaneResults(options = {}) {
|
|
|
438
444
|
...entry.files_changed.slice(0, 50).map((f) => ({ type: 'file', ref: f })),
|
|
439
445
|
];
|
|
440
446
|
entry.assignment_completed = forceCompleteAssignment(lane.assignment_id, artifacts, `pln#534 on-behalf integration: ${lane.summary.slice(0, 120)}`, actor, cwd);
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
+
// trp#928 — use the cascade helper (was releaseClaimWithCascade — same
|
|
448
|
+
// logic for the last-claim rule but the cascade wrapper LOGS per-claim,
|
|
449
|
+
// so a silent ownership failure is observable in the runtime event log
|
|
450
|
+
// rather than only in this in-memory `reasons` string).
|
|
451
|
+
const cascade = releaseClaimsCascade([assignment.claim_id], { cwd, planStatus: 'done' });
|
|
452
|
+
logCascadeReleaseResult({ actor, trigger: 'harvest_integrate', assignment_id: lane.assignment_id, claim_id: assignment.claim_id, cascade, cwd });
|
|
453
|
+
const claimEntry = cascade.entries[0];
|
|
454
|
+
entry.claim_released = claimEntry?.released === true;
|
|
455
|
+
if (claimEntry && !claimEntry.released) {
|
|
456
|
+
reasons.push(`claim release ${claimEntry.reason}${claimEntry.error ? `: ${claimEntry.error}` : ''}`);
|
|
447
457
|
}
|
|
448
458
|
}
|
|
449
459
|
else {
|
|
@@ -455,15 +465,15 @@ export function integrateLaneResults(options = {}) {
|
|
|
455
465
|
catch (err) {
|
|
456
466
|
reasons.push(`assignment ${target} transition rejected: ${err instanceof Error ? err.message : String(err)}`);
|
|
457
467
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
reasons.push(`claim release
|
|
468
|
+
const cascade = releaseClaimsCascade([assignment.claim_id], {
|
|
469
|
+
cwd,
|
|
470
|
+
planStatus: lane.status === 'blocked' ? 'blocked' : undefined,
|
|
471
|
+
});
|
|
472
|
+
logCascadeReleaseResult({ actor, trigger: 'harvest_integrate', assignment_id: lane.assignment_id, claim_id: assignment.claim_id, cascade, cwd });
|
|
473
|
+
const claimEntry = cascade.entries[0];
|
|
474
|
+
entry.claim_released = claimEntry?.released === true;
|
|
475
|
+
if (claimEntry && !claimEntry.released) {
|
|
476
|
+
reasons.push(`claim release ${claimEntry.reason}${claimEntry.error ? `: ${claimEntry.error}` : ''}`);
|
|
467
477
|
}
|
|
468
478
|
}
|
|
469
479
|
// Durable trace of the integration.
|
|
@@ -615,12 +625,16 @@ export function harvestOrphaned(options) {
|
|
|
615
625
|
...report.files_changed.slice(0, 50).map((f) => ({ type: 'file', ref: f })),
|
|
616
626
|
];
|
|
617
627
|
report.assignment_completed = forceCompleteAssignment(options.assignmentId, artifacts, 'pln#554 harvest --orphaned: worker died before delivering; work recovered from worktree', actor, cwd);
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
628
|
+
// trp#928 — log per-claim via releaseClaimsCascade instead of the raw
|
|
629
|
+
// releaseClaimWithCascade so an ownership_denied outcome is visible in the
|
|
630
|
+
// runtime event log (previously trapped into report.errors only, which
|
|
631
|
+
// dies with the CLI invocation).
|
|
632
|
+
const cascade = releaseClaimsCascade([assignment.claim_id], { cwd, planStatus: 'done' });
|
|
633
|
+
logCascadeReleaseResult({ actor, trigger: 'harvest_integrate', assignment_id: options.assignmentId, claim_id: assignment.claim_id, cascade, cwd });
|
|
634
|
+
const claimEntry = cascade.entries[0];
|
|
635
|
+
report.claim_released = claimEntry?.released === true;
|
|
636
|
+
if (claimEntry && !claimEntry.released) {
|
|
637
|
+
report.errors.push(`claim release ${claimEntry.reason}${claimEntry.error ? `: ${claimEntry.error}` : ''}`);
|
|
624
638
|
}
|
|
625
639
|
}
|
|
626
640
|
else {
|