tickmarkr 2.4.1 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +110 -24
  2. package/dist/adapters/claude-code.js +7 -2
  3. package/dist/adapters/codex.d.ts +1 -0
  4. package/dist/adapters/codex.js +66 -5
  5. package/dist/adapters/types.d.ts +4 -0
  6. package/dist/adapters/types.js +33 -0
  7. package/dist/cli/commands/approve.d.ts +42 -0
  8. package/dist/cli/commands/approve.js +80 -13
  9. package/dist/cli/commands/doctor.d.ts +234 -0
  10. package/dist/cli/commands/doctor.js +139 -5
  11. package/dist/cli/commands/report.js +15 -44
  12. package/dist/cli/commands/scope.js +36 -6
  13. package/dist/cli/commands/stats.d.ts +2 -0
  14. package/dist/cli/commands/stats.js +42 -23
  15. package/dist/cli/commands/status.js +20 -4
  16. package/dist/cli/commands/ui.js +42 -53
  17. package/dist/cli/commands/unlock.d.ts +1 -1
  18. package/dist/cli/commands/unlock.js +59 -9
  19. package/dist/cli/help.d.ts +219 -0
  20. package/dist/cli/help.js +212 -0
  21. package/dist/cli/index.d.ts +42 -2
  22. package/dist/cli/index.js +23 -8
  23. package/dist/drivers/herdr.d.ts +7 -2
  24. package/dist/drivers/herdr.js +78 -48
  25. package/dist/drivers/orca.d.ts +3 -2
  26. package/dist/drivers/orca.js +43 -4
  27. package/dist/drivers/subprocess.d.ts +1 -0
  28. package/dist/drivers/subprocess.js +3 -0
  29. package/dist/drivers/types.d.ts +14 -0
  30. package/dist/gates/artifact-manifest.d.ts +50 -0
  31. package/dist/gates/artifact-manifest.js +23 -0
  32. package/dist/plan/scope.d.ts +25 -0
  33. package/dist/plan/scope.js +92 -12
  34. package/dist/report/operator-record.d.ts +49 -0
  35. package/dist/report/operator-record.js +137 -0
  36. package/dist/run/daemon.d.ts +3 -4
  37. package/dist/run/daemon.js +18 -10
  38. package/dist/run/lock.d.ts +59 -2
  39. package/dist/run/lock.js +184 -26
  40. package/dist/run/operator-state.d.ts +86 -0
  41. package/dist/run/operator-state.js +165 -0
  42. package/dist/run/supervision.d.ts +32 -0
  43. package/dist/run/supervision.js +138 -17
  44. package/dist/tui/cockpit/capture.d.ts +19 -0
  45. package/dist/tui/cockpit/capture.js +89 -1
  46. package/dist/tui/cockpit/components.d.ts +15 -1
  47. package/dist/tui/cockpit/components.js +79 -9
  48. package/dist/tui/cockpit/decision-actions.d.ts +147 -0
  49. package/dist/tui/cockpit/decision-actions.js +315 -0
  50. package/dist/tui/cockpit/derive.d.ts +1 -1
  51. package/dist/tui/cockpit/derive.js +2 -0
  52. package/dist/tui/cockpit/evidence-view.d.ts +119 -0
  53. package/dist/tui/cockpit/evidence-view.js +210 -0
  54. package/dist/tui/cockpit/home-view.d.ts +88 -0
  55. package/dist/tui/cockpit/home-view.js +240 -0
  56. package/dist/tui/cockpit/keys.d.ts +125 -0
  57. package/dist/tui/cockpit/keys.js +31 -0
  58. package/dist/tui/cockpit/layout.d.ts +14 -0
  59. package/dist/tui/cockpit/layout.js +15 -0
  60. package/dist/tui/cockpit/live-runtime.d.ts +46 -0
  61. package/dist/tui/cockpit/live-runtime.js +683 -0
  62. package/dist/tui/cockpit/live-store.d.ts +289 -0
  63. package/dist/tui/cockpit/live-store.js +308 -0
  64. package/dist/tui/cockpit/live.d.ts +21 -1
  65. package/dist/tui/cockpit/live.js +12 -1
  66. package/dist/tui/cockpit/run-view.d.ts +100 -0
  67. package/dist/tui/cockpit/run-view.js +202 -0
  68. package/dist/tui/cockpit/shell.d.ts +50 -0
  69. package/dist/tui/cockpit/shell.js +74 -0
  70. package/dist/tui/cockpit/theme.d.ts +27 -0
  71. package/dist/tui/cockpit/theme.js +21 -0
  72. package/package.json +1 -1
  73. package/skills/tickmarkr-auto/SKILL.md +10 -1
  74. package/skills/tickmarkr-loop/SKILL.md +72 -1
  75. package/skills/tickmarkr-overseer/SKILL.md +12 -0
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readdirSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { stateDirName } from "../../graph/graph.js";
4
+ import { assignmentChannel, buildOperatorRecord, formatOperatorRecordRow } from "../../report/operator-record.js";
4
5
  import { Journal } from "../../run/journal.js";
