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.
Files changed (144) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +131 -0
  3. package/bin/faberun.mjs +25 -0
  4. package/integrations/claude-code/statusline-bench.sh +42 -0
  5. package/integrations/claude-code/statusline.sh +80 -0
  6. package/package.json +33 -0
  7. package/skills/faberun/SKILL.md +24 -0
  8. package/skills/faberun/references/contract.md +380 -0
  9. package/skills/faberun/references/engineering.md +29 -0
  10. package/skills/faberun/references/handoffs.md +26 -0
  11. package/skills/faberun/references/operations.md +184 -0
  12. package/skills/faberun/references/rules.md +35 -0
  13. package/skills/faberun/references/workflow.md +23 -0
  14. package/skills/init-agentkit/SKILL.md +108 -0
  15. package/skills/init-agentkit/scripts/install-agentkit.sh +127 -0
  16. package/skills/init-agentkit/templates/.claude/commands/create-adr.md +44 -0
  17. package/skills/init-agentkit/templates/.github/workflows/quality.yml +43 -0
  18. package/skills/init-agentkit/templates/.sentrux/baseline.json +9 -0
  19. package/skills/init-agentkit/templates/.sentrux/rules.toml +21 -0
  20. package/skills/init-agentkit/templates/AGENTS.md +110 -0
  21. package/skills/init-agentkit/templates/docs/ABSTRACTIONS.md +30 -0
  22. package/skills/init-agentkit/templates/docs/ARCHITECTURE.md +31 -0
  23. package/skills/init-agentkit/templates/docs/GETTING-STARTED.md +44 -0
  24. package/skills/init-agentkit/templates/docs/VISION.md +33 -0
  25. package/skills/init-agentkit/templates/docs/adr/0001-record-architecture-decisions.md +36 -0
  26. package/skills/init-agentkit/templates/docs/adr/0002-root-managed-ai-guidance.md +37 -0
  27. package/skills/init-agentkit/templates/docs/adr/0003-sentrux-structural-quality-gates.md +49 -0
  28. package/skills/init-agentkit/templates/docs/adr/README.md +52 -0
  29. package/skills/init-agentkit/templates/docs/sentrux.md +66 -0
  30. package/skills/init-agentkit/templates/githooks/commit-msg +22 -0
  31. package/skills/init-agentkit/templates/githooks/pre-commit +32 -0
  32. package/src/campaign/brief.mjs +394 -0
  33. package/src/campaign/chain.mjs +555 -0
  34. package/src/campaign/handoff.mjs +516 -0
  35. package/src/campaign/index.mjs +300 -0
  36. package/src/campaign/journal.mjs +347 -0
  37. package/src/campaign/layout.mjs +51 -0
  38. package/src/campaign/metrics-evals.mjs +25 -0
  39. package/src/campaign/metrics.mjs +517 -0
  40. package/src/campaign/projection.mjs +250 -0
  41. package/src/campaign/record.mjs +102 -0
  42. package/src/campaign/unpark.mjs +56 -0
  43. package/src/cli/brand.mjs +205 -0
  44. package/src/cli/campaign.mjs +730 -0
  45. package/src/cli/contract.mjs +67 -0
  46. package/src/cli/init.mjs +170 -0
  47. package/src/cli/launch.mjs +239 -0
  48. package/src/cli/seat.mjs +139 -0
  49. package/src/cli/setup.mjs +294 -0
  50. package/src/cli/skills.mjs +105 -0
  51. package/src/cli/update.mjs +216 -0
  52. package/src/cli.mjs +525 -0
  53. package/src/contract/articles.mjs +12 -0
  54. package/src/contract/assert.mjs +162 -0
  55. package/src/contract/definition-of-done.mjs +97 -0
  56. package/src/contract/final-verification.mjs +96 -0
  57. package/src/contract/index.mjs +641 -0
  58. package/src/contract/judge-envelope.mjs +25 -0
  59. package/src/contract/review-modes.mjs +151 -0
  60. package/src/contract/runtime.mjs +204 -0
  61. package/src/contract/schema-version.mjs +25 -0
  62. package/src/contract/scope-findings.mjs +77 -0
  63. package/src/contract/snapshot.mjs +639 -0
  64. package/src/contract/task-packet.mjs +495 -0
  65. package/src/contract/untrusted.mjs +75 -0
  66. package/src/contract/verification.mjs +185 -0
  67. package/src/contract/worker-result.mjs +138 -0
  68. package/src/engine/assignment.mjs +63 -0
  69. package/src/engine/backoff.mjs +492 -0
  70. package/src/engine/bulk-read.mjs +361 -0
  71. package/src/engine/cancel.mjs +177 -0
  72. package/src/engine/detach.mjs +101 -0
  73. package/src/engine/dispatch.mjs +752 -0
  74. package/src/engine/failover.mjs +192 -0
  75. package/src/engine/gate.mjs +183 -0
  76. package/src/engine/judge-gate.mjs +517 -0
  77. package/src/engine/lifecycle.mjs +772 -0
  78. package/src/engine/live-preflight.mjs +299 -0
  79. package/src/engine/mutation.mjs +146 -0
  80. package/src/engine/notify-queue.mjs +327 -0
  81. package/src/engine/process-identity.mjs +72 -0
  82. package/src/engine/process.mjs +774 -0
  83. package/src/engine/prompts.mjs +289 -0
  84. package/src/engine/recover.mjs +300 -0
  85. package/src/engine/result-file.mjs +222 -0
  86. package/src/engine/resume.mjs +635 -0
  87. package/src/engine/retry.mjs +334 -0
  88. package/src/engine/review.mjs +228 -0
  89. package/src/engine/run-command.mjs +287 -0
  90. package/src/engine/run-identity.mjs +411 -0
  91. package/src/engine/runtime-discovery.mjs +235 -0
  92. package/src/engine/scheduler.mjs +526 -0
  93. package/src/engine/scope.mjs +378 -0
  94. package/src/engine/settle.mjs +207 -0
  95. package/src/engine/state.mjs +148 -0
  96. package/src/engine/supervise.mjs +713 -0
  97. package/src/engine/verify.mjs +167 -0
  98. package/src/harnesses/agy/index.mjs +62 -0
  99. package/src/harnesses/catalogue.mjs +509 -0
  100. package/src/harnesses/claude/index.mjs +90 -0
  101. package/src/harnesses/codex/index.mjs +87 -0
  102. package/src/harnesses/dsh/closed-packet.patch.yml +42 -0
  103. package/src/harnesses/dsh/index.mjs +210 -0
  104. package/src/harnesses/dsh/runner.mjs +259 -0
  105. package/src/harnesses/exec-jsonl/index.mjs +788 -0
  106. package/src/harnesses/index.mjs +508 -0
  107. package/src/harnesses/protocol.mjs +531 -0
  108. package/src/harnesses/replay/bin.mjs +386 -0
  109. package/src/harnesses/replay/index.mjs +238 -0
  110. package/src/harnesses/zcode/index.mjs +276 -0
  111. package/src/host/config.mjs +87 -0
  112. package/src/host/home.mjs +149 -0
  113. package/src/host/package.mjs +23 -0
  114. package/src/host/preflight.mjs +520 -0
  115. package/src/host/tool-policy-decisions.mjs +341 -0
  116. package/src/host/tool-policy-hook.mjs +270 -0
  117. package/src/notify/index.mjs +359 -0
  118. package/src/notify/os-macos.mjs +81 -0
  119. package/src/repo/declared-paths.mjs +220 -0
  120. package/src/repo/integrate.mjs +546 -0
  121. package/src/repo/scope-closure.mjs +665 -0
  122. package/src/repo/signal-block.mjs +16 -0
  123. package/src/repo/signal.mjs +222 -0
  124. package/src/repo/source-identity.mjs +295 -0
  125. package/src/repo/workspace.mjs +557 -0
  126. package/src/repo/worktree.mjs +352 -0
  127. package/src/report/final.mjs +200 -0
  128. package/src/report/metrics-report.mjs +99 -0
  129. package/src/report/next.mjs +383 -0
  130. package/src/report/render.mjs +716 -0
  131. package/src/run/disk-gc.mjs +251 -0
  132. package/src/run/lock.mjs +329 -0
  133. package/src/run/node-store.mjs +62 -0
  134. package/src/run/operations.mjs +286 -0
  135. package/src/run/store.mjs +187 -0
  136. package/src/run/usage.mjs +337 -0
  137. package/src/seat/harnesses.mjs +83 -0
  138. package/src/seat/index.mjs +239 -0
  139. package/src/seat/tmux.mjs +208 -0
  140. package/src/util.mjs +0 -0
  141. package/src/web/api.mjs +371 -0
  142. package/src/web/boundary.mjs +88 -0
  143. package/src/web/index.html +299 -0
  144. 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;