faberun 0.19.0 → 0.19.2
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/package.json +1 -1
- package/skills/faberun/references/contract.md +17 -5
- package/src/campaign/index.mjs +56 -1
- package/src/cli/brand.mjs +4 -5
- package/src/cli/launch.mjs +1 -1
- package/src/cli/plan.mjs +164 -27
- package/src/cli/spec.mjs +17 -7
- package/src/cli.mjs +1 -0
- package/src/contract/definition-of-done.mjs +50 -0
- package/src/contract/final-verification.mjs +21 -0
- package/src/contract/index.mjs +20 -8
- package/src/contract/verification.mjs +63 -2
- package/src/engine/cancel.mjs +3 -3
- package/src/engine/dispatch.mjs +2 -1
- package/src/engine/failover.mjs +11 -4
- package/src/engine/judge-gate.mjs +7 -11
- package/src/engine/process.mjs +40 -2
- package/src/engine/resume.mjs +2 -1
- package/src/engine/runtime-discovery.mjs +11 -0
- package/src/engine/scheduler.mjs +15 -24
- package/src/engine/scope.mjs +13 -6
- package/src/host/home.mjs +38 -0
- package/src/host/preflight.mjs +46 -14
- package/src/plan/pipeline.mjs +34 -5
- package/src/plan/proof-run.mjs +121 -0
- package/src/plan/repo-facts.mjs +5 -39
- package/src/plan/sizing.mjs +72 -4
- package/src/plan/spec.mjs +25 -1
- package/src/plan/template.mjs +69 -15
- package/src/report/final.mjs +44 -7
- package/src/report/message.mjs +7 -7
- package/src/report/render.mjs +98 -139
- package/src/report/role-usage.mjs +145 -0
- package/src/run/node-store.mjs +33 -0
- package/src/util.mjs +46 -0
package/src/run/node-store.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import { validateNodeSnapshot } from "../contract/snapshot.mjs";
|
|
|
11
11
|
|
|
12
12
|
/** @typedef {ReturnType<typeof import("./lock.mjs").acquire>} LockHandle */
|
|
13
13
|
/** @typedef {import("../contract/index.mjs").NodeSnapshot} NodeSnapshot */
|
|
14
|
+
/** @typedef {import("../contract/index.mjs").ValidatedContract} ValidatedContract */
|
|
14
15
|
|
|
15
16
|
const NODES_DIR_NAME = "nodes";
|
|
16
17
|
|
|
@@ -60,3 +61,35 @@ export function listNodeSnapshots(runDir) {
|
|
|
60
61
|
throw error;
|
|
61
62
|
}
|
|
62
63
|
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The persisted node snapshots of one run.
|
|
67
|
+
*
|
|
68
|
+
* `tolerateMissing` returns only the snapshots that exist instead of refusing
|
|
69
|
+
* the run. For resume and supervise a node the contract declares and the run
|
|
70
|
+
* never persisted is corruption and stays fatal; for `cancel` it is the
|
|
71
|
+
* ordinary shape of what is being cancelled. A launch writes `contract.json`
|
|
72
|
+
* first and can die before it writes any node -- probing providers, shelling
|
|
73
|
+
* out to git, claiming the run ref -- and the directory then holds a
|
|
74
|
+
* contract, an occupied ref and no node state at all. Such a node started
|
|
75
|
+
* nothing, holds no invocation and no worktree, so there is nothing to
|
|
76
|
+
* terminate and only the git names to release, which is what cancel is for.
|
|
77
|
+
*
|
|
78
|
+
* @param {string} runDir
|
|
79
|
+
* @param {ValidatedContract} contract
|
|
80
|
+
* @param {{tolerateMissing?: boolean}} [options]
|
|
81
|
+
* @returns {NodeSnapshot[]}
|
|
82
|
+
*/
|
|
83
|
+
export function readRunNodes(runDir, contract, options = {}) {
|
|
84
|
+
const names = listNodeSnapshots(runDir);
|
|
85
|
+
const expected = new Map(contract.nodes.map((node) => [`${node.id}.json`, node]));
|
|
86
|
+
for (const name of names) if (!expected.has(name)) throw new TypeError(`unexpected persisted node snapshot ${name}`);
|
|
87
|
+
return contract.nodes.flatMap((node) => {
|
|
88
|
+
const name = `${node.id}.json`;
|
|
89
|
+
if (!names.includes(name)) {
|
|
90
|
+
if (options.tolerateMissing === true) return [];
|
|
91
|
+
throw new TypeError(`missing persisted node snapshot ${name}`);
|
|
92
|
+
}
|
|
93
|
+
return [validateNodeSnapshot(readNodeSnapshot(runDir, node.id), node)];
|
|
94
|
+
});
|
|
95
|
+
}
|
package/src/util.mjs
CHANGED
|
@@ -229,3 +229,49 @@ export function readJsonTolerant(path) {
|
|
|
229
229
|
export function finite(value) {
|
|
230
230
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
231
231
|
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Split a command string into the words a POSIX shell would pass as argv, for
|
|
235
|
+
* the two readers handed a command as a display string rather than an argv: a
|
|
236
|
+
* Definition of Done `command` proof, whose words `engine/scope.mjs` matches
|
|
237
|
+
* against the files the node wrote, and the node:test filters one declares
|
|
238
|
+
* (`engine/judge-gate.mjs`). Quotes group and are stripped; whitespace
|
|
239
|
+
* outside them separates.
|
|
240
|
+
*
|
|
241
|
+
* Deliberately not a shell: no expansion, no substitution, no operators, and
|
|
242
|
+
* no backslash escape -- on Windows a backslash is a path separator and
|
|
243
|
+
* `C:\Users\x` must survive this intact. The question both callers ask is
|
|
244
|
+
* "which words does this command name", and a quoted path holding a space is
|
|
245
|
+
* one word: splitting it on whitespace produced two fragments that matched no
|
|
246
|
+
* file, so a proof naming a real path read as naming none.
|
|
247
|
+
*
|
|
248
|
+
* @param {string} text
|
|
249
|
+
* @returns {string[]}
|
|
250
|
+
*/
|
|
251
|
+
export function shellWords(text) {
|
|
252
|
+
/** @type {string[]} */
|
|
253
|
+
const words = [];
|
|
254
|
+
/** @type {string|null} */
|
|
255
|
+
let current = null;
|
|
256
|
+
/** @type {string|null} */
|
|
257
|
+
let quote = null;
|
|
258
|
+
for (const character of text) {
|
|
259
|
+
if (quote === null && /\s/u.test(character)) {
|
|
260
|
+
if (current !== null) words.push(current);
|
|
261
|
+
current = null;
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (quote === null && (character === '"' || character === "'")) {
|
|
265
|
+
quote = character;
|
|
266
|
+
current ??= "";
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (quote === character) {
|
|
270
|
+
quote = null;
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
current = (current ?? "") + character;
|
|
274
|
+
}
|
|
275
|
+
if (current !== null) words.push(current);
|
|
276
|
+
return words;
|
|
277
|
+
}
|