5
6
  import { normalizeGateOutcome } from "../../run/outcome.js";
6
7
  // The operator-facing schema is deliberately closed. Adding a fact to the table therefore requires
@@ -17,20 +18,15 @@ export const STATS_COLUMNS = [
17
18
  "infra reds",
18
19
  "within-task rescues",
19
20
  ];
20
- const assignmentAuthor = (data) => {
21
- const assignment = data.assignment;
22
- if (!assignment || typeof assignment !== "object")
23
- return undefined;
24
- const { adapter, model } = assignment;
25
- return typeof adapter === "string" && typeof model === "string"
26
- ? `${adapter}:${model}`
27
- : undefined;
28
- };
21
+ const assignmentAuthor = assignmentChannel;
29
22
  // Current journals write the reviewer into review prose; the structured field is also accepted so
30
23
  // journals produced through gateResultJournalData retain their stronger identity representation.
31
24
  const reviewerFrom = (data) => {
32
25
  if (typeof data.reviewer === "string" && data.reviewer.trim())
33
26
  return data.reviewer.trim();
27
+ const meta = typeof data.meta === "object" && data.meta !== null ? data.meta : undefined;
28
+ if (meta && "reviewer" in meta && typeof meta.reviewer === "string" && meta.reviewer.trim())
29
+ return meta.reviewer.trim();
34
30
  if (typeof data.details !== "string")
35
31
  return undefined;
36
32
  return /\breviewer(?:\s+|:\s*)([\w@./+-]+:[\w@./+-]+)/iu.exec(data.details)?.[1];
@@ -79,8 +75,10 @@ export function collectChannelStats(cwd = process.cwd()) {
79
75
  }
80
76
  return channel;
81
77
  };
78
+ const allEvents = [];
82
79
  for (const runId of runIds) {
83
80
  const events = Journal.open(cwd, runId).read();
81
+ allEvents.push(...events);
84
82
  const tasks = new Map();
85
83
  for (const [eventIndex, event] of events.entries()) {
86
84
  if (!event.taskId)
@@ -124,6 +122,16 @@ export function collectChannelStats(cwd = process.cwd()) {
124
122
  }
125
123
  continue;
126
124
  }
125
+ if (event.event === "review-leg2") {
126
+ const author = dispatchedAuthorFor(history, event.data)
127
+ ?? (typeof event.data.author === "string" && event.data.author.includes(":") ? event.data.author : undefined);
128
+ if (author) {
129
+ const reviewer = reviewerFrom(event.data);
130
+ if (reviewer)
131
+ channelFor(author).reviewers.add(reviewer);
132
+ }
133
+ continue;
134
+ }
127
135
  if (event.event !== "gate-result")
128
136
  continue;
129
137
  const author = dispatchedAuthorFor(history, event.data);
@@ -170,26 +178,37 @@ export function collectChannelStats(cwd = process.cwd()) {
170
178
  infraReds: channel.infraReds,
171
179
  rescues: [...channel.rescues].sort((a, b) => a.localeCompare(b, "en")),
172
180
  })),
