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.
- package/README.md +110 -24
- package/dist/adapters/claude-code.js +7 -2
- package/dist/adapters/codex.d.ts +1 -0
- package/dist/adapters/codex.js +66 -5
- package/dist/adapters/types.d.ts +4 -0
- package/dist/adapters/types.js +33 -0
- package/dist/cli/commands/approve.d.ts +42 -0
- package/dist/cli/commands/approve.js +80 -13
- package/dist/cli/commands/doctor.d.ts +234 -0
- package/dist/cli/commands/doctor.js +139 -5
- package/dist/cli/commands/report.js +15 -44
- package/dist/cli/commands/scope.js +36 -6
- package/dist/cli/commands/stats.d.ts +2 -0
- package/dist/cli/commands/stats.js +42 -23
- package/dist/cli/commands/status.js +20 -4
- package/dist/cli/commands/ui.js +42 -53
- package/dist/cli/commands/unlock.d.ts +1 -1
- package/dist/cli/commands/unlock.js +59 -9
- package/dist/cli/help.d.ts +219 -0
- package/dist/cli/help.js +212 -0
- package/dist/cli/index.d.ts +42 -2
- package/dist/cli/index.js +23 -8
- package/dist/drivers/herdr.d.ts +7 -2
- package/dist/drivers/herdr.js +78 -48
- package/dist/drivers/orca.d.ts +3 -2
- package/dist/drivers/orca.js +43 -4
- package/dist/drivers/subprocess.d.ts +1 -0
- package/dist/drivers/subprocess.js +3 -0
- package/dist/drivers/types.d.ts +14 -0
- package/dist/gates/artifact-manifest.d.ts +50 -0
- package/dist/gates/artifact-manifest.js +23 -0
- package/dist/plan/scope.d.ts +25 -0
- package/dist/plan/scope.js +92 -12
- package/dist/report/operator-record.d.ts +49 -0
- package/dist/report/operator-record.js +137 -0
- package/dist/run/daemon.d.ts +3 -4
- package/dist/run/daemon.js +18 -10
- package/dist/run/lock.d.ts +59 -2
- package/dist/run/lock.js +184 -26
- package/dist/run/operator-state.d.ts +86 -0
- package/dist/run/operator-state.js +165 -0
- package/dist/run/supervision.d.ts +32 -0
- package/dist/run/supervision.js +138 -17
- package/dist/tui/cockpit/capture.d.ts +19 -0
- package/dist/tui/cockpit/capture.js +89 -1
- package/dist/tui/cockpit/components.d.ts +15 -1
- package/dist/tui/cockpit/components.js +79 -9
- package/dist/tui/cockpit/decision-actions.d.ts +147 -0
- package/dist/tui/cockpit/decision-actions.js +315 -0
- package/dist/tui/cockpit/derive.d.ts +1 -1
- package/dist/tui/cockpit/derive.js +2 -0
- package/dist/tui/cockpit/evidence-view.d.ts +119 -0
- package/dist/tui/cockpit/evidence-view.js +210 -0
- package/dist/tui/cockpit/home-view.d.ts +88 -0
- package/dist/tui/cockpit/home-view.js +240 -0
- package/dist/tui/cockpit/keys.d.ts +125 -0
- package/dist/tui/cockpit/keys.js +31 -0
- package/dist/tui/cockpit/layout.d.ts +14 -0
- package/dist/tui/cockpit/layout.js +15 -0
- package/dist/tui/cockpit/live-runtime.d.ts +46 -0
- package/dist/tui/cockpit/live-runtime.js +683 -0
- package/dist/tui/cockpit/live-store.d.ts +289 -0
- package/dist/tui/cockpit/live-store.js +308 -0
- package/dist/tui/cockpit/live.d.ts +21 -1
- package/dist/tui/cockpit/live.js +12 -1
- package/dist/tui/cockpit/run-view.d.ts +100 -0
- package/dist/tui/cockpit/run-view.js +202 -0
- package/dist/tui/cockpit/shell.d.ts +50 -0
- package/dist/tui/cockpit/shell.js +74 -0
- package/dist/tui/cockpit/theme.d.ts +27 -0
- package/dist/tui/cockpit/theme.js +21 -0
- package/package.json +1 -1
- package/skills/tickmarkr-auto/SKILL.md +10 -1
- package/skills/tickmarkr-loop/SKILL.md +72 -1
- 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 =
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
lines.push(
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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(
|
|
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
|
|
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();
|
package/dist/cli/commands/ui.js
CHANGED
|
@@ -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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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 =
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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(
|
|
1
|
+
export declare function unlock(argv: string[], cwd?: string): Promise<string>;
|
|
@@ -1,11 +1,61 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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 {};
|