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,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The worker result sidecar: the file a worker writes its structured outcome
|
|
3
|
+
* into, and everything needed to find, read, canonicalise and clear it.
|
|
4
|
+
*
|
|
5
|
+
* It is a file and not the provider's final message because a provider's last
|
|
6
|
+
* message is prose it may decorate, truncate or repeat. A worker in an attempt
|
|
7
|
+
* worktree writes into that worktree, so the result has to be materialised back
|
|
8
|
+
* into the run directory -- which is what `materializeAttemptResult` and the
|
|
9
|
+
* result-materialization invocation are for.
|
|
10
|
+
*
|
|
11
|
+
* The sidecar carries no `changedFiles`: the controller measures the changed
|
|
12
|
+
* paths itself and records them as `state.scope.changedPaths`, so nothing here
|
|
13
|
+
* ever read a worker-declared list to pass along.
|
|
14
|
+
*/
|
|
15
|
+
import { attemptWorkspace } from "../repo/worktree.mjs";
|
|
16
|
+
import { errorCode, errorMessage } from "../util.mjs";
|
|
17
|
+
import { existsSync, readFileSync, unlinkSync } from "node:fs";
|
|
18
|
+
import { extractJson } from "../harnesses/protocol.mjs";
|
|
19
|
+
import { invocationResult } from "./process.mjs";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
import { parseJudge } from "./prompts.mjs";
|
|
22
|
+
import { parseWorkerResult } from "../contract/worker-result.mjs";
|
|
23
|
+
import { readJson, writeJsonAtomic, writeTextAtomic } from "../run/store.mjs";
|
|
24
|
+
import { routeRuntimeForState, runtimeSnapshot } from "./failover.mjs";
|
|
25
|
+
|
|
26
|
+
/** @typedef {import("./lifecycle.mjs").Invocation} Invocation */
|
|
27
|
+
/** @typedef {import("../contract/index.mjs").NodeSnapshot} NodeSnapshot */
|
|
28
|
+
/** @typedef {import("../contract/index.mjs").ValidatedContract} ValidatedContract */
|
|
29
|
+
/** @typedef {import("../contract/index.mjs").ValidatedNode} ValidatedNode */
|
|
30
|
+
/** @typedef {import("../contract/worker-result.mjs").WorkerResult} WorkerResult */
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {string} runDir
|
|
34
|
+
* @param {string} nodeId
|
|
35
|
+
* @returns {string}
|
|
36
|
+
*/
|
|
37
|
+
export function workerResultPath(runDir, nodeId) {
|
|
38
|
+
return join(runDir, "results", `${nodeId}.json`);
|
|
39
|
+
}
|
|
40
|
+
/** @param {string} runDir @param {string} nodeId @param {string} workspace @returns {string} */
|
|
41
|
+
export function attemptWorkerResultPath(runDir, nodeId, workspace) {
|
|
42
|
+
return join(workspace, ".runs", "results", `${nodeId}.json`);
|
|
43
|
+
}
|
|
44
|
+
/** @param {string} workspace @param {string} nodeId */
|
|
45
|
+
export function clearAttemptWorkerResult(workspace, nodeId) {
|
|
46
|
+
try { unlinkSync(attemptWorkerResultPath("", nodeId, workspace)); } catch (error) {
|
|
47
|
+
if (errorCode(error) !== "ENOENT") throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** @param {string} runDir @param {NodeSnapshot} state @param {ValidatedNode} node */
|
|
51
|
+
export function materializeAttemptResult(runDir, state, node) {
|
|
52
|
+
const workspace = attemptWorkspace(state);
|
|
53
|
+
if (!workspace) return;
|
|
54
|
+
const source = attemptWorkerResultPath(runDir, node.id, workspace);
|
|
55
|
+
if (!existsSync(source)) return;
|
|
56
|
+
writeTextAtomic(workerResultPath(runDir, node.id), readFileSync(source, "utf8"));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The run-owned result file is the primary recovery source. The provider's
|
|
60
|
+
* final message is intentionally only redundant input.
|
|
61
|
+
*
|
|
62
|
+
* @param {string} runDir
|
|
63
|
+
* @param {string} nodeId
|
|
64
|
+
* @returns {WorkerResult|null}
|
|
65
|
+
*/
|
|
66
|
+
export function readWorkerResultFile(runDir, nodeId) {
|
|
67
|
+
const path = workerResultPath(runDir, nodeId);
|
|
68
|
+
if (!existsSync(path)) return null;
|
|
69
|
+
try {
|
|
70
|
+
return parseWorkerResult(JSON.stringify(readJson(path)));
|
|
71
|
+
} catch (error) {
|
|
72
|
+
throw new TypeError(`canonical worker result ${path} is invalid: ${errorMessage(error)}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** @param {string} runDir @param {string} nodeId @param {WorkerResult} result */
|
|
76
|
+
function persistWorkerResultFile(runDir, nodeId, result) {
|
|
77
|
+
writeJsonAtomic(workerResultPath(runDir, nodeId), result);
|
|
78
|
+
}
|
|
79
|
+
/** @param {string} runDir @param {string} nodeId */
|
|
80
|
+
export function clearWorkerResultFile(runDir, nodeId) {
|
|
81
|
+
try { unlinkSync(workerResultPath(runDir, nodeId)); } catch (error) {
|
|
82
|
+
if (errorCode(error) !== "ENOENT") throw error;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** First line of the one-turn result-materialization prompt. */
|
|
86
|
+
export const RESULT_MATERIALIZATION_PROMPT_HEADER = "The implementation is already complete.";
|
|
87
|
+
/**
|
|
88
|
+
* The canonical result text without validation. Presence is authoritative:
|
|
89
|
+
* adoption decisions must surface a present-but-invalid file as an invalid
|
|
90
|
+
* result, never treat it as missing work.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} runDir
|
|
93
|
+
* @param {string} nodeId
|
|
94
|
+
* @returns {string|null}
|
|
95
|
+
*/
|
|
96
|
+
export function canonicalWorkerResultText(runDir, nodeId) {
|
|
97
|
+
const path = workerResultPath(runDir, nodeId);
|
|
98
|
+
if (!existsSync(path)) return null;
|
|
99
|
+
try { return JSON.stringify(readJson(path)); } catch { return readFileSync(path, "utf8"); }
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The materialization mode must survive controller interruption, so it is
|
|
103
|
+
* derived from the persisted invocation prompt — the run-owned record of what
|
|
104
|
+
* that turn was asked to do — instead of in-memory job state.
|
|
105
|
+
*
|
|
106
|
+
* @param {Invocation|null|undefined} invocation
|
|
107
|
+
* @returns {boolean}
|
|
108
|
+
*/
|
|
109
|
+
export function isResultMaterializationInvocation(invocation) {
|
|
110
|
+
if (!invocation?.promptPath) return false;
|
|
111
|
+
try {
|
|
112
|
+
return readFileSync(invocation.promptPath, "utf8").startsWith(RESULT_MATERIALIZATION_PROMPT_HEADER);
|
|
113
|
+
} catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* @param {string} runDir
|
|
119
|
+
* @param {ValidatedNode} node
|
|
120
|
+
* @param {unknown} providerResult
|
|
121
|
+
* @returns {WorkerResult}
|
|
122
|
+
*/
|
|
123
|
+
export function resolveWorkerResult(runDir, node, providerResult) {
|
|
124
|
+
const fromFile = readWorkerResultFile(runDir, node.id);
|
|
125
|
+
if (fromFile) return fromFile;
|
|
126
|
+
const result = parseWorkerResult(String(extractJson(providerResult) ?? providerResult ?? ""));
|
|
127
|
+
persistWorkerResultFile(runDir, node.id, result);
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* @param {string} prompt
|
|
132
|
+
* @param {string} resultPath
|
|
133
|
+
* @returns {string}
|
|
134
|
+
*/
|
|
135
|
+
export function workerProtocolPrompt(prompt, resultPath) {
|
|
136
|
+
return [
|
|
137
|
+
prompt,
|
|
138
|
+
"Controller worker protocol:",
|
|
139
|
+
`Before your final response, write the required worker-result JSON object to this canonical result file: ${resultPath}`,
|
|
140
|
+
"Your final provider message is redundant; the result file is the recovery source.",
|
|
141
|
+
].join("\n\n");
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Parse a result persisted in a node checkpoint or operation settlement. The
|
|
145
|
+
* provider stream remains the first source of evidence; this path is used only
|
|
146
|
+
* after that stream is unavailable.
|
|
147
|
+
*
|
|
148
|
+
* @param {unknown} value
|
|
149
|
+
* @returns {string|null}
|
|
150
|
+
*/
|
|
151
|
+
function parsePersistedWorkerResult(value) {
|
|
152
|
+
if (value === undefined || value === null) return null;
|
|
153
|
+
try {
|
|
154
|
+
const serialized = typeof value === "string" ? value : JSON.stringify(value);
|
|
155
|
+
parseWorkerResult(String(extractJson(serialized) ?? serialized));
|
|
156
|
+
return serialized;
|
|
157
|
+
} catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Durable worker-result evidence for recovery, in authority order: the run-owned
|
|
163
|
+
* canonical file first, then the operation settlement, then the node snapshot.
|
|
164
|
+
*
|
|
165
|
+
* @param {string} runDir
|
|
166
|
+
* @param {NodeSnapshot} state
|
|
167
|
+
* @param {Invocation} invocation
|
|
168
|
+
* @param {Record<string, unknown>|null} settlement
|
|
169
|
+
* @returns {string|null}
|
|
170
|
+
*/
|
|
171
|
+
export function persistedWorkerResult(runDir, state, invocation, settlement) {
|
|
172
|
+
const fromFile = canonicalWorkerResultText(runDir, state.id);
|
|
173
|
+
if (fromFile !== null) return fromFile;
|
|
174
|
+
const fromSettlement = parsePersistedWorkerResult(settlement?.result);
|
|
175
|
+
if (fromSettlement) return fromSettlement;
|
|
176
|
+
if (invocation.status !== "active") return parsePersistedWorkerResult(state.result);
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* @param {NodeSnapshot} state
|
|
181
|
+
* @param {Invocation} invocation
|
|
182
|
+
* @param {Record<string, unknown>|null} settlement
|
|
183
|
+
* @returns {unknown|null}
|
|
184
|
+
*/
|
|
185
|
+
export function persistedJudgeResult(state, invocation, settlement) {
|
|
186
|
+
const candidates = [settlement?.result, invocation.status !== "active" ? state.gate : null];
|
|
187
|
+
for (const candidate of candidates) {
|
|
188
|
+
if (candidate === undefined || candidate === null) continue;
|
|
189
|
+
try {
|
|
190
|
+
const serialized = typeof candidate === "string" ? candidate : JSON.stringify(candidate);
|
|
191
|
+
parseJudge(serialized);
|
|
192
|
+
return serialized;
|
|
193
|
+
} catch {
|
|
194
|
+
// Invalid candidate: skip it and try the next evidence source.
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* The worker result backing a judge-phase recovery. The canonical file is
|
|
201
|
+
* primary: a present-but-invalid file throws so the caller surfaces an
|
|
202
|
+
* invalid result, and only an absent file falls back to the transcript.
|
|
203
|
+
*
|
|
204
|
+
* @param {string} runDir
|
|
205
|
+
* @param {NodeSnapshot} state
|
|
206
|
+
* @param {ValidatedContract} contract
|
|
207
|
+
* @param {ValidatedNode} node
|
|
208
|
+
* @returns {WorkerResult|null}
|
|
209
|
+
*/
|
|
210
|
+
export function recoverWorkerResult(runDir, state, contract, node) {
|
|
211
|
+
const fromFile = canonicalWorkerResultText(runDir, state.id);
|
|
212
|
+
if (fromFile !== null) {
|
|
213
|
+
// Presence is authoritative: an unparsable canonical file is an invalid
|
|
214
|
+
// result, never a license to adopt provider-derived evidence instead.
|
|
215
|
+
return parseWorkerResult(String(extractJson(fromFile) ?? fromFile));
|
|
216
|
+
}
|
|
217
|
+
const invocation = [...(state.invocations ?? [])].reverse().find((item) => item.phase === "worker");
|
|
218
|
+
if (!invocation) return null;
|
|
219
|
+
const result = invocationResult(invocation, invocation.runtimeId ? runtimeSnapshot(contract, invocation.runtimeId) : routeRuntimeForState(contract, node, state, "worker"));
|
|
220
|
+
if (result?.status !== "done") return null;
|
|
221
|
+
try { return parseWorkerResult(result.result ?? ""); } catch { return null; }
|
|
222
|
+
}
|