voidmachine 5.0.3 → 5.0.4
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/core-assets/.claude-plugin/plugin.json +1 -1
- package/core-assets/data/catalog.v3.json +8 -8
- package/core-assets/data/certification.json +1 -1
- package/core-assets/data/model.json +7 -7
- package/core-assets/hooks/_void-hook.mjs +80 -46
- package/core-assets/skills/void-autopilot/SKILL.md +14 -13
- package/core-assets/skills/void-implement/SKILL.md +2 -2
- package/core-assets/skills/void-orchestrate/SKILL.md +16 -0
- package/core-assets/skills/void-verify/.source +2 -0
- package/core-assets/skills/void-verify/SKILL.md +19 -0
- package/core-assets/skills/void-verify/references/ci-admission.mjs +25 -0
- package/core-assets/skills/void-verify/references/ci-feedback.md +51 -0
- package/core-assets/skills/void-verify/references/quality.yml +34 -0
- package/dist/main.js +100 -68
- package/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "harness",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.4",
|
|
4
4
|
"description": "VoidCorp universal craftsman skills (void-tdd, void-typescript-strict, void-hexagonal-architecture, void-domain-driven-design, void-functional, void-code-review, void-api-and-interface-design, void-source-driven-development, void-context, void-learn, void-decide, void-claude-md, void-observability, void-security-guidance, ...) plus 12 PreToolUse enforcement hooks (tdd-guard, no-any, protect-sensitive-files, block-dangerous-bash, no-ai-design-slop, ...) and PostToolUse/SessionStart lifecycle hooks (incl. trim-large-output token frugality), five read-only critics including doctrine-critic, native architecture/security/QA specialists, and the void-graph, void-doctor and void-audit gestures, which are skills like everything else.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "VoidCorp",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"source": {
|
|
6
6
|
"kind": "adapter",
|
|
7
7
|
"version": "3.0.0-alpha.1",
|
|
8
|
-
"rootHash": "sha256:
|
|
8
|
+
"rootHash": "sha256:56012433234f5e7d537066868543df205e92f817003384489c7e698778a49ab2"
|
|
9
9
|
},
|
|
10
10
|
"nodes": [
|
|
11
11
|
{
|
|
@@ -3551,7 +3551,7 @@
|
|
|
3551
3551
|
}
|
|
3552
3552
|
],
|
|
3553
3553
|
"id": "skill:void-autopilot",
|
|
3554
|
-
"lines":
|
|
3554
|
+
"lines": 395,
|
|
3555
3555
|
"name": "void-autopilot",
|
|
3556
3556
|
"owner": "folpe",
|
|
3557
3557
|
"pack": null,
|
|
@@ -3560,7 +3560,7 @@
|
|
|
3560
3560
|
"codex"
|
|
3561
3561
|
],
|
|
3562
3562
|
"source": "packages/core/skills/void-autopilot/SKILL.md",
|
|
3563
|
-
"staticTokens":
|
|
3563
|
+
"staticTokens": 7051,
|
|
3564
3564
|
"type": "skill"
|
|
3565
3565
|
}
|
|
3566
3566
|
},
|
|
@@ -4340,7 +4340,7 @@
|
|
|
4340
4340
|
"codex"
|
|
4341
4341
|
],
|
|
4342
4342
|
"source": "packages/core/skills/void-implement/SKILL.md",
|
|
4343
|
-
"staticTokens":
|
|
4343
|
+
"staticTokens": 5800,
|
|
4344
4344
|
"type": "skill"
|
|
4345
4345
|
}
|
|
4346
4346
|
},
|
|
@@ -4677,7 +4677,7 @@
|
|
|
4677
4677
|
}
|
|
4678
4678
|
],
|
|
4679
4679
|
"id": "skill:void-orchestrate",
|
|
4680
|
-
"lines":
|
|
4680
|
+
"lines": 327,
|
|
4681
4681
|
"name": "void-orchestrate",
|
|
4682
4682
|
"owner": "folpe",
|
|
4683
4683
|
"pack": null,
|
|
@@ -4686,7 +4686,7 @@
|
|
|
4686
4686
|
"codex"
|
|
4687
4687
|
],
|
|
4688
4688
|
"source": "packages/core/skills/void-orchestrate/SKILL.md",
|
|
4689
|
-
"staticTokens":
|
|
4689
|
+
"staticTokens": 4673,
|
|
4690
4690
|
"type": "skill"
|
|
4691
4691
|
}
|
|
4692
4692
|
},
|
|
@@ -5373,7 +5373,7 @@
|
|
|
5373
5373
|
}
|
|
5374
5374
|
],
|
|
5375
5375
|
"id": "skill:void-verify",
|
|
5376
|
-
"lines":
|
|
5376
|
+
"lines": 203,
|
|
5377
5377
|
"name": "void-verify",
|
|
5378
5378
|
"owner": "folpe",
|
|
5379
5379
|
"pack": null,
|
|
@@ -5382,7 +5382,7 @@
|
|
|
5382
5382
|
"codex"
|
|
5383
5383
|
],
|
|
5384
5384
|
"source": "packages/core/skills/void-verify/SKILL.md",
|
|
5385
|
-
"staticTokens":
|
|
5385
|
+
"staticTokens": 2552,
|
|
5386
5386
|
"type": "skill"
|
|
5387
5387
|
}
|
|
5388
5388
|
},
|
|
@@ -1703,7 +1703,7 @@
|
|
|
1703
1703
|
}
|
|
1704
1704
|
],
|
|
1705
1705
|
"id": "skill:void-autopilot",
|
|
1706
|
-
"lines":
|
|
1706
|
+
"lines": 395,
|
|
1707
1707
|
"name": "void-autopilot",
|
|
1708
1708
|
"owner": "folpe",
|
|
1709
1709
|
"pack": null,
|
|
@@ -1712,7 +1712,7 @@
|
|
|
1712
1712
|
"codex"
|
|
1713
1713
|
],
|
|
1714
1714
|
"source": "packages/core/skills/void-autopilot/SKILL.md",
|
|
1715
|
-
"staticTokens":
|
|
1715
|
+
"staticTokens": 7051,
|
|
1716
1716
|
"type": "skill"
|
|
1717
1717
|
},
|
|
1718
1718
|
{
|
|
@@ -2204,7 +2204,7 @@
|
|
|
2204
2204
|
"codex"
|
|
2205
2205
|
],
|
|
2206
2206
|
"source": "packages/core/skills/void-implement/SKILL.md",
|
|
2207
|
-
"staticTokens":
|
|
2207
|
+
"staticTokens": 5800,
|
|
2208
2208
|
"type": "skill"
|
|
2209
2209
|
},
|
|
2210
2210
|
{
|
|
@@ -2415,7 +2415,7 @@
|
|
|
2415
2415
|
}
|
|
2416
2416
|
],
|
|
2417
2417
|
"id": "skill:void-orchestrate",
|
|
2418
|
-
"lines":
|
|
2418
|
+
"lines": 327,
|
|
2419
2419
|
"name": "void-orchestrate",
|
|
2420
2420
|
"owner": "folpe",
|
|
2421
2421
|
"pack": null,
|
|
@@ -2424,7 +2424,7 @@
|
|
|
2424
2424
|
"codex"
|
|
2425
2425
|
],
|
|
2426
2426
|
"source": "packages/core/skills/void-orchestrate/SKILL.md",
|
|
2427
|
-
"staticTokens":
|
|
2427
|
+
"staticTokens": 4673,
|
|
2428
2428
|
"type": "skill"
|
|
2429
2429
|
},
|
|
2430
2430
|
{
|
|
@@ -2859,7 +2859,7 @@
|
|
|
2859
2859
|
}
|
|
2860
2860
|
],
|
|
2861
2861
|
"id": "skill:void-verify",
|
|
2862
|
-
"lines":
|
|
2862
|
+
"lines": 203,
|
|
2863
2863
|
"name": "void-verify",
|
|
2864
2864
|
"owner": "folpe",
|
|
2865
2865
|
"pack": null,
|
|
@@ -2868,7 +2868,7 @@
|
|
|
2868
2868
|
"codex"
|
|
2869
2869
|
],
|
|
2870
2870
|
"source": "packages/core/skills/void-verify/SKILL.md",
|
|
2871
|
-
"staticTokens":
|
|
2871
|
+
"staticTokens": 2552,
|
|
2872
2872
|
"type": "skill"
|
|
2873
2873
|
}
|
|
2874
2874
|
],
|
|
@@ -11093,6 +11093,7 @@ function executeContextContinuity(rawInput, root, runtime3, now) {
|
|
|
11093
11093
|
|
|
11094
11094
|
init_define_VOID_SYNTAX_WORKER_IDENTITY();
|
|
11095
11095
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
11096
|
+
import { realpathSync as realpathSync5 } from "node:fs";
|
|
11096
11097
|
import { homedir as homedir2 } from "node:os";
|
|
11097
11098
|
import { join as join12 } from "node:path";
|
|
11098
11099
|
|
|
@@ -12984,6 +12985,39 @@ function metadataCommands(snapshot, event, sequence, contextPercent, workspaceMi
|
|
|
12984
12985
|
return commands;
|
|
12985
12986
|
}
|
|
12986
12987
|
|
|
12988
|
+
init_define_VOID_SYNTAX_WORKER_IDENTITY();
|
|
12989
|
+
var discoverySchema = object2({ result: object2({ process_info: object2({
|
|
12990
|
+
pane_id: string2(),
|
|
12991
|
+
foreground_processes: array(object2({
|
|
12992
|
+
pid: number2().check(int(), _gte(2))
|
|
12993
|
+
})).check(_minLength(1), _maxLength(256))
|
|
12994
|
+
}) }) });
|
|
12995
|
+
function foreground(output, pane) {
|
|
12996
|
+
try {
|
|
12997
|
+
const value = JSON.parse(output.toString("utf8"));
|
|
12998
|
+
if (record3(value)?.["error"] !== void 0) return void 0;
|
|
12999
|
+
const result = discoverySchema.safeParse(value);
|
|
13000
|
+
if (!result.success || result.data.result.process_info.pane_id !== pane) return void 0;
|
|
13001
|
+
return result.data.result.process_info.foreground_processes.map((item) => item.pid);
|
|
13002
|
+
} catch {
|
|
13003
|
+
return void 0;
|
|
13004
|
+
}
|
|
13005
|
+
}
|
|
13006
|
+
function proveOwnership(pids, call) {
|
|
13007
|
+
let pid = process.pid;
|
|
13008
|
+
const visited = /* @__PURE__ */ new Set();
|
|
13009
|
+
for (let depth = 0; depth < 64; depth++) {
|
|
13010
|
+
if (visited.has(pid)) return "parent-cycle";
|
|
13011
|
+
visited.add(pid);
|
|
13012
|
+
if (pids.includes(pid)) return void 0;
|
|
13013
|
+
const parent = call("ps", ["-o", "ppid=", "-p", String(pid)]).toString("utf8").trim();
|
|
13014
|
+
if (!/^[0-9]+$/.test(parent) || !Number.isSafeInteger(Number(parent))) return "invalid-parent";
|
|
13015
|
+
pid = Number(parent);
|
|
13016
|
+
if (pid <= 1) return "ownership-unproven";
|
|
13017
|
+
}
|
|
13018
|
+
return "parent-depth-exceeded";
|
|
13019
|
+
}
|
|
13020
|
+
|
|
12987
13021
|
init_define_VOID_SYNTAX_WORKER_IDENTITY();
|
|
12988
13022
|
import { createHash as createHash4 } from "node:crypto";
|
|
12989
13023
|
import { closeSync as closeSync3, constants as constants4, fstatSync as fstatSync3, lstatSync as lstatSync4, opendirSync, openSync as openSync3, readSync as readSync3, realpathSync as realpathSync4 } from "node:fs";
|
|
@@ -13294,9 +13328,9 @@ function executeHerdrMetadata(raw, root, env, runtime3, run = runCommand) {
|
|
|
13294
13328
|
};
|
|
13295
13329
|
try {
|
|
13296
13330
|
const common = call("git", ["rev-parse", "--path-format=absolute", "--git-common-dir"]).trim();
|
|
13331
|
+
const inventory = () => paneList.parse(JSON.parse(call("herdr", ["pane", "list"]))).result.panes;
|
|
13297
13332
|
const snapshot = readOrchestra(join12(env["HOME"] ?? homedir2(), ".local/state/orchestra"), common, root, () => {
|
|
13298
|
-
|
|
13299
|
-
return response.result.panes.flatMap((pane) => typeof pane.label === "string" && typeof pane.cwd === "string" ? [{
|
|
13333
|
+
return inventory().flatMap((pane) => typeof pane.label === "string" && typeof pane.cwd === "string" ? [{
|
|
13300
13334
|
pane_id: pane.pane_id,
|
|
13301
13335
|
workspace_id: pane.workspace_id,
|
|
13302
13336
|
label: pane.label,
|
|
@@ -13304,7 +13338,29 @@ function executeHerdrMetadata(raw, root, env, runtime3, run = runCommand) {
|
|
|
13304
13338
|
...pane.tokens === void 0 ? {} : { tokens: pane.tokens }
|
|
13305
13339
|
}] : []);
|
|
13306
13340
|
});
|
|
13307
|
-
if (snapshot === void 0)
|
|
13341
|
+
if (snapshot === void 0) {
|
|
13342
|
+
if (event !== "SessionStart") return { status: "skipped", details: { reason: "no-orchestra-mission" } };
|
|
13343
|
+
const paneId = env["HERDR_PANE_ID"];
|
|
13344
|
+
if (!paneId || !env["HERDR_SOCKET_PATH"]) throw Error("missing-herdr-context");
|
|
13345
|
+
const panes = inventory();
|
|
13346
|
+
const matches2 = panes.filter((pane2) => pane2.pane_id === paneId);
|
|
13347
|
+
const pane = matches2[0];
|
|
13348
|
+
if (matches2.length !== 1 || !pane || !pane.cwd || realpathSync5(pane.cwd) !== realpathSync5(root)) {
|
|
13349
|
+
throw Error("Herdr caller pane missing or foreign worktree");
|
|
13350
|
+
}
|
|
13351
|
+
if (pane.label && panes.filter((other) => other.label === pane.label && typeof other.cwd === "string" && realpathSync5(other.cwd) === realpathSync5(root)).length !== 1) {
|
|
13352
|
+
throw Error("ambiguous Herdr coordinator identity");
|
|
13353
|
+
}
|
|
13354
|
+
const pids = foreground(Buffer.from(call("herdr", ["pane", "process-info", "--pane", paneId])), paneId);
|
|
13355
|
+
if (pids === void 0) throw Error("invalid Herdr process discovery");
|
|
13356
|
+
const denied = proveOwnership(pids, (command, args) => Buffer.from(call(command, args)));
|
|
13357
|
+
if (denied !== void 0) throw Error(denied);
|
|
13358
|
+
return { status: "ok", details: { host: "herdr", mission: "absent" }, output: { hookSpecificOutput: {
|
|
13359
|
+
hookEventName: "SessionStart",
|
|
13360
|
+
additionalContext: `Validated Herdr caller: ${paneId}. No Orchestra mission selected.
|
|
13361
|
+
Use void-orchestrate before delegating a ticket: register the mission, create or reuse its worker tab, and launch the worker once. Revalidate live identity before acting; this hook never launches workers.`
|
|
13362
|
+
} } };
|
|
13363
|
+
}
|
|
13308
13364
|
const workspace = snapshot.coordinator ? record3(record3(JSON.parse(call("herdr", ["workspace", "get", snapshot.pane.workspace_id])))?.["result"]) : void 0;
|
|
13309
13365
|
const workspaceMission = record3(record3(workspace?.["workspace"])?.["tokens"])?.["mission"];
|
|
13310
13366
|
const percent = measureHerdrContext(input, root, runtime3, now);
|
|
@@ -13341,8 +13397,17 @@ Read mission.md and the current brief/report before resuming; Herdr IDs are hint
|
|
|
13341
13397
|
};
|
|
13342
13398
|
} catch (error) {
|
|
13343
13399
|
const reason = (error instanceof Error ? error.message : String(error)).slice(0, 500);
|
|
13344
|
-
return {
|
|
13345
|
-
|
|
13400
|
+
return {
|
|
13401
|
+
status: "degraded",
|
|
13402
|
+
details: { reason },
|
|
13403
|
+
diagnostic: `herdr-metadata: ${reason}
|
|
13404
|
+
`,
|
|
13405
|
+
...event === "SessionStart" ? { output: { hookSpecificOutput: {
|
|
13406
|
+
hookEventName: "SessionStart",
|
|
13407
|
+
additionalContext: `Herdr discovery refused: ${reason}.
|
|
13408
|
+
Do not act on this host until resolved. Native delegation independent of that action may continue.`
|
|
13409
|
+
} } } : {}
|
|
13410
|
+
};
|
|
13346
13411
|
}
|
|
13347
13412
|
}
|
|
13348
13413
|
|
|
@@ -13356,41 +13421,10 @@ var runCommand2 = (command, args, options) => {
|
|
|
13356
13421
|
if (result.error !== void 0 || result.status !== 0) throw Error("process-failed");
|
|
13357
13422
|
return result.stdout;
|
|
13358
13423
|
};
|
|
13359
|
-
var discoverySchema = object2({ result: object2({ process_info: object2({
|
|
13360
|
-
pane_id: string2(),
|
|
13361
|
-
foreground_processes: array(object2({
|
|
13362
|
-
pid: number2().check(int(), _gte(2))
|
|
13363
|
-
})).check(_minLength(1), _maxLength(256))
|
|
13364
|
-
}) }) });
|
|
13365
13424
|
function refused(reason) {
|
|
13366
13425
|
return { status: "degraded", details: { reason }, diagnostic: `herdr-session: ${reason}
|
|
13367
13426
|
` };
|
|
13368
13427
|
}
|
|
13369
|
-
function foreground(output, pane) {
|
|
13370
|
-
try {
|
|
13371
|
-
const value = JSON.parse(output.toString("utf8"));
|
|
13372
|
-
if (record3(value)?.["error"] !== void 0) return void 0;
|
|
13373
|
-
const result = discoverySchema.safeParse(value);
|
|
13374
|
-
if (!result.success || result.data.result.process_info.pane_id !== pane) return void 0;
|
|
13375
|
-
return result.data.result.process_info.foreground_processes.map((item) => item.pid);
|
|
13376
|
-
} catch {
|
|
13377
|
-
return void 0;
|
|
13378
|
-
}
|
|
13379
|
-
}
|
|
13380
|
-
function proveOwnership(pids, call) {
|
|
13381
|
-
let pid = process.pid;
|
|
13382
|
-
const visited = /* @__PURE__ */ new Set();
|
|
13383
|
-
for (let depth = 0; depth < 64; depth++) {
|
|
13384
|
-
if (visited.has(pid)) return "parent-cycle";
|
|
13385
|
-
visited.add(pid);
|
|
13386
|
-
if (pids.includes(pid)) return void 0;
|
|
13387
|
-
const parent = call("ps", ["-o", "ppid=", "-p", String(pid)]).toString("utf8").trim();
|
|
13388
|
-
if (!/^[0-9]+$/.test(parent) || !Number.isSafeInteger(Number(parent))) return "invalid-parent";
|
|
13389
|
-
pid = Number(parent);
|
|
13390
|
-
if (pid <= 1) return "ownership-unproven";
|
|
13391
|
-
}
|
|
13392
|
-
return "parent-depth-exceeded";
|
|
13393
|
-
}
|
|
13394
13428
|
function nativeHook(env) {
|
|
13395
13429
|
const path2 = join13(
|
|
13396
13430
|
env["CODEX_HOME"] || join13(env["HOME"] || homedir3(), ".codex"),
|
|
@@ -13453,7 +13487,7 @@ function executeHerdrSession(input, env, runtime3, run = runCommand2, clock = ()
|
|
|
13453
13487
|
|
|
13454
13488
|
init_define_VOID_SYNTAX_WORKER_IDENTITY();
|
|
13455
13489
|
import { randomUUID } from "node:crypto";
|
|
13456
|
-
import { lstatSync as lstatSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync11, readdirSync as readdirSync3, realpathSync as
|
|
13490
|
+
import { lstatSync as lstatSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync11, readdirSync as readdirSync3, realpathSync as realpathSync6, renameSync as renameSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
13457
13491
|
import { basename as basename5, dirname as dirname6, isAbsolute as isAbsolute7, join as join16 } from "node:path";
|
|
13458
13492
|
|
|
13459
13493
|
init_define_VOID_SYNTAX_WORKER_IDENTITY();
|
|
@@ -13512,20 +13546,20 @@ function claimedPath(root, sessionId) {
|
|
|
13512
13546
|
if (!within(runs, path2) || lstatSync5(dirname6(path2), { throwIfNoEntry: false })?.isDirectory() !== true) {
|
|
13513
13547
|
return void 0;
|
|
13514
13548
|
}
|
|
13515
|
-
return within(
|
|
13549
|
+
return within(realpathSync6(runs), realpathSync6(dirname6(path2))) ? path2 : void 0;
|
|
13516
13550
|
} catch {
|
|
13517
13551
|
return void 0;
|
|
13518
13552
|
}
|
|
13519
13553
|
}
|
|
13520
13554
|
function waitingRun(root, cwd, now) {
|
|
13521
13555
|
try {
|
|
13522
|
-
const here =
|
|
13556
|
+
const here = realpathSync6(cwd);
|
|
13523
13557
|
const directory = join16(root, "agents", "pending");
|
|
13524
13558
|
return readdirSync3(directory).filter((name) => !name.startsWith(".")).slice(0, 64).some((name) => {
|
|
13525
13559
|
const marker = record3(JSON.parse(readFileSync11(join16(directory, name), "utf8")));
|
|
13526
13560
|
const createdAt = marker?.["createdAt"];
|
|
13527
13561
|
const runCwd = marker?.["cwd"];
|
|
13528
|
-
return typeof createdAt === "number" && now - createdAt < PARKING_WINDOW_MS && typeof runCwd === "string" &&
|
|
13562
|
+
return typeof createdAt === "number" && now - createdAt < PARKING_WINDOW_MS && typeof runCwd === "string" && realpathSync6(runCwd) === here;
|
|
13529
13563
|
});
|
|
13530
13564
|
} catch {
|
|
13531
13565
|
return false;
|
|
@@ -13953,7 +13987,7 @@ import { createHash as createHash5 } from "node:crypto";
|
|
|
13953
13987
|
import {
|
|
13954
13988
|
lstatSync as lstatSync7,
|
|
13955
13989
|
mkdirSync as mkdirSync5,
|
|
13956
|
-
realpathSync as
|
|
13990
|
+
realpathSync as realpathSync7,
|
|
13957
13991
|
writeFileSync as writeFileSync4
|
|
13958
13992
|
} from "node:fs";
|
|
13959
13993
|
import { join as join18, relative as relative7 } from "node:path";
|
|
@@ -14024,11 +14058,11 @@ ${errors}
|
|
|
14024
14058
|
|
|
14025
14059
|
function safeOutputDirectory(root) {
|
|
14026
14060
|
try {
|
|
14027
|
-
const canonicalRoot =
|
|
14061
|
+
const canonicalRoot = realpathSync7(root);
|
|
14028
14062
|
const directory = voidMachinePath(root, "outputs");
|
|
14029
14063
|
mkdirSync5(directory, { recursive: true, mode: 448 });
|
|
14030
14064
|
const info = lstatSync7(directory);
|
|
14031
|
-
const canonicalDirectory2 =
|
|
14065
|
+
const canonicalDirectory2 = realpathSync7(directory);
|
|
14032
14066
|
if (!info.isDirectory() || info.isSymbolicLink() || !within(canonicalRoot, canonicalDirectory2)) {
|
|
14033
14067
|
return void 0;
|
|
14034
14068
|
}
|
|
@@ -14064,7 +14098,7 @@ function executeTrim(rawInput, root, env) {
|
|
|
14064
14098
|
const hash = createHash5("sha256").update(extracted.text).digest("hex").slice(0, 12);
|
|
14065
14099
|
const tool = extracted.tool.replaceAll(/[^A-Za-z0-9_]/g, "_").slice(0, 80);
|
|
14066
14100
|
const file = join18(directory, `${tool}-${process.pid}-${Date.now()}-${hash}.log`);
|
|
14067
|
-
const spillPath = relative7(
|
|
14101
|
+
const spillPath = relative7(realpathSync7(root), file).replaceAll("\\", "/");
|
|
14068
14102
|
const plan = planOutputTrim(extracted.text, {
|
|
14069
14103
|
tool: extracted.tool,
|
|
14070
14104
|
thresholdBytes,
|
|
@@ -14351,13 +14385,13 @@ Resolve before claiming done. This never blocks.
|
|
|
14351
14385
|
|
|
14352
14386
|
init_define_VOID_SYNTAX_WORKER_IDENTITY();
|
|
14353
14387
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
14354
|
-
import { closeSync as closeSync4, existsSync as existsSync8, lstatSync as lstatSync8, openSync as openSync4, readSync as readSync4, realpathSync as
|
|
14388
|
+
import { closeSync as closeSync4, existsSync as existsSync8, lstatSync as lstatSync8, openSync as openSync4, readSync as readSync4, realpathSync as realpathSync8 } from "node:fs";
|
|
14355
14389
|
import { dirname as dirname8, isAbsolute as isAbsolute10, join as join21, resolve as resolve9 } from "node:path";
|
|
14356
14390
|
var GIT_TIMEOUT_MS2 = 5e3;
|
|
14357
14391
|
var GIT_MAX_OUTPUT_BYTES = 1e6;
|
|
14358
14392
|
function canonical2(path2) {
|
|
14359
14393
|
try {
|
|
14360
|
-
return
|
|
14394
|
+
return realpathSync8(resolve9(path2));
|
|
14361
14395
|
} catch {
|
|
14362
14396
|
return resolve9(path2);
|
|
14363
14397
|
}
|
|
@@ -90,8 +90,8 @@ skip a step, merge, or decide that a refusal does not apply to it.
|
|
|
90
90
|
|
|
91
91
|
**Orchestrator.** Runs the loop. Evaluates no ticket, edits no code, reads no diff.
|
|
92
92
|
|
|
93
|
-
**Workers**, one per slot. Each carries one ticket
|
|
94
|
-
worktree
|
|
93
|
+
**Workers**, one per slot, are commit-only. Each carries one ticket to a committed candidate
|
|
94
|
+
and proof references in its own worktree; the coordinator owns pushes and pull requests.
|
|
95
95
|
|
|
96
96
|
**Reviewer.** The independent pass of `void-implement`, invoked by ORCH through
|
|
97
97
|
`void-orchestrate` in a fresh read-only context on the exact head SHA. `autopilot review`
|
|
@@ -203,8 +203,8 @@ in flight set it when they create or push their branch and drop it with a merged
|
|
|
203
203
|
own settings still count. So start each worktree from
|
|
204
204
|
`origin/<base>` and never move a local base branch while units are in flight. The baseline
|
|
205
205
|
recorded at `assign` is written once: a second `--before` for the same ticket is refused, so a unit
|
|
206
|
-
cannot re-record the state it left as the state it found. It is compared by the
|
|
207
|
-
|
|
206
|
+
cannot re-record the state it left as the state it found. It is compared by the coordinator before it
|
|
207
|
+
publishes, with `autopilot fingerprint --after <ticket>`, and again by the kernel before it arms a
|
|
208
208
|
merge. A changed or missing baseline sends the ticket to a human, unpublished; only that person
|
|
209
209
|
deletes the record.
|
|
210
210
|
|
|
@@ -273,21 +273,22 @@ It runs `void-implement` whole in that worktree, and starts its mission with the
|
|
|
273
273
|
mission what it has done: each specialist result through `mission specialist-event`, each proof
|
|
274
274
|
through `mission verify`, a committed candidate through `mission writer-event`. The journal accepts
|
|
275
275
|
only these typed events, no free note; what is not yet one of them does not survive a respawn, and
|
|
276
|
-
uncommitted edits survive only in the worktree.
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
draft waits.
|
|
276
|
+
uncommitted edits survive only in the worktree. During development, the coordinator may create or reuse a draft pull request
|
|
277
|
+
for the branch; drafts are permitted for development, never for merge admission. After the worker
|
|
278
|
+
returns its commit and green proofs, the coordinator runs `autopilot fingerprint --after <ticket>`,
|
|
279
|
+
pushes, and must mark the same pull request ready (create one only if absent), then move the ticket
|
|
280
|
+
to In Review. The independent reviewer examines ready pull requests only; an unfinished draft waits.
|
|
281
281
|
Blocking findings come back as a hand-back and are corrected as a batch, per `void-implement`.
|
|
282
282
|
|
|
283
283
|
On a hand-back the worker reads the reason: failing checks, blocking findings, a conflict, or a
|
|
284
284
|
base that moved. It updates its branch by merging the base into it,
|
|
285
|
-
never by rewriting pushed history, re-runs its proofs, and
|
|
285
|
+
never by rewriting pushed history, re-runs its proofs, and returns its commit to the coordinator.
|
|
286
|
+
The coordinator updates the same PR; the worker does not create another one.
|
|
286
287
|
|
|
287
288
|
May: run every `void-implement` pass whose predicate fires, run its own gates, apply a migration in
|
|
288
|
-
dev/local only,
|
|
289
|
+
dev/local only, and return its commits and proofs to the coordinator.
|
|
289
290
|
|
|
290
|
-
May not: enable auto-merge, merge anything, post or record a verdict, run `autopilot review`, move a ticket to Done, close or cancel a ticket, touch another ticket's branch or worktree,
|
|
291
|
+
May not: enable auto-merge, merge anything, post or record a verdict, run `autopilot review`, push, create or change pull requests, move a ticket to Done, close or cancel a ticket, touch another ticket's branch or worktree,
|
|
291
292
|
prune the mission journals, or write the git state the repository shares -- `refs/stash`, tags,
|
|
292
293
|
notes, remotes, the repository config.
|
|
293
294
|
|
|
@@ -332,7 +333,7 @@ head, then sends the ticket to a human. An ejected head whose own checks fail go
|
|
|
332
333
|
worker as failing checks. A pull request in conflict with the base goes back to its worker. The
|
|
333
334
|
worker classifies the conflict as the typed `conflict` judgment on the conflicting head, posted
|
|
334
335
|
through `autopilot judgment conflict-class`: `mechanical` it resolves, re-runs
|
|
335
|
-
its proofs and
|
|
336
|
+
its proofs and returns its commit for coordinator publication; `semantic` -- two intents that disagree -- it leaves alone, and the kernel
|
|
336
337
|
sends the ticket to a human. A worker never picks a side of a semantic conflict to keep the loop
|
|
337
338
|
moving.
|
|
338
339
|
|
|
@@ -102,7 +102,7 @@ Run in order. Each pass names the skill it composes and the predicate that fires
|
|
|
102
102
|
3. **Source grounding** (ALWAYS; the source depends on what the change touches). Read the relevant sources before the first line, per the Anti-rustine section of `PHILOSOPHY.md`. For a third-party configuration, schema or call signature, compose `void-source-driven-development`: official documentation **of the installed version**, never an upgrade by default. For internal code, read the owning contract, types, source and project docs of what you are about to change. Assumed semantics produce bugs that cost hours to find, and the assumption is invisible in the diff — which is why this runs before the writing rather than at review. Cite a source briefly when it helps understand or record a choice; a missing citation never blocks on its own.
|
|
103
103
|
4. **Migration safety** (IF it changes a DB schema or ships a migration). Compose `void-migrations` (and `void-drizzle-migration-safe` on a Drizzle/Postgres stack): zero-downtime, two-phase, batched backfill, locking analysis. **Once the migration is generated and safety-reviewed, apply it to the dev/local database before the TDD and E2E passes run** — otherwise those tests execute against a stale schema and prove nothing about the new shape. **This cycle only ever applies to dev/local; production migrations run through CI / GitHub Actions on merge, never from a worker or this session** (see the `void-migrations` anti-rule). A schema change must never reach the rest of the cycle without this pass.
|
|
104
104
|
5. **Convene the panel** (ALWAYS once, in `team` mode). With `panel.provider: orchestrator`, consume ORCH's supplied panel and never launch another. Otherwise call `mission dispatch --json`: its first `invoke-specialists` action is at `stage: 'pre-implementation'`, before code. Compose `void-orchestrate` for native invocation. Pass the envelope's runtime, agentName, missionId, contextPack, reviewSubject and reviewScope unchanged; record actual starts and complete results using `mission specialist-event`. Reconcile an existing invocation before dispatch; uncertain identity means no launch. Keep at most four simultaneous agents, wait in bounded slices, and never fail an agent merely because a wait timed out. Findings brief the single writer; a concrete blocked verdict is resolved before implementation. Later controller stages reuse this transport and never replay preparation. Missing required capability is diagnosed, never fabricated.
|
|
105
|
-
6. **TDD implementation** (ALWAYS). The single lead writer composes `void-tdd` + `void-testing`. Red, green, refactor. Unit tests for the behavior, green before moving on. Review specialists remain read-only.
|
|
105
|
+
6. **TDD implementation** (ALWAYS). During development, the coordinator may create or reuse a draft pull request for the branch. Keep work-in-progress PRs in draft and run focused local checks; do not wait for full remote CI per iteration. `void-verify` owns the transition to complete integration checks and its portable CI template. The single lead writer composes `void-tdd` + `void-testing`. Red, green, refactor. Unit tests for the behavior, green before moving on. Review specialists remain read-only.
|
|
106
106
|
7. **Async + idempotency** (IF it sends email, calls an external side-effecting API, enqueues a job, or mints a single-use token). Compose `void-async-safety`: idempotency keys, replay/dedup window, bounded retries, single-use enforcement.
|
|
107
107
|
8. **End-to-end tests** (IF it touches a user-facing flow). Write/extend the E2E suite (Playwright). The path a user actually walks, not just the unit.
|
|
108
108
|
9. **UX/UI pass** (IF it touches a UI surface). The interface is held to production craft, not just "it renders". Compose `void-frontend-design` (build-time craft) + `void-ui-review` (the audit/critique ceiling: AI-slop test, squint test, interaction-state coverage, technical audit) + `void-accessibility`, across the baseline (BACK+FRONT parity, mobile and desktop, and the states a user actually hits — loading / error / empty). Browser-verified QA (live screenshots) runs via `void-qa` (the claude-in-chrome MCP). A UI ticket is not shippable until this pass is **verified, not assumed**.
|
|
@@ -115,7 +115,7 @@ Run in order. Each pass names the skill it composes and the predicate that fires
|
|
|
115
115
|
This exists because the passes above have a measured hit rate of zero on a whole class of defect. Six shipped on 2026-08-06 with typecheck, lint, 2,700 tests and five CI checks green: five were caught by using the thing on the real tree, one by the harness itself, none by CI and none by the human merge gate. One of them reported "5 path(s) not extracted" about files it had read, parsed and indexed — the exact lie the feature existed to prevent — and it surfaced by running the command on the repo, after merge. Under autopilot nobody comes back to the previous ticket, so a defect found that way is a defect not found at all.
|
|
116
116
|
**A fixture is not a dogfood.** The fixture is the shortcut that would have missed every one of the six: the two oversized generated artifacts that exposed the false count existed only in the real tree. **And "the command did not crash" is not an observation** — the output has to be read, and quoted rather than summarised as "works". A surface that genuinely cannot run here (a deploy, a remote service) has its limitation named, never worked around in silence. Keep the run read-only or reversible.
|
|
117
117
|
14. **Dispose of what you found** (ALWAYS). A cycle surfaces defects that are not this ticket: an incoherent skill, a stale doc, a contract that lies. Do not file them on their merits. Compare each against the unit you are building, judged against the program objective: it either blocks this unit and is fixed here, or it beats this unit and becomes the next one, or it is **dropped**. Write one recap line per finding naming what it lost to. Record only evidence that cannot be reconstructed — a failure seen once, a measurement — with the command that replays it. The admission rules are `void-ticket`'s; this pass is where they fire.
|
|
118
|
-
15. **Ship** (ALWAYS). Compose `void-commit-discipline`,
|
|
118
|
+
15. **Ship** (ALWAYS). Compose `void-commit-discipline`, reuse the existing PR for the branch (create one only if absent), attach verification evidence, and mark the same pull request ready when the candidate meets the review handoff requirements. Then move a tracker-backed ticket to **In Review**. Move it to **Done** only after merge and final verification of the merged state. Commits are **bisectable** — one logical change each, dependency-ordered (infra → domain + tests → edge/UI + tests), each independently valid. Under `void-autopilot` the worker is commit-only: it returns the commit and proof references to the coordinator, who owns pushes, PR creation/readiness and tracker updates. The worker never enables auto-merge, merges, posts the review status or moves the ticket to Done; the loop's reviewer performs the independent review of step 11 on the PR head SHA.
|
|
119
119
|
|
|
120
120
|
---
|
|
121
121
|
|
|
@@ -40,6 +40,22 @@ or act on an unrelated project. Native work independent of that action may conti
|
|
|
40
40
|
mandatory independent review or required isolation remains a blocker; do not
|
|
41
41
|
replace it with self-review or silently weaken its requirement.
|
|
42
42
|
|
|
43
|
+
## First ticket and optional worker tabs
|
|
44
|
+
|
|
45
|
+
SessionStart discovers Herdr even before central mission files exist. Its advice
|
|
46
|
+
requires a fresh caller pane at the canonical checkout and process ownership;
|
|
47
|
+
environment variables alone never authorize a host action. Revalidate before launch.
|
|
48
|
+
On a new implementation ticket, reuse the assigned writer if one exists. Otherwise,
|
|
49
|
+
in a validated Herdr session, register the mission and planned worker first, then
|
|
50
|
+
create one background tab in the resolved coordinator workspace with the assigned
|
|
51
|
+
worktree as cwd. Read the installed `herdr tab` schema, preserve focus with
|
|
52
|
+
`--no-focus`, and use the returned root pane for the single agent start below.
|
|
53
|
+
Reattach a matching existing worker instead of opening another tab. The coordinator
|
|
54
|
+
stays in its existing pane. Hooks only discover and advise; they never create tabs,
|
|
55
|
+
launch agents or write central files. Without Herdr, use native delegation normally.
|
|
56
|
+
An invalid host or explicit mission selection refuses only that host action;
|
|
57
|
+
independent native work may continue with the limitation stated.
|
|
58
|
+
|
|
43
59
|
## Brief and bound the delegation
|
|
44
60
|
|
|
45
61
|
Use a fresh context by default. Choose a supported fork only when the task needs
|
|
@@ -10,3 +10,5 @@ Sources for `void-verify` skill (see `docs/plans/skill-audits/void-verify.md` fo
|
|
|
10
10
|
Strategy: distill. Slim skill. Adapted for void-machine's specific signal set (12 items, each mapped to a skill or hook).
|
|
11
11
|
|
|
12
12
|
No new hooks (composes with all existing).
|
|
13
|
+
|
|
14
|
+
- GitHub Actions event, concurrency and merge-queue documentation: https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows ; https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/control-workflow-concurrency ; https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-a-merge-queue
|
|
@@ -19,6 +19,25 @@ If you find yourself wanting to skip the checklist "because it's a small change,
|
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
22
|
+
## Local development and integration
|
|
23
|
+
|
|
24
|
+
During development, run the smallest meaningful configured test command against the
|
|
25
|
+
changed behavior, plus affected types and local hooks. Keep the PR in draft; do not
|
|
26
|
+
wait for full remote CI after each edit or commit. Fresh local evidence still gates
|
|
27
|
+
claims about the behavior tested. `commands.testUnit` and `commands.typecheck` in
|
|
28
|
+
`.void/config.json` describe project commands; preserve their argv.
|
|
29
|
+
|
|
30
|
+
When the candidate is committed and independently reviewed, run the applicable final
|
|
31
|
+
checks below and mark the PR ready. Wait for real required CI on that head and, if
|
|
32
|
+
configured, the merge group before integration. Local success cannot replace a required
|
|
33
|
+
server check. Returning to draft cancels obsolete PR work without resetting reviews.
|
|
34
|
+
|
|
35
|
+
For an explicitly requested CI setup or optimization, use the installed
|
|
36
|
+
[executable admission guard and workflow template](references/ci-feedback.md).
|
|
37
|
+
They defer draft validation with an explicit non-success outcome, rerun on readiness,
|
|
38
|
+
and preserve merge queue checks. Never install them or change remote protection merely
|
|
39
|
+
because this skill was invoked.
|
|
40
|
+
|
|
22
41
|
## The checklist
|
|
23
42
|
|
|
24
43
|
Read item by item. Do not run from memory. Each item is OBSERVED, not assumed.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// tdd-cover: e2e packages/cli/src/lib/ci-feedback.test.ts
|
|
3
|
+
// An admission refusal is not test evidence. Keep this step inside the required
|
|
4
|
+
// quality job: a skipped required job reports success in GitHub Actions.
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
const name = process.env.GITHUB_EVENT_NAME;
|
|
9
|
+
if (!['pull_request', 'merge_group', 'push', 'workflow_dispatch'].includes(name)) {
|
|
10
|
+
throw new Error('CI_INVALID_EVENT: unsupported or missing GitHub event.');
|
|
11
|
+
}
|
|
12
|
+
const event = JSON.parse(readFileSync(process.env.GITHUB_EVENT_PATH, 'utf8'));
|
|
13
|
+
if (name === 'pull_request') {
|
|
14
|
+
if (typeof event?.pull_request?.draft !== 'boolean') {
|
|
15
|
+
throw new Error('CI_INVALID_EVENT: pull request draft state is missing or malformed.');
|
|
16
|
+
}
|
|
17
|
+
if (event.pull_request.draft) {
|
|
18
|
+
throw new Error('CI_DEFERRED_DRAFT: preparation only; run focused tests locally, then mark ready for review to start integration validation. No tests were run.');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
process.stdout.write('CI_ADMITTED: run the complete integration checks for this revision.\n');
|
|
22
|
+
} catch (error) {
|
|
23
|
+
process.stderr.write(`${error instanceof Error ? error.message : 'CI_INVALID_EVENT: unreadable event.'}\n`);
|
|
24
|
+
process.exitCode = 1;
|
|
25
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Fast local feedback, real integration checks
|
|
2
|
+
|
|
3
|
+
The sibling files are shipped with this skill and copied by `init` into the selected
|
|
4
|
+
runtime's skill directory. Resolve them relative to the SKILL.md you actually loaded,
|
|
5
|
+
not from a developer checkout or a global installation.
|
|
6
|
+
|
|
7
|
+
On an explicit CI configuration request:
|
|
8
|
+
|
|
9
|
+
1. Inspect existing workflows, required check names, package manager, branch names,
|
|
10
|
+
review publication and release dependencies. Preserve their owners. Do not overwrite
|
|
11
|
+
existing workflows with this example or install a second quality workflow.
|
|
12
|
+
2. Adopt the behavior in `quality.yml` into the existing quality workflow. Copy
|
|
13
|
+
`ci-admission.mjs` to `.github/scripts/ci-admission.mjs`. Keep the admission step
|
|
14
|
+
inside an existing **required** quality job, before dependency installation, without
|
|
15
|
+
`continue-on-error`, `always()` on later quality steps, or a job-level draft skip.
|
|
16
|
+
3. Set up the project's actual package manager and immutable lockfile installation.
|
|
17
|
+
The example uses npm. For pnpm/Bun use their pinned setup and frozen install.
|
|
18
|
+
Define `ci:verify` in package.json to run the actual complete lint, typecheck, build,
|
|
19
|
+
and test commands appropriate to the project. Do not equate `testUnit` with the
|
|
20
|
+
whole integration suite; discover the project's existing CI contract. Missing
|
|
21
|
+
`ci:verify` fails rather than silently passing. Never use `--if-present` or `true`.
|
|
22
|
+
4. Preserve remote protection and exact-head checks. Keep the required check names
|
|
23
|
+
stable. Review protection adoption separately if no quality check is required;
|
|
24
|
+
this template alone does not configure GitHub protection. Retain the separate
|
|
25
|
+
`void-enforce` floor and independent review. This template does not replace either.
|
|
26
|
+
5. Test draft, ready without another commit, new head, return to draft, merge group,
|
|
27
|
+
and manual run. `CI_DEFERRED_DRAFT` is expected non-admission during preparation,
|
|
28
|
+
not a failed test: tests have not run. Readiness runs the full suite even at the
|
|
29
|
+
same SHA. Never treat a skipped required job as successful test evidence.
|
|
30
|
+
|
|
31
|
+
Development runs configured local tests for the changed behavior and affected types.
|
|
32
|
+
There is no network hook or mandatory remote wait per edit. Only obsolete PR runs are
|
|
33
|
+
cancelled. Distinct merge groups, manual runs and release runs must have distinct
|
|
34
|
+
concurrency keys: `cancel-in-progress: false` alone still replaces pending runs in a
|
|
35
|
+
shared group. Keep integration checks before entry to the protected branch; checking
|
|
36
|
+
only after merge permits broken integration.
|
|
37
|
+
|
|
38
|
+
GitHub couples required PR and merge queue checks. Preserve both. Remove a redundant
|
|
39
|
+
post-merge run only after checking that no release, promotion or artifact consumer
|
|
40
|
+
needs it, and that the protected integration path proves the merged result. The
|
|
41
|
+
example has no push trigger; add one where it provides distinct release evidence.
|
|
42
|
+
|
|
43
|
+
Rollback: restore the prior workflow triggers and admission step together. Restoring
|
|
44
|
+
full draft CI costs runner time but does not weaken the floor. Leave protection and
|
|
45
|
+
review evidence unchanged. Existing missions and receipts are not migrated.
|
|
46
|
+
|
|
47
|
+
Sources read for GitHub Actions (hosted service):
|
|
48
|
+
- https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#pull_request
|
|
49
|
+
- https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/control-workflow-concurrency
|
|
50
|
+
- https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-a-merge-queue
|
|
51
|
+
- https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/control-jobs-with-conditions
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Copy this template and its sibling ci-admission.mjs into .github/workflows/
|
|
2
|
+
# and .github/scripts/ respectively. Read ci-feedback.md before adoption.
|
|
3
|
+
# Adapt branch names and package-manager setup to the project. Define ci:verify
|
|
4
|
+
# in package.json as its real complete validation command; absence fails closed.
|
|
5
|
+
name: quality
|
|
6
|
+
on:
|
|
7
|
+
pull_request:
|
|
8
|
+
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
|
|
9
|
+
merge_group:
|
|
10
|
+
types: [checks_requested]
|
|
11
|
+
workflow_dispatch: {}
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
concurrency:
|
|
15
|
+
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.run_id }}
|
|
16
|
+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
17
|
+
jobs:
|
|
18
|
+
quality:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
timeout-minutes: 20
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
|
23
|
+
with:
|
|
24
|
+
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
|
25
|
+
persist-credentials: false
|
|
26
|
+
- name: Admit integration validation
|
|
27
|
+
run: node .github/scripts/ci-admission.mjs
|
|
28
|
+
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
|
|
29
|
+
with:
|
|
30
|
+
node-version: 24
|
|
31
|
+
cache: npm
|
|
32
|
+
- run: npm ci
|
|
33
|
+
- name: Run the project integration checks
|
|
34
|
+
run: npm run ci:verify
|
package/dist/main.js
CHANGED
|
@@ -35768,6 +35768,7 @@ init_esm_shims();
|
|
|
35768
35768
|
|
|
35769
35769
|
// ../mission-engine/dist/orchestration/review-loop.js
|
|
35770
35770
|
init_esm_shims();
|
|
35771
|
+
var BOUNDED_REVIEW_BUDGET = { maxRounds: 3, maxCorrectionBatches: 2 };
|
|
35771
35772
|
var SHA256 = /^sha256:[a-f0-9]{64}$/;
|
|
35772
35773
|
function validNativeContextId(value) {
|
|
35773
35774
|
return typeof value === "string" && value.trim().length > 0 && value.length <= 160 && [...value].every((char) => char.charCodeAt(0) >= 32 && (char.charCodeAt(0) < 127 || char.charCodeAt(0) > 159));
|
|
@@ -36727,7 +36728,7 @@ function staleDispatchCompletions(events) {
|
|
|
36727
36728
|
function specialistId2(value) {
|
|
36728
36729
|
return /^core:[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value);
|
|
36729
36730
|
}
|
|
36730
|
-
function validProvenanceRecovery(input2, existing) {
|
|
36731
|
+
function validProvenanceRecovery(input2, existing, afterSeqExclusive = 0) {
|
|
36731
36732
|
const disposition = input2.request.disposition;
|
|
36732
36733
|
const observation2 = input2.observation;
|
|
36733
36734
|
const bindings = observation2.reviewBindings;
|
|
@@ -36741,10 +36742,10 @@ function validProvenanceRecovery(input2, existing) {
|
|
|
36741
36742
|
const observedSubject = observation2.reviewSubject;
|
|
36742
36743
|
return bindings.every((binding3) => {
|
|
36743
36744
|
const original = existing.find((item) => item.event.eventId === binding3.completionEventId);
|
|
36744
|
-
if (original === void 0 || !disposition.completionEventIds.includes(binding3.completionEventId) || original.event.subject !== "core:independent-code-reviewer" || original.event.source !== observation2.expectedSource || original.completion.verdict !== "pass" || original.completion.findings.some((finding) => finding.classification !== "advisory") || original.completion.contractVersion !== observation2.contractVersions[original.event.subject] || field4(original.event, "inputHash") !== observation2.currentInputHashes[original.event.subject] || canonicalJsonHash(original.completion) !== binding3.completionHash || !sameReviewSubject(binding3.review, observedSubject) || binding3.review.taskId !== original.event.missionId || binding3.review.writerId !== writerId || binding3.review.provenance.kind !== "native-context" || binding3.review.provenance.contextId !== field4(original.event, "contextId"))
|
|
36745
|
+
if (parseReviewReceipt(binding3.review) === void 0 || original === void 0 || original.event.seq <= afterSeqExclusive || !disposition.completionEventIds.includes(binding3.completionEventId) || original.event.subject !== "core:independent-code-reviewer" || original.event.source !== observation2.expectedSource || original.completion.verdict !== "pass" || original.completion.findings.some((finding) => finding.classification !== "advisory") || original.completion.contractVersion !== observation2.contractVersions[original.event.subject] || field4(original.event, "inputHash") !== observation2.currentInputHashes[original.event.subject] || canonicalJsonHash(original.completion) !== binding3.completionHash || !sameReviewSubject(binding3.review, observedSubject) || binding3.review.taskId !== original.event.missionId || binding3.review.writerId !== writerId || binding3.review.provenance.kind !== "native-context" || binding3.review.provenance.contextId !== field4(original.event, "contextId"))
|
|
36745
36746
|
return false;
|
|
36746
|
-
const requested = events.find((event) => event.seq < original.event.seq && matchesRequest(original.event, event) && field4(event, "contractVersion") === original.completion.contractVersion && `runtime:${String(field4(event, "runtime"))}` === observation2.expectedSource);
|
|
36747
|
-
return requested !== void 0 && events.some((event) => event.kind === "specialist.started" && event.source === observation2.expectedSource && event.subject === original.event.subject && event.seq > requested.seq && event.seq < original.event.seq && ["stage", "reviewRound", "inputHash", "contextId"].every((key) => field4(event, key) === field4(original.event, key)));
|
|
36747
|
+
const requested = events.find((event) => event.seq > afterSeqExclusive && event.seq < original.event.seq && matchesRequest(original.event, event) && field4(event, "contractVersion") === original.completion.contractVersion && `runtime:${String(field4(event, "runtime"))}` === observation2.expectedSource);
|
|
36748
|
+
return requested !== void 0 && events.some((event) => event.kind === "specialist.started" && event.source === observation2.expectedSource && event.subject === original.event.subject && event.seq > requested.seq && event.seq < original.event.seq && ["stage", "reviewRound", "inputHash", "contractVersion", "contextId"].every((key) => field4(event, key) === field4(original.event, key)));
|
|
36748
36749
|
});
|
|
36749
36750
|
}
|
|
36750
36751
|
function pendingPreparationClarification(input2, previousRecovery) {
|
|
@@ -36795,11 +36796,11 @@ function admitStoppedMission(input2, projectedHistory = input2.stream.events) {
|
|
|
36795
36796
|
return refuse2("stale-journal", "Re-observe the journal before requesting recovery");
|
|
36796
36797
|
if (ambiguousEffects(events))
|
|
36797
36798
|
return refuse2("ambiguous-effect", "Reconcile the unfinished external effect; recovery cannot replay it");
|
|
36799
|
+
const existing = completions(events).filter((item) => field4(item.event, "stage") === observation2.stage);
|
|
36798
36800
|
const previousRecovery = events.filter((event) => event.kind === "mission.recovered").at(-1);
|
|
36799
|
-
if (previousRecovery !== void 0 && !events.some((event) => event.seq > previousRecovery.seq && event.kind === "lead-writer.completed") && !pendingPreparationClarification(input2, previousRecovery)) {
|
|
36801
|
+
if (previousRecovery !== void 0 && !events.some((event) => event.seq > previousRecovery.seq && event.kind === "lead-writer.completed") && !pendingPreparationClarification(input2, previousRecovery) && !validProvenanceRecovery(input2, existing, previousRecovery.seq)) {
|
|
36800
36802
|
return refuse2("no-recovery-progress", "Record actual corrective progress before reopening another stopped episode");
|
|
36801
36803
|
}
|
|
36802
|
-
const existing = completions(events).filter((item) => field4(item.event, "stage") === observation2.stage);
|
|
36803
36804
|
const disposition = request.disposition;
|
|
36804
36805
|
const replacement = disposition.kind === "preparation-invocation";
|
|
36805
36806
|
if (replacement && !preparationInvocationReplacement(input2)) {
|
|
@@ -36879,7 +36880,7 @@ function admitStoppedMission(input2, projectedHistory = input2.stream.events) {
|
|
|
36879
36880
|
requiredSpecialists: required2,
|
|
36880
36881
|
contractVersions: observation2.contractVersions,
|
|
36881
36882
|
currentInputHashes: observation2.currentInputHashes,
|
|
36882
|
-
maxRounds: observation2.maxRounds
|
|
36883
|
+
...observation2.reviewPolicy === "bounded-corrections-v1" ? BOUNDED_REVIEW_BUDGET : { maxRounds: observation2.maxRounds }
|
|
36883
36884
|
};
|
|
36884
36885
|
const originalReview = reduceReviewLoop({ ...reviewInput, events: projectedHistory });
|
|
36885
36886
|
if (originalReview.issues.some((issue4) => replacement ? issue4.code !== "reused-context" || !inadmissible.includes(issue4.eventId) : issue4.code !== "wrong-review-round")) {
|
|
@@ -36893,7 +36894,7 @@ function admitStoppedMission(input2, projectedHistory = input2.stream.events) {
|
|
|
36893
36894
|
return refuse2("incomplete-required-panel", "Every required preparation specialist must have its original matching PASS triplet before recovery");
|
|
36894
36895
|
}
|
|
36895
36896
|
const consumedRounds = Math.max(0, ...(replacement ? projectedHistory : projected).filter((event) => ["specialist.completed", "specialist.failed"].includes(event.kind) && field4(event, "stage") === observation2.stage).map((event) => Number(field4(event, "reviewRound"))));
|
|
36896
|
-
if (!replacement && disposition.kind !== "review-provenance" && consumedRounds >=
|
|
36897
|
+
if (!replacement && disposition.kind !== "review-provenance" && consumedRounds >= reviewInput.maxRounds)
|
|
36897
36898
|
return refuse2("review-budget-exhausted", "The real review budget is exhausted; recovery cannot reset it");
|
|
36898
36899
|
const consumedCorrectionBatches = projected.filter((event) => event.kind === "lead-writer.completed" && field4(event, "actionKind") === "run-correction").length;
|
|
36899
36900
|
if (disposition.kind === "review-provenance" && consumedCorrectionBatches > 2) {
|
|
@@ -36915,7 +36916,7 @@ function admitStoppedMission(input2, projectedHistory = input2.stream.events) {
|
|
|
36915
36916
|
inadmissibleCompletionEventIds: inadmissible,
|
|
36916
36917
|
roundCorrections,
|
|
36917
36918
|
consumedRounds,
|
|
36918
|
-
remainingRounds: Math.max(0,
|
|
36919
|
+
remainingRounds: Math.max(0, reviewInput.maxRounds - consumedRounds),
|
|
36919
36920
|
...!replacement && disposition.kind !== "review-provenance" ? {} : {
|
|
36920
36921
|
consumedCorrectionBatches,
|
|
36921
36922
|
remainingCorrectionBatches: 2 - consumedCorrectionBatches
|
|
@@ -36960,11 +36961,12 @@ function recoveryObservation(value) {
|
|
|
36960
36961
|
"currentInputHashes",
|
|
36961
36962
|
"maxRounds",
|
|
36962
36963
|
"expectedSource",
|
|
36964
|
+
...value["reviewPolicy"] === void 0 ? [] : ["reviewPolicy"],
|
|
36963
36965
|
...value["resolutionArtifact"] === void 0 ? [] : ["resolutionArtifact"],
|
|
36964
36966
|
...value["reviewSubject"] === void 0 ? [] : ["reviewSubject"],
|
|
36965
36967
|
...value["reviewBindings"] === void 0 ? [] : ["reviewBindings"],
|
|
36966
36968
|
...value["evidenceDependencies"] === void 0 ? [] : ["evidenceDependencies"]
|
|
36967
|
-
]) || !["pre-implementation", "post-implementation"].includes(String(value["stage"])) || !["runtime:codex", "runtime:claude"].includes(String(value["expectedSource"])) || typeof value["maxRounds"] !== "number" || !Number.isSafeInteger(value["maxRounds"]) || value["maxRounds"] < 1 || value["maxRounds"] > 8 || value["contractVersions"] === void 0 || !record10(value["contractVersions"]) || value["currentInputHashes"] === void 0 || !record10(value["currentInputHashes"]))
|
|
36969
|
+
]) || value["reviewPolicy"] !== void 0 && value["reviewPolicy"] !== "bounded-corrections-v1" || !["pre-implementation", "post-implementation"].includes(String(value["stage"])) || !["runtime:codex", "runtime:claude"].includes(String(value["expectedSource"])) || typeof value["maxRounds"] !== "number" || !Number.isSafeInteger(value["maxRounds"]) || value["maxRounds"] < 1 || value["maxRounds"] > 8 || value["contractVersions"] === void 0 || !record10(value["contractVersions"]) || value["currentInputHashes"] === void 0 || !record10(value["currentInputHashes"]))
|
|
36968
36970
|
return false;
|
|
36969
36971
|
return Object.keys(value["contractVersions"]).length <= 64 && Object.keys(value["currentInputHashes"]).length <= 64 && Object.entries(value["contractVersions"]).every(([key, version3]) => specialistId2(key) && typeof version3 === "number" && Number.isSafeInteger(version3) && version3 >= 1 && version3 <= 1e4) && Object.entries(value["currentInputHashes"]).every(([key, hash7]) => specialistId2(key) && typeof hash7 === "string" && /^sha256:[a-f0-9]{64}$/.test(hash7)) && (value["reviewSubject"] === void 0 || isReviewSubject(value["reviewSubject"])) && (value["reviewBindings"] === void 0 || Array.isArray(value["reviewBindings"]) && value["reviewBindings"].length <= 64 && value["reviewBindings"].every(reviewBinding)) && (value["resolutionArtifact"] === void 0 || artifact(value["resolutionArtifact"])) && (value["evidenceDependencies"] === void 0 || record10(value["evidenceDependencies"]) && Object.keys(value["evidenceDependencies"]).length <= 64 && Object.entries(value["evidenceDependencies"]).every(([key, hash7]) => key.length > 0 && key.length <= 500 && typeof hash7 === "string" && /^sha256:[a-f0-9]{64}$/.test(hash7)));
|
|
36970
36972
|
}
|
|
@@ -39070,8 +39072,7 @@ function orchestrateMissionTeam(originalInput) {
|
|
|
39070
39072
|
requiredSpecialists: requiredSpecialists(input2.plan, "post-implementation", start.boundedReview),
|
|
39071
39073
|
contractVersions: contractVersions(input2.plan),
|
|
39072
39074
|
currentInputHashes: input2.currentInputHashesByStage["post-implementation"],
|
|
39073
|
-
|
|
39074
|
-
...start.boundedReview ? { maxCorrectionBatches: 2 } : {},
|
|
39075
|
+
...start.boundedReview ? BOUNDED_REVIEW_BUDGET : { maxRounds: input2.maxReviewRounds },
|
|
39075
39076
|
evidenceObligations: obligations,
|
|
39076
39077
|
validProofIds: input2.stream.events.flatMap((event) => {
|
|
39077
39078
|
if (event.kind !== "evidence.recorded")
|
|
@@ -46578,7 +46579,7 @@ function dependencyEntries(pkg) {
|
|
|
46578
46579
|
function technology(id3, version3, source2) {
|
|
46579
46580
|
return Object.freeze({ id: id3, version: version3, sources: Object.freeze([source2]) });
|
|
46580
46581
|
}
|
|
46581
|
-
function packageTechnologies(workspace) {
|
|
46582
|
+
function packageTechnologies(workspace, recipe) {
|
|
46582
46583
|
const technologies = /* @__PURE__ */ new Map();
|
|
46583
46584
|
const source2 = workspace.path === "." ? "package.json" : `${workspace.path}/package.json`;
|
|
46584
46585
|
for (const [dependency, version3] of dependencyEntries(workspace.packageJson)) {
|
|
@@ -46591,7 +46592,10 @@ function packageTechnologies(workspace) {
|
|
|
46591
46592
|
}
|
|
46592
46593
|
}
|
|
46593
46594
|
if (workspace.path === ".") {
|
|
46594
|
-
|
|
46595
|
+
const declaredNode2 = workspace.packageJson.engines?.node;
|
|
46596
|
+
const projectNode = typeof declaredNode2 === "string" && /^\d+\.\d+\.\d+$/.test(declaredNode2) ? declaredNode2 : null;
|
|
46597
|
+
const projectNodeSource = projectNode === null && typeof declaredNode2 === "string" ? `package.json:engines.node:${declaredNode2}` : "package.json:engines.node";
|
|
46598
|
+
technologies.set("node", recipe === "runtime-v1" ? technology("node", exactVersion(process.versions.node), "runtime:node") : technology("node", projectNode, projectNodeSource));
|
|
46595
46599
|
const packageManager = workspace.packageJson.packageManager;
|
|
46596
46600
|
if (packageManager !== void 0) {
|
|
46597
46601
|
const separator = packageManager.indexOf("@");
|
|
@@ -46617,7 +46621,7 @@ function packageTechnologies(workspace) {
|
|
|
46617
46621
|
function validChangedFile(file2) {
|
|
46618
46622
|
return file2.length > 0 && !file2.startsWith("/") && !file2.startsWith("../") && !file2.includes("/../") && !file2.includes("\\") && posix.normalize(file2) === file2;
|
|
46619
46623
|
}
|
|
46620
|
-
function detectProfileInput(root, files) {
|
|
46624
|
+
function detectProfileInput(root, files, recipe = "runtime-v1") {
|
|
46621
46625
|
if (!files.every(validChangedFile)) {
|
|
46622
46626
|
throw new Error("PROFILE_INPUT_INVALID: changed files must remain inside the project root");
|
|
46623
46627
|
}
|
|
@@ -46625,7 +46629,7 @@ function detectProfileInput(root, files) {
|
|
|
46625
46629
|
const rootWorkspace = workspaces.find((workspace) => workspace.path === ".");
|
|
46626
46630
|
const projects2 = workspaces.map((workspace) => Object.freeze({
|
|
46627
46631
|
path: workspace.path,
|
|
46628
|
-
technologies: packageTechnologies(workspace)
|
|
46632
|
+
technologies: packageTechnologies(workspace, recipe)
|
|
46629
46633
|
}));
|
|
46630
46634
|
if (projects2.length > 1) {
|
|
46631
46635
|
const rootProject = projects2.find((project) => project.path === ".");
|
|
@@ -55863,17 +55867,18 @@ async function createMission(root, input2) {
|
|
|
55863
55867
|
);
|
|
55864
55868
|
await writeMissionMetadata(run2, safeInput, createdAt);
|
|
55865
55869
|
}
|
|
55866
|
-
async function writeMissionControllerPlan(root, missionId, plan2, ticket, baseCommit) {
|
|
55870
|
+
async function writeMissionControllerPlan(root, missionId, plan2, ticket, baseCommit, inputRecipe) {
|
|
55867
55871
|
const run2 = await existingRunDirectory(root, missionId);
|
|
55868
|
-
const routingHash = missionControllerRoutingHash(plan2, ticket, baseCommit);
|
|
55872
|
+
const routingHash = missionControllerRoutingHash(plan2, ticket, baseCommit, inputRecipe);
|
|
55869
55873
|
const handle3 = await open$1(join(run2, "controller-plan.json"), "wx", 384);
|
|
55870
55874
|
try {
|
|
55871
55875
|
await handle3.writeFile(`${JSON.stringify({
|
|
55872
|
-
schemaVersion: baseCommit === void 0 ? 1 : 2,
|
|
55876
|
+
schemaVersion: inputRecipe === void 0 ? baseCommit === void 0 ? 1 : 2 : 3,
|
|
55873
55877
|
routingHash,
|
|
55874
55878
|
plan: plan2,
|
|
55875
55879
|
ticket,
|
|
55876
|
-
...baseCommit === void 0 ? {} : { baseCommit }
|
|
55880
|
+
...baseCommit === void 0 ? {} : { baseCommit },
|
|
55881
|
+
...inputRecipe === void 0 ? {} : { inputRecipe }
|
|
55877
55882
|
})}
|
|
55878
55883
|
`, "utf8");
|
|
55879
55884
|
} finally {
|
|
@@ -55902,28 +55907,38 @@ async function loadMissionControllerPlan(root, missionId) {
|
|
|
55902
55907
|
"routingHash",
|
|
55903
55908
|
"plan",
|
|
55904
55909
|
"ticket",
|
|
55905
|
-
...parsed.schemaVersion === 2 ? ["baseCommit"] : []
|
|
55910
|
+
...parsed.schemaVersion === 2 || parsed.schemaVersion === 3 ? ["baseCommit"] : [],
|
|
55911
|
+
...parsed.schemaVersion === 3 ? ["inputRecipe"] : []
|
|
55906
55912
|
])) {
|
|
55907
55913
|
throw new Error("MISSION_CONTROLLER_PLAN_INVALID: plan envelope is malformed");
|
|
55908
55914
|
}
|
|
55909
55915
|
const plan2 = parseMissionSpecialistPlan(parsed.plan);
|
|
55910
55916
|
const ticket = parseMissionControllerTicket(parsed.ticket);
|
|
55911
55917
|
const routingHash = typeof parsed.routingHash === "string" ? parsed.routingHash : "";
|
|
55912
|
-
const baseCommit = parsed.schemaVersion === 2 ? parsed.baseCommit : void 0;
|
|
55913
|
-
|
|
55918
|
+
const baseCommit = parsed.schemaVersion === 2 || parsed.schemaVersion === 3 ? parsed.baseCommit : void 0;
|
|
55919
|
+
const inputRecipe = parsed.schemaVersion === 3 && parsed.inputRecipe === "project-v1" ? "project-v1" : void 0;
|
|
55920
|
+
if (parsed.schemaVersion !== 1 && parsed.schemaVersion !== 2 && parsed.schemaVersion !== 3 || parsed.schemaVersion === 3 && inputRecipe === void 0 || (parsed.schemaVersion === 2 || parsed.schemaVersion === 3) && (typeof baseCommit !== "string" || !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(baseCommit)) || !/^sha256:[a-f0-9]{64}$/.test(routingHash)) {
|
|
55914
55921
|
throw new Error("MISSION_CONTROLLER_PLAN_INVALID: plan integrity check failed");
|
|
55915
55922
|
}
|
|
55916
55923
|
const base = typeof baseCommit === "string" ? { baseCommit } : {};
|
|
55917
|
-
if (missionControllerRoutingHash(plan2, ticket, base.baseCommit) !== routingHash) {
|
|
55924
|
+
if (missionControllerRoutingHash(plan2, ticket, base.baseCommit, inputRecipe) !== routingHash) {
|
|
55918
55925
|
throw new Error("MISSION_CONTROLLER_PLAN_INVALID: plan integrity check failed");
|
|
55919
55926
|
}
|
|
55920
|
-
return Object.freeze({ plan: plan2, ticket, routingHash, ...base });
|
|
55927
|
+
return Object.freeze({ plan: plan2, ticket, routingHash, ...base, ...inputRecipe === void 0 ? {} : { inputRecipe } });
|
|
55921
55928
|
}
|
|
55922
|
-
function missionControllerRoutingHash(plan2, ticket, baseCommit) {
|
|
55929
|
+
function missionControllerRoutingHash(plan2, ticket, baseCommit, inputRecipe) {
|
|
55930
|
+
if (inputRecipe !== void 0 && (inputRecipe !== "project-v1" || baseCommit === void 0)) {
|
|
55931
|
+
throw new Error("MISSION_CONTROLLER_PLAN_INVALID: project input recipe requires a committed baseline");
|
|
55932
|
+
}
|
|
55923
55933
|
if (baseCommit !== void 0 && !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(baseCommit)) {
|
|
55924
55934
|
throw new Error("MISSION_REVIEW_BASE_INVALID: expected a full commit object ID");
|
|
55925
55935
|
}
|
|
55926
|
-
return canonicalJsonHash({
|
|
55936
|
+
return canonicalJsonHash({
|
|
55937
|
+
plan: plan2,
|
|
55938
|
+
ticket,
|
|
55939
|
+
...baseCommit === void 0 ? {} : { baseCommit },
|
|
55940
|
+
...inputRecipe === void 0 ? {} : { inputRecipe }
|
|
55941
|
+
});
|
|
55927
55942
|
}
|
|
55928
55943
|
function unknownRecord2(value) {
|
|
55929
55944
|
return typeof value === "object" && Boolean(value) && !Array.isArray(value);
|
|
@@ -56335,6 +56350,7 @@ async function loadProfiles(root, coreProfileDirectory) {
|
|
|
56335
56350
|
// src/lib/mission-inputs.ts
|
|
56336
56351
|
var execFile2 = promisify(execFile);
|
|
56337
56352
|
var MAX_TICKET_BYTES = 1e5;
|
|
56353
|
+
var MAX_REVIEW_PATCH_BYTES = 16e6;
|
|
56338
56354
|
async function readTicket(root, ticketPath) {
|
|
56339
56355
|
const canonicalRoot = await realpath(resolve(root));
|
|
56340
56356
|
const loaded = await readBoundedProjectFile({
|
|
@@ -56390,7 +56406,7 @@ async function missionReviewBase(root) {
|
|
|
56390
56406
|
throw new Error("MISSION_REVIEW_BASE_INVALID: a committed Git baseline is required");
|
|
56391
56407
|
}
|
|
56392
56408
|
}
|
|
56393
|
-
async function captureMissionReviewSubject(root, baseCommit, committed = false) {
|
|
56409
|
+
async function captureMissionReviewSubject(root, baseCommit, committed = false, hashFormat = committed ? "committed" : "legacy") {
|
|
56394
56410
|
if (baseCommit === void 0) {
|
|
56395
56411
|
throw new Error(
|
|
56396
56412
|
"MISSION_REVIEW_BASE_MISSING: legacy mission has no review baseline; preserve its history and start a new mission"
|
|
@@ -56430,7 +56446,17 @@ async function captureMissionReviewSubject(root, baseCommit, committed = false)
|
|
|
56430
56446
|
"--relative",
|
|
56431
56447
|
...range,
|
|
56432
56448
|
...paths
|
|
56433
|
-
],
|
|
56449
|
+
], {
|
|
56450
|
+
...options2,
|
|
56451
|
+
maxBuffer: MAX_REVIEW_PATCH_BYTES
|
|
56452
|
+
}).catch((error62) => {
|
|
56453
|
+
if (error62 instanceof Error && "code" in error62 && error62.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") {
|
|
56454
|
+
throw new Error(
|
|
56455
|
+
`MISSION_REVIEW_PATCH_TOO_LARGE: Git review patch exceeds ${MAX_REVIEW_PATCH_BYTES} bytes`
|
|
56456
|
+
);
|
|
56457
|
+
}
|
|
56458
|
+
throw error62;
|
|
56459
|
+
}),
|
|
56434
56460
|
execFile2("git", [
|
|
56435
56461
|
"diff",
|
|
56436
56462
|
"--no-ext-diff",
|
|
@@ -56463,7 +56489,7 @@ async function captureMissionReviewSubject(root, baseCommit, committed = false)
|
|
|
56463
56489
|
files,
|
|
56464
56490
|
hash: canonicalJsonHash({
|
|
56465
56491
|
baseCommit,
|
|
56466
|
-
...
|
|
56492
|
+
...hashFormat === "legacy" ? {} : { reviewedCommit },
|
|
56467
56493
|
diff: patch.stdout,
|
|
56468
56494
|
files: files.files
|
|
56469
56495
|
})
|
|
@@ -56537,10 +56563,10 @@ function detectedStack(root, profileInput) {
|
|
|
56537
56563
|
status: "known"
|
|
56538
56564
|
});
|
|
56539
56565
|
}
|
|
56540
|
-
async function planMission(root, ticketPath, generatedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
56541
|
-
return (await planBoundMission(root, ticketPath, generatedAt)).plan;
|
|
56566
|
+
async function planMission(root, ticketPath, generatedAt = (/* @__PURE__ */ new Date()).toISOString(), inputRecipe = "runtime-v1") {
|
|
56567
|
+
return (await planBoundMission(root, ticketPath, generatedAt, void 0, void 0, inputRecipe)).plan;
|
|
56542
56568
|
}
|
|
56543
|
-
async function compileMission(root, ticket, generatedAt, detectedFiles, specialistCatalog) {
|
|
56569
|
+
async function compileMission(root, ticket, generatedAt, detectedFiles, specialistCatalog, inputRecipe = "runtime-v1") {
|
|
56544
56570
|
const [coreRoot, diff] = await Promise.all([
|
|
56545
56571
|
findCoreSource(),
|
|
56546
56572
|
detectedFiles ?? gitFiles(root)
|
|
@@ -56550,7 +56576,7 @@ async function compileMission(root, ticket, generatedAt, detectedFiles, speciali
|
|
|
56550
56576
|
loadProfiles(root, join(coreRoot, "profiles")),
|
|
56551
56577
|
specialistCatalog ?? loadSpecialists(coreRoot)
|
|
56552
56578
|
]);
|
|
56553
|
-
const profileInput = detectProfileInput(root, diff.files);
|
|
56579
|
+
const profileInput = detectProfileInput(root, diff.files, inputRecipe);
|
|
56554
56580
|
const stack = detectedStack(root, profileInput);
|
|
56555
56581
|
return compileMissionPlan({
|
|
56556
56582
|
ticket: { id: ticket.id, title: ticket.title, body: ticket.body },
|
|
@@ -56570,9 +56596,9 @@ function controllerTicketBinding(ticket) {
|
|
|
56570
56596
|
contentHash: `sha256:${createHash("sha256").update(ticket.body).digest("hex")}`
|
|
56571
56597
|
});
|
|
56572
56598
|
}
|
|
56573
|
-
async function planBoundMission(root, ticketPath, generatedAt = (/* @__PURE__ */ new Date()).toISOString(), detectedFiles, specialistCatalog) {
|
|
56599
|
+
async function planBoundMission(root, ticketPath, generatedAt = (/* @__PURE__ */ new Date()).toISOString(), detectedFiles, specialistCatalog, inputRecipe = "runtime-v1") {
|
|
56574
56600
|
const ticket = await readTicket(root, ticketPath);
|
|
56575
|
-
const plan2 = await compileMission(root, ticket, generatedAt, detectedFiles, specialistCatalog);
|
|
56601
|
+
const plan2 = await compileMission(root, ticket, generatedAt, detectedFiles, specialistCatalog, inputRecipe);
|
|
56576
56602
|
return Object.freeze({
|
|
56577
56603
|
plan: plan2,
|
|
56578
56604
|
ticket: controllerTicketBinding(ticket)
|
|
@@ -67920,7 +67946,7 @@ async function recordSpecialistRequests(root, missionId, envelopes, planHash) {
|
|
|
67920
67946
|
init_esm_shims();
|
|
67921
67947
|
|
|
67922
67948
|
// package.json
|
|
67923
|
-
var version2 = "5.0.
|
|
67949
|
+
var version2 = "5.0.4";
|
|
67924
67950
|
|
|
67925
67951
|
// src/lib/runs/verify.ts
|
|
67926
67952
|
var MAX_CAPTURE_BYTES = 64 * 1024;
|
|
@@ -68430,8 +68456,8 @@ async function migrateMissionSpecialist(roots, missionId, request) {
|
|
|
68430
68456
|
throw new Error(`SPECIALIST_CONTRACT_MIGRATION_NATIVE: ${capability.limitations.join("; ")}`);
|
|
68431
68457
|
}
|
|
68432
68458
|
const assets = await observeSpecialistMigrationAssets(coreRoot, installRoot, identity2.runtime);
|
|
68433
|
-
const subject = await
|
|
68434
|
-
const live = await planBoundMission(workRoot, stored.ticket.path, (/* @__PURE__ */ new Date()).toISOString(), subject.files);
|
|
68459
|
+
const subject = await captureControllerReviewSubject(workRoot, stored.baseCommit, current.inspected.stream.events);
|
|
68460
|
+
const live = await planBoundMission(workRoot, stored.ticket.path, (/* @__PURE__ */ new Date()).toISOString(), subject.files, void 0, stored.inputRecipe);
|
|
68435
68461
|
if (live.ticket.path !== stored.ticket.path || live.ticket.contentHash !== stored.ticket.contentHash) {
|
|
68436
68462
|
throw new Error("MISSION_TICKET_CHANGED: migration cannot replace the bound ticket");
|
|
68437
68463
|
}
|
|
@@ -68440,7 +68466,7 @@ async function migrateMissionSpecialist(roots, missionId, request) {
|
|
|
68440
68466
|
canonicalJsonHash({ routing: specialist.proof.inputHash, subject: subject.hash })
|
|
68441
68467
|
]));
|
|
68442
68468
|
const comparisonCatalog = await loadSpecialistMigrationComparisonCatalog(coreRoot, assets.declaration, stored.plan);
|
|
68443
|
-
const comparison = await planBoundMission(workRoot, stored.ticket.path, (/* @__PURE__ */ new Date()).toISOString(), subject.files, comparisonCatalog);
|
|
68469
|
+
const comparison = await planBoundMission(workRoot, stored.ticket.path, (/* @__PURE__ */ new Date()).toISOString(), subject.files, comparisonCatalog, stored.inputRecipe);
|
|
68444
68470
|
const originalHashes = Object.fromEntries(comparison.plan.specialists.map((specialist) => [
|
|
68445
68471
|
specialist.specialistId,
|
|
68446
68472
|
canonicalJsonHash({ routing: specialist.proof.inputHash, subject: subject.hash })
|
|
@@ -68465,6 +68491,18 @@ async function migrateMissionSpecialist(roots, missionId, request) {
|
|
|
68465
68491
|
nativeAgentSha256: assets.nativeAgentSha256
|
|
68466
68492
|
} };
|
|
68467
68493
|
}
|
|
68494
|
+
function boundedMissionReview(events) {
|
|
68495
|
+
return events.some((event) => event.kind === "mission.started" && objectField(event.payload, "reviewPolicy") === "bounded-corrections-v1") || events.some((event) => event.kind === "mission.recovered" && objectField(objectField(objectField(event.payload, "request"), "disposition"), "kind") === "review-provenance");
|
|
68496
|
+
}
|
|
68497
|
+
function captureControllerReviewSubject(root, baseCommit, events, requireCommitted = false) {
|
|
68498
|
+
const committedFormat = events.some((event) => event.kind === "mission.started" && objectField(event.payload, "reviewPolicy") === "bounded-corrections-v1");
|
|
68499
|
+
return captureMissionReviewSubject(
|
|
68500
|
+
root,
|
|
68501
|
+
baseCommit,
|
|
68502
|
+
requireCommitted || boundedMissionReview(events),
|
|
68503
|
+
committedFormat ? "committed" : "legacy"
|
|
68504
|
+
);
|
|
68505
|
+
}
|
|
68468
68506
|
async function recoverStoppedMission(roots, missionId, request) {
|
|
68469
68507
|
const { workRoot, installRoot } = roots;
|
|
68470
68508
|
const [stored, current, coreRoot] = await Promise.all([
|
|
@@ -68496,8 +68534,13 @@ async function recoverStoppedMission(roots, missionId, request) {
|
|
|
68496
68534
|
}
|
|
68497
68535
|
}
|
|
68498
68536
|
const implemented = inspected.stream.events.some((event) => event.kind === "lead-writer.completed" && objectField(event.payload, "actionKind") !== "run-preparation-correction");
|
|
68499
|
-
const subject = implemented ? await
|
|
68500
|
-
|
|
68537
|
+
const subject = implemented ? await captureControllerReviewSubject(
|
|
68538
|
+
workRoot,
|
|
68539
|
+
stored.baseCommit,
|
|
68540
|
+
inspected.stream.events,
|
|
68541
|
+
provenanceRecovery
|
|
68542
|
+
) : void 0;
|
|
68543
|
+
const live = await planBoundMission(workRoot, stored.ticket.path, (/* @__PURE__ */ new Date()).toISOString(), subject?.files, void 0, stored.inputRecipe);
|
|
68501
68544
|
if (live.ticket.path !== stored.ticket.path || live.ticket.contentHash !== stored.ticket.contentHash) {
|
|
68502
68545
|
throw new Error("MISSION_TICKET_CHANGED: recovery cannot replace the original ticket");
|
|
68503
68546
|
}
|
|
@@ -68514,7 +68557,7 @@ async function recoverStoppedMission(roots, missionId, request) {
|
|
|
68514
68557
|
throw new Error("SPECIALIST_CONTRACT_MIGRATION_INVALID: declared or installed contract changed");
|
|
68515
68558
|
}
|
|
68516
68559
|
const comparisonCatalog = await loadSpecialistMigrationComparisonCatalog(coreRoot, assets.declaration, stored.plan);
|
|
68517
|
-
const comparison = await planBoundMission(workRoot, stored.ticket.path, (/* @__PURE__ */ new Date()).toISOString(), subject.files, comparisonCatalog);
|
|
68560
|
+
const comparison = await planBoundMission(workRoot, stored.ticket.path, (/* @__PURE__ */ new Date()).toISOString(), subject.files, comparisonCatalog, stored.inputRecipe);
|
|
68518
68561
|
for (const specialist of comparison.plan.specialists) {
|
|
68519
68562
|
if (specialist.specialistId !== assets.declaration.specialistId) {
|
|
68520
68563
|
currentInputHashes[specialist.specialistId] = canonicalJsonHash({
|
|
@@ -68526,19 +68569,19 @@ async function recoverStoppedMission(roots, missionId, request) {
|
|
|
68526
68569
|
}
|
|
68527
68570
|
const provenance4 = request.disposition.kind !== "review-provenance" ? void 0 : await readRecoveryReviewBindings(workRoot, request.disposition.resolutionArtifact.path);
|
|
68528
68571
|
const resolutionArtifact = provenance4?.artifact ?? (request.disposition.kind === "review-blocker" || request.disposition.kind === "command-correction" ? await recoveryResolutionArtifact(workRoot, request.disposition.resolutionArtifact.path) : void 0);
|
|
68529
|
-
const committedSubject = provenance4 === void 0 ? void 0 : await captureMissionReviewSubject(workRoot, stored.baseCommit, true);
|
|
68530
68572
|
const opaqueContextRecovery = request.disposition.kind === "controller-defect" && request.disposition.defect === "opaque-native-context";
|
|
68531
68573
|
const recoverySpecialists = opaqueContextRecovery || request.disposition.kind === "preparation-invocation" ? migration.plan.specialists.filter((value) => value.state === "applicable" && value.stages?.includes("pre-implementation")) : migration.plan.specialists;
|
|
68532
68574
|
const observation2 = {
|
|
68533
|
-
...provenance4 === void 0 ||
|
|
68575
|
+
...provenance4 === void 0 || subject?.baseCommit === void 0 || subject.reviewedCommit === void 0 ? {} : {
|
|
68534
68576
|
reviewBindings: provenance4.bindings,
|
|
68535
68577
|
reviewSubject: {
|
|
68536
68578
|
taskId: missionId,
|
|
68537
|
-
baseCommit:
|
|
68538
|
-
reviewedCommit:
|
|
68579
|
+
baseCommit: subject.baseCommit,
|
|
68580
|
+
reviewedCommit: subject.reviewedCommit,
|
|
68539
68581
|
acceptanceCriteriaHash: stored.ticket.contentHash
|
|
68540
68582
|
}
|
|
68541
68583
|
},
|
|
68584
|
+
...implemented && (provenanceRecovery || boundedMissionReview(inspected.stream.events)) ? { reviewPolicy: "bounded-corrections-v1" } : {},
|
|
68542
68585
|
stage: implemented ? "post-implementation" : "pre-implementation",
|
|
68543
68586
|
maxRounds: 2,
|
|
68544
68587
|
evidenceDependencies: { "git:working-tree": current.project.diffHash },
|
|
@@ -68602,13 +68645,14 @@ async function dispatchMissionSpecialists(roots, input2, generatedAt = (/* @__PU
|
|
|
68602
68645
|
const inspected = current?.inspected ?? initialInspection;
|
|
68603
68646
|
requireOpenMission(inspected.stream.events);
|
|
68604
68647
|
const implemented = inspected.stream.events.some((event) => event.kind === "lead-writer.completed" && isUnknownRecord(event.payload) && event.payload.actionKind !== "run-preparation-correction");
|
|
68605
|
-
const
|
|
68606
|
-
const reviewSubject = implemented ? await captureMissionReviewSubject(workRoot, stored.baseCommit, boundedReview) : void 0;
|
|
68648
|
+
const reviewSubject = implemented ? await captureControllerReviewSubject(workRoot, stored.baseCommit, inspected.stream.events) : void 0;
|
|
68607
68649
|
const live = await planBoundMission(
|
|
68608
68650
|
workRoot,
|
|
68609
68651
|
stored.ticket.path,
|
|
68610
68652
|
generatedAt,
|
|
68611
|
-
reviewSubject?.files
|
|
68653
|
+
reviewSubject?.files,
|
|
68654
|
+
void 0,
|
|
68655
|
+
stored.inputRecipe
|
|
68612
68656
|
);
|
|
68613
68657
|
const livePlan = live.plan;
|
|
68614
68658
|
if (live.ticket.path !== stored.ticket.path || live.ticket.contentHash !== stored.ticket.contentHash) {
|
|
@@ -68639,7 +68683,7 @@ async function dispatchMissionSpecialists(roots, input2, generatedAt = (/* @__PU
|
|
|
68639
68683
|
throw new Error("SPECIALIST_CONTRACT_MIGRATION_INVALID: declared or installed contract changed");
|
|
68640
68684
|
}
|
|
68641
68685
|
const catalog = await loadSpecialistMigrationComparisonCatalog(coreRoot, assets.declaration, stored.plan);
|
|
68642
|
-
const comparison = await planBoundMission(workRoot, stored.ticket.path, generatedAt, reviewSubject.files, catalog);
|
|
68686
|
+
const comparison = await planBoundMission(workRoot, stored.ticket.path, generatedAt, reviewSubject.files, catalog, stored.inputRecipe);
|
|
68643
68687
|
for (const specialist of comparison.plan.specialists) {
|
|
68644
68688
|
if (specialist.specialistId !== assets.declaration.specialistId) {
|
|
68645
68689
|
currentInputHashes[specialist.specialistId] = canonicalJsonHash({
|
|
@@ -68674,19 +68718,6 @@ async function dispatchMissionSpecialists(roots, input2, generatedAt = (/* @__PU
|
|
|
68674
68718
|
reviewedCommit: reviewSubject.reviewedCommit,
|
|
68675
68719
|
acceptanceCriteriaHash: stored.ticket.contentHash
|
|
68676
68720
|
};
|
|
68677
|
-
if (reviewBinding2 !== void 0) {
|
|
68678
|
-
const restored = validatedRecoveredReviewEvents(inspected.stream.events);
|
|
68679
|
-
if (!restored.ok) throw new Error(`MISSION_RECOVERY_INVALID: ${restored.reasons.join("; ")}`);
|
|
68680
|
-
const hasProvenanceRecovery = inspected.stream.events.some((event) => event.kind === "mission.recovered" && objectField(objectField(objectField(event.payload, "request"), "disposition"), "kind") === "review-provenance");
|
|
68681
|
-
if (hasProvenanceRecovery) for (const event of restored.events) {
|
|
68682
|
-
if (event.kind !== "specialist.completed") continue;
|
|
68683
|
-
const receipt2 = parseSpecialistCompletionValue(objectField(event.payload, "completion"))?.review;
|
|
68684
|
-
const hash7 = objectField(event.payload, "inputHash");
|
|
68685
|
-
if (receipt2 !== void 0 && sameReviewSubject(receipt2, reviewBinding2) && typeof hash7 === "string") {
|
|
68686
|
-
currentInputHashes[event.subject] = hash7;
|
|
68687
|
-
}
|
|
68688
|
-
}
|
|
68689
|
-
}
|
|
68690
68721
|
const decision = orchestrateMissionTeam({
|
|
68691
68722
|
...reviewBinding2 === void 0 ? {} : { reviewSubject: reviewBinding2 },
|
|
68692
68723
|
plan: stored.plan,
|
|
@@ -69010,7 +69041,7 @@ Fix: ${parsed.fix}
|
|
|
69010
69041
|
const journal = roots.installRoot;
|
|
69011
69042
|
try {
|
|
69012
69043
|
if (parsed.kind === "plan") {
|
|
69013
|
-
const plan2 = await planMission(root, parsed.ticketPath);
|
|
69044
|
+
const plan2 = await planMission(root, parsed.ticketPath, void 0, "project-v1");
|
|
69014
69045
|
process.stdout.write(parsed.json ? `${JSON.stringify(plan2)}
|
|
69015
69046
|
` : `${renderPlan(plan2)}
|
|
69016
69047
|
`);
|
|
@@ -69095,7 +69126,7 @@ ${dispatched.envelopes.map((envelope) => `${envelope.agentName} ${envelope.stage
|
|
|
69095
69126
|
}
|
|
69096
69127
|
if (parsed.kind === "start") {
|
|
69097
69128
|
const baseCommit = parsed.ticketPath === void 0 ? void 0 : await missionReviewBase(root);
|
|
69098
|
-
const bound = parsed.ticketPath === void 0 ? void 0 : await planBoundMission(root, parsed.ticketPath);
|
|
69129
|
+
const bound = parsed.ticketPath === void 0 ? void 0 : await planBoundMission(root, parsed.ticketPath, void 0, void 0, void 0, "project-v1");
|
|
69099
69130
|
const plan2 = bound?.plan;
|
|
69100
69131
|
const diff = plan2 === void 0 ? await gitFiles(root) : void 0;
|
|
69101
69132
|
const stack = diff === void 0 ? void 0 : detectedStack(root, detectProfileInput(root, diff.files));
|
|
@@ -69114,7 +69145,7 @@ ${dispatched.envelopes.map((envelope) => `${envelope.agentName} ${envelope.stage
|
|
|
69114
69145
|
const controllerPlan = plan2 === void 0 ? void 0 : missionSpecialistPlan(plan2);
|
|
69115
69146
|
const ticketBinding = bound?.ticket;
|
|
69116
69147
|
const runtimeIdentity = bound === void 0 ? void 0 : coordinatorRuntimeIdentity(process.env);
|
|
69117
|
-
const routingHash = controllerPlan === void 0 ? void 0 : ticketBinding === void 0 ? void 0 : missionControllerRoutingHash(controllerPlan, ticketBinding, baseCommit);
|
|
69148
|
+
const routingHash = controllerPlan === void 0 ? void 0 : ticketBinding === void 0 ? void 0 : missionControllerRoutingHash(controllerPlan, ticketBinding, baseCommit, "project-v1");
|
|
69118
69149
|
await createMission(journal, {
|
|
69119
69150
|
missionId,
|
|
69120
69151
|
title: parsed.title,
|
|
@@ -69141,7 +69172,8 @@ ${dispatched.envelopes.map((envelope) => `${envelope.agentName} ${envelope.stage
|
|
|
69141
69172
|
missionId,
|
|
69142
69173
|
controllerPlan,
|
|
69143
69174
|
ticketBinding,
|
|
69144
|
-
baseCommit
|
|
69175
|
+
baseCommit,
|
|
69176
|
+
"project-v1"
|
|
69145
69177
|
);
|
|
69146
69178
|
if (storedHash !== routingHash) {
|
|
69147
69179
|
throw new Error("MISSION_CONTROLLER_PLAN_INVALID: routing hash changed while storing");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "voidmachine",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.4",
|
|
4
4
|
"description": "void-machine CLI — install an opinionated engineering doctrine on any project; render deterministic project health. Run via `npx voidmachine`; command: void-machine (alias vm).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Folpe @ VoidCorp",
|
|
@@ -58,8 +58,8 @@
|
|
|
58
58
|
"ws": "8.21.0",
|
|
59
59
|
"yaml": "^2.5.0",
|
|
60
60
|
"zod": "^4.6.5",
|
|
61
|
+
"@voidcorp/harness-graph": "5.0.4",
|
|
61
62
|
"@voidcorp/hook-runner": "0.0.0",
|
|
62
|
-
"@voidcorp/harness-graph": "5.0.3",
|
|
63
63
|
"@voidcorp/mission-engine": "0.0.0",
|
|
64
64
|
"@voidcorp/void-machine": "0.0.0"
|
|
65
65
|
},
|