faberun 0.3.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/LICENSE +21 -0
- package/README.md +131 -0
- package/bin/faberun.mjs +25 -0
- package/integrations/claude-code/statusline-bench.sh +42 -0
- package/integrations/claude-code/statusline.sh +80 -0
- package/package.json +33 -0
- package/skills/faberun/SKILL.md +24 -0
- package/skills/faberun/references/contract.md +380 -0
- package/skills/faberun/references/engineering.md +29 -0
- package/skills/faberun/references/handoffs.md +26 -0
- package/skills/faberun/references/operations.md +184 -0
- package/skills/faberun/references/rules.md +35 -0
- package/skills/faberun/references/workflow.md +23 -0
- package/skills/init-agentkit/SKILL.md +108 -0
- package/skills/init-agentkit/scripts/install-agentkit.sh +127 -0
- package/skills/init-agentkit/templates/.claude/commands/create-adr.md +44 -0
- package/skills/init-agentkit/templates/.github/workflows/quality.yml +43 -0
- package/skills/init-agentkit/templates/.sentrux/baseline.json +9 -0
- package/skills/init-agentkit/templates/.sentrux/rules.toml +21 -0
- package/skills/init-agentkit/templates/AGENTS.md +110 -0
- package/skills/init-agentkit/templates/docs/ABSTRACTIONS.md +30 -0
- package/skills/init-agentkit/templates/docs/ARCHITECTURE.md +31 -0
- package/skills/init-agentkit/templates/docs/GETTING-STARTED.md +44 -0
- package/skills/init-agentkit/templates/docs/VISION.md +33 -0
- package/skills/init-agentkit/templates/docs/adr/0001-record-architecture-decisions.md +36 -0
- package/skills/init-agentkit/templates/docs/adr/0002-root-managed-ai-guidance.md +37 -0
- package/skills/init-agentkit/templates/docs/adr/0003-sentrux-structural-quality-gates.md +49 -0
- package/skills/init-agentkit/templates/docs/adr/README.md +52 -0
- package/skills/init-agentkit/templates/docs/sentrux.md +66 -0
- package/skills/init-agentkit/templates/githooks/commit-msg +22 -0
- package/skills/init-agentkit/templates/githooks/pre-commit +32 -0
- package/src/campaign/brief.mjs +394 -0
- package/src/campaign/chain.mjs +555 -0
- package/src/campaign/handoff.mjs +516 -0
- package/src/campaign/index.mjs +300 -0
- package/src/campaign/journal.mjs +347 -0
- package/src/campaign/layout.mjs +51 -0
- package/src/campaign/metrics-evals.mjs +25 -0
- package/src/campaign/metrics.mjs +517 -0
- package/src/campaign/projection.mjs +250 -0
- package/src/campaign/record.mjs +102 -0
- package/src/campaign/unpark.mjs +56 -0
- package/src/cli/brand.mjs +205 -0
- package/src/cli/campaign.mjs +730 -0
- package/src/cli/contract.mjs +67 -0
- package/src/cli/init.mjs +170 -0
- package/src/cli/launch.mjs +239 -0
- package/src/cli/seat.mjs +139 -0
- package/src/cli/setup.mjs +294 -0
- package/src/cli/skills.mjs +105 -0
- package/src/cli/update.mjs +216 -0
- package/src/cli.mjs +525 -0
- package/src/contract/articles.mjs +12 -0
- package/src/contract/assert.mjs +162 -0
- package/src/contract/definition-of-done.mjs +97 -0
- package/src/contract/final-verification.mjs +96 -0
- package/src/contract/index.mjs +641 -0
- package/src/contract/judge-envelope.mjs +25 -0
- package/src/contract/review-modes.mjs +151 -0
- package/src/contract/runtime.mjs +204 -0
- package/src/contract/schema-version.mjs +25 -0
- package/src/contract/scope-findings.mjs +77 -0
- package/src/contract/snapshot.mjs +639 -0
- package/src/contract/task-packet.mjs +495 -0
- package/src/contract/untrusted.mjs +75 -0
- package/src/contract/verification.mjs +185 -0
- package/src/contract/worker-result.mjs +138 -0
- package/src/engine/assignment.mjs +63 -0
- package/src/engine/backoff.mjs +492 -0
- package/src/engine/bulk-read.mjs +361 -0
- package/src/engine/cancel.mjs +177 -0
- package/src/engine/detach.mjs +101 -0
- package/src/engine/dispatch.mjs +752 -0
- package/src/engine/failover.mjs +192 -0
- package/src/engine/gate.mjs +183 -0
- package/src/engine/judge-gate.mjs +517 -0
- package/src/engine/lifecycle.mjs +772 -0
- package/src/engine/live-preflight.mjs +299 -0
- package/src/engine/mutation.mjs +146 -0
- package/src/engine/notify-queue.mjs +327 -0
- package/src/engine/process-identity.mjs +72 -0
- package/src/engine/process.mjs +774 -0
- package/src/engine/prompts.mjs +289 -0
- package/src/engine/recover.mjs +300 -0
- package/src/engine/result-file.mjs +222 -0
- package/src/engine/resume.mjs +635 -0
- package/src/engine/retry.mjs +334 -0
- package/src/engine/review.mjs +228 -0
- package/src/engine/run-command.mjs +287 -0
- package/src/engine/run-identity.mjs +411 -0
- package/src/engine/runtime-discovery.mjs +235 -0
- package/src/engine/scheduler.mjs +526 -0
- package/src/engine/scope.mjs +378 -0
- package/src/engine/settle.mjs +207 -0
- package/src/engine/state.mjs +148 -0
- package/src/engine/supervise.mjs +713 -0
- package/src/engine/verify.mjs +167 -0
- package/src/harnesses/agy/index.mjs +62 -0
- package/src/harnesses/catalogue.mjs +509 -0
- package/src/harnesses/claude/index.mjs +90 -0
- package/src/harnesses/codex/index.mjs +87 -0
- package/src/harnesses/dsh/closed-packet.patch.yml +42 -0
- package/src/harnesses/dsh/index.mjs +210 -0
- package/src/harnesses/dsh/runner.mjs +259 -0
- package/src/harnesses/exec-jsonl/index.mjs +788 -0
- package/src/harnesses/index.mjs +508 -0
- package/src/harnesses/protocol.mjs +531 -0
- package/src/harnesses/replay/bin.mjs +386 -0
- package/src/harnesses/replay/index.mjs +238 -0
- package/src/harnesses/zcode/index.mjs +276 -0
- package/src/host/config.mjs +87 -0
- package/src/host/home.mjs +149 -0
- package/src/host/package.mjs +23 -0
- package/src/host/preflight.mjs +520 -0
- package/src/host/tool-policy-decisions.mjs +341 -0
- package/src/host/tool-policy-hook.mjs +270 -0
- package/src/notify/index.mjs +359 -0
- package/src/notify/os-macos.mjs +81 -0
- package/src/repo/declared-paths.mjs +220 -0
- package/src/repo/integrate.mjs +546 -0
- package/src/repo/scope-closure.mjs +665 -0
- package/src/repo/signal-block.mjs +16 -0
- package/src/repo/signal.mjs +222 -0
- package/src/repo/source-identity.mjs +295 -0
- package/src/repo/workspace.mjs +557 -0
- package/src/repo/worktree.mjs +352 -0
- package/src/report/final.mjs +200 -0
- package/src/report/metrics-report.mjs +99 -0
- package/src/report/next.mjs +383 -0
- package/src/report/render.mjs +716 -0
- package/src/run/disk-gc.mjs +251 -0
- package/src/run/lock.mjs +329 -0
- package/src/run/node-store.mjs +62 -0
- package/src/run/operations.mjs +286 -0
- package/src/run/store.mjs +187 -0
- package/src/run/usage.mjs +337 -0
- package/src/seat/harnesses.mjs +83 -0
- package/src/seat/index.mjs +239 -0
- package/src/seat/tmux.mjs +208 -0
- package/src/util.mjs +0 -0
- package/src/web/api.mjs +371 -0
- package/src/web/boundary.mjs +88 -0
- package/src/web/index.html +299 -0
- package/src/web/server.mjs +552 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verification the controller runs itself, in the attempt workspace, after the
|
|
3
|
+
* worker says it is done.
|
|
4
|
+
*
|
|
5
|
+
* The worker is also told to run these commands, and its word is not the proof:
|
|
6
|
+
* the controller re-runs them and persists each attempt, because a worker that
|
|
7
|
+
* claims a passing suite it never ran is exactly the failure this exists to
|
|
8
|
+
* catch. `recoverVerificationAttempts` reads back what a crashed controller had
|
|
9
|
+
* already proved, so a resume does not pay for the same suite twice.
|
|
10
|
+
*/
|
|
11
|
+
import { attemptWorkspace } from "../repo/worktree.mjs";
|
|
12
|
+
import { boundedUtf8, errorMessage } from "../util.mjs";
|
|
13
|
+
import { compactVerification } from "../contract/verification.mjs";
|
|
14
|
+
import { finalVerificationCommands } from "../contract/final-verification.mjs";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { terminateInvocation } from "./process.mjs";
|
|
17
|
+
import { writeNode } from "./state.mjs";
|
|
18
|
+
import { runVerification } from "./run-command.mjs";
|
|
19
|
+
|
|
20
|
+
/** @typedef {import("../repo/integrate.mjs").CandidateEvidence} CandidateEvidence */
|
|
21
|
+
/** @typedef {import("../cli.mjs").LockHandle} LockHandle */
|
|
22
|
+
/** @typedef {import("../contract/index.mjs").NodeSnapshot} NodeSnapshot */
|
|
23
|
+
/** @typedef {import("../contract/index.mjs").ValidatedContract} ValidatedContract */
|
|
24
|
+
/** @typedef {import("../contract/index.mjs").ValidatedNode} ValidatedNode */
|
|
25
|
+
/** @typedef {import("../contract/verification.mjs").VerificationAttempt} VerificationAttempt */
|
|
26
|
+
/** @typedef {import("../contract/verification.mjs").VerificationAttemptResult} VerificationAttemptResult */
|
|
27
|
+
/** @typedef {import("../contract/index.mjs").VerificationState} VerificationState */
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {VerificationAttemptResult|null|undefined} result
|
|
31
|
+
* @returns {VerificationAttemptResult}
|
|
32
|
+
*/
|
|
33
|
+
function boundedVerificationAttemptResult(result) {
|
|
34
|
+
return {
|
|
35
|
+
passed: Boolean(result?.passed),
|
|
36
|
+
stdout: boundedUtf8(result?.stdout ?? "", 2 * 1024),
|
|
37
|
+
stderr: boundedUtf8(result?.stderr ?? "", 2 * 1024),
|
|
38
|
+
error: result?.error ? boundedUtf8(result.error, 2 * 1024) : null,
|
|
39
|
+
exitCode: Number.isInteger(result?.exitCode) ? result?.exitCode ?? null : null,
|
|
40
|
+
signal: result?.signal ?? null,
|
|
41
|
+
timedOut: Boolean(result?.timedOut),
|
|
42
|
+
durationMs: Number.isFinite(result?.durationMs) ? result?.durationMs ?? null : null,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* @param {NodeSnapshot} state
|
|
47
|
+
* @returns {import("../contract/index.mjs").VerificationAttempt[]}
|
|
48
|
+
*/
|
|
49
|
+
function verificationAttemptRecords(state) {
|
|
50
|
+
if (!state.verification || !Array.isArray(state.verification.attempts)) {
|
|
51
|
+
state.verification = { passed: false, commands: [], completed: false, attempts: [] };
|
|
52
|
+
}
|
|
53
|
+
return state.verification.attempts ?? [];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* @param {string} runDir
|
|
57
|
+
* @param {NodeSnapshot} state
|
|
58
|
+
* @param {LockHandle} lock
|
|
59
|
+
* @param {VerificationAttempt} attempt
|
|
60
|
+
*/
|
|
61
|
+
function persistVerificationAttempt(runDir, state, lock, attempt) {
|
|
62
|
+
const attempts = verificationAttemptRecords(state);
|
|
63
|
+
const index = attempts.findIndex((item) => item.invocationId === attempt.invocationId);
|
|
64
|
+
if (index >= 0) attempts[index] = { ...attempts[index], ...attempt };
|
|
65
|
+
else attempts.push({ ...attempt, completedAt: attempt.completedAt ?? null, result: attempt.result ?? null });
|
|
66
|
+
state.verification ??= { passed: false, commands: [], completed: false, attempts: [] };
|
|
67
|
+
state.verification.attempts = attempts.slice(-16);
|
|
68
|
+
writeNode(runDir, state, lock);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* @param {ValidatedContract} contract
|
|
72
|
+
* @param {string} runDir
|
|
73
|
+
* @param {ValidatedNode} node
|
|
74
|
+
* @param {NodeSnapshot} state
|
|
75
|
+
* @param {LockHandle} lock
|
|
76
|
+
* @returns {Promise<import("../contract/index.mjs").VerificationState>}
|
|
77
|
+
*/
|
|
78
|
+
export async function executeControllerVerification(contract, runDir, node, state, lock) {
|
|
79
|
+
if (state.verification?.completed === true) return /** @type {import("../contract/index.mjs").VerificationState} */ (state.verification);
|
|
80
|
+
state.verification = {
|
|
81
|
+
passed: false,
|
|
82
|
+
commands: [],
|
|
83
|
+
completed: false,
|
|
84
|
+
attempts: [...(state.verification?.attempts ?? [])],
|
|
85
|
+
};
|
|
86
|
+
writeNode(runDir, state, lock);
|
|
87
|
+
const workspace = attemptWorkspace(state) ?? contract.cwd;
|
|
88
|
+
try {
|
|
89
|
+
const result = await runVerification([...node.taskPacket.verification, ...finalVerificationCommands(contract, node)], workspace, {
|
|
90
|
+
logDir: join(runDir, "logs", `${node.id}.${state.attempt}.verification`),
|
|
91
|
+
writeFiles: node.taskPacket.writeFiles ?? [],
|
|
92
|
+
onAttemptStart: (attempt) => persistVerificationAttempt(runDir, state, lock, attempt),
|
|
93
|
+
onAttemptSpawn: (attempt) => persistVerificationAttempt(runDir, state, lock, attempt),
|
|
94
|
+
onAttemptComplete: (attempt) => persistVerificationAttempt(runDir, state, lock, {
|
|
95
|
+
...attempt,
|
|
96
|
+
result: boundedVerificationAttemptResult(attempt.result),
|
|
97
|
+
}),
|
|
98
|
+
});
|
|
99
|
+
state.verification = {
|
|
100
|
+
...compactVerification(result),
|
|
101
|
+
completed: true,
|
|
102
|
+
attempts: verificationAttemptRecords(state),
|
|
103
|
+
};
|
|
104
|
+
} catch (error) {
|
|
105
|
+
state.verification = {
|
|
106
|
+
...state.verification,
|
|
107
|
+
completed: true,
|
|
108
|
+
passed: false,
|
|
109
|
+
error: boundedUtf8(errorMessage(error), 4 * 1024),
|
|
110
|
+
attempts: verificationAttemptRecords(state),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
writeNode(runDir, state, lock);
|
|
114
|
+
return state.verification;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* @param {string} runDir
|
|
118
|
+
* @param {NodeSnapshot} state
|
|
119
|
+
* @param {LockHandle} lock
|
|
120
|
+
* @returns {Promise<void>}
|
|
121
|
+
*/
|
|
122
|
+
export async function recoverVerificationAttempts(runDir, state, lock) {
|
|
123
|
+
const active = (state.verification?.attempts ?? []).filter((attempt) => attempt.status === "active");
|
|
124
|
+
if (!active.length) return;
|
|
125
|
+
for (const attempt of active) {
|
|
126
|
+
if (attempt.pid) {
|
|
127
|
+
try {
|
|
128
|
+
await terminateInvocation({
|
|
129
|
+
id: attempt.invocationId,
|
|
130
|
+
pid: attempt.pid,
|
|
131
|
+
processGroupId: attempt.processGroupId,
|
|
132
|
+
processStartToken: attempt.processStartToken,
|
|
133
|
+
}, { graceMs: 500, killGraceMs: 1_000, runDir });
|
|
134
|
+
} catch (error) {
|
|
135
|
+
throw new Error(`verification attempt ${attempt.invocationId} could not be terminated: ${errorMessage(error)}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
persistVerificationAttempt(runDir, state, lock, {
|
|
139
|
+
...attempt,
|
|
140
|
+
status: "crashed",
|
|
141
|
+
completedAt: new Date().toISOString(),
|
|
142
|
+
result: { passed: false, stdout: "", stderr: "", error: "verification controller interrupted", exitCode: null, signal: null, timedOut: false, durationMs: null },
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
state.verification = { ...state.verification, completed: false, passed: false };
|
|
146
|
+
delete state.verification.error;
|
|
147
|
+
writeNode(runDir, state, lock);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* @param {ValidatedContract} contract
|
|
151
|
+
* @param {ValidatedNode} node
|
|
152
|
+
* @param {NodeSnapshot} state
|
|
153
|
+
* @param {string} runDir
|
|
154
|
+
* @param {string} workspace
|
|
155
|
+
* @returns {Promise<import("../repo/integrate.mjs").CandidateEvidence>}
|
|
156
|
+
*/
|
|
157
|
+
export async function verifyCandidateWorkspace(contract, node, state, runDir, workspace) {
|
|
158
|
+
try {
|
|
159
|
+
const result = await runVerification([...node.taskPacket.verification, ...finalVerificationCommands(contract, node)], workspace, {
|
|
160
|
+
logDir: join(runDir, "logs", `${node.id}.${state.attempt}.candidate-verification`),
|
|
161
|
+
writeFiles: node.taskPacket.writeFiles ?? [],
|
|
162
|
+
});
|
|
163
|
+
return compactVerification(result);
|
|
164
|
+
} catch (error) {
|
|
165
|
+
return { passed: false, error: boundedUtf8(errorMessage(error), 4 * 1024) };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { normalizeAgyResult, parseVersion } from "../protocol.mjs";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @type {import("../index.mjs").HarnessAdapter}
|
|
5
|
+
*/
|
|
6
|
+
export const agyHarness = {
|
|
7
|
+
capabilities: {
|
|
8
|
+
structuredOutput: true,
|
|
9
|
+
promptTransport: "argv",
|
|
10
|
+
maxArgvPromptBytes: 128 * 1024,
|
|
11
|
+
sandbox: false,
|
|
12
|
+
permissions: false,
|
|
13
|
+
continuation: true,
|
|
14
|
+
tokenBudget: false,
|
|
15
|
+
costBudget: false,
|
|
16
|
+
usage: true,
|
|
17
|
+
cost: false,
|
|
18
|
+
toolPolicy: false,
|
|
19
|
+
// `--output-format=stream-json` writes one JSON line per event as the
|
|
20
|
+
// turn runs, not one dump at exit.
|
|
21
|
+
streamsOutput: true,
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
// command() always passes --dangerously-skip-permissions.
|
|
25
|
+
permissionExecution: null,
|
|
26
|
+
|
|
27
|
+
/** @param {import("../index.mjs").HarnessRuntime} runtime @returns {string} */
|
|
28
|
+
executable(runtime) {
|
|
29
|
+
return process.env.FABERUN_AGY_BIN ?? runtime.executable ?? "agy";
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
/** @param {import("../index.mjs").HarnessRuntime} runtime @returns {string[]} */
|
|
33
|
+
versionArgs(runtime) {
|
|
34
|
+
return runtime.versionArgs ?? ["--version"];
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
parseVersion,
|
|
38
|
+
|
|
39
|
+
/** @param {import("../index.mjs").HarnessRuntime} runtime @param {string} prompt @param {import("../index.mjs").CommandOptions} options @returns {import("../index.mjs").HarnessCommand} */
|
|
40
|
+
command(runtime, prompt, options) {
|
|
41
|
+
const continuationId = options.continuationId ?? null;
|
|
42
|
+
const args = [
|
|
43
|
+
`--model=${runtime.model}`,
|
|
44
|
+
"--output-format=stream-json",
|
|
45
|
+
"--dangerously-skip-permissions",
|
|
46
|
+
`--print-timeout=${runtime.printTimeout ?? "24h"}`,
|
|
47
|
+
];
|
|
48
|
+
if (continuationId) args.push(`--conversation=${continuationId}`);
|
|
49
|
+
if (runtime.reasoning) {
|
|
50
|
+
const effort = ["xhigh", "max"].includes(runtime.reasoning) ? "high" : runtime.reasoning;
|
|
51
|
+
args.push(`--effort=${effort}`);
|
|
52
|
+
}
|
|
53
|
+
if (options.schema) args.push(`--json-schema=${JSON.stringify(options.schema)}`);
|
|
54
|
+
args.push(`--print=${prompt}`);
|
|
55
|
+
return { executable: this.executable(runtime), args, promptTransport: "argv", input: null };
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
normalize: normalizeAgyResult,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const harness = agyHarness;
|
|
62
|
+
export default agyHarness;
|