tickmarkr 2.5.4 → 2.5.6
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/README.md +3 -1
- package/dist/adapters/registry.js +6 -1
- package/dist/adapters/types.d.ts +3 -0
- package/dist/cli/commands/approve.d.ts +1 -0
- package/dist/cli/commands/approve.js +118 -9
- package/dist/cli/commands/doctor.d.ts +10 -0
- package/dist/cli/commands/doctor.js +44 -29
- package/dist/cli/commands/fleet.js +199 -33
- package/dist/cli/commands/init.js +196 -6
- package/dist/cli/commands/plan.js +20 -4
- package/dist/cli/commands/resume.js +4 -2
- package/dist/cli/commands/run.js +11 -2
- package/dist/cli/commands/verify.js +102 -81
- package/dist/cli/help.d.ts +2 -0
- package/dist/cli/help.js +3 -1
- package/dist/config/config.d.ts +25 -8
- package/dist/config/config.js +41 -29
- package/dist/config/fleet-overlay.d.ts +3 -9
- package/dist/config/fleet-overlay.js +114 -19
- package/dist/config/fleet-why.d.ts +7 -0
- package/dist/config/fleet-why.js +5 -0
- package/dist/drivers/index.d.ts +15 -1
- package/dist/drivers/index.js +39 -10
- package/dist/drivers/orca.d.ts +119 -10
- package/dist/drivers/orca.js +781 -110
- package/dist/gates/baseline.d.ts +17 -3
- package/dist/gates/baseline.js +63 -15
- package/dist/gates/cache.d.ts +101 -0
- package/dist/gates/cache.js +401 -0
- package/dist/gates/llm.d.ts +3 -0
- package/dist/gates/llm.js +11 -0
- package/dist/gates/review.d.ts +28 -3
- package/dist/gates/review.js +106 -14
- package/dist/gates/run-gates.d.ts +9 -1
- package/dist/gates/run-gates.js +407 -102
- package/dist/gates/test-manifest.d.ts +128 -0
- package/dist/gates/test-manifest.js +463 -0
- package/dist/gates/test-reporter.d.ts +4 -0
- package/dist/gates/test-reporter.js +57 -0
- package/dist/graph/graph.d.ts +2 -0
- package/dist/graph/graph.js +45 -1
- package/dist/route/preference.d.ts +22 -1
- package/dist/route/preference.js +123 -25
- package/dist/route/router.js +31 -6
- package/dist/run/consult.js +5 -4
- package/dist/run/daemon.d.ts +15 -0
- package/dist/run/daemon.js +638 -167
- package/dist/run/execution-budget.d.ts +25 -0
- package/dist/run/execution-budget.js +142 -0
- package/dist/run/git.d.ts +50 -1
- package/dist/run/git.js +131 -12
- package/dist/run/journal.d.ts +16 -2
- package/dist/run/journal.js +115 -25
- package/dist/run/lease.d.ts +58 -0
- package/dist/run/lease.js +310 -0
- package/dist/run/merge.d.ts +2 -0
- package/dist/run/merge.js +91 -3
- package/dist/run/operator-state.d.ts +11 -0
- package/dist/run/operator-state.js +17 -3
- package/dist/run/recovery.d.ts +8 -0
- package/dist/run/recovery.js +25 -0
- package/dist/run/repair-selection.d.ts +12 -0
- package/dist/run/repair-selection.js +56 -0
- package/dist/run/stall.d.ts +6 -1
- package/dist/run/stall.js +60 -3
- package/dist/tui/cockpit/board.d.ts +96 -0
- package/dist/tui/cockpit/board.js +346 -0
- package/dist/tui/cockpit/decision-actions.js +2 -0
- package/dist/tui/cockpit/layout.d.ts +5 -1
- package/dist/tui/cockpit/layout.js +8 -3
- package/dist/tui/cockpit/live-runtime.js +71 -21
- package/dist/tui/cockpit/run-view.d.ts +7 -5
- package/dist/tui/cockpit/run-view.js +12 -11
- package/dist/tui/ink/fleet-app.d.ts +49 -27
- package/dist/tui/ink/fleet-app.js +229 -38
- package/package.json +2 -2
- package/skills/tickmarkr-overseer/SKILL.md +173 -113
- package/skills/tickmarkr-overseer/scripts/grade-ci.sh +29 -2
package/dist/cli/commands/run.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
2
|
import { allAdapters, discoverChannels, probeAll, readDoctor } from "../../adapters/registry.js";
|
|
3
3
|
import { ROUTING_MODES } from "../../config/config.js";
|
|
4
|
-
import { parseDriverOverride, pickDriver } from "../../drivers/index.js";
|
|
4
|
+
import { classifyHost, parseDriverOverride, pickDriver, preflightHostDriver } from "../../drivers/index.js";
|
|
5
5
|
import { loadGraph, onDiskSpecHash, readCompileRefusal } from "../../graph/graph.js";
|
|
6
6
|
import { formatSummary, resolveRunMode, runDaemon } from "../../run/daemon.js";
|
|
7
7
|
import { isRunLockLive } from "../../run/lock.js";
|
|
@@ -106,6 +106,7 @@ export const RAIL_ROWS = {
|
|
|
106
106
|
"gate-fingerprint-cap": { label: "repeat capped", tone: "attention" },
|
|
107
107
|
// a scope red every collateral prediction already named: the daemon parks the task WITHOUT
|
|
108
108
|
// charging an attempt, which is the most consequential unchargeable decision it makes
|
|
109
|
+
"scope-request": { label: "scope requested", tone: "attention" },
|
|
109
110
|
"scope-authoring": { label: "authoring defect", tone: "attention" },
|
|
110
111
|
"session-reset": { label: "session reset", tone: "attention" },
|
|
111
112
|
"worker-mode-fallback": { label: "dispatch mode", tone: "attention" },
|
|
@@ -131,6 +132,9 @@ export const RAIL_ROWS = {
|
|
|
131
132
|
"phase-start": { label: "gate start", tone: "active" },
|
|
132
133
|
"gate-result": { label: "gate", tone: "pass" },
|
|
133
134
|
"baseline-wait": { label: "baseline wait", tone: "active" },
|
|
135
|
+
"suite-wait": { label: "suite wait", tone: "active" },
|
|
136
|
+
"suite-wait-ceiling": { label: "suite wait ceiling", tone: "attention" },
|
|
137
|
+
"worker-reaped-before-harvest": { label: "worker reaped", tone: "attention" },
|
|
134
138
|
"suite-budget": { label: "suite budget", tone: "attention" },
|
|
135
139
|
"gate-replayed": { label: "gate replayed", tone: "attention" },
|
|
136
140
|
"gate-reused": { label: "gate reused", tone: "neutral" },
|
|
@@ -320,6 +324,7 @@ const RAIL_PROJECTION = {
|
|
|
320
324
|
? `to ${d.chosen}${typeof d.static === "string" ? ` over ${d.static}` : ""}`
|
|
321
325
|
: undefined,
|
|
322
326
|
// both rehash hashes: the ladder reaches `from` (null on an unbound journal) and never `to`
|
|
327
|
+
"scope-request": (d) => Array.isArray(d.paths) ? `files[]: ${d.paths.join(", ")}` : undefined,
|
|
323
328
|
"graph-rehash": (d) => (typeof d.to === "string" ? `to ${d.to.slice(0, 12)}` : undefined),
|
|
324
329
|
"repair-dispatch": (d) => typeof d.diffBytes === "number" ? `diff ${d.diffBytes}B${d.capped === true ? ", capped" : ""}` : undefined,
|
|
325
330
|
"tip-moved": (d) => typeof d.gatedCommit === "string" && typeof d.branchTip === "string"
|
|
@@ -328,6 +333,8 @@ const RAIL_PROJECTION = {
|
|
|
328
333
|
"trust-auto-answer": (d) => typeof d.adapter === "string" ? `${d.adapter}${typeof d.phase === "string" ? ` ${d.phase}` : ""}` : undefined,
|
|
329
334
|
// SB-1: the census the ceiling released beside, and the budget the round ran under versus the one it
|
|
330
335
|
// would have had on an empty census — none of `{count, occupancyCap, conservativeCap}` is on the ladder
|
|
336
|
+
"suite-wait": (d) => typeof d.count === "number" ? `waiting for ${d.count} command(s)` : undefined,
|
|
337
|
+
"suite-wait-ceiling": (d) => typeof d.count === "number" ? `${d.count} still live after ${d.waitedMs}ms` : undefined,
|
|
331
338
|
"suite-budget": (d) => typeof d.count === "number" && typeof d.conservativeCap === "number" && typeof d.occupancyCap === "number"
|
|
332
339
|
? `beside ${d.count}, cap ${d.conservativeCap} not ${d.occupancyCap}`
|
|
333
340
|
: undefined,
|
|
@@ -503,6 +510,8 @@ export async function run(argv, cwd = process.cwd()) {
|
|
|
503
510
|
throw new Error(`--route-strict: routing lints present, refusing to dispatch:\n${lints.join("\n")}`);
|
|
504
511
|
}
|
|
505
512
|
await assertRefsWritable(cwd, "run");
|
|
513
|
+
const host = classifyHost();
|
|
514
|
+
preflightHostDriver(cfg, driverOverride, host);
|
|
506
515
|
// The run id is minted HERE rather than inside the daemon, because the narration sink has to know
|
|
507
516
|
// which run it is narrating before the first event arrives (the daemon's `narrate` callback is
|
|
508
517
|
// handed an event and nothing else, and `run-start` carries no run id). `runDaemon` uses the id
|
|
@@ -512,7 +521,7 @@ export async function run(argv, cwd = process.cwd()) {
|
|
|
512
521
|
const s = await runDaemon(cwd, {
|
|
513
522
|
runId,
|
|
514
523
|
concurrency: values.concurrency ? Number(values.concurrency) : undefined,
|
|
515
|
-
driver: bindNarration(pickDriver(cfg, driverOverride), narrate),
|
|
524
|
+
driver: bindNarration(pickDriver(cfg, driverOverride, host), narrate),
|
|
516
525
|
mode: flagMode,
|
|
517
526
|
supersedes: values.supersedes,
|
|
518
527
|
narrate,
|
|
@@ -6,13 +6,15 @@ import { parseArgs } from "node:util";
|
|
|
6
6
|
import { allAdapters, probeAll, readDoctor, rolePools } from "../../adapters/registry.js";
|
|
7
7
|
import { channelKey } from "../../adapters/types.js";
|
|
8
8
|
import { loadConfig } from "../../config/config.js";
|
|
9
|
-
import { captureBaseline, detectGateCommands } from "../../gates/baseline.js";
|
|
9
|
+
import { captureBaseline, detectGateCommands, staleFileCountCommands } from "../../gates/baseline.js";
|
|
10
10
|
import { modelProvider } from "../../gates/review.js";
|
|
11
11
|
import { runGates } from "../../gates/run-gates.js";
|
|
12
12
|
import { getTask, loadGraph } from "../../graph/graph.js";
|
|
13
13
|
import { GATE_NAMES } from "../../graph/schema.js";
|
|
14
|
+
import { executionSignal } from "../../run/execution-budget.js";
|
|
14
15
|
import { linkNodeModules, removeWorktree, shGit, shGitOk } from "../../run/git.js";
|
|
15
16
|
import { Journal } from "../../run/journal.js";
|
|
17
|
+
import { withRepositoryLease } from "../../run/lease.js";
|
|
16
18
|
/**
|
|
17
19
|
* tickmarkr verify — the gate battery as a standalone command (OPERATING-MODEL-2026-08-11 item 3).
|
|
18
20
|
*
|
|
@@ -249,95 +251,114 @@ export async function verify(argv, cwd = process.cwd()) {
|
|
|
249
251
|
// ponytail: tmpdir means the baseline cache dies on reboot/cleanup — worst case is one re-capture.
|
|
250
252
|
const stateDir = verifyStateDir(cwd);
|
|
251
253
|
mkdirSync(stateDir, { recursive: true });
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
}
|
|
270
|
-
if (cached) {
|
|
271
|
-
console.error(`verify: reusing cached baseline for ${mergeBase.slice(0, 12)} (${cachePath})`);
|
|
272
|
-
baseline = cached;
|
|
254
|
+
const recordJournal = values.record ? Journal.open(stateRoot, values.record) : undefined;
|
|
255
|
+
// OBS-1042: one runner root per repository across every linked worktree — the capture and the
|
|
256
|
+
// battery below run under a reservation keyed on the common git dir, and a waiter says who holds it.
|
|
257
|
+
return withRepositoryLease(cwd, () => leasedVerify(), {
|
|
258
|
+
signal: executionSignal(),
|
|
259
|
+
onWait: (holder) => {
|
|
260
|
+
console.error(`verify: waiting for the repository's runner lease held by pid ${holder.pid} in ${holder.cwd}`);
|
|
261
|
+
recordJournal?.append("suite-wait", values.task ?? "VERIFY", { holderPid: holder.pid, holderCwd: holder.cwd });
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
async function leasedVerify() {
|
|
265
|
+
let baseline;
|
|
266
|
+
const cachePath = baselineCachePath(cwd, mergeBase, commands);
|
|
267
|
+
const verdictlessMarker = `${cachePath}.verdictless`;
|
|
268
|
+
mkdirSync(dirname(cachePath), { recursive: true });
|
|
269
|
+
if (values.baseline) {
|
|
270
|
+
baseline = JSON.parse(readFileSync(values.baseline, "utf8"));
|
|
273
271
|
}
|
|
274
272
|
else {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
console.error(`verify: capturing baseline at merge-base ${mergeBase.slice(0, 12)} (cached by base, lockfile and command hashes at ${cachePath})`);
|
|
280
|
-
}
|
|
281
|
-
const baseDir = join(stateDir, `base-${mergeBase.slice(0, 12)}`);
|
|
282
|
-
await shGit(`git worktree remove --force '${baseDir}'`, cwd); // stale leftover from an interrupted run
|
|
283
|
-
await shGitOk(`git worktree add --detach '${baseDir}' '${mergeBase}'`, cwd);
|
|
284
|
-
try {
|
|
285
|
-
linkNodeModules(cwd, baseDir, { force: true });
|
|
286
|
-
baseline = await captureBaseline(baseDir, commands);
|
|
287
|
-
const missing = verdictlessCommands(baseline, commands);
|
|
273
|
+
let cached;
|
|
274
|
+
if (existsSync(cachePath)) {
|
|
275
|
+
cached = JSON.parse(readFileSync(cachePath, "utf8"));
|
|
276
|
+
const missing = verdictlessCommands(cached, commands);
|
|
288
277
|
if (missing.length) {
|
|
289
|
-
|
|
278
|
+
console.error(`verify: cached baseline recorded no verdict for ${missing.join(", ")}; it was not reusable and will be recaptured`);
|
|
290
279
|
rmSync(cachePath, { force: true });
|
|
280
|
+
cached = undefined;
|
|
291
281
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
282
|
+
// OBS-1044: a cached vitest count that is a stdout sum may be inflated by nested runner echo;
|
|
283
|
+
// applying it would manufacture a deficit. Only a manifest-derived count is a floor.
|
|
284
|
+
const stale = cached ? staleFileCountCommands(cached, commands, cwd) : [];
|
|
285
|
+
if (stale.length) {
|
|
286
|
+
console.error(`verify: cached baseline's file count for ${stale.join(", ")} is not manifest-derived; it will be recaptured`);
|
|
287
|
+
rmSync(cachePath, { force: true });
|
|
288
|
+
cached = undefined;
|
|
295
289
|
}
|
|
296
290
|
}
|
|
297
|
-
|
|
298
|
-
|
|
291
|
+
if (cached) {
|
|
292
|
+
console.error(`verify: reusing cached baseline for ${mergeBase.slice(0, 12)} (${cachePath})`);
|
|
293
|
+
baseline = cached;
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
if (existsSync(verdictlessMarker)) {
|
|
297
|
+
console.error(`verify: prior baseline recorded no verdict and was not cached; recapturing merge-base ${mergeBase.slice(0, 12)}`);
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
console.error(`verify: capturing baseline at merge-base ${mergeBase.slice(0, 12)} (cached by base, lockfile and command hashes at ${cachePath})`);
|
|
301
|
+
}
|
|
302
|
+
const baseDir = join(stateDir, `base-${mergeBase.slice(0, 12)}`);
|
|
303
|
+
await shGit(`git worktree remove --force '${baseDir}'`, cwd); // stale leftover from an interrupted run
|
|
304
|
+
await shGitOk(`git worktree add --detach '${baseDir}' '${mergeBase}'`, cwd);
|
|
305
|
+
try {
|
|
306
|
+
linkNodeModules(cwd, baseDir, { force: true });
|
|
307
|
+
baseline = await captureBaseline(baseDir, commands);
|
|
308
|
+
const missing = verdictlessCommands(baseline, commands);
|
|
309
|
+
if (missing.length) {
|
|
310
|
+
writeFileSync(verdictlessMarker, JSON.stringify({ base: mergeBase, commands: missing }) + "\n");
|
|
311
|
+
rmSync(cachePath, { force: true });
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
writeFileSync(cachePath, JSON.stringify(baseline, null, 2));
|
|
315
|
+
rmSync(verdictlessMarker, { force: true });
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
finally {
|
|
319
|
+
await removeWorktree(cwd, baseDir);
|
|
320
|
+
}
|
|
299
321
|
}
|
|
300
322
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
console.error(`verify: ${e.result.pass ? "✓" : "✗"} ${e.result.gate} — ${e.result.details.split("\n")[0] ?? ""}`);
|
|
317
|
-
},
|
|
318
|
-
});
|
|
319
|
-
const green = results.length > 0 && results.every((r) => r.pass || r.meta?.skipped === true);
|
|
320
|
-
const reviewRows = results.filter((result) => result.gate === "review");
|
|
321
|
-
const reviewFindings = reviewRows.flatMap((result) => result.details.split("\n").flatMap((line) => {
|
|
322
|
-
const match = /^- \[([^\]]+)\] (.*)$/.exec(line);
|
|
323
|
-
return match ? [{ classification: match[1], note: match[2], reviewer: result.meta?.reviewer }] : [];
|
|
324
|
-
}));
|
|
325
|
-
const artifactPath = join(artifactDir, "verify-results.json");
|
|
326
|
-
writeFileSync(artifactPath, JSON.stringify({ base: baseTip, head, mergeBase, green, gateRows: results, reviewFindings }, null, 2) + "\n");
|
|
327
|
-
console.error(`verify: artifacts written to ${artifactPath}`);
|
|
328
|
-
const review = reviewRows.at(-1);
|
|
329
|
-
if (recordJournal && review) {
|
|
330
|
-
recordJournal.append("review-leg2", values.task ?? "VERIFY", {
|
|
331
|
-
base: baseTip, head, mergeBase, author: channelKey(author), artifactPath,
|
|
332
|
-
...review,
|
|
323
|
+
const artifactDir = join(stateDir, new Date().toISOString().replace(/[:.]/g, "-"));
|
|
324
|
+
mkdirSync(artifactDir, { recursive: true });
|
|
325
|
+
const { results } = await runGates(task, {
|
|
326
|
+
worktree: cwd, baseRef: mergeBase,
|
|
327
|
+
result: { ok: true, summary: "standalone verify — no worker claims to trust", deviations: [], raw: "" },
|
|
328
|
+
author, commands, baseline, channels, ...(judgeChannels ? { judgeChannels } : {}), adapters, cfg,
|
|
329
|
+
verificationScope: "standalone", artifactDir, stateDir: join(stateRoot, ".tickmarkr"),
|
|
330
|
+
onGate: (e) => {
|
|
331
|
+
if (e.phase === "start")
|
|
332
|
+
console.error(`verify: → ${e.gate} (${e.index}/${e.total})`);
|
|
333
|
+
else if (e.phase === "note")
|
|
334
|
+
console.error(`verify: note ${e.gate} ${e.name} ${JSON.stringify(e.payload)}`);
|
|
335
|
+
else
|
|
336
|
+
console.error(`verify: ${e.result.pass ? "✓" : "✗"} ${e.result.gate} — ${e.result.details.split("\n")[0] ?? ""}`);
|
|
337
|
+
},
|
|
333
338
|
});
|
|
339
|
+
const green = results.length > 0 && results.every((r) => r.pass || r.meta?.skipped === true);
|
|
340
|
+
const reviewRows = results.filter((result) => result.gate === "review");
|
|
341
|
+
const reviewFindings = reviewRows.flatMap((result) => result.details.split("\n").flatMap((line) => {
|
|
342
|
+
const match = /^- \[([^\]]+)\] (.*)$/.exec(line);
|
|
343
|
+
return match ? [{ classification: match[1], note: match[2], reviewer: result.meta?.reviewer }] : [];
|
|
344
|
+
}));
|
|
345
|
+
const artifactPath = join(artifactDir, "verify-results.json");
|
|
346
|
+
writeFileSync(artifactPath, JSON.stringify({ base: baseTip, head, mergeBase, green, gateRows: results, reviewFindings }, null, 2) + "\n");
|
|
347
|
+
console.error(`verify: artifacts written to ${artifactPath}`);
|
|
348
|
+
const review = reviewRows.at(-1);
|
|
349
|
+
if (recordJournal && review) {
|
|
350
|
+
recordJournal.append("review-leg2", values.task ?? "VERIFY", {
|
|
351
|
+
base: baseTip, head, mergeBase, author: channelKey(author), artifactPath,
|
|
352
|
+
...review,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
if (values.json) {
|
|
356
|
+
return { out: JSON.stringify({ base: baseTip, head, mergeBase, green, artifactPath, results }, null, 2), code: green ? 0 : 2 };
|
|
357
|
+
}
|
|
358
|
+
const lines = results.map((r) => `${r.pass ? "PASS" : "FAIL"} ${r.gate}\n${r.details.split("\n").map((l) => ` ${l}`).join("\n")}`);
|
|
359
|
+
const verdict = green
|
|
360
|
+
? `verify GREEN — ${results.length} gate(s) passed on ${mergeBase.slice(0, 12)}..${head.slice(0, 12)} (merge is a human decision; artifacts: ${artifactPath})`
|
|
361
|
+
: `verify RED — first failure decides; artifacts: ${artifactPath}`;
|
|
362
|
+
return { out: [...lines, "", verdict].join("\n"), code: green ? 0 : 2 };
|
|
334
363
|
}
|
|
335
|
-
if (values.json) {
|
|
336
|
-
return { out: JSON.stringify({ base: baseTip, head, mergeBase, green, artifactPath, results }, null, 2), code: green ? 0 : 2 };
|
|
337
|
-
}
|
|
338
|
-
const lines = results.map((r) => `${r.pass ? "PASS" : "FAIL"} ${r.gate}\n${r.details.split("\n").map((l) => ` ${l}`).join("\n")}`);
|
|
339
|
-
const verdict = green
|
|
340
|
-
? `verify GREEN — ${results.length} gate(s) passed on ${mergeBase.slice(0, 12)}..${head.slice(0, 12)} (merge is a human decision; artifacts: ${artifactPath})`
|
|
341
|
-
: `verify RED — first failure decides; artifacts: ${artifactPath}`;
|
|
342
|
-
return { out: [...lines, "", verdict].join("\n"), code: green ? 0 : 2 };
|
|
343
364
|
}
|
package/dist/cli/help.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export declare const COMMAND_HELP: {
|
|
|
15
15
|
"--docs": string;
|
|
16
16
|
"--fresh": string;
|
|
17
17
|
"--yes": string;
|
|
18
|
+
"--remove": string;
|
|
18
19
|
};
|
|
19
20
|
examples: string[];
|
|
20
21
|
};
|
|
@@ -160,6 +161,7 @@ export declare const COMMAND_HELP: {
|
|
|
160
161
|
usage: string;
|
|
161
162
|
description: string;
|
|
162
163
|
options: {
|
|
164
|
+
"--files <glob,\u2026>": string;
|
|
163
165
|
"--by <name>": string;
|
|
164
166
|
"--reason <text>": string;
|
|
165
167
|
"--waive": string;
|
package/dist/cli/help.js
CHANGED
|
@@ -10,7 +10,8 @@ export const COMMAND_HELP = {
|
|
|
10
10
|
"--docs": "Include agent guidance documents with --agent.",
|
|
11
11
|
"--fresh": "Force fresh adapter probes instead of reusing recent health.",
|
|
12
12
|
"--yes": "Skip the interactive setup wizard and use defaults.",
|
|
13
|
-
|
|
13
|
+
"--remove": "Reverse init without scaffolding: delete state, skills, docs block, unedited spec, and prune worktrees.",
|
|
14
|
+
}, examples: ["init --yes", "init --agent --force --docs", "init --remove"],
|
|
14
15
|
},
|
|
15
16
|
doctor: {
|
|
16
17
|
usage: "doctor [options]", description: "Probe configured adapters and models and print diagnostics. Default probing may make model calls and refresh a stale catalog.",
|
|
@@ -128,6 +129,7 @@ export const COMMAND_HELP = {
|
|
|
128
129
|
approve: {
|
|
129
130
|
usage: "approve <run-id> <task-id> [options]", description: "Append a validated park decision. A running owner may enact it; a closed run requires a separate resume. Decisions cannot be undone.",
|
|
130
131
|
options: {
|
|
132
|
+
"--files <glob,…>": "Extend files[] for a scope-request park with comma-separated repository-relative globs; required for scope approval.",
|
|
131
133
|
"--by <name>": "Name the actor (default: current OS user).",
|
|
132
134
|
"--reason <text>": "Record the decision reason.",
|
|
133
135
|
"--waive": "Waive only the identified failed gate.",
|
package/dist/config/config.d.ts
CHANGED
|
@@ -132,7 +132,17 @@ export declare function effectiveReviewPolicy(files: ReadonlyArray<string>, revi
|
|
|
132
132
|
* under-flagged one skips the only gate that exists for it.
|
|
133
133
|
*/
|
|
134
134
|
export declare function criticalPathHits(files: ReadonlyArray<string>, criticalPaths?: ReadonlyArray<string>): string[];
|
|
135
|
+
export declare const ExecutionPolicySchema: z.ZodObject<{
|
|
136
|
+
boundedInfrastructure: z.ZodDefault<z.ZodBoolean>;
|
|
137
|
+
repairSelection: z.ZodDefault<z.ZodBoolean>;
|
|
138
|
+
taskExecutionLimitMs: z.ZodNumber;
|
|
139
|
+
}, z.core.$strict>;
|
|
135
140
|
export declare const TickmarkrConfigSchema: z.ZodObject<{
|
|
141
|
+
executionPolicy: z.ZodOptional<z.ZodObject<{
|
|
142
|
+
boundedInfrastructure: z.ZodDefault<z.ZodBoolean>;
|
|
143
|
+
repairSelection: z.ZodDefault<z.ZodBoolean>;
|
|
144
|
+
taskExecutionLimitMs: z.ZodNumber;
|
|
145
|
+
}, z.core.$strict>>;
|
|
136
146
|
concurrency: z.ZodNumber;
|
|
137
147
|
driver: z.ZodEnum<{
|
|
138
148
|
auto: "auto";
|
|
@@ -364,24 +374,31 @@ export type FleetTierAssignment = {
|
|
|
364
374
|
export type FleetEditable = {
|
|
365
375
|
denyAdapters: string[];
|
|
366
376
|
denyModels: string[];
|
|
377
|
+
denyWorkersAdapters?: string[];
|
|
378
|
+
denyWorkersModels?: string[];
|
|
379
|
+
allowOut?: string[];
|
|
367
380
|
tiers: Record<string, Record<string, FleetTierAssignment | null>>;
|
|
368
381
|
map: Record<string, MapEntry>;
|
|
369
382
|
floors: Record<string, Tier>;
|
|
370
383
|
};
|
|
371
384
|
export declare function repoOverlayPath(repoRoot: string): string;
|
|
372
385
|
export declare function readOverlayFile(path: string): Record<string, unknown>;
|
|
386
|
+
/** A discovered fleet universe row: the served models of one adapter and, per model, the resolved
|
|
387
|
+
* identity doctor recorded for a floating alias (LEG2-T3 round 2 finding 3). */
|
|
388
|
+
export type FleetUniverseRow = {
|
|
389
|
+
adapter: string;
|
|
390
|
+
models: string[];
|
|
391
|
+
identities?: Record<string, string>;
|
|
392
|
+
};
|
|
393
|
+
/** Does a deny/allow entry name this universe channel? Grammar A1 (adapter id, bare model id,
|
|
394
|
+
* adapter:model) plus the recorded identity (bare or adapter:identity) — the collector's authority. */
|
|
395
|
+
export declare function universeEntryMatches(row: FleetUniverseRow, model: string, entry: string): boolean;
|
|
373
396
|
/** Grammar A1 membership test: does any discovered universe row cover this deny/allow entry
|
|
374
397
|
* (adapter id, bare model id, or adapter:model)? Uncovered entries name channels the probe
|
|
375
398
|
* did not serve this session (failed auth, rate limit, retired sku) — they are NOT editable
|
|
376
399
|
* membership state and must survive a membership write verbatim, never be re-derived away. */
|
|
377
|
-
export declare function universeCovers(universe:
|
|
378
|
-
|
|
379
|
-
models: string[];
|
|
380
|
-
}[], entry: string): boolean;
|
|
381
|
-
export declare function fleetEditableFromConfig(cfg: TickmarkrConfig, universe?: {
|
|
382
|
-
adapter: string;
|
|
383
|
-
models: string[];
|
|
384
|
-
}[]): FleetEditable;
|
|
400
|
+
export declare function universeCovers(universe: FleetUniverseRow[], entry: string): boolean;
|
|
401
|
+
export declare function fleetEditableFromConfig(cfg: TickmarkrConfig, universe?: FleetUniverseRow[]): FleetEditable;
|
|
385
402
|
/** Resolve which layer last set a dotted fleet path (defaults < global < repo). */
|
|
386
403
|
export declare function fleetKeyLayer(repoRoot: string, dotted: string, opts?: {
|
|
387
404
|
globalDir?: string;
|
package/dist/config/config.js
CHANGED
|
@@ -264,7 +264,14 @@ const ShapeGateParticipationSchema = z
|
|
|
264
264
|
});
|
|
265
265
|
}
|
|
266
266
|
});
|
|
267
|
+
export const ExecutionPolicySchema = z.object({
|
|
268
|
+
boundedInfrastructure: z.boolean().default(false),
|
|
269
|
+
repairSelection: z.boolean().default(false),
|
|
270
|
+
taskExecutionLimitMs: z.number().int().positive(),
|
|
271
|
+
}).strict();
|
|
267
272
|
export const TickmarkrConfigSchema = z.object({
|
|
273
|
+
// Opt-in recovery experiment. The original run snapshot owns this across resumes.
|
|
274
|
+
executionPolicy: ExecutionPolicySchema.optional(),
|
|
268
275
|
concurrency: z.number().int().positive(),
|
|
269
276
|
driver: z.enum(["auto", "herdr", "subprocess", "orca"]),
|
|
270
277
|
integrationBranchPrefix: z
|
|
@@ -887,6 +894,14 @@ export function readOverlayFile(path) {
|
|
|
887
894
|
throw new ConfigError(`invalid overlay at ${path}`);
|
|
888
895
|
return raw;
|
|
889
896
|
}
|
|
897
|
+
/** Does a deny/allow entry name this universe channel? Grammar A1 (adapter id, bare model id,
|
|
898
|
+
* adapter:model) plus the recorded identity (bare or adapter:identity) — the collector's authority. */
|
|
899
|
+
export function universeEntryMatches(row, model, entry) {
|
|
900
|
+
if (entry === row.adapter || entry === model || entry === `${row.adapter}:${model}`)
|
|
901
|
+
return true;
|
|
902
|
+
const identity = row.identities?.[model];
|
|
903
|
+
return identity !== undefined && (entry === identity || entry === `${row.adapter}:${identity}`);
|
|
904
|
+
}
|
|
890
905
|
/** Grammar A1 membership test: does any discovered universe row cover this deny/allow entry
|
|
891
906
|
* (adapter id, bare model id, or adapter:model)? Uncovered entries name channels the probe
|
|
892
907
|
* did not serve this session (failed auth, rate limit, retired sku) — they are NOT editable
|
|
@@ -894,10 +909,13 @@ export function readOverlayFile(path) {
|
|
|
894
909
|
export function universeCovers(universe, entry) {
|
|
895
910
|
return universe.some((row) => entry === row.adapter || row.models.some((m) => entry === m || entry === `${row.adapter}:${m}`));
|
|
896
911
|
}
|
|
897
|
-
// v1.92 fleet membership: with a discovered `universe` (classified models only) the
|
|
898
|
-
//
|
|
899
|
-
// routing.deny adapters/models
|
|
900
|
-
//
|
|
912
|
+
// v1.92 fleet membership: with a discovered `universe` (classified models only) the routing.allow
|
|
913
|
+
// complement (fail-closed) rides as `allowOut` — adapter ids and adapter:model keys — BESIDE the
|
|
914
|
+
// authored routing.deny adapters/models entries, which ride VERBATIM in their own lists (never
|
|
915
|
+
// workers). LEG2-T3 round 2 finding 2: authored entries are never collapsed into a membership key;
|
|
916
|
+
// OBS-1046: nor is an allow exclusion folded into a deny list — the two reasons stay distinct so
|
|
917
|
+
// one Space press clears one; finding 3: allow admission honours recorded identity.
|
|
918
|
+
// Absent universe ⇒ deny arrays verbatim and no allowOut, so non-UI callers stay byte-identical.
|
|
901
919
|
export function fleetEditableFromConfig(cfg, universe) {
|
|
902
920
|
const tiers = {};
|
|
903
921
|
for (const [adapter, entry] of Object.entries(cfg.tiers)) {
|
|
@@ -906,46 +924,36 @@ export function fleetEditableFromConfig(cfg, universe) {
|
|
|
906
924
|
tiers[adapter][model] = { tier };
|
|
907
925
|
}
|
|
908
926
|
}
|
|
909
|
-
|
|
910
|
-
|
|
927
|
+
const denyAdapters = [...(cfg.routing.deny?.adapters ?? [])].sort();
|
|
928
|
+
const denyModels = [...(cfg.routing.deny?.models ?? [])].sort();
|
|
929
|
+
let allowOut;
|
|
911
930
|
if (universe !== undefined) {
|
|
912
|
-
const { allow
|
|
913
|
-
// Grammar A1 (disallowedBy precedent): entries match adapter id, bare model id, or adapter:model.
|
|
914
|
-
// Inlined rather than imported — route/preference.ts imports this module.
|
|
915
|
-
const denyEntries = [...(deny?.adapters ?? []), ...(deny?.models ?? [])];
|
|
931
|
+
const { allow } = cfg.routing;
|
|
916
932
|
const allowEntries = allow ? [...(allow.adapters ?? []), ...(allow.models ?? [])] : null;
|
|
917
|
-
const
|
|
918
|
-
const matches = (e) => e === adapter || e === model || e === `${adapter}:${model}`;
|
|
919
|
-
if (denyEntries.some(matches))
|
|
920
|
-
return true;
|
|
921
|
-
return allowEntries !== null && !allowEntries.some(matches);
|
|
922
|
-
};
|
|
933
|
+
const notAdmitted = (row, model) => allowEntries !== null && !allowEntries.some((entry) => universeEntryMatches(row, model, entry));
|
|
923
934
|
const adaptersOut = [];
|
|
924
935
|
const modelsOut = [];
|
|
925
936
|
for (const row of universe) {
|
|
926
|
-
const out = row.models.filter((m) =>
|
|
927
|
-
if (row.models.length ? out.length === row.models.length :
|
|
937
|
+
const out = row.models.filter((m) => notAdmitted(row, m));
|
|
938
|
+
if (row.models.length ? out.length === row.models.length : notAdmitted(row, "")) {
|
|
928
939
|
adaptersOut.push(row.adapter);
|
|
929
940
|
}
|
|
930
941
|
else {
|
|
931
942
|
modelsOut.push(...out.map((m) => `${row.adapter}:${m}`));
|
|
932
943
|
}
|
|
933
944
|
}
|
|
934
|
-
// OBS-517: deny entries the probe universe
|
|
935
|
-
//
|
|
936
|
-
|
|
937
|
-
denyAdapters = [...new Set([
|
|
938
|
-
...adaptersOut,
|
|
939
|
-
...(deny?.adapters ?? []).filter((e) => !universeCovers(universe, e)),
|
|
940
|
-
])].sort();
|
|
941
|
-
denyModels = [...new Set([
|
|
942
|
-
...modelsOut,
|
|
943
|
-
...(deny?.models ?? []).filter((e) => !universeCovers(universe, e)),
|
|
944
|
-
])].sort();
|
|
945
|
+
// OBS-517: authored deny entries — covered by the probe universe or not — ride verbatim (above),
|
|
946
|
+
// so a transient probe failure can never un-deny one on the next write (fail-open on the ban list).
|
|
947
|
+
allowOut = [...new Set([...adaptersOut, ...modelsOut])].sort();
|
|
945
948
|
}
|
|
946
949
|
return {
|
|
947
950
|
denyAdapters,
|
|
948
951
|
denyModels,
|
|
952
|
+
...(allowOut !== undefined ? { allowOut } : {}),
|
|
953
|
+
// OBS-994: workers-only deny is never folded into the all-seats complement above — it is
|
|
954
|
+
// returned verbatim beside the flat scopes so the fleet browser can show and toggle it.
|
|
955
|
+
denyWorkersAdapters: [...(cfg.routing.deny?.workers?.adapters ?? [])].sort(),
|
|
956
|
+
denyWorkersModels: [...(cfg.routing.deny?.workers?.models ?? [])].sort(),
|
|
949
957
|
tiers,
|
|
950
958
|
map: structuredClone(cfg.routing.map),
|
|
951
959
|
floors: { ...cfg.routing.floors },
|
|
@@ -988,6 +996,10 @@ export function formatFleetPrint(repoRoot, opts = {}) {
|
|
|
988
996
|
annotate(["routing", "deny", "adapters"]);
|
|
989
997
|
if (effective.routing.deny?.models !== undefined)
|
|
990
998
|
annotate(["routing", "deny", "models"]);
|
|
999
|
+
if (effective.routing.deny?.workers?.adapters !== undefined)
|
|
1000
|
+
annotate(["routing", "deny", "workers", "adapters"]);
|
|
1001
|
+
if (effective.routing.deny?.workers?.models !== undefined)
|
|
1002
|
+
annotate(["routing", "deny", "workers", "models"]);
|
|
991
1003
|
for (const shape of Object.keys(effective.routing.map))
|
|
992
1004
|
annotate(["routing", "map", shape]);
|
|
993
1005
|
annotate(["routing", "floors"]);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type FleetEditable, type RoutingMode } from "./config.js";
|
|
1
|
+
import { type FleetEditable, type FleetUniverseRow, type RoutingMode } from "./config.js";
|
|
2
2
|
/** Fleet-owned overlay keys — the only config surface `tickmarkr fleet` may write. */
|
|
3
3
|
export declare const FLEET_OVERLAY_KEYS: readonly ["routing", "tiers"];
|
|
4
4
|
export type FleetOverlayWrite = {
|
|
@@ -19,10 +19,7 @@ export type FleetOverlayWrite = {
|
|
|
19
19
|
consult?: string[];
|
|
20
20
|
};
|
|
21
21
|
};
|
|
22
|
-
universe?:
|
|
23
|
-
adapter: string;
|
|
24
|
-
models: string[];
|
|
25
|
-
}[];
|
|
22
|
+
universe?: FleetUniverseRow[];
|
|
26
23
|
};
|
|
27
24
|
export type FleetFirstTouch = {
|
|
28
25
|
vendor: string;
|
|
@@ -33,10 +30,7 @@ export declare function fleetFirstTouchProvenance(note: string, firstTouch: Flee
|
|
|
33
30
|
* keys, ordering, scalar style, and comments; fresh provenance is written directly on its tier node. */
|
|
34
31
|
export declare function renderFleetOverlayWrite(priorBytes: string, write: FleetOverlayWrite): string;
|
|
35
32
|
/** Build the repo overlay fragment fleet would write for edits since session start. */
|
|
36
|
-
export declare function fleetRepoOverlayFromDelta(initial: FleetEditable, edited: FleetEditable, existingRepo?: Record<string, unknown>, firstTouches?: Readonly<Record<string, FleetFirstTouch>>, universe?:
|
|
37
|
-
adapter: string;
|
|
38
|
-
models: string[];
|
|
39
|
-
}[]): Record<string, unknown>;
|
|
33
|
+
export declare function fleetRepoOverlayFromDelta(initial: FleetEditable, edited: FleetEditable, existingRepo?: Record<string, unknown>, firstTouches?: Readonly<Record<string, FleetFirstTouch>>, universe?: FleetUniverseRow[]): Record<string, unknown>;
|
|
40
34
|
export declare function repoOverlayYaml(overlay: Record<string, unknown>): string;
|
|
41
35
|
export declare function serializeFleetOverlay(overlay: Record<string, unknown>): string;
|
|
42
36
|
export declare function unifiedYamlDiff(before: string, after: string, label?: string): string;
|