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,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A node's persisted state and the event log beside it: every status change goes
|
|
3
|
+
* through `transition`, which writes the snapshot and appends one event.
|
|
4
|
+
*
|
|
5
|
+
* `ensureTerminalEvent` exists because a crash can leave a node terminal on disk
|
|
6
|
+
* with no event recorded for it, and the campaign readers project from events.
|
|
7
|
+
*/
|
|
8
|
+
import { CONTRACT_VERSION, PROTOCOL_SCHEMA_VERSION } from "../harnesses/index.mjs";
|
|
9
|
+
import { SETTLED } from "./prompts.mjs";
|
|
10
|
+
import { appendJsonl } from "../run/store.mjs";
|
|
11
|
+
import { excerpt } from "../util.mjs";
|
|
12
|
+
import { hasOperationSettlement, operationNextState, settleInvocation } from "../run/operations.mjs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { readFileSync } from "node:fs";
|
|
15
|
+
|
|
16
|
+
import { writeNodeSnapshot } from "../run/node-store.mjs";
|
|
17
|
+
import { validateEvent } from "../contract/snapshot.mjs";
|
|
18
|
+
|
|
19
|
+
/** @typedef {ReturnType<typeof import("../run/lock.mjs").acquire>} LockHandle */
|
|
20
|
+
/** @typedef {import("../contract/index.mjs").NodeSnapshot} NodeSnapshot */
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {string} runDir
|
|
24
|
+
* @param {NodeSnapshot} state
|
|
25
|
+
* @param {import("../contract/index.mjs").NodeStatus} status
|
|
26
|
+
* @param {Record<string, unknown>} [patch]
|
|
27
|
+
* @param {LockHandle|null} [lock]
|
|
28
|
+
*/
|
|
29
|
+
export function transition(runDir, state, status, patch = {}, lock = null) {
|
|
30
|
+
lock?.assert();
|
|
31
|
+
const from = state.status;
|
|
32
|
+
const updatedAt = new Date().toISOString();
|
|
33
|
+
Object.assign(state, patch, { status, updatedAt });
|
|
34
|
+
writeNode(runDir, state, lock);
|
|
35
|
+
if (status === "done" && process.env.FABERUN_INTEGRATION_INTERRUPT === "after-state") {
|
|
36
|
+
throw new Error("integration interrupted after node state write");
|
|
37
|
+
}
|
|
38
|
+
const invocation = state.invocations?.at(-1);
|
|
39
|
+
if (invocation && hasOperationSettlement(runDir, invocation.id)) {
|
|
40
|
+
settleInvocation(runDir, invocation, { nextState: operationNextState(state) });
|
|
41
|
+
}
|
|
42
|
+
appendTransitionEvent(runDir, state, from, status, {}, lock);
|
|
43
|
+
if (SETTLED.has(status)) {
|
|
44
|
+
const note = state.gate?.summary ?? resultSummary(state.result) ?? state.error?.message;
|
|
45
|
+
process.stdout.write(`[node] ${state.id} ${status}${note ? ` · ${note}` : ""}\n`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* @param {string} runDir
|
|
50
|
+
* @param {NodeSnapshot} state
|
|
51
|
+
* @param {LockHandle|null} [lock]
|
|
52
|
+
*/
|
|
53
|
+
export function ensureTerminalEvent(runDir, state, lock = null) {
|
|
54
|
+
if (!hasDoneEvent(runDir, state.id, state.attempt)) {
|
|
55
|
+
appendTransitionEvent(runDir, state, "done", "done", { recovery: "terminal side effects replayed" }, lock);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Whether a `done` transition for this node and attempt was already durably
|
|
60
|
+
* recorded, at any point in the past. This is the idempotent signal for "this
|
|
61
|
+
* attempt's integration effects were already fully applied at least once" —
|
|
62
|
+
* a later, unrelated change to the node's current status is not evidence
|
|
63
|
+
* that they need reapplying.
|
|
64
|
+
*
|
|
65
|
+
* @param {string} runDir
|
|
66
|
+
* @param {string} nodeId
|
|
67
|
+
* @param {number} attempt
|
|
68
|
+
* @returns {boolean}
|
|
69
|
+
*/
|
|
70
|
+
export function hasDoneEvent(runDir, nodeId, attempt) {
|
|
71
|
+
let text = "";
|
|
72
|
+
try { text = readFileSync(join(runDir, "events.jsonl"), "utf8"); } catch {
|
|
73
|
+
// ENOENT: no events file yet means no terminal event was recorded.
|
|
74
|
+
}
|
|
75
|
+
return text.split("\n").filter(Boolean).some((line) => {
|
|
76
|
+
try {
|
|
77
|
+
const event = JSON.parse(line);
|
|
78
|
+
return event.node === nodeId && event.to === "done" && event.attempt === attempt;
|
|
79
|
+
} catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* @param {unknown} result
|
|
86
|
+
* @returns {string|null}
|
|
87
|
+
*/
|
|
88
|
+
function resultSummary(result) {
|
|
89
|
+
if (typeof result === "object" && result !== null && "summary" in result) {
|
|
90
|
+
const summary = /** @type {{summary?: unknown}} */ (result).summary;
|
|
91
|
+
if (typeof summary === "string") return summary;
|
|
92
|
+
}
|
|
93
|
+
return typeof result === "string" && result ? excerpt(result) : null;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* @param {string} runDir
|
|
97
|
+
* @param {NodeSnapshot} state
|
|
98
|
+
* @param {string} from
|
|
99
|
+
* @param {string} to
|
|
100
|
+
* @param {Record<string, unknown>} [details]
|
|
101
|
+
* @param {LockHandle|null} [lock]
|
|
102
|
+
*/
|
|
103
|
+
export function appendTransitionEvent(runDir, state, from, to, details = {}, lock = null) {
|
|
104
|
+
lock?.assert();
|
|
105
|
+
/** @type {Record<string, unknown>} */
|
|
106
|
+
const event = {
|
|
107
|
+
schemaVersion: PROTOCOL_SCHEMA_VERSION,
|
|
108
|
+
contractVersion: CONTRACT_VERSION,
|
|
109
|
+
at: state.updatedAt,
|
|
110
|
+
node: state.id,
|
|
111
|
+
sourceIdentity: state.sourceIdentity,
|
|
112
|
+
packetHash: state.packetHash,
|
|
113
|
+
from,
|
|
114
|
+
to,
|
|
115
|
+
phase: state.phase,
|
|
116
|
+
...details,
|
|
117
|
+
};
|
|
118
|
+
if (state.attempt) event.attempt = state.attempt;
|
|
119
|
+
if (state.runtime?.id) event.runtime = state.runtime.id;
|
|
120
|
+
if (state.error?.code) event.error = state.error.code;
|
|
121
|
+
if (state.gate?.verdict) event.verdict = state.gate.verdict;
|
|
122
|
+
if (state.gate?.summary) event.summary = state.gate.summary;
|
|
123
|
+
if (state.revisions) event.revisions = state.revisions;
|
|
124
|
+
const invocation = state.invocations?.at(-1);
|
|
125
|
+
if (invocation?.id) event.invocationId = invocation.id;
|
|
126
|
+
validateEvent(event);
|
|
127
|
+
appendJsonl(join(runDir, "events.jsonl"), event);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* @param {string} runDir
|
|
131
|
+
* @param {NodeSnapshot} state
|
|
132
|
+
* @param {import("../contract/index.mjs").ExecutionOverride} override
|
|
133
|
+
* @param {LockHandle} lock
|
|
134
|
+
*/
|
|
135
|
+
export function recordExecutionOverride(runDir, state, override, lock) {
|
|
136
|
+
const entry = { ...override, at: override.at ?? new Date().toISOString() };
|
|
137
|
+
state.executionOverrides = [...(state.executionOverrides ?? []), entry];
|
|
138
|
+
writeNode(runDir, state, lock);
|
|
139
|
+
appendTransitionEvent(runDir, state, state.status, state.status, { override: entry, recovery: entry.decision }, lock);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* @param {string} runDir
|
|
143
|
+
* @param {NodeSnapshot} state
|
|
144
|
+
* @param {LockHandle|null} [lock]
|
|
145
|
+
*/
|
|
146
|
+
export function writeNode(runDir, state, lock = null) {
|
|
147
|
+
writeNodeSnapshot(runDir, state, lock);
|
|
148
|
+
}
|