181
+ operatorRecords: buildOperatorRecord(allEvents),
173
182
  };
174
183
  }
175
184
  const percent = (rate) => `${Number((rate * 100).toFixed(1))}%`;
176
185
  export function renderStats(report) {
177
186
  const lines = [`tickmarkr stats — ${report.runs} run${report.runs === 1 ? "" : "s"}`];
178
- if (report.channels.length === 0)
187
+ if (report.channels.length === 0 && (!report.operatorRecords || report.operatorRecords.length === 0)) {
179
188
  return [...lines, "no channels"].join("\n");
180
- lines.push(STATS_COLUMNS.join(" | "));
181
- for (const channel of report.channels) {
182
- lines.push([
183
- channel.author,
184
- channel.reviewers.join(", ") || "—",
185
- channel.dispatches,
186
- channel.deliveries,
187
- percent(channel.deliveryRate),
188
- channel.attemptsToGreen === null ? "—" : Number(channel.attemptsToGreen.toFixed(2)),
189
- channel.realReds,
190
- channel.infraReds,
191
- channel.rescues.join("; ") || "—",
192
- ].join(" | "));
189
+ }
190
+ if (report.channels.length > 0) {
191
+ lines.push(STATS_COLUMNS.join(" | "));
192
+ for (const channel of report.channels) {
193
+ lines.push([
194
+ channel.author,
195
+ channel.reviewers.join(", ") || "—",
196
+ channel.dispatches,
197
+ channel.deliveries,
198
+ percent(channel.deliveryRate),
199
+ channel.attemptsToGreen === null ? "—" : Number(channel.attemptsToGreen.toFixed(2)),
200
+ channel.realReds,
201
+ channel.infraReds,
202
+ channel.rescues.join("; ") || "—",
203
+ ].join(" | "));
204
+ }
205
+ }
206
+ if (report.operatorRecords && report.operatorRecords.length > 0) {
207
+ lines.push("");
208
+ lines.push("channels:");
209
+ for (const row of report.operatorRecords) {
210
+ lines.push(` ${formatOperatorRecordRow(row)}`);
211
+ }
193
212
  }
194
213
  return lines.join("\n");
195
214
  }
@@ -13,9 +13,8 @@ import { isPidLive, runLockRunId, runStatusLine } from "../../run/lock.js";
13
13
  import { normalizeGateOutcome } from "../../run/outcome.js";
14
14
  import { desiredPanes } from "../../run/reconcile.js";
15
15
  import { normalizeStallSnapshot } from "../../run/stall.js";
16
- import { armWatchSupervision, readSupervision, supervisionText } from "../../run/supervision.js";
16
+ import { armWatchSupervision, observeNamedRun, WATCH_OWNER_ENV, readSupervision, supervisionText } from "../../run/supervision.js";
17
17
  import { deriveRunCockpitData, } from "../../tui/cockpit/derive.js";
18
- import { COCKPIT_COLUMN_FLOOR } from "../../tui/cockpit/layout.js";
19
18
  import { cellWidth, fitCells, wrapCells } from "../../tui/cockpit/width.js";
20
19
  import { version } from "./version.js";
21
20
  // The board is a LIVE surface, so it draws through brand.ts's operator role map and never the vivid
@@ -1093,7 +1092,7 @@ binaryVersion, now = Date.now(), animationFrame = 0, workerLiveness = new Map(),
1093
1092
  }
1094
1093
  // TTY: the operator-approved task table. The daemon gives this surface a ~110-column pane first;
1095
1094
  // narrower terminals keep the same facts in stacked rows. Every cut/wrap goes through width.ts.
1096
- const boardColumns = Math.max(COCKPIT_COLUMN_FLOOR, width);
1095
+ const boardColumns = Math.max(40, width);
1097
1096
  const dot = dim(" · ");
1098
1097
  const separator = ` ${dim("│")} `;
