brainclaw 1.28.1 → 1.28.2
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/dist/brainclaw-vscode.vsix +0 -0
- package/dist/commands/code-map.js +2 -0
- package/dist/commands/doctor.js +1 -0
- package/dist/commands/harvest.js +32 -43
- package/dist/commands/loops-handlers.js +66 -3
- package/dist/commands/mcp-write-coordination.js +413 -137
- package/dist/commands/mcp.js +32 -4
- package/dist/core/agentrun-reconciler.js +138 -4
- package/dist/core/claims.js +4 -1
- package/dist/core/code-map/backend.js +8 -0
- package/dist/core/execution-adapters.js +15 -7
- package/dist/core/hygiene-policy.js +2 -1
- package/dist/core/loop-turn-dispatch.js +18 -1
- package/dist/core/loops/attempt-authority.js +22 -4
- package/dist/core/loops/attempt-generations.js +17 -4
- package/dist/core/loops/attempt-reservation.js +14 -1
- package/dist/core/loops/attempt-takeover.js +173 -76
- package/dist/core/loops/reconcile-turn.js +224 -26
- package/dist/core/loops/result-reducers.js +8 -8
- package/dist/core/loops/turn-execution.js +38 -19
- package/dist/core/loops/types.js +3 -0
- package/dist/core/loops/verbs.js +1 -1
- package/dist/facts.js +9 -9
- package/dist/facts.json +8 -8
- package/docs/code-map.md +10 -0
- package/docs/concepts/loop-engine.md +5 -0
- package/package.json +1 -1
|
Binary file
|
|
@@ -108,6 +108,8 @@ function printStatus(status, options) {
|
|
|
108
108
|
}
|
|
109
109
|
console.log('Code Map status');
|
|
110
110
|
console.log(` Store: ${status.store_exists ? 'present' : 'absent'}`);
|
|
111
|
+
console.log(` Root: ${status.resolution.project_root}`);
|
|
112
|
+
console.log(` Path: ${status.resolution.store_path}`);
|
|
111
113
|
console.log(` ${badgeLine(status.freshness_badge)}`);
|
|
112
114
|
if (status.stats) {
|
|
113
115
|
console.log(` Files: ${status.stats.files_indexed}`);
|
package/dist/commands/doctor.js
CHANGED
package/dist/commands/harvest.js
CHANGED
|
@@ -25,54 +25,43 @@ import { commitWorktreeOnBehalf, worktreesBaseDir, resolveGitToplevel } from '..
|
|
|
25
25
|
import { closeReviewLoopFromLaneResult } from '../core/review-loop-close.js';
|
|
26
26
|
import { closeIdeationLoopFromLaneResult } from '../core/ideation-loop-close.js';
|
|
27
27
|
import { dispatchReviewLoopTurn, turnOwnedLoopEnabled } from '../core/review-loop-turn-dispatch.js';
|
|
28
|
-
import {
|
|
29
|
-
import { findReservationByAssignmentId } from '../core/loops/attempt-reservation.js';
|
|
30
|
-
import { resolveTurnGenerationChain } from '../core/loops/attempt-generations.js';
|
|
28
|
+
import { reconcileTurnOwnedLane, turnOwnedLaneEvidence } from '../core/loops/reconcile-turn.js';
|
|
31
29
|
import { getLoop } from '../core/loops/store.js';
|
|
32
30
|
import { phasePolicy } from '../core/loops/kind-policies.js';
|
|
33
|
-
import { readCompletionSignals } from '../core/runtime-signals.js';
|
|
34
31
|
import { reconcileClaimConformity } from '../core/claim-conformity.js';
|
|
35
32
|
import { toWarningDetail } from '../core/warnings.js';
|
|
36
33
|
import { harvestHarnessObservation } from '../core/harness-adapters/index.js';
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
&& reservation.phase === 'critique'
|
|
69
|
-
&& lane.artifact_type === 'critique'
|
|
70
|
-
&& (lane.body ?? '').trim().length > 0
|
|
71
|
-
? [{ body: lane.body.trim() }]
|
|
72
|
-
: undefined;
|
|
73
|
-
const result = reconcileTurn({ turn_id: reservation.turn_id, lane: enrichedLane, cwd, critiques });
|
|
74
|
-
return { reservation, result };
|
|
75
|
-
}
|
|
34
|
+
/**
|
|
35
|
+
* pln#630 PR3a — finalize a TURN-OWNED review lane via the exactly-once `reconcileTurn`
|
|
36
|
+
* instead of the legacy `closeReviewLoopFromLaneResult`. Returns `undefined` for a legacy
|
|
37
|
+
* (non-reserved) lane so the caller runs the unchanged legacy path — this is the
|
|
38
|
+
* exactly-one-finalizer discriminator: a lane is turn-owned iff a reservation OWNS its
|
|
39
|
+
* assignment_id (only the turn-owned dispatch writes a reservation file).
|
|
40
|
+
*
|
|
41
|
+
* Evidence sourcing (the load-bearing subtlety): a real reviewer's LANE-RESULT.json is
|
|
42
|
+
* KEYLESS — the review brief never asks the worker to echo turn_id/run_id/nonce — so
|
|
43
|
+
* read-strict `reconcileTurn` (which matches lane.{turn_id,run_id,nonce} against the
|
|
44
|
+
* attempt) would REJECT it. We source the keys authoritatively: turn_id + run_id are
|
|
45
|
+
* deterministic from the reservation, and the NONCE — the non-derivable proof that THIS
|
|
46
|
+
* launch generation actually ran — comes from the coordinator's completion SENTINEL
|
|
47
|
+
* (written mechanically by the ack-wrapper with the launch-grant token). A caller/test
|
|
48
|
+
* that already supplies keyed lanes is honored (lane.* wins); a stale generation's
|
|
49
|
+
* sentinel carries the old token → still rejected, preserving the anti-stale guarantee.
|
|
50
|
+
*/
|
|
51
|
+
/**
|
|
52
|
+
* The turn-owned FINALIZATION discriminator (pln#630, review Finding 1). A lane finalizes via
|
|
53
|
+
* the exactly-once reconcileTurn ONLY if a committed reservation OWNS it AND turn-keyed evidence
|
|
54
|
+
* (the nonce) is available — from the lane or the coordinator's completion SENTINEL. Without the
|
|
55
|
+
* nonce, reconcileTurn's read-strict gate can NEVER converge: this is reachable in production
|
|
56
|
+
* when a turn-owned dispatch WON the fence but did not ack-wrap-spawn (inbox_only / IDE-only
|
|
57
|
+
* reviewer, command_ready_manual, capacity cap, BRAINCLAW_NO_SPAWN, worktree-creation failure) —
|
|
58
|
+
* it minted a reservation but no sentinel will ever be written. Returning undefined there routes
|
|
59
|
+
* the lane to the LEGACY presence-based closer so the loop still converges instead of stalling
|
|
60
|
+
* forever. This is SAFE: the exactly-once SPAWN guarantee is enforced at DISPATCH by the launch
|
|
61
|
+
* fence (already run), so using legacy FINALIZATION for a sentinel-less lane reintroduces no
|
|
62
|
+
* double-spawn; and a sentinel that lands after a legacy close makes a later reconcile a
|
|
63
|
+
* terminal-loop idempotent no-op.
|
|
64
|
+
*/
|
|
76
65
|
/**
|
|
77
66
|
* Map a `reconcileTurn` result onto the `ReviewLoopCloseResult` shape harvest records for
|
|
78
67
|
* observability (entry.review_loop / CLI). No keep_claim / next_turn: the request_changes
|
|
@@ -9,7 +9,7 @@ import { loadSequence } from '../core/sequence.js';
|
|
|
9
9
|
import { createActionRequired, loadActionRequired } from '../core/actions.js';
|
|
10
10
|
import { selectImplementationReviewer } from '../core/reviewer-policy.js';
|
|
11
11
|
import { handleBclawCoordinate } from './mcp-write-coordination.js';
|
|
12
|
-
import { add_artifact, advance, AwaitingFileApplyApprovalError, closeLoop, complete_turn, computeNextExpected, getLoop, IdempotencyKeyReusedError, IdempotencyOwnerMismatchError, listLoopEvents, listLoops, LockLostError, LockTimeoutError, openLoop, pause, provideInput, requestInput, resume, sweepPauseTimeouts, takeoverLoopAttempt, readLocalAuthorityHome, turn, VersionConflictError, withLoopLock, artifactEvidenceDigest, attachContinuationActionRequired, ensureContinuation, } from '../core/loops/index.js';
|
|
12
|
+
import { add_artifact, advance, AwaitingFileApplyApprovalError, closeLoop, complete_turn, computeNextExpected, getLoop, IdempotencyKeyReusedError, IdempotencyOwnerMismatchError, listLoopEvents, listLoops, LockLostError, LockTimeoutError, openLoop, pause, provideInput, requestInput, resume, sweepPauseTimeouts, takeoverLoopAttempt, readLocalAuthorityHome, turn, VersionConflictError, withLoopLock, artifactEvidenceDigest, attachContinuationActionRequired, ensureContinuation, deriveWorkerReplyContract, evaluatePhaseAdvanceGate, } from '../core/loops/index.js';
|
|
13
13
|
import { BclawLoopRequestSchema, BCLAW_LOOP_INTENTS, } from '../core/loops/facade-schema.js';
|
|
14
14
|
// NextExpectedHint type now lives in src/core/loops/next-expected.ts
|
|
15
15
|
// (hoisted per can_e57c7782 follow-up so MCP facade + CLI share the
|
|
@@ -141,7 +141,7 @@ function proposedPipelineActions(loop, cwd) {
|
|
|
141
141
|
}
|
|
142
142
|
return [];
|
|
143
143
|
}
|
|
144
|
-
function errorResponse(intent, code, message, durationMs, result = null) {
|
|
144
|
+
function errorResponse(intent, code, message, durationMs, result = null, nextActions = []) {
|
|
145
145
|
return {
|
|
146
146
|
response: {
|
|
147
147
|
status: 'error',
|
|
@@ -152,10 +152,72 @@ function errorResponse(intent, code, message, durationMs, result = null) {
|
|
|
152
152
|
warnings: [],
|
|
153
153
|
error: `${code}: ${message}`,
|
|
154
154
|
duration_ms: durationMs,
|
|
155
|
+
...(nextActions.length > 0 ? { next_actions: nextActions } : {}),
|
|
155
156
|
},
|
|
156
157
|
summary: `✘ bclaw_loop[${intent}] ${code}: ${message}`,
|
|
157
158
|
};
|
|
158
159
|
}
|
|
160
|
+
function continuationUnavailableDiagnostic(loop, cwd) {
|
|
161
|
+
const phase = loop.phases.find((candidate) => candidate.name === loop.current_phase);
|
|
162
|
+
const gate = phase?.advance_gate;
|
|
163
|
+
const gateOutcome = evaluatePhaseAdvanceGate(loop, gate, cwd);
|
|
164
|
+
const contract = deriveWorkerReplyContract(loop);
|
|
165
|
+
const blockers = [];
|
|
166
|
+
const probableCauses = [];
|
|
167
|
+
const nextActions = [];
|
|
168
|
+
if (!gateOutcome.advance && gateOutcome.gate_reason)
|
|
169
|
+
blockers.push(gateOutcome.gate_reason);
|
|
170
|
+
const assigned = loop.slots.filter((slot) => slot.status === 'assigned' && slot.assignment_id);
|
|
171
|
+
const open = loop.slots.filter((slot) => slot.status === 'open'
|
|
172
|
+
&& !(loop.kind === 'ideation' && loop.current_phase === 'critique' && slot.role === 'champion'));
|
|
173
|
+
if (assigned.length > 0) {
|
|
174
|
+
probableCauses.push('one or more dispatched worker results have not converged into gate evidence');
|
|
175
|
+
for (const slot of assigned) {
|
|
176
|
+
nextActions.push({
|
|
177
|
+
tool: 'bclaw_find',
|
|
178
|
+
args: { entity: 'agent_run', filter: { assignment_id: slot.assignment_id, limit: 10 } },
|
|
179
|
+
when: `reconcile and inspect the AgentRun projection for slot ${slot.slot_id}`,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
for (const slot of open) {
|
|
184
|
+
probableCauses.push(`slot ${slot.slot_id} has not been dispatched`);
|
|
185
|
+
nextActions.push({
|
|
186
|
+
tool: 'bclaw_loop',
|
|
187
|
+
args: { intent: 'turn', loop_id: loop.id, slot_id: slot.slot_id, input: loop.goal ?? loop.title, dispatch: true },
|
|
188
|
+
when: `dispatch open slot ${slot.slot_id}`,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
if (loop.kind === 'ideation') {
|
|
192
|
+
const draft = [...loop.artifacts].reverse().find((artifact) => artifact.type === 'plan_draft');
|
|
193
|
+
if (!draft)
|
|
194
|
+
blockers.push('no attested plan_draft artifact is available for continuation');
|
|
195
|
+
if ((loop.linked?.plan_ids?.length ?? 0) === 0)
|
|
196
|
+
blockers.push('the source loop is not linked to a plan');
|
|
197
|
+
if ((loop.linked?.sequence_ids?.length ?? 0) !== 1)
|
|
198
|
+
blockers.push('the source loop must link exactly one implementation sequence');
|
|
199
|
+
}
|
|
200
|
+
else if (loop.kind === 'implementation') {
|
|
201
|
+
const handoff = [...loop.artifacts].reverse().find((artifact) => artifact.type === 'handoff' && artifact.ref);
|
|
202
|
+
if (!handoff)
|
|
203
|
+
blockers.push('no attested handoff with a reviewable ref is available');
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
result: {
|
|
207
|
+
loop_id: loop.id,
|
|
208
|
+
phase: loop.current_phase,
|
|
209
|
+
gate: {
|
|
210
|
+
expected: contract?.requirements ?? (gate ? [gate] : []),
|
|
211
|
+
observed: gateOutcome.gate_reason ?? 'gate satisfied or no phase gate',
|
|
212
|
+
passed: gateOutcome.advance,
|
|
213
|
+
},
|
|
214
|
+
blockers: [...new Set(blockers)],
|
|
215
|
+
probable_causes: [...new Set(probableCauses)],
|
|
216
|
+
next_actions: nextActions,
|
|
217
|
+
},
|
|
218
|
+
next_actions: nextActions,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
159
221
|
function inferIntent(args) {
|
|
160
222
|
if (!args || typeof args !== 'object')
|
|
161
223
|
return 'unknown';
|
|
@@ -712,7 +774,8 @@ export async function handleBclawLoop(options) {
|
|
|
712
774
|
const actions = proposedPipelineActions(source, options.cwd);
|
|
713
775
|
const action = actions[req.action_index];
|
|
714
776
|
if (!action) {
|
|
715
|
-
|
|
777
|
+
const diagnostic = continuationUnavailableDiagnostic(source, options.cwd);
|
|
778
|
+
return errorResponse('continue', 'continuation_unavailable', `no executable continuation action ${req.action_index} for ${source.id}; inspect gate/blockers and execute the supplied recovery actions`, Date.now() - startMs, diagnostic.result, diagnostic.next_actions);
|
|
716
779
|
}
|
|
717
780
|
const sourceArtifactId = action.args?.linked?.source_artifact_id;
|
|
718
781
|
const sourceArtifact = source.artifacts.find((artifact) => artifact.artifact_id === sourceArtifactId);
|