brainclaw 1.26.2 → 1.27.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 +13 -0
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/cli/register-coordination.js +65 -1
- package/dist/commands/attempt-authority.js +80 -0
- package/dist/commands/harvest.js +140 -61
- package/dist/commands/loop.js +34 -0
- package/dist/commands/loops-handlers.js +87 -14
- package/dist/commands/mcp-catalog.js +42 -18
- package/dist/commands/mcp-schemas.generated.js +44 -0
- package/dist/commands/mcp-write-claims.js +128 -1
- package/dist/commands/mcp-write-coordination.js +146 -76
- package/dist/core/agent-capability.js +1 -1
- package/dist/core/agentrun-reconciler.js +148 -22
- package/dist/core/agentruns.js +254 -29
- package/dist/core/assignment-request-schema.js +7 -0
- package/dist/core/assignment-sweeper.js +5 -3
- package/dist/core/assignments.js +131 -33
- package/dist/core/claim-request-schema.js +7 -0
- package/dist/core/claims.js +53 -2
- package/dist/core/dispatch-status.js +16 -6
- package/dist/core/dispatcher.js +51 -51
- package/dist/core/entity-operations.js +20 -0
- package/dist/core/events.js +4 -0
- package/dist/core/execution-adapters.js +160 -14
- package/dist/core/execution-contract.js +345 -0
- package/dist/core/execution.js +130 -16
- package/dist/core/harness-adapters/base.js +150 -0
- package/dist/core/harness-adapters/claude.js +39 -0
- package/dist/core/harness-adapters/codex.js +57 -0
- package/dist/core/harness-adapters/harvest.js +109 -0
- package/dist/core/harness-adapters/index.js +8 -0
- package/dist/core/harness-adapters/prompt-only.js +13 -0
- package/dist/core/harness-adapters/registry.js +48 -0
- package/dist/core/harness-adapters/result.js +33 -0
- package/dist/core/harness-adapters/types.js +2 -0
- package/dist/core/ideation-loop-close.js +25 -2
- package/dist/core/instruction-templates.js +3 -2
- package/dist/core/loop-turn-dispatch.js +207 -0
- package/dist/core/loops/artifact-contract.js +11 -0
- package/dist/core/loops/attempt-authority.js +476 -0
- package/dist/core/loops/attempt-generations.js +509 -0
- package/dist/core/loops/attempt-reservation.js +197 -35
- package/dist/core/loops/attempt-rollout.js +404 -0
- package/dist/core/loops/attempt-takeover.js +155 -0
- package/dist/core/loops/bootstrap-acquire.js +7 -3
- package/dist/core/loops/evidence.js +187 -0
- package/dist/core/loops/facade-schema.js +41 -10
- package/dist/core/loops/gate-policy.js +485 -0
- package/dist/core/loops/impl-bind.js +37 -79
- package/dist/core/loops/index.js +9 -0
- package/dist/core/loops/iteration-engine.js +31 -19
- package/dist/core/loops/kind-policies.js +90 -0
- package/dist/core/loops/lock.js +71 -13
- package/dist/core/loops/reconcile-turn.js +235 -18
- package/dist/core/loops/result-reducers.js +99 -10
- package/dist/core/loops/store.js +30 -3
- package/dist/core/loops/turn-execution.js +480 -0
- package/dist/core/loops/types.js +113 -2
- package/dist/core/loops/verbs.js +332 -99
- package/dist/core/loops/verify-command.js +31 -8
- package/dist/core/loops/workspace-digest.js +54 -0
- package/dist/core/review-loop-close.js +25 -3
- package/dist/core/review-loop-turn-dispatch.js +210 -161
- package/dist/core/runtime-signals.js +62 -25
- package/dist/core/schema.js +35 -0
- package/dist/core/spawn-check.js +3 -2
- package/dist/core/upgrades/backup.js +27 -4
- package/dist/facts.js +7 -6
- package/dist/facts.json +6 -5
- package/docs/cli.md +49 -1
- package/docs/concepts/attempt-authority.md +407 -0
- package/docs/concepts/evidence-attestations.md +135 -0
- package/docs/concepts/execution-contract.md +166 -0
- package/docs/concepts/harness-adapters.md +166 -0
- package/docs/concepts/ideation-loop.md +5 -4
- package/docs/concepts/loop-engine.md +302 -113
- package/docs/index.md +4 -1
- package/docs/integrations/codex.md +3 -3
- package/docs/integrations/mcp.md +59 -5
- package/docs/loops/debug.md +144 -0
- package/docs/loops/ideation.md +158 -0
- package/docs/loops/implementation.md +154 -0
- package/docs/loops/research.md +136 -0
- package/docs/loops/review.md +200 -0
- package/docs/mcp-schema-changelog.md +14 -5
- package/package.json +1 -1
|
@@ -26,8 +26,11 @@ import { spawnSync } from 'node:child_process';
|
|
|
26
26
|
import path from 'node:path';
|
|
27
27
|
import { getLoop } from './store.js';
|
|
28
28
|
import { withLoopLock } from './lock.js';
|
|
29
|
-
import {
|
|
29
|
+
import { addArtifactWithEvidence } from './verbs.js';
|
|
30
30
|
import { artifactsInIteration } from './iteration-engine.js';
|
|
31
|
+
import { evidenceDigest } from './evidence.js';
|
|
32
|
+
import { eligibleArtifactsForPurpose } from './gate-policy.js';
|
|
33
|
+
import { captureWorkspaceDigest } from './workspace-digest.js';
|
|
31
34
|
import { VERIFY_DEFAULT_TIMEOUT_MS, LOOP_ARTIFACT_BODY_MAX_BYTES, } from './types.js';
|
|
32
35
|
/** VerifyReportBodySchema caps stdout_tail/stderr_tail at 1024. */
|
|
33
36
|
const TAIL_MAX = 1024;
|
|
@@ -115,9 +118,10 @@ function fitBody(report) {
|
|
|
115
118
|
}
|
|
116
119
|
return { ...report, stdout_tail: undefined, stderr_tail: undefined };
|
|
117
120
|
}
|
|
118
|
-
export function buildVerifyReportBody(config, result) {
|
|
121
|
+
export function buildVerifyReportBody(config, result, bindings) {
|
|
119
122
|
return fitBody({
|
|
120
123
|
command: config.command.join(' '),
|
|
124
|
+
command_argv: config.command,
|
|
121
125
|
exit_code: result.exit_code,
|
|
122
126
|
passed: result.passed,
|
|
123
127
|
duration_ms: result.duration_ms,
|
|
@@ -125,11 +129,13 @@ export function buildVerifyReportBody(config, result) {
|
|
|
125
129
|
timed_out: result.timed_out,
|
|
126
130
|
stdout_tail: result.stdout_tail || undefined,
|
|
127
131
|
stderr_tail: result.stderr_tail || undefined,
|
|
132
|
+
...bindings,
|
|
128
133
|
});
|
|
129
134
|
}
|
|
130
|
-
/** True when
|
|
135
|
+
/** True when an authoritative, still-fresh engine report exists for this iteration. */
|
|
131
136
|
function hasVerifyReportForIteration(thread, iteration) {
|
|
132
|
-
|
|
137
|
+
const reports = artifactsInIteration(thread, iteration).filter((artifact) => artifact.type === 'verify_report');
|
|
138
|
+
return eligibleArtifactsForPurpose(thread, reports, 'command_green').eligible.length > 0;
|
|
133
139
|
}
|
|
134
140
|
/**
|
|
135
141
|
* Run the configured verify command and record a deterministic `verify_report` for the
|
|
@@ -170,7 +176,12 @@ export function runVerify(input, cwd) {
|
|
|
170
176
|
return { thread: snapshot.thread, deduped: true };
|
|
171
177
|
// --- OUT OF LOCK: run the command (may take minutes). ---
|
|
172
178
|
const { config, iteration, phase } = snapshot;
|
|
173
|
-
const
|
|
179
|
+
const command_digest = evidenceDigest({ command: config.command });
|
|
180
|
+
const workspaceBefore = captureWorkspaceDigest(config.cwd);
|
|
181
|
+
const runResult = runner(config);
|
|
182
|
+
const workspaceAfter = captureWorkspaceDigest(config.cwd);
|
|
183
|
+
const workspace_stable = workspaceBefore === workspaceAfter;
|
|
184
|
+
const reportAfterRun = buildVerifyReportBody(config, { ...runResult, passed: runResult.passed && workspace_stable }, { command_digest, workspace_digest: workspaceAfter, workspace_stable });
|
|
174
185
|
// --- Lock scope 2: re-check idempotency (by SNAPSHOT iteration), then append. ---
|
|
175
186
|
return withLoopLock({
|
|
176
187
|
cwd,
|
|
@@ -185,11 +196,24 @@ export function runVerify(input, cwd) {
|
|
|
185
196
|
// landed (a concurrent verify won). Checking the snapshot (not the current)
|
|
186
197
|
// iteration is what makes this correct after a concurrent advance (review F1).
|
|
187
198
|
if (hasVerifyReportForIteration(thread, iteration)) {
|
|
188
|
-
return { thread, report, deduped: true };
|
|
199
|
+
return { thread, report: reportAfterRun, deduped: true };
|
|
189
200
|
}
|
|
190
|
-
|
|
201
|
+
// Close the final out-of-lock race: the bytes verified above must still be the
|
|
202
|
+
// bytes present at the evidence commit boundary. A later gate independently
|
|
203
|
+
// repeats this freshness check so a post-commit mutation also fails closed.
|
|
204
|
+
const workspaceAtCommit = captureWorkspaceDigest(config.cwd);
|
|
205
|
+
const commitStable = workspace_stable && workspaceAtCommit === workspaceAfter;
|
|
206
|
+
const report = buildVerifyReportBody(config, { ...runResult, passed: runResult.passed && commitStable }, { command_digest, workspace_digest: workspaceAtCommit, workspace_stable: commitStable });
|
|
207
|
+
const updated = addArtifactWithEvidence({
|
|
191
208
|
id: input.loop_id,
|
|
192
209
|
actor: input.actor,
|
|
210
|
+
evidence_context: {
|
|
211
|
+
channel: 'verify_command',
|
|
212
|
+
producer_kind: 'engine',
|
|
213
|
+
producer_id: 'brainclaw:verify-command',
|
|
214
|
+
command_digest,
|
|
215
|
+
workspace_digest: workspaceAtCommit,
|
|
216
|
+
},
|
|
193
217
|
artifact: {
|
|
194
218
|
// Stamp the SNAPSHOT phase + iteration so the report is attributed to the
|
|
195
219
|
// iteration whose code it actually tested — never a later iteration a
|
|
@@ -198,7 +222,6 @@ export function runVerify(input, cwd) {
|
|
|
198
222
|
iteration,
|
|
199
223
|
type: 'verify_report',
|
|
200
224
|
body: JSON.stringify(report),
|
|
201
|
-
produced_by: 'engine',
|
|
202
225
|
},
|
|
203
226
|
}, cwd);
|
|
204
227
|
const art = updated.artifacts[updated.artifacts.length - 1];
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
function fallbackFiles(root, current = root) {
|
|
6
|
+
const files = [];
|
|
7
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
8
|
+
if (entry.name === '.brainclaw' || entry.name === '.git' || entry.name === 'node_modules')
|
|
9
|
+
continue;
|
|
10
|
+
const absolute = path.join(current, entry.name);
|
|
11
|
+
if (entry.isDirectory())
|
|
12
|
+
files.push(...fallbackFiles(root, absolute));
|
|
13
|
+
else if (entry.isFile())
|
|
14
|
+
files.push(path.relative(root, absolute));
|
|
15
|
+
}
|
|
16
|
+
return files;
|
|
17
|
+
}
|
|
18
|
+
/** Hash the exact tracked/untracked workspace bytes at a gate boundary. */
|
|
19
|
+
export function captureWorkspaceDigest(cwd) {
|
|
20
|
+
const rootResult = spawnSync('git', ['-C', cwd, 'rev-parse', '--show-toplevel'], {
|
|
21
|
+
encoding: 'utf8', shell: false, maxBuffer: 1024 * 1024,
|
|
22
|
+
});
|
|
23
|
+
const gitRoot = rootResult.status === 0 ? rootResult.stdout.trim() : undefined;
|
|
24
|
+
let files;
|
|
25
|
+
let root;
|
|
26
|
+
if (gitRoot) {
|
|
27
|
+
root = path.resolve(gitRoot);
|
|
28
|
+
const listed = spawnSync('git', ['-C', root, 'ls-files', '--cached', '--others', '--exclude-standard', '-z'], {
|
|
29
|
+
encoding: 'buffer', shell: false, maxBuffer: 32 * 1024 * 1024,
|
|
30
|
+
});
|
|
31
|
+
files = listed.status === 0
|
|
32
|
+
? listed.stdout.toString('utf8').split('\0').filter(Boolean)
|
|
33
|
+
: fallbackFiles(root);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
root = path.resolve(cwd);
|
|
37
|
+
files = fallbackFiles(root);
|
|
38
|
+
}
|
|
39
|
+
const hash = crypto.createHash('sha256').update(root.normalize('NFC'));
|
|
40
|
+
for (const relative of [...new Set(files)].sort()) {
|
|
41
|
+
const absolute = path.resolve(root, relative);
|
|
42
|
+
try {
|
|
43
|
+
const stat = fs.lstatSync(absolute);
|
|
44
|
+
if (!stat.isFile())
|
|
45
|
+
continue;
|
|
46
|
+
hash.update('\0').update(relative.normalize('NFC')).update('\0').update(fs.readFileSync(absolute));
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
hash.update('\0missing\0').update(relative.normalize('NFC'));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return hash.digest('hex');
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=workspace-digest.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getLoop } from './loops/store.js';
|
|
2
|
-
import {
|
|
2
|
+
import { completeTurnWithEvidence, advance } from './loops/verbs.js';
|
|
3
3
|
import { withLoopLock } from './loops/lock.js';
|
|
4
4
|
import { LOOP_ARTIFACT_BODY_MAX_BYTES } from './loops/types.js';
|
|
5
5
|
/** review-loop:lop_xxx → the loop id (mirrors assignment-reconciler.ts). */
|
|
@@ -93,8 +93,19 @@ export function closeReviewLoopFromLaneResult(assignment, lane, actor, cwd, opti
|
|
|
93
93
|
if (verdict === 'approve') {
|
|
94
94
|
if (slot) {
|
|
95
95
|
// isVerdictAccepted fires reviewer_green ONLY on an "accepted…" body.
|
|
96
|
-
|
|
96
|
+
completeTurnWithEvidence({
|
|
97
97
|
id: loopId, slot_id: slot.slot_id, actor,
|
|
98
|
+
evidence_context: {
|
|
99
|
+
channel: 'complete_turn',
|
|
100
|
+
producer_kind: 'slot',
|
|
101
|
+
producer_id: slot.slot_id,
|
|
102
|
+
agent_id: slot.agent_id,
|
|
103
|
+
slot_id: slot.slot_id,
|
|
104
|
+
slot_role: slot.role,
|
|
105
|
+
turn_id: slot.current_turn_id,
|
|
106
|
+
assignment_id: slot.assignment_id ?? assignment.id,
|
|
107
|
+
claim_id: slot.claim_id,
|
|
108
|
+
},
|
|
98
109
|
// pln#639 BUG-2 — the phase the slot was DISPATCHED in, not the
|
|
99
110
|
// loop's phase at close time. Same defect as the ideation closer;
|
|
100
111
|
// fixed here too because this is the far more travelled path.
|
|
@@ -145,8 +156,19 @@ export function closeReviewLoopFromLaneResult(assignment, lane, actor, cwd, opti
|
|
|
145
156
|
// is a planned follow-up, so a human drives it. (No re-dispatch means no
|
|
146
157
|
// worktree reuse, so the claim is released by harvest as usual.)
|
|
147
158
|
const symmetric = loop.protocol?.review_mode === 'symmetric';
|
|
148
|
-
|
|
159
|
+
completeTurnWithEvidence({
|
|
149
160
|
id: loopId, slot_id: slot.slot_id, actor,
|
|
161
|
+
evidence_context: {
|
|
162
|
+
channel: 'complete_turn',
|
|
163
|
+
producer_kind: 'slot',
|
|
164
|
+
producer_id: slot.slot_id,
|
|
165
|
+
agent_id: slot.agent_id,
|
|
166
|
+
slot_id: slot.slot_id,
|
|
167
|
+
slot_role: slot.role,
|
|
168
|
+
turn_id: slot.current_turn_id,
|
|
169
|
+
assignment_id: slot.assignment_id ?? assignment.id,
|
|
170
|
+
claim_id: slot.claim_id,
|
|
171
|
+
},
|
|
150
172
|
// pln#639 BUG-2 — dispatch phase, not close-time phase (see above).
|
|
151
173
|
artifact: { phase: slot.phase ?? loop.current_phase, type: 'verdict', body: capVerdictBody('changes-requested', detail) },
|
|
152
174
|
}, cwd);
|