1099
1098
  const tipFailed = tipPhase?.state === "failed";
@@ -1343,6 +1342,15 @@ export async function status(argv, cwd = process.cwd(), opts = {}) {
1343
1342
  const sleep = opts.sleep ?? defaultSleep;
1344
1343
  const now = opts.now ?? Date.now;
1345
1344
  const bounded = Number.isFinite(iterations);
1345
+ // Bounded snapshots and printed twins never import Ink. Public interactive watch
1346
+ // selects its run once through the same launcher as manual UI.
1347
+ if (!bounded && !eventStream && !argv.includes("--plain") && process.stdin.isTTY === true && process.stdout.isTTY === true) {
1348
+ const { ui } = await import("./ui.js");
1349
+ const result = await ui([...(namedRunId ? [namedRunId] : []), "--view", "run"], {}, cwd);
1350
+ if (typeof result !== "string")
1351
+ throw new Error(result.out);
1352
+ return "";
1353
+ }
1346
1354
  const frames = [];
1347
1355
  const eventLines = [];
1348
1356
  const sep = "\n---\n";
@@ -1455,7 +1463,8 @@ export async function status(argv, cwd = process.cwd(), opts = {}) {
1455
1463
  // it INVERTS it: armSupervision's interval outlives the board in any host that outlives one board, so
1456
1464
  // a dead board keeps writing and reads ARMED. That is the over-claiming direction, the one an operator
1457
1465
  // acts on, and the one this instrument exists to close.
1458
- const armed = bounded ? undefined : armWatchSupervision(cwd, opts.supervisionBeatMs);
1466
+ const boardObservation = !bounded && namedRunId && process.env[WATCH_OWNER_ENV] ? observeNamedRun(cwd, namedRunId) : undefined;
1467
+ const armed = bounded || boardObservation ? undefined : armWatchSupervision(cwd, opts.supervisionBeatMs);
1459
1468
  let alternateScreen = false;
1460
1469
  const leaveAlternateScreen = () => {
1461
1470
  if (!alternateScreen)
@@ -1491,6 +1500,8 @@ export async function status(argv, cwd = process.cwd(), opts = {}) {
1491
1500
  if (exitingFromSignal)
1492
1501
  return;
1493
1502
  exitingFromSignal = true;
1503
+ armed?.disarm();
1504
+ boardObservation?.close();
1494
1505
  if (titleSaved) {
1495
1506
  process.removeListener("exit", restoreTitle);
1496
1507
  restoreTitle();
@@ -1507,6 +1518,8 @@ export async function status(argv, cwd = process.cwd(), opts = {}) {
1507
1518
  process.stdout.write(ENTER_ALTERNATE_SCREEN);
1508
1519
  alternateScreen = true;
1509
1520
  process.once("exit", leaveAlternateScreen);
1521
+ }
1522
+ if (!bounded) {
1510
1523
  for (const [signal, code] of [["SIGINT", 130], ["SIGTERM", 143]]) {
1511
1524
  const handler = exitFromSignal(code);
1512
1525
  signalHandlers.push([signal, handler]);
@@ -1515,6 +1528,8 @@ export async function status(argv, cwd = process.cwd(), opts = {}) {
1515
1528
  }
1516
1529
  try {
1517
1530
  for (let i = 0; i < iterations; i++) {
1531
+ if (boardObservation?.stopRequested())
1532
+ break;
1518
1533
  const nowMs = now();
1519
1534
  const decisionEvents = eventStream || webhookUrl ? consumeDecisionEvents() : [];
1520
1535
  let frame;
@@ -1558,6 +1573,7 @@ export async function status(argv, cwd = process.cwd(), opts = {}) {
1558
1573
  // armSupervision's presence files). Otherwise the first pane closed would render the second
1559
1574
  // pane's own tier down while it is drawing frames.
1560
1575
  armed?.disarm();
1576
+ boardObservation?.close();
1561
1577
  if (titleSaved) {
1562
1578
  process.removeListener("exit", restoreTitle);
1563
1579
  restoreTitle();
@@ -1,64 +1,53 @@
1
+ import { join } from "node:path";
2
+ import { Journal, parseRunId } from "../../run/journal.js";
3
+ import { stateDirName } from "../../graph/graph.js";
4
+ import { JournalTail } from "../../tui/cockpit/live-store.js";
1
5
  const NON_TTY_MSG = "tickmarkr ui: the cockpit requires a TTY — use `tickmarkr fleet --print` or `tickmarkr status --watch` for line-mode output";
2
- /**
3
- * The setup surface's own flag. `tickmarkr ui --setup [run-id]` opens the one
4
- * surface that writes — parked human decisions, approved or upheld through the
5
- * production command path behind an explicit confirm.
6
- */
7
- const SETUP_FLAG = "--setup";
8
6
  export async function ui(argv, io = {}, cwd = process.cwd()) {
9
7
  const input = io.input ?? process.stdin;
10
8
  const output = io.output ?? process.stdout;
11
- if (input.isTTY !== true || output.isTTY !== true) {
9
+ if (input.isTTY !== true || output.isTTY !== true)
12
10
  return { out: NON_TTY_MSG, code: 1 };
13
- }
14
- if (argv.includes("--demo")) {
15
- const { runCockpitDemo } = await import("../../tui/cockpit/demo.js");
16
- const { version } = await import("./version.js");
17
- await runCockpitDemo({
18
- input,
19
- output,
20
- binaryVersion: await version(),
21
- });
22
- return "ui: closed";
23
- }
24
- const setup = argv.includes(SETUP_FLAG);
25
- const unknownFlag = argv.find((arg) => arg.startsWith("-") && arg !== SETUP_FLAG);
26
- if (unknownFlag) {
27
- return { out: `tickmarkr ui: unknown flag ${unknownFlag}`, code: 1 };
28
- }
29
- const live = await import("../../tui/cockpit/live.js");
30
- const { version } = await import("./version.js");
31
- const binaryVersion = await version();
32
- const explicit = argv.find((arg) => !arg.startsWith("-"));
33
- let runId;
11
+ let explicit;
12
+ let view = "home";
13
+ let setup = false;
14
+ for (let i = 0; i < argv.length; i++) {
15
+ const arg = argv[i];
16
+ if (arg === "--setup")
17
+ setup = true;
18
+ else if (arg === "--view") {
19
+ const value = argv[++i];
20
+ if (value !== "home" && value !== "run" && value !== "evidence")
21
+ return { out: "tickmarkr ui: --view must be home, run or evidence", code: 1 };
22
+ view = value;
23
+ }
24
+ else if (arg.startsWith("-"))
25
+ return { out: `tickmarkr ui: unknown flag ${arg}`, code: 1 };
26
+ else if (explicit !== undefined)
27
+ return { out: "tickmarkr ui: expected at most one run ID", code: 1 };
28
+ else
29
+ explicit = arg;
30
+ }
31
+ let runId = null;
34
32
  try {
35
- runId = live.selectEngagementRunId(cwd, explicit);
33
+ runId = explicit === undefined ? Journal.latestRunId(cwd, { withJournal: true }) : parseRunId(explicit);
34
+ // Refuse an invalid explicit target before importing or borrowing the terminal.
35
+ if (runId) {
36
+ const initial = new JournalTail(join(cwd, stateDirName(cwd), "runs", runId, "journal.jsonl")).poll();
37
+ if (initial.status === "unreadable" || initial.status === "corrupt")
38
+ throw new Error(initial.error?.error ?? initial.errors[0]?.error ?? "journal corrupt");
39
+ }
36
40
  }
37
41
  catch (error) {
38
- return { out: `tickmarkr ui: ${error.message}`, code: 1 };
39
- }
40
- if (runId === null) {
41
- return {
42
- out: "tickmarkr ui: no engagement found — start one with `tickmarkr run` first",
43
- code: 1,
44
- };
42
+ return { out: `tickmarkr ui: cannot read engagement ${runId ?? explicit ?? "latest"}: ${error.message}`, code: 1 };
45
43
  }
46
- // Refuse before drawing anything: unreadable journal bytes must never
47
- // become a plausible surface derived from nothing.
48
- try {
49
- live.deriveLiveRunCockpitData(live.loadEngagementSource(cwd, runId), binaryVersion);
50
- }
51
- catch (error) {
52
- return {
53
- out: `tickmarkr ui: cannot read engagement ${runId}: ${error.message}`,
54
- code: 1,
55
- };
56
- }
57
- if (setup) {
58
- const { runSetupDecisionsCockpit } = await import("../../tui/cockpit/setup-cockpit.js");
59
- await runSetupDecisionsCockpit({ input, output, cwd, runId });
60
- return "ui: closed";
61
- }
62
- await live.runLiveCockpit({ input, output, cwd, runId, binaryVersion });
44
+ const { runLiveCockpit } = await import("../../tui/cockpit/live.js");
45
+ const { version } = await import("./version.js");
46
+ const options = {
47
+ input, output, cwd, runId: runId ?? "run-no-run", binaryVersion: await version(),
48
+ observeRun: runId !== null, initialView: runId === null ? "home" : setup ? "run" : view,
49
+ initialParks: setup,
50
+ };
51
+ await runLiveCockpit(options);
63
52
  return "ui: closed";
64
53
  }
@@ -1 +1 @@
1
- export declare function unlock(_argv: string[], cwd?: string): Promise<string>;
1
+ export declare function unlock(argv: string[], cwd?: string): Promise<string>;
@@ -1,11 +1,61 @@
1
- import { unlockRun } from "../../run/lock.js";
2
- // LOCK-03: thin formatter over unlockRun. A live-holder refusal propagates as the throw — the
3
- // dispatcher prints `tickmarkr unlock: …` and exits 1 (src/cli/index.ts).
4
- export async function unlock(_argv, cwd = process.cwd()) {
5
- const r = unlockRun(cwd);
6
- if (!r.held)
7
- return "no lock held — nothing to remove";
8
- if (r.garbage)
1
+ import { createHash } from "node:crypto";
2
+ import { createInterface } from "node:readline/promises";
3
+ import { parseArgs } from "node:util";
4
+ import { commitGarbageUnlock, commitUnlock, previewGarbageUnlock, previewUnlock } from "../../run/lock.js";
5
+ // LOCK-03/R16-17: thin formatter over lock.ts's preview/commit pair. `tickmarkr unlock <run-id>`
6
+ // recovers a valid, provably-dead lock naming exactly that run (R17: a run ID is required — no
7
+ // bare "unlock whatever is there"); `tickmarkr unlock --garbage` recovers an unparseable snapshot
8
+ // identified by bytes/inode instead, never inventing a run ID. Both mutate only after TTY
9
+ // confirmation or --yes, and both re-check fresh at commit time — this command never trusts the
10
+ // preview it showed the operator for the removal itself.
11
+ export async function unlock(argv, cwd = process.cwd()) {
12
+ const { values, positionals } = parseArgs({
13
+ args: argv,
14
+ options: { garbage: { type: "boolean" }, yes: { type: "boolean" } },
15
+ allowPositionals: true,
16
+ });
17
+ const confirm = async (question) => {
18
+ if (values.yes)
19
+ return true;
20
+ if (process.stdin.isTTY !== true || process.stdout.isTTY !== true)
21
+ return false;
22
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
23
+ try {
24
+ return /^(?:y|yes)$/i.test((await rl.question(`${question} [y/N] `)).trim());
25
+ }
26
+ finally {
27
+ rl.close();
28
+ }
29
+ };
30
+ if (values.garbage) {
31
+ const preview = previewGarbageUnlock(cwd);
32
+ if (!preview.held)
33
+ return "no lock held — nothing to remove";
34
+ if (!preview.eligible)
35
+ throw new Error(preview.reason);
36
+ const bytes = preview.raw.length === 0 ? "empty" : `${preview.raw.length} byte${preview.raw.length === 1 ? "" : "s"}`;
37
+ const digest = createHash("sha256").update(preview.raw).digest("hex");
38
+ if (!(await confirm(`Remove garbage lock (${bytes}, inode ${preview.ino}, sha256 ${digest})?`))) {
39
+ throw new Error("unlock --garbage: not confirmed (pass --yes or confirm at a TTY)");
40
+ }
41
+ const commit = commitGarbageUnlock(cwd, { ino: preview.ino, raw: preview.raw });
42
+ if (!commit.removed)
43
+ throw new Error(commit.reason);
9
44
  return "removed garbage lock (unparseable payload)";
10
- return `removed stale lock — holder pid ${r.pid}${r.runId ? ` (run ${r.runId})` : ""} is dead`;
45
+ }
46
+ const runId = positionals[0];
47
+ if (!runId)
48
+ throw new Error("usage: tickmarkr unlock <run-id> [--yes] | tickmarkr unlock --garbage [--yes]");
49
+ const preview = previewUnlock(cwd, runId);
50
+ if (!preview.held)
51
+ return "no lock held — nothing to remove";
52
+ if (!preview.eligible)
53
+ throw new Error(preview.reason);
54
+ if (!(await confirm(`Remove lock held by dead pid ${preview.pid} (run ${preview.runId})?`))) {
55
+ throw new Error("unlock: not confirmed (pass --yes or confirm at a TTY)");
56
+ }
57
+ const commit = commitUnlock(cwd, preview);
58
+ if (!commit.removed)
59
+ throw new Error(commit.reason);
60
+ return `removed stale lock — holder pid ${commit.pid} (run ${commit.runId}) is dead`;
11
61
  }
@@ -0,0 +1,219 @@
1
+ type Help = {
2
+ usage: string;
3
+ description: string;
4
+ options: Record<string, string>;
5
+ examples: string[];
6
+ };
7
+ export declare const COMMAND_HELP: {
8
+ init: {
9
+ usage: string;
10
+ description: string;
11
+ options: {
12
+ "--global-dir <path>": string;
13
+ "--agent": string;
14
+ "--force": string;
15
+ "--docs": string;
16
+ "--fresh": string;
17
+ "--yes": string;
18
+ };
19
+ examples: string[];
20
+ };
21
+ doctor: {
22
+ usage: string;
23
+ description: string;
24
+ options: {
25
+ "--models": string;
26
+ "--fix": string;
27
+ "--fix-only": string;
28
+ "--cached": string;
29
+ "--cached-only": string;
30
+ "--probe-preflight": string;
31
+ "--preflight": string;
32
+ "--refresh-catalog": string;
33
+ "--catalog-only": string;
34
+ };
35
+ examples: string[];
36
+ };
37
+ fleet: {
38
+ usage: string;
39
+ description: string;
40
+ options: {
41
+ "--print": string;
42
+ "--why": string;
43
+ "--global-dir <path>": string;
44
+ "--fresh": string;
45
+ };
46
+ examples: string[];
47
+ };
48
+ compile: {
49
+ usage: string;
50
+ description: string;
51
+ options: {
52
+ "--type <speckit|prd|gsd|native>": string;
53
+ "--dry-run": string;
54
+ "--strict": string;
55
+ };
56
+ examples: string[];
57
+ };
58
+ scope: {
59
+ usage: string;
60
+ description: string;
61
+ options: {
62
+ "--preview": string;
63
+ "--yes": string;
64
+ "--force": string;
65
+ };
66
+ examples: string[];
67
+ };
68
+ plan: {
69
+ usage: string;
70
+ description: string;
71
+ options: {
72
+ "--mode <risk-based|partner-led|staff-led>": string;
73
+ "--driver <auto|herdr|subprocess|orca>": string;
74
+ };
75
+ examples: string[];
76
+ };
77
+ run: {
78
+ usage: string;
79
+ description: string;
80
+ options: {
81
+ "--concurrency <N>": string;
82
+ "--driver <auto|herdr|subprocess|orca>": string;
83
+ "--route-strict": string;
84
+ "--no-explore": string;
85
+ "--mode <risk-based|partner-led|staff-led>": string;
86
+ "--quality": string;
87
+ "--supersedes <run-id>": string;
88
+ };
89
+ examples: string[];
90
+ };
91
+ status: {
92
+ usage: string;
93
+ description: string;
94
+ options: {
95
+ "--oneline": string;
96
+ "--watch": string;
97
+ "--plain": string;
98
+ "--events": string;
99
+ "--jsonl": string;
100
+ "--decision-events": string;
101
+ "--webhook <url>": string;
102
+ };
103
+ examples: string[];
104
+ };
105
+ stats: {
106
+ usage: string;
107
+ description: string;
108
+ options: {};
109
+ examples: string[];
110
+ };
111
+ resume: {
112
+ usage: string;
113
+ description: string;
114
+ options: {
115
+ "--graph-changed": string;
116
+ "--retry-failed": string;
117
+ "--driver <auto|herdr|subprocess|orca>": string;
118
+ };
119
+ examples: string[];
120
+ };
121
+ report: {
122
+ usage: string;
123
+ description: string;
124
+ options: {
125
+ "--md": string;
126
+ "--compare <baseline-run-id>": string;
127
+ "--bundle <path>": string;
128
+ };
129
+ examples: string[];
130
+ };
131
+ profile: {
132
+ usage: string;
133
+ description: string;
134
+ options: {
135
+ "--explain <shape> <adapter:model> [sub|api]": string;
136
+ "--weight <0|0.5>": string;
137
+ "--reason <text>": string;
138
+ };
139
+ examples: string[];
140
+ };
141
+ ui: {
142
+ usage: string;
143
+ description: string;
144
+ options: {
145
+ "--view <home|run|evidence>": string;
146
+ "--setup": string;
147
+ };
148
+ examples: string[];
149
+ };
150
+ unlock: {
151
+ usage: string;
152
+ description: string;
153
+ options: {
154
+ "--garbage": string;
155
+ "--yes": string;
156
+ };
157
+ examples: string[];
158
+ };
159
+ approve: {
160
+ usage: string;
161
+ description: string;
162
+ options: {
163
+ "--by <name>": string;
164
+ "--reason <text>": string;
165
+ "--waive": string;
166
+ "--uphold": string;
167
+ "--recheck": string;
168
+ "--review-rounds <N>": string;
169
+ };
170
+ examples: string[];
171
+ };
172
+ beat: {
173
+ usage: string;
174
+ description: string;
175
+ options: {
176
+ "--seat <identity>": string;
177
+ "--arm-id <identity>": string;
178
+ "--pct <0..100>": string;
179
+ "--threshold-pct <0..100>": string;
180
+ "--stand-down": string;
181
+ };
182
+ examples: string[];
183
+ };
184
+ version: {
185
+ usage: string;
186
+ description: string;
187
+ options: {
188
+ "--dist": string;
189
+ };
190
+ examples: string[];
191
+ };
192
+ verify: {
193
+ usage: string;
194
+ description: string;
195
+ options: {
196
+ "--base <ref>": string;
197
+ "--criteria <file>": string;
198
+ "--task <id>": string;
199
+ "--files <glob>": string;
200
+ "--author <adapter:model>": string;
201
+ "--baseline <path>": string;
202
+ "--record <run-id>": string;
203
+ "--json": string;
204
+ "--no-review": string;
205
+ "--no-acceptance": string;
206
+ };
207
+ examples: string[];
208
+ };
209
+ eval: {
210
+ usage: string;
211
+ description: string;
212
+ options: {};
213
+ examples: string[];
214
+ };
215
+ };
216
+ export declare const PROFILE_HELP: Record<string, Help>;
217
+ export declare function hasHelpFlag(argv: readonly string[]): boolean;
218
+ export declare function commandHelp(command: string, argv?: readonly string[]): string;
219
+ export {};