tickmarkr 2.6.3 → 2.6.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/README.md +10 -2
- package/dist/adapters/codex.d.ts +2 -1
- package/dist/adapters/codex.js +26 -5
- package/dist/adapters/model-lints.d.ts +1 -0
- package/dist/adapters/model-lints.js +48 -1
- package/dist/adapters/model-windows.d.ts +2 -1
- package/dist/adapters/model-windows.js +18 -7
- package/dist/adapters/types.d.ts +6 -0
- package/dist/cli/commands/approve.js +27 -2
- package/dist/cli/commands/doctor.d.ts +2 -0
- package/dist/cli/commands/doctor.js +6 -1
- package/dist/cli/commands/fleet.js +241 -74
- package/dist/cli/commands/init.js +5 -3
- package/dist/cli/commands/report.d.ts +39 -2
- package/dist/cli/commands/report.js +227 -10
- package/dist/cli/commands/resume.js +2 -4
- package/dist/cli/commands/run.d.ts +11 -0
- package/dist/cli/commands/run.js +33 -5
- package/dist/cli/commands/status.js +30 -3
- package/dist/cli/commands/verify.js +332 -121
- package/dist/compile/native.js +130 -0
- package/dist/config/config.d.ts +25 -7
- package/dist/config/config.js +92 -11
- package/dist/route/router.d.ts +11 -0
- package/dist/route/router.js +29 -8
- package/dist/run/daemon.js +8 -3
- package/dist/run/journal.d.ts +97 -1
- package/dist/run/journal.js +337 -2
- package/dist/tui/cockpit/live-store.d.ts +6 -0
- package/dist/tui/cockpit/live-store.js +36 -11
- package/package.json +1 -1
- package/skills/tickmarkr-auto/SKILL.md +2 -2
- package/skills/tickmarkr-loop/SKILL.md +10 -4
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
import type { BaselineCommand } from "../../gates/baseline.js";
|
|
2
2
|
import { type ChannelCost } from "../../report/cost.js";
|
|
3
|
-
import { type JournalEvent, type TelemetryRow } from "../../run/journal.js";
|
|
4
|
-
export
|
|
3
|
+
import { type JournalEvent, type OwedFold, type TelemetryRow } from "../../run/journal.js";
|
|
4
|
+
export interface FirstPass {
|
|
5
|
+
passed: number;
|
|
6
|
+
known: number;
|
|
7
|
+
unknown: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* End-to-end first pass over the finished tasks of the SELECTED journal (no cross-run scan). A task
|
|
11
|
+
* closed done, merged, or named done by the newest run-end passes only as one dispatch at attempt 0
|
|
12
|
+
* straight to its merge. A task whose predecessor or dispatch evidence is missing — no run-start, a run
|
|
13
|
+
* that supersedes another, no dispatch, a first dispatch not at attempt 0 or not recording lifetime
|
|
14
|
+
* worker dispatch ordinal 0 — is unknown whatever else it shows: attempts reset at every release, so
|
|
15
|
+
* only the lifetime ordinal proves no earlier dispatch is missing. Otherwise any park, approval, failure, repair, restore or second dispatch is a known miss — a
|
|
16
|
+
* run-end summary's park or a restart before its merge included — and a would-be pass with no merge is
|
|
17
|
+
* unknown, never counted as a pass.
|
|
18
|
+
*/
|
|
19
|
+
export declare function endToEndFirstPass(events: readonly JournalEvent[]): FirstPass;
|
|
20
|
+
export declare const firstPassText: ({ passed, known, unknown }: FirstPass) => string;
|
|
21
|
+
/** The CURRENT owed-check fold. `cwd` is any checkout of the run's repository. */
|
|
22
|
+
export declare const currentOwed: (events: readonly JournalEvent[], cwd?: string) => OwedFold;
|
|
23
|
+
export declare const outstandingText: (fold: OwedFold) => string;
|
|
24
|
+
export declare const needsYouText: (events: readonly JournalEvent[], owed: OwedFold) => string;
|
|
25
|
+
/** The wall window alone, for the one-line form: the same partition, never task-time. */
|
|
26
|
+
export declare const wallText: (events: readonly JournalEvent[]) => string;
|
|
27
|
+
/**
|
|
28
|
+
* The three lead lines. `finished` is the surface's own done tally and tip reading (plus any
|
|
29
|
+
* comparability or supersession note); the rest is folded here from the journal and the current fold.
|
|
30
|
+
*/
|
|
31
|
+
export declare function leadLines(events: readonly JournalEvent[], owed: OwedFold, finished: {
|
|
32
|
+
tally: string;
|
|
33
|
+
tip: string;
|
|
34
|
+
notes?: string[];
|
|
35
|
+
}): [string, string, string];
|
|
36
|
+
/**
|
|
37
|
+
* The lead at `columns`: the first row of finished, time and needs you are the first three physical
|
|
38
|
+
* lines, and every continuation follows them, naming the lead it continues.
|
|
39
|
+
*/
|
|
40
|
+
export declare const leadRows: (lead: readonly string[], columns: number) => string[];
|
|
41
|
+
export declare function renderMarkdownRecord(runId: string, events: JournalEvent[], prices?: ChannelCost[], rows?: TelemetryRow[], suite?: BaselineCommand, cwd?: string): string;
|
|
5
42
|
export declare function report(argv: string[], cwd?: string): Promise<string>;
|
|
@@ -11,10 +11,11 @@ import { compareRuns } from "../../report/compare.js";
|
|
|
11
11
|
import { estimateCosts } from "../../report/cost.js";
|
|
12
12
|
import { assignmentChannel, buildOperatorRecord, formatChannelMoney, formatChannelTokens, formatTokenUsage as fields, totalTokens as total, } from "../../report/operator-record.js";
|
|
13
13
|
import { cellsOf, cellSummary } from "../../route/profile.js";
|
|
14
|
-
import { Journal, loadRoutingProfile } from "../../run/journal.js";
|
|
14
|
+
import { effectiveEvents, foldOwedChecks, Journal, loadRoutingProfile, OWED_DISCHARGE_EVENT, } from "../../run/journal.js";
|
|
15
15
|
import { baselineProvenanceOf, fingerprintsOf, forgivenFingerprints, forgivenGateRow, formatBaselineProvenance, formatFingerprints, formatForgiven, formatTipProof, runEndTipProof, } from "../../run/daemon.js";
|
|
16
|
-
import { formatSpan, WALL_PRIORITY_TEXT, wallBudget, wallBudgetFacts } from "../../run/wall-budget.js";
|
|
16
|
+
import { formatSpan, WALL_PRIORITY, WALL_PRIORITY_TEXT, wallBudget, wallBudgetFacts } from "../../run/wall-budget.js";
|
|
17
17
|
import { deriveRunCockpitData } from "../../tui/cockpit/derive.js";
|
|
18
|
+
import { cellWidth, wrapCells } from "../../tui/cockpit/width.js";
|
|
18
19
|
const n = (x) => x.toLocaleString("en-US"); // explicit locale — CI/darwin flake guard
|
|
19
20
|
const EM = "—";
|
|
20
21
|
const firstLine = (s) => {
|
|
@@ -129,6 +130,192 @@ const supersession = (events) => {
|
|
|
129
130
|
const supersedes = typeof start?.data.supersedes === "string" ? start.data.supersedes : undefined;
|
|
130
131
|
return { ...(typeof by === "string" ? { supersededBy: by } : {}), ...(supersedes ? { supersedes } : {}) };
|
|
131
132
|
};
|
|
133
|
+
// ── CG1 (v2.6.4): the lead — what finished, where the wall went, what needs you NOW ──────────────
|
|
134
|
+
// Status and report open with the same three lines, folded here once. Finished work and first pass
|
|
135
|
+
// come from this journal's own lineage; time is the existing disjoint wall partition (never summed
|
|
136
|
+
// task-time); outstanding is the CURRENT owed-check fold, re-read on every call, never the run-end copy.
|
|
137
|
+
// Rows that prove a task's lineage was not one uninterrupted dispatch: a park, an approval, a failure,
|
|
138
|
+
// a repair, or a restart restoring it.
|
|
139
|
+
const INTERRUPTED = new Set(["task-human", "task-failed", "task-approved", "repair-dispatch", "resume-restore"]);
|
|
140
|
+
/**
|
|
141
|
+
* End-to-end first pass over the finished tasks of the SELECTED journal (no cross-run scan). A task
|
|
142
|
+
* closed done, merged, or named done by the newest run-end passes only as one dispatch at attempt 0
|
|
143
|
+
* straight to its merge. A task whose predecessor or dispatch evidence is missing — no run-start, a run
|
|
144
|
+
* that supersedes another, no dispatch, a first dispatch not at attempt 0 or not recording lifetime
|
|
145
|
+
* worker dispatch ordinal 0 — is unknown whatever else it shows: attempts reset at every release, so
|
|
146
|
+
* only the lifetime ordinal proves no earlier dispatch is missing. Otherwise any park, approval, failure, repair, restore or second dispatch is a known miss — a
|
|
147
|
+
* run-end summary's park or a restart before its merge included — and a would-be pass with no merge is
|
|
148
|
+
* unknown, never counted as a pass.
|
|
149
|
+
*/
|
|
150
|
+
export function endToEndFirstPass(events) {
|
|
151
|
+
const start = events.find((e) => e.event === "run-start");
|
|
152
|
+
const predecessorUnread = !start || typeof start.data.supersedes === "string";
|
|
153
|
+
const runEnd = [...events].reverse().find((e) => e.event === "run-end");
|
|
154
|
+
const finished = new Set([
|
|
155
|
+
...events.flatMap((e) => e.taskId && (e.event === "task-done" || e.event === "merge") ? [e.taskId] : []),
|
|
156
|
+
...(Array.isArray(runEnd?.data.done) ? runEnd.data.done.map(String) : []),
|
|
157
|
+
]);
|
|
158
|
+
const fp = { passed: 0, known: 0, unknown: 0 };
|
|
159
|
+
const names = (e, buckets, taskId) => e.event === "run-end"
|
|
160
|
+
&& buckets.some((b) => Array.isArray(e.data[b]) && e.data[b].map(String).includes(taskId));
|
|
161
|
+
for (const taskId of finished) {
|
|
162
|
+
const own = events.filter((e) => e.taskId === taskId);
|
|
163
|
+
const dispatches = own.filter((e) => e.event === "task-dispatch");
|
|
164
|
+
// Run-level rows name the task too: a run-end summary parking or failing it is a park wherever it
|
|
165
|
+
// sits, and between its first dispatch and its merge any not-done bucket or a restart means the
|
|
166
|
+
// lineage crossed an engagement boundary — never one uninterrupted dispatch.
|
|
167
|
+
const at = events.findIndex((e) => e.taskId === taskId && e.event === "task-dispatch");
|
|
168
|
+
const end = events.findIndex((e) => e.taskId === taskId && e.event === "merge");
|
|
169
|
+
const span = at < 0 ? [] : events.slice(at, end < 0 ? undefined : end);
|
|
170
|
+
const crossed = events.some((e) => names(e, ["human", "failed"], taskId))
|
|
171
|
+
|| span.some((e) => e.event === "run-resume" || names(e, ["blocked", "pending"], taskId));
|
|
172
|
+
// Missing predecessor or dispatch evidence is unknown BEFORE any outcome is classified: a park read
|
|
173
|
+
// without its initial dispatch (none, a first one past attempt 0, or one whose lifetime ordinal is
|
|
174
|
+
// unrecorded or past 0), or in a run continuing an unread predecessor, is no known miss either.
|
|
175
|
+
const origin = dispatches[0]?.data;
|
|
176
|
+
if (predecessorUnread || origin?.attempt !== 0 || origin.workerDispatchOrdinal !== 0)
|
|
177
|
+
fp.unknown++;
|
|
178
|
+
else if (dispatches.length > 1 || crossed || own.some((e) => INTERRUPTED.has(e.event)))
|
|
179
|
+
fp.known++;
|
|
180
|
+
else if (!own.some((e) => e.event === "merge"))
|
|
181
|
+
fp.unknown++;
|
|
182
|
+
else {
|
|
183
|
+
fp.passed++;
|
|
184
|
+
fp.known++;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return fp;
|
|
188
|
+
}
|
|
189
|
+
export const firstPassText = ({ passed, known, unknown }) => known === 0 && unknown > 0
|
|
190
|
+
? `end-to-end first pass unknown (${unknown} without lineage)`
|
|
191
|
+
: `end-to-end first pass ${passed}/${known}${unknown ? ` + ${unknown} unknown` : ""}`;
|
|
192
|
+
/** The CURRENT owed-check fold. `cwd` is any checkout of the run's repository. */
|
|
193
|
+
export const currentOwed = (events, cwd = process.cwd()) => foldOwedChecks(events, cwd);
|
|
194
|
+
export const outstandingText = (fold) => fold.known
|
|
195
|
+
? `outstanding ${fold.debt}${fold.outstanding.length ? ` (${fold.outstanding.map((o) => `${o.taskId} ${o.gate}`).join(", ")})` : ""}`
|
|
196
|
+
: "outstanding unknown";
|
|
197
|
+
// A run-end summary's buckets read as the lifecycle row each names; later effective rows move them on.
|
|
198
|
+
const SUMMARY_STATE = { done: "task-done", failed: "task-failed", human: "task-human", blocked: "task-blocked", pending: "task-pending" };
|
|
199
|
+
/**
|
|
200
|
+
* Tasks the CURRENT lifecycle leaves waiting on the operator: every run-end summary sets the tasks its
|
|
201
|
+
* buckets name, and each effective lifecycle row after it moves its task on — so a summary naming a
|
|
202
|
+
* park or failure the task rows never wrote still counts, and a later approval or merge clears it.
|
|
203
|
+
*/
|
|
204
|
+
const waitingOnYou = (events) => {
|
|
205
|
+
const last = new Map();
|
|
206
|
+
for (const e of effectiveEvents(events)) {
|
|
207
|
+
if (e.event === "run-end") {
|
|
208
|
+
for (const [bucket, state] of Object.entries(SUMMARY_STATE)) {
|
|
209
|
+
if (Array.isArray(e.data[bucket]))
|
|
210
|
+
for (const id of e.data[bucket])
|
|
211
|
+
last.set(String(id), state);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
else if (e.taskId && ["task-dispatch", "task-done", "task-failed", "task-human", "task-approved", "merge"].includes(e.event))
|
|
215
|
+
last.set(e.taskId, e.event);
|
|
216
|
+
}
|
|
217
|
+
const states = [...last.values()];
|
|
218
|
+
return { parked: states.filter((s) => s === "task-human").length, failed: states.filter((s) => s === "task-failed").length };
|
|
219
|
+
};
|
|
220
|
+
export const needsYouText = (events, owed) => {
|
|
221
|
+
const { parked, failed } = waitingOnYou(events);
|
|
222
|
+
const why = owed.known ? "" : ` · unknown: ${owed.unknown[0]?.reason ?? "owed checks unreadable"}`;
|
|
223
|
+
return `needs you: ${outstandingText(owed)} · ${parked} parked · ${failed} failed${why}`;
|
|
224
|
+
};
|
|
225
|
+
/** The wall window alone, for the one-line form: the same partition, never task-time. */
|
|
226
|
+
export const wallText = (events) => {
|
|
227
|
+
const budget = wallBudget(events);
|
|
228
|
+
return budget ? `${formatSpan(budget.wallMs)} wall` : "wall not measurable";
|
|
229
|
+
};
|
|
230
|
+
/** Time: the existing disjoint wall partition's window and its largest exposed buckets — never task-time. */
|
|
231
|
+
const timeLead = (events) => {
|
|
232
|
+
const budget = wallBudget(events);
|
|
233
|
+
if (!budget)
|
|
234
|
+
return "time not measurable — the journal names no run-start with a readable timestamp";
|
|
235
|
+
const top = [...WALL_PRIORITY.map((bucket) => [bucket, budget.exposedMs[bucket]]), ["residual", budget.residualMs]]
|
|
236
|
+
.filter(([, ms]) => ms > 0).sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
237
|
+
return `time ${formatSpan(budget.wallMs)} wall, each instant once${top.length ? `: ${top.map(([bucket, ms]) => `${bucket} ${formatSpan(ms)}`).join(" · ")}` : ""}`;
|
|
238
|
+
};
|
|
239
|
+
/**
|
|
240
|
+
* The three lead lines. `finished` is the surface's own done tally and tip reading (plus any
|
|
241
|
+
* comparability or supersession note); the rest is folded here from the journal and the current fold.
|
|
242
|
+
*/
|
|
243
|
+
export function leadLines(events, owed, finished) {
|
|
244
|
+
return [
|
|
245
|
+
`finished ${[finished.tally, finished.tip, firstPassText(endToEndFirstPass(events)), ...(finished.notes ?? [])].join(" · ")}`,
|
|
246
|
+
timeLead(events),
|
|
247
|
+
needsYouText(events, owed),
|
|
248
|
+
];
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* A lead line packed into `columns` at its ` · ` fact boundaries: a fact that does not fit beside the
|
|
252
|
+
* last moves whole to a `prefix`ed continuation row, so narrowing costs rows and never drops a fact.
|
|
253
|
+
*/
|
|
254
|
+
const wrapFacts = (line, columns, prefix) => {
|
|
255
|
+
const rows = [];
|
|
256
|
+
// Read fact by fact without a hand-split (the learning-section source pin forbids one here).
|
|
257
|
+
for (const [, fact] of line.matchAll(/(?:^| · )(.+?)(?= · |$)/gu)) {
|
|
258
|
+
const last = rows.at(-1);
|
|
259
|
+
if (last !== undefined && cellWidth(`${last} · ${fact}`) <= columns)
|
|
260
|
+
rows[rows.length - 1] = `${last} · ${fact}`;
|
|
261
|
+
else
|
|
262
|
+
rows.push(...wrapCells(last === undefined ? fact : `${prefix}${fact}`, columns, { continuationPrefix: " " }));
|
|
263
|
+
}
|
|
264
|
+
return rows;
|
|
265
|
+
};
|
|
266
|
+
const LEAD_LABELS = ["finished", "time", "needs you"];
|
|
267
|
+
/**
|
|
268
|
+
* The lead at `columns`: the first row of finished, time and needs you are the first three physical
|
|
269
|
+
* lines, and every continuation follows them, naming the lead it continues.
|
|
270
|
+
*/
|
|
271
|
+
export const leadRows = (lead, columns) => {
|
|
272
|
+
const wrapped = lead.map((line, i) => wrapFacts(line, columns, ` ${LEAD_LABELS[i]}: `));
|
|
273
|
+
return [...wrapped.map((rows) => rows[0]), ...wrapped.flatMap((rows) => rows.slice(1))];
|
|
274
|
+
};
|
|
275
|
+
/**
|
|
276
|
+
* The discharge rows the current fold validates ON THEIR OWN: each is folded alone, at its own journal
|
|
277
|
+
* position among every non-discharge row, so one valid proof never lends its validity to a refused
|
|
278
|
+
* sibling naming the same check, and a proof written before its obligation is never validated by it.
|
|
279
|
+
*/
|
|
280
|
+
const validatedDischarges = (events, cwd) => {
|
|
281
|
+
return events.filter((e) => e.event === OWED_DISCHARGE_EVENT).flatMap((row) => {
|
|
282
|
+
const ids = Array.isArray(row.data.ids) ? row.data.ids.map(String) : [];
|
|
283
|
+
// In journal order: a discharge that precedes its obligation stays where it was written, never moved after it.
|
|
284
|
+
const fold = currentOwed(events.filter((e) => e.event !== OWED_DISCHARGE_EVENT || e === row), cwd);
|
|
285
|
+
if (!ids.length || !ids.every((id) => fold.discharged.includes(id)))
|
|
286
|
+
return [];
|
|
287
|
+
return [{ row, gates: ids.map((id) => fold.acceptedRisk.find((o) => o.id === id)?.gate ?? "unknown") }];
|
|
288
|
+
});
|
|
289
|
+
};
|
|
290
|
+
/** A leg2 row's own reviewer fact: top-level, or under the verify gate row's meta it spreads. */
|
|
291
|
+
const leg2Seat = (data, key) => data[key] ?? (typeof data.meta === "object" && data.meta !== null ? data.meta[key] : undefined);
|
|
292
|
+
/**
|
|
293
|
+
* A review-leg2 row supersedes the daemon review only through its OWN validated discharge of a review
|
|
294
|
+
* check: a passing row whose artifact was captured, bound to that discharge's artifact and range, and
|
|
295
|
+
* naming the very reviewer seat (key and vendor) the discharge validated. A failed or capture-failed
|
|
296
|
+
* row, or one naming another seat, never borrows a sibling's proof.
|
|
297
|
+
*/
|
|
298
|
+
const leg2Discharge = (leg2, validated) => {
|
|
299
|
+
const d = leg2.data;
|
|
300
|
+
if (d.pass !== true || d.artifactAvailability !== "available" || typeof d.artifactSha256 !== "string")
|
|
301
|
+
return undefined;
|
|
302
|
+
return validated.find(({ row, gates }) => {
|
|
303
|
+
const seat = row.data.reviewer;
|
|
304
|
+
return row.taskId === leg2.taskId && gates.includes("review")
|
|
305
|
+
&& row.data.artifactSha256 === d.artifactSha256 && row.data.artifactPath === d.artifactPath
|
|
306
|
+
&& row.data.head === d.head && row.data.mergeBase === d.mergeBase
|
|
307
|
+
&& typeof seat?.key === "string" && leg2Seat(d, "reviewer") === seat.key && leg2Seat(d, "vendor") === seat.vendor;
|
|
308
|
+
})?.row;
|
|
309
|
+
};
|
|
310
|
+
const RUN_END_BUCKETS = ["done", "failed", "human", "blocked", "pending"];
|
|
311
|
+
/** The record's done tally: tasks closed done in the journal or its newest run-end, over every task it names. */
|
|
312
|
+
const recordTally = (events) => {
|
|
313
|
+
const runEnd = [...events].reverse().find((e) => e.event === "run-end");
|
|
314
|
+
const bucket = (key) => Array.isArray(runEnd?.data[key]) ? runEnd.data[key].map(String) : [];
|
|
315
|
+
const done = new Set([...bucket("done"), ...events.flatMap((e) => e.taskId && (e.event === "task-done" || e.event === "merge") ? [e.taskId] : [])]);
|
|
316
|
+
const all = new Set([...done, ...RUN_END_BUCKETS.flatMap(bucket), ...events.flatMap((e) => e.taskId && e.event.startsWith("task-") ? [e.taskId] : [])]);
|
|
317
|
+
return `${done.size}/${all.size} done`;
|
|
318
|
+
};
|
|
132
319
|
const taskIds = (events) => {
|
|
133
320
|
const seen = new Set();
|
|
134
321
|
const out = [];
|
|
@@ -234,7 +421,7 @@ const fingerprintClauses = (data) => {
|
|
|
234
421
|
+ `; forgiven vs baseline: ${formatFingerprints(forgiven)} — ${formatBaselineProvenance(baselineProvenanceOf(data.baselineProvenance))}`;
|
|
235
422
|
};
|
|
236
423
|
// VIS-07 / REC-01: derived only from the run journal, telemetry, and local configuration.
|
|
237
|
-
export function renderMarkdownRecord(runId, events, prices = [], rows = [], suite) {
|
|
424
|
+
export function renderMarkdownRecord(runId, events, prices = [], rows = [], suite, cwd) {
|
|
238
425
|
const runStart = events.find((e) => e.event === "run-start");
|
|
239
426
|
const runEnd = [...events].reverse().find((e) => e.event === "run-end");
|
|
240
427
|
const baseRef = typeof runStart?.data.baseRef === "string" ? runStart.data.baseRef : EM;
|
|
@@ -258,7 +445,11 @@ export function renderMarkdownRecord(runId, events, prices = [], rows = [], suit
|
|
|
258
445
|
if (!usageLines.length)
|
|
259
446
|
usageLines.push("- **not recorded:** attempts/windows: not measurable; tokens: not measurable; price: not measurable");
|
|
260
447
|
const sup = supersession(events);
|
|
448
|
+
const owed = currentOwed(events, cwd);
|
|
449
|
+
const validated = validatedDischarges(events, cwd);
|
|
261
450
|
const lines = [
|
|
451
|
+
...leadLines(events, owed, { tally: recordTally(events), tip: `tip verify ${verificationOf(runId, events)}` }).map((line) => `- ${line}`),
|
|
452
|
+
"",
|
|
262
453
|
`# tickmarkr engagement`,
|
|
263
454
|
"",
|
|
264
455
|
`- **runId:** ${runId}`,
|
|
@@ -277,7 +468,8 @@ export function renderMarkdownRecord(runId, events, prices = [], rows = [], suit
|
|
|
277
468
|
"",
|
|
278
469
|
...usageLines,
|
|
279
470
|
`- **wall-clock:** ${wallClock(runStart, runEnd)}`,
|
|
280
|
-
`- **
|
|
471
|
+
`- **end-to-end first pass:** ${firstPassText(endToEndFirstPass(events)).replace("end-to-end first pass ", "")} — this journal's dispatch, park, approval and merge lineage`,
|
|
472
|
+
`- **first-attempt rate (engagement-local telemetry):** ${firstAttemptRate} — attempts restart at every resume`,
|
|
281
473
|
`- **gate failures:** ${[...gateFailures.entries()].map(([gate, failures]) => `${gate}: ${failures}`).join(", ") || "none recorded"}`,
|
|
282
474
|
`- **consults:** ${events.filter((e) => e.event === "consult-verdict").length}`,
|
|
283
475
|
`- **escalations:** ${events.filter((e) => e.event === "escalation").length}`,
|
|
@@ -349,7 +541,12 @@ export function renderMarkdownRecord(runId, events, prices = [], rows = [], suit
|
|
|
349
541
|
const identityText = identity
|
|
350
542
|
? `reviewer ${identity.reviewer}${identity.vendor ? ` (vendor: ${identity.vendor}; provider: ${identity.provider})` : ` (provider: ${identity.provider})`}`
|
|
351
543
|
: firstLine(row.data.details);
|
|
352
|
-
|
|
544
|
+
// CG1: only this row's own validated discharge of a review check lets it supersede the daemon review.
|
|
545
|
+
const discharge = leg2Discharge(row, validated);
|
|
546
|
+
const claim = discharge
|
|
547
|
+
? `supersedes daemon review — validated discharge ${discharge.data.ids.map(String).join(", ")}`
|
|
548
|
+
: "unvalidated — no validated owed-check discharge; the daemon review stands";
|
|
549
|
+
lines.push(` - review-leg2: ${pass} (${claim}) — ${identityText}`);
|
|
353
550
|
}
|
|
354
551
|
}
|
|
355
552
|
else
|
|
@@ -422,12 +619,19 @@ function textReport(runId, events, rows, cwd, suite) {
|
|
|
422
619
|
const consults = events.filter((e) => e.event === "consult-verdict").length;
|
|
423
620
|
const failovers = events.filter((e) => e.event === "quota-failover").length;
|
|
424
621
|
const sup = supersession(events);
|
|
622
|
+
// CG1: at a configured terminal width the lead wraps its continuations after the three lead lines;
|
|
623
|
+
// report() fits every other row of the complete output (fitColumns).
|
|
624
|
+
const lead = leadLines(events, currentOwed(events, cwd), { tally: recordTally(events), tip: `tip verify ${verificationOf(runId, events)}` });
|
|
625
|
+
const columns = process.stdout.columns;
|
|
425
626
|
return [
|
|
627
|
+
...(columns ? leadRows(lead, columns) : lead),
|
|
426
628
|
`tickmarkr engagement — ${runId}`,
|
|
427
629
|
...(sup.supersededBy ? [`superseded by ${sup.supersededBy}`] : []),
|
|
428
630
|
...(sup.supersedes ? [`supersedes ${sup.supersedes}`] : []),
|
|
429
631
|
"",
|
|
430
|
-
|
|
632
|
+
// CG1: the telemetry rows are engagement-local — attempts restart at every resume; the lead's
|
|
633
|
+
// end-to-end first pass is the lineage reading.
|
|
634
|
+
"engagement summary — audit trail: engagement-local telemetry, attempts restart at every resume",
|
|
431
635
|
...[...groups.entries()].map(([k, g]) => ` ${k.padEnd(30)} tasks ${g.rows.length}, attempts ${g.rows.reduce((s, r) => s + r.attempts, 0)}, done ${g.rows.filter((r) => r.outcome === "done").length}`),
|
|
432
636
|
"",
|
|
433
637
|
`tickmark rate: ${gateRan ? Math.round((100 * gatePass) / gateRan) : 0}% (${gatePass}/${gateRan})${declinedCount ? ` · declined: ${declinedCount}` : ""}`,
|
|
@@ -448,6 +652,14 @@ function textReport(runId, events, rows, cwd, suite) {
|
|
|
448
652
|
` probes this run (route-deviation explore): ${probes}`,
|
|
449
653
|
].join("\n");
|
|
450
654
|
}
|
|
655
|
+
/**
|
|
656
|
+
* CG1: at a configured terminal width EVERY row of the complete text output fits it — the lead, the
|
|
657
|
+
* body, a --bundle receipt and --compare output alike: a row that overflows wraps beneath its own
|
|
658
|
+
* indent, no fact dropped; a row that already fits (the lead's own layout) is untouched.
|
|
659
|
+
*/
|
|
660
|
+
const fitColumns = (out, columns = process.stdout.columns) => columns
|
|
661
|
+
? out.replace(/^.*$/gmu, (line) => wrapCells(line, columns, { continuationPrefix: `${/^ */u.exec(line)[0]} ` }).join("\n"))
|
|
662
|
+
: out;
|
|
451
663
|
// T4 (v1.50): TTY-only brand pass over the text report — title frame + dim section chrome; row
|
|
452
664
|
// text and alignment untouched (the doctor/status system). Gated on ttyVisual(): the non-TTY
|
|
453
665
|
// surface returns untouched, and --md never styles (the record is a document surface).
|
|
@@ -455,8 +667,8 @@ const stylizeReport = (out) => {
|
|
|
455
667
|
if (!ttyVisual())
|
|
456
668
|
return out;
|
|
457
669
|
return out
|
|
458
|
-
.replace(
|
|
459
|
-
.replace(/^(engagement summary — audit trail
|
|
670
|
+
.replace(/^tickmarkr engagement — .*$/m, (head) => `${title(head)}\n${rule()}`) // non-global /m ⇒ the title line only
|
|
671
|
+
.replace(/^(engagement summary — audit trail:[^\n]*|wall budget — [^\n]*|spend — tokens[^\n]*|spend — money:|learning \([^\n]*)$/gm, (l) => dim(l));
|
|
460
672
|
};
|
|
461
673
|
export async function report(argv, cwd = process.cwd()) {
|
|
462
674
|
const { values, positionals } = parseArgs({
|
|
@@ -503,8 +715,13 @@ export async function report(argv, cwd = process.cwd()) {
|
|
|
503
715
|
throw new Error(outcome.reason);
|
|
504
716
|
comparison = "\n" + outcome.text;
|
|
505
717
|
}
|
|
718
|
+
// CG1: the bundle receipt follows the three lead lines and their wrapped continuations, never above
|
|
719
|
+
// them (in markdown, after the blank line that closes the lead list, so it never joins its last item).
|
|
720
|
+
const afterLead = (out, at = 3) => bundleNote
|
|
721
|
+
? out.replace(new RegExp(`^(?:[^\\n]*\\n){${at}}(?: [^\\n]*\\n)*`, "u"), (lead) => `${lead}${bundleNote.trimEnd()}\n`)
|
|
722
|
+
: out;
|
|
506
723
|
if (values.md) {
|
|
507
|
-
return
|
|
724
|
+
return afterLead(renderMarkdownRecord(runId, events, estimateCosts(rows, cfg.cost), rows, suite, cwd), 4) + comparison;
|
|
508
725
|
}
|
|
509
|
-
return
|
|
726
|
+
return fitColumns(afterLead(stylizeReport(textReport(runId, events, rows, cwd, suite))) + comparison);
|
|
510
727
|
}
|
|
@@ -5,10 +5,8 @@ import { classifyHost, parseDriverOverride, pickDriver, preflightHostDriver } fr
|
|
|
5
5
|
import { loadGraph } from "../../graph/graph.js";
|
|
6
6
|
import { formatSummary, runDaemon } from "../../run/daemon.js";
|
|
7
7
|
import { denyPreferCollisionLine, denyPreferCollisions } from "../../route/preference.js";
|
|
8
|
-
import { narrationSink, bindNarration } from "./run.js";
|
|
8
|
+
import { finalRail, narrationSink, bindNarration, summaryGreen } from "./run.js";
|
|
9
9
|
import { assertRefsWritable } from "../../run/git.js";
|
|
10
|
-
const summaryGreen = (s) => s.failed.length === 0 && s.human.length === 0 && s.blocked.length === 0 && s.pending.length === 0
|
|
11
|
-
&& s.tipVerify !== "failed";
|
|
12
10
|
export async function resume(argv, cwd = process.cwd()) {
|
|
13
11
|
const { values, positionals } = parseArgs({
|
|
14
12
|
args: argv,
|
|
@@ -56,6 +54,6 @@ export async function resume(argv, cwd = process.cwd()) {
|
|
|
56
54
|
// the operator named, so a resumed run's lifecycle rows name THIS run and not a generic word.
|
|
57
55
|
narrate,
|
|
58
56
|
});
|
|
59
|
-
const out = `resumed ${s.runId} — ${formatSummary(s)}`;
|
|
57
|
+
const out = `resumed ${s.runId} — ${formatSummary(s)}${finalRail(s, cwd)}`;
|
|
60
58
|
return { out, code: summaryGreen(s) ? 0 : 2 };
|
|
61
59
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ExecutorDriver } from "../../drivers/types.js";
|
|
2
|
+
import { type RunSummary } from "../../run/daemon.js";
|
|
2
3
|
import { type JournalEvent } from "../../run/journal.js";
|
|
3
4
|
/** Closed repetitive set the rail suppresses on a TTY. An ungated `phase-start` joins them below —
|
|
4
5
|
* it is a phase counter, while a phase-start CARRYING a gate is the gate start the rail draws. */
|
|
@@ -64,6 +65,16 @@ export declare const narrationSink: (runId: string) => (event: JournalEvent) =>
|
|
|
64
65
|
* wrap their driver here so neither can forget the binding.
|
|
65
66
|
*/
|
|
66
67
|
export declare function bindNarration<D extends ExecutorDriver>(driver: D, narrate: (event: JournalEvent) => void): D;
|
|
68
|
+
/** Execution green: the exit-code predicate run and resume share. Users' scripts read it; debt never moves it. */
|
|
69
|
+
export declare const summaryGreen: (s: RunSummary) => boolean;
|
|
70
|
+
/**
|
|
71
|
+
* CG2 (v2.6.4): the last line of a run or resume, read from the CURRENT owed-check fold after the
|
|
72
|
+
* daemon returns — never the run-end row's copy, which a later `verify --record` discharge leaves
|
|
73
|
+
* historical. "verified" requires execution green AND outstanding empty AND known; an execution-green
|
|
74
|
+
* run that owes a check, or whose debt is unknown, says so and is not green. A partial or failed run
|
|
75
|
+
* adds nothing: its summary keeps its own wording and is never called complete.
|
|
76
|
+
*/
|
|
77
|
+
export declare function finalRail(s: RunSummary, cwd: string): string;
|
|
67
78
|
export declare function run(argv: string[], cwd?: string): Promise<{
|
|
68
79
|
out: string;
|
|
69
80
|
code: number;
|
package/dist/cli/commands/run.js
CHANGED
|
@@ -6,10 +6,11 @@ import { loadGraph, onDiskSpecHash, readCompileRefusal } from "../../graph/graph
|
|
|
6
6
|
import { formatSummary, resolveRunMode, runDaemon } from "../../run/daemon.js";
|
|
7
7
|
import { isRunLockLive } from "../../run/lock.js";
|
|
8
8
|
import { route, NO_EXPLORE_ENV } from "../../route/router.js";
|
|
9
|
-
import { formatJournalNarration, loadRoutingProfile, newRunId } from "../../run/journal.js";
|
|
9
|
+
import { formatJournalNarration, Journal, loadRoutingProfile, newRunId } from "../../run/journal.js";
|
|
10
10
|
import { normalizeGateOutcome } from "../../run/outcome.js";
|
|
11
11
|
import { GLYPHS, LIVE } from "../../brand.js";
|
|
12
12
|
import { cellWidth, fitCells } from "../../tui/cockpit/width.js";
|
|
13
|
+
import { currentOwed, outstandingText } from "./report.js";
|
|
13
14
|
import { assertRefsWritable } from "../../run/git.js";
|
|
14
15
|
// ── the operator event rail (v1.99 T2) ──────────────────────────────────────────────────────────
|
|
15
16
|
// A run's TTY narration is an operator EVENT RAIL, not the journal dump it used to echo. The
|
|
@@ -166,17 +167,25 @@ const bucket = (value) => (Array.isArray(value) ? value.length : 0);
|
|
|
166
167
|
*
|
|
167
168
|
* a fatal crash, a failed task or a failed integration tip is a FAILURE;
|
|
168
169
|
* an incomplete run — parked, blocked or still-pending work — wants ATTENTION;
|
|
170
|
+
* so does a complete execution that owes a check, or whose owed-check fold is unknown (CG2);
|
|
169
171
|
* only a run with none of those is a pass.
|
|
170
172
|
*
|
|
171
173
|
* A rail that paints the terminal record by the same predicate the exit code uses can never show a
|
|
172
|
-
* green tickmark over a run whose exit code is 2
|
|
174
|
+
* green tickmark over a run whose exit code is 2 — and, since the exit code is execution-based, the
|
|
175
|
+
* debt clause is the one place the tone is stricter than it: exit 0 with an owed check is not green.
|
|
176
|
+
* The fold is the one this row carries, reduced at its own append: live, that IS the current fold.
|
|
173
177
|
*/
|
|
174
178
|
const runEndTone = (data) => {
|
|
175
179
|
if (data.fatal === true || bucket(data.failed) > 0 || data.tipVerify === "failed")
|
|
176
180
|
return "fail";
|
|
177
181
|
if (bucket(data.human) + bucket(data.blocked) + bucket(data.pending) > 0)
|
|
178
182
|
return "attention";
|
|
179
|
-
return "pass";
|
|
183
|
+
return recordedDebt(data) === 0 ? "pass" : "attention";
|
|
184
|
+
};
|
|
185
|
+
/** The outstanding count a run-end record's own fold carries; undefined when it carries none or an unknown one. */
|
|
186
|
+
const recordedDebt = (data) => {
|
|
187
|
+
const owed = data.owedChecks;
|
|
188
|
+
return owed?.known === true && Array.isArray(owed.outstanding) ? owed.outstanding.length : undefined;
|
|
180
189
|
};
|
|
181
190
|
/** A gate row's non-failure is spelled across `pass`, `skipped`, `verdict` and `infra`, and a row may
|
|
182
191
|
* state NONE of them (src/run/outcome.ts) — so the rail reads the canonical outcome rather than
|
|
@@ -330,6 +339,8 @@ const RAIL_PROJECTION = {
|
|
|
330
339
|
"tip-moved": (d) => typeof d.gatedCommit === "string" && typeof d.branchTip === "string"
|
|
331
340
|
? `gated ${d.gatedCommit.slice(0, 12)}, tip ${d.branchTip.slice(0, 12)}`
|
|
332
341
|
: undefined,
|
|
342
|
+
// CG2: the terminal row names its debt — a legacy record without a fold is unknown, never zero
|
|
343
|
+
"run-end": (d) => `outstanding ${recordedDebt(d) ?? "unknown"}`,
|
|
333
344
|
"trust-auto-answer": (d) => typeof d.adapter === "string" ? `${d.adapter}${typeof d.phase === "string" ? ` ${d.phase}` : ""}` : undefined,
|
|
334
345
|
// SB-1: the census the ceiling released beside, and the budget the round ran under versus the one it
|
|
335
346
|
// would have had on an empty census — none of `{count, occupancyCap, conservativeCap}` is on the ladder
|
|
@@ -420,8 +431,25 @@ export function bindNarration(driver, narrate) {
|
|
|
420
431
|
driver.narrateWith?.(narrate);
|
|
421
432
|
return driver;
|
|
422
433
|
}
|
|
423
|
-
|
|
434
|
+
/** Execution green: the exit-code predicate run and resume share. Users' scripts read it; debt never moves it. */
|
|
435
|
+
export const summaryGreen = (s) => s.failed.length === 0 && s.human.length === 0 && s.blocked.length === 0 && s.pending.length === 0
|
|
424
436
|
&& s.tipVerify !== "failed";
|
|
437
|
+
/**
|
|
438
|
+
* CG2 (v2.6.4): the last line of a run or resume, read from the CURRENT owed-check fold after the
|
|
439
|
+
* daemon returns — never the run-end row's copy, which a later `verify --record` discharge leaves
|
|
440
|
+
* historical. "verified" requires execution green AND outstanding empty AND known; an execution-green
|
|
441
|
+
* run that owes a check, or whose debt is unknown, says so and is not green. A partial or failed run
|
|
442
|
+
* adds nothing: its summary keeps its own wording and is never called complete.
|
|
443
|
+
*/
|
|
444
|
+
export function finalRail(s, cwd) {
|
|
445
|
+
if (!summaryGreen(s))
|
|
446
|
+
return "";
|
|
447
|
+
const owed = currentOwed(Journal.open(cwd, s.runId).read(), cwd);
|
|
448
|
+
if (owed.known && owed.outstanding.length === 0)
|
|
449
|
+
return `\nverified — execution complete, ${outstandingText(owed)}`;
|
|
450
|
+
const why = owed.known ? "" : ` (${owed.unknown[0]?.reason ?? "owed checks unreadable"})`;
|
|
451
|
+
return `\nexecution complete; ${outstandingText(owed)}${why} — not green until outstanding is empty and known`;
|
|
452
|
+
}
|
|
425
453
|
// v1.51 T2: --quality is a pure compatibility alias for `--mode partner-led` (this run only). It
|
|
426
454
|
// carries no one-band floor raise of its own — and since the OBS-89 rip (v1.60) route() no longer
|
|
427
455
|
// reads the retired TICKMARKR_QUALITY env at all, so no downstream code can raise a floor on its
|
|
@@ -526,7 +554,7 @@ export async function run(argv, cwd = process.cwd()) {
|
|
|
526
554
|
supersedes: values.supersedes,
|
|
527
555
|
narrate,
|
|
528
556
|
});
|
|
529
|
-
const out = `run ${s.runId} finished — ${formatSummary(s)} (merge to main is a human decision)`;
|
|
557
|
+
const out = `run ${s.runId} finished — ${formatSummary(s)} (merge to main is a human decision)${finalRail(s, cwd)}`;
|
|
530
558
|
return { out, code: summaryGreen(s) ? 0 : 2 };
|
|
531
559
|
}
|
|
532
560
|
finally {
|
|
@@ -12,6 +12,7 @@ import { authorsNote, doneAuthors } from "../../run/operator-state.js";
|
|
|
12
12
|
import { projectOperatorSummary } from "../../run/operator-summary.js";
|
|
13
13
|
import { trackJournalRows } from "../../run/protocol.js";
|
|
14
14
|
import { newestPark, permittedDecisionVerbs } from "./approve.js";
|
|
15
|
+
import { currentOwed, endToEndFirstPass, firstPassText, leadLines, leadRows, needsYouText, wallText } from "./report.js";
|
|
15
16
|
import { Journal, bindingToken, effectiveEvents, formatJournalNarration, parseJournalText, physicalLine, engagementComparable, isQualityFailureParkKind, parseRunId, preservedRefsByTask, recordedTaskFailureKind, runHasEnded, upheldFeedbackByTask, } from "../../run/journal.js";
|
|
16
17
|
import { isPidLive, runLockRunId, runStatusLine } from "../../run/lock.js";
|
|
17
18
|
import { normalizeGateOutcome } from "../../run/outcome.js";
|
|
@@ -448,6 +449,8 @@ export const shortGoal = (goal, max) => {
|
|
|
448
449
|
};
|
|
449
450
|
/** Columns the machine row always spends on the task title, however long its machinery segment runs. */
|
|
450
451
|
const MACHINE_TITLE_FLOOR = 24;
|
|
452
|
+
/** The plain print's own width when none is configured: its machine rows are laid out, and parsed whole, at it. */
|
|
453
|
+
const MACHINE_COLUMNS = 120;
|
|
451
454
|
// The approved task board derives an area from files[] rather than adding another graph field.
|
|
452
455
|
// Known tickmarkr domains keep their reviewed names; other repositories fall back to their first
|
|
453
456
|
// meaningful path segment so the shipped surface never turns a foreign project into UNMAPPED rows.
|
|
@@ -1037,7 +1040,7 @@ binaryVersion, now = Date.now(), animationFrame = 0, workerLiveness = new Map(),
|
|
|
1037
1040
|
// SUP-01: derived by stat() alone, from THIS frame's clock. status stays a reader — nothing here
|
|
1038
1041
|
// creates, touches or reaps a beat file, so an absent supervision dir stays absent.
|
|
1039
1042
|
const supervision = readSupervision(cwd, now);
|
|
1040
|
-
const width = process.stdout.columns ??
|
|
1043
|
+
const width = process.stdout.columns ?? MACHINE_COLUMNS;
|
|
1041
1044
|
const done = journalRowsOnly
|
|
1042
1045
|
? recordedDone(renderedTasks, taskRows)
|
|
1043
1046
|
: effective.tasks.filter((task) => task.status === "done").length;
|
|
@@ -1055,6 +1058,16 @@ binaryVersion, now = Date.now(), animationFrame = 0, workerLiveness = new Map(),
|
|
|
1055
1058
|
if (!taskIds.has(taskId))
|
|
1056
1059
|
phases.delete(taskId);
|
|
1057
1060
|
const hotPhase = [...phases.values()].sort((a, b) => a.order - b.order).at(-1);
|
|
1061
|
+
// CG1: the three lead lines — finished, wall, needs you from the CURRENT owed-check fold — open
|
|
1062
|
+
// every frame, plain and terminal; the header below keeps every comparability, supersession and verify fact.
|
|
1063
|
+
const lead = runId
|
|
1064
|
+
? leadLines(events, currentOwed(events, cwd), {
|
|
1065
|
+
// The header's own rule: while the tip is unverified the tally never reads as a finished run.
|
|
1066
|
+
tally: comparable ? tipPhase ? `${done}/${total} tasks done` : `${done}/${total} done` : NOT_COMPARABLE_CLAIM,
|
|
1067
|
+
tip: verify ? `tip ${verify}` : "tip verify pending",
|
|
1068
|
+
notes: supersededBy ? [`superseded by ${supersededBy}`] : [],
|
|
1069
|
+
}).map(sanitizeTaskText)
|
|
1070
|
+
: [];
|
|
1058
1071
|
// OBS-738: recovery facts stay on the journal's two established reducers. The prefix fold keeps an
|
|
1059
1072
|
// older journal equally readable by asking upheldFeedbackByTask what was active at that exact
|
|
1060
1073
|
// restore boundary; current resume-restore rows additionally carry the same task for narration.
|
|
@@ -1151,8 +1164,16 @@ binaryVersion, now = Date.now(), animationFrame = 0, workerLiveness = new Map(),
|
|
|
1151
1164
|
? `tickmarkr status${divider}${runLine ?? `run ${runId}`}${zone}${supersededBy ? `${divider}superseded by ${supersededBy}` : ""}${!comparable ? `${divider}${NOT_COMPARABLE_NOTICE}` : ""}${divider}${liveness(events, daemon, now).replaceAll(" · ", divider)}${verify ? `${divider}${verify}` : ""}${divider}${tipPhase ? `${done}/${total} tasks done${divider}run not verified` : `${done}/${total} done`}`
|
|
1152
1165
|
: `tickmarkr status${zone}${divider}no runs yet${divider}${done}/${total} done`;
|
|
1153
1166
|
const legendLine = ` gates: ${GATE_NAMES.map((gate) => `${GATE_KEYS[gate]} ${gate}`).join(divider)}`;
|
|
1167
|
+
// At its machine width and wider the plain print keeps each machine row whole — one row per task and
|
|
1168
|
+
// its evidence line after it, which consumers parse. A configured width narrower than that holds for
|
|
1169
|
+
// EVERY row: one that overflows wraps beneath its own indent, its identity kept on the first.
|
|
1170
|
+
const body = [header, legendLine, ` ${supervisionText(supervision, divider)}`, ...rows];
|
|
1171
|
+
const wrapRow = (line) => wrapCells(line, width, { continuationPrefix: `${/^ */u.exec(line)[0]} ` });
|
|
1154
1172
|
return {
|
|
1155
|
-
content: [
|
|
1173
|
+
content: [
|
|
1174
|
+
...leadRows(lead, width),
|
|
1175
|
+
...(width >= MACHINE_COLUMNS ? body : body.flatMap(wrapRow)),
|
|
1176
|
+
].join("\n"),
|
|
1156
1177
|
...(hotPhase ? { hotPhase } : {}),
|
|
1157
1178
|
...(runId ? { runId } : {}),
|
|
1158
1179
|
workerPhases: [...phases.values()].filter((phase) => phase.phase === "worker"),
|
|
@@ -1349,6 +1370,7 @@ binaryVersion, now = Date.now(), animationFrame = 0, workerLiveness = new Map(),
|
|
|
1349
1370
|
const effort = effortPanel(g.tasks, record && !comparable ? undefined : events, boardColumns);
|
|
1350
1371
|
return {
|
|
1351
1372
|
content: boardRows([
|
|
1373
|
+
...leadRows(lead, boardColumns),
|
|
1352
1374
|
...headerRows,
|
|
1353
1375
|
...nowLine,
|
|
1354
1376
|
supervisionLegend,
|
|
@@ -1404,7 +1426,12 @@ const oneLine = (cwd, namedRunId) => {
|
|
|
1404
1426
|
`verify ${recordedVerify(record.events)}`,
|
|
1405
1427
|
]
|
|
1406
1428
|
: [NOT_COMPARABLE_CLAIM, "verify unavailable"];
|
|
1407
|
-
|
|
1429
|
+
// CG1: the lead's facts in one line — lineage first pass, the wall window, and what needs you from
|
|
1430
|
+
// the CURRENT fold, so a discharge after run-end moves this line too.
|
|
1431
|
+
const { events } = record;
|
|
1432
|
+
return sanitizeTaskText([
|
|
1433
|
+
record.runId, ...claims, firstPassText(endToEndFirstPass(events)), wallText(events), needsYouText(events, currentOwed(events, cwd)),
|
|
1434
|
+
].join(" · "));
|
|
1408
1435
|
};
|
|
1409
1436
|
export async function status(argv, cwd = process.cwd(), opts = {}) {
|
|
1410
1437
|
if (argv.some((arg) => arg === "--help" || arg === "-h"))
|