tickmarkr 2.6.3 → 2.6.5
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/catalog-remote.d.ts +23 -0
- package/dist/adapters/catalog-remote.js +38 -0
- package/dist/adapters/claude-code.d.ts +1 -1
- package/dist/adapters/claude-code.js +4 -1
- package/dist/adapters/codex-commit-check.d.ts +25 -0
- package/dist/adapters/codex-commit-check.js +108 -0
- package/dist/adapters/codex.d.ts +2 -1
- package/dist/adapters/codex.js +26 -5
- package/dist/adapters/model-discovery-check.d.ts +33 -0
- package/dist/adapters/model-discovery-check.js +123 -0
- package/dist/adapters/model-lints.d.ts +2 -1
- package/dist/adapters/model-lints.js +52 -1
- package/dist/adapters/model-windows.d.ts +2 -1
- package/dist/adapters/model-windows.js +18 -7
- package/dist/adapters/prompt.js +3 -2
- package/dist/adapters/types.d.ts +13 -0
- package/dist/cli/commands/approve.js +37 -6
- package/dist/cli/commands/doctor.d.ts +4 -0
- package/dist/cli/commands/doctor.js +20 -3
- package/dist/cli/commands/fleet.d.ts +10 -1
- package/dist/cli/commands/fleet.js +279 -95
- package/dist/cli/commands/init.js +10 -4
- package/dist/cli/commands/plan.js +65 -4
- 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.d.ts +1 -1
- package/dist/cli/commands/verify.js +356 -123
- package/dist/compile/native.js +130 -0
- package/dist/config/config.d.ts +25 -7
- package/dist/config/config.js +92 -11
- package/dist/config/fleet-overlay.d.ts +2 -1
- package/dist/config/fleet-overlay.js +77 -4
- package/dist/drivers/orca.d.ts +19 -1
- package/dist/drivers/orca.js +161 -31
- package/dist/gates/cache.d.ts +4 -0
- package/dist/gates/cache.js +10 -2
- package/dist/gates/llm.d.ts +4 -1
- package/dist/gates/llm.js +6 -1
- package/dist/gates/review.js +8 -3
- package/dist/gates/run-gates.d.ts +3 -2
- package/dist/gates/run-gates.js +91 -50
- package/dist/route/router.d.ts +11 -0
- package/dist/route/router.js +29 -8
- package/dist/run/daemon.js +158 -22
- package/dist/run/git.js +9 -0
- package/dist/run/journal.d.ts +101 -1
- package/dist/run/journal.js +352 -2
- package/dist/run/launchservices-check.d.ts +56 -0
- package/dist/run/launchservices-check.js +117 -0
- package/dist/run/operator-state.d.ts +22 -2
- package/dist/run/operator-state.js +20 -3
- package/dist/run/title-environment.d.ts +45 -0
- package/dist/run/title-environment.js +127 -0
- package/dist/run/title-preload.cjs +38 -0
- package/dist/run/title-preload.d.cts +1 -0
- package/dist/tui/cockpit/board.js +12 -6
- package/dist/tui/cockpit/keys.d.ts +8 -0
- package/dist/tui/cockpit/keys.js +4 -1
- package/dist/tui/cockpit/layout.d.ts +2 -2
- package/dist/tui/cockpit/layout.js +2 -2
- package/dist/tui/cockpit/live-runtime.js +46 -20
- package/dist/tui/cockpit/live-store.d.ts +30 -1
- package/dist/tui/cockpit/live-store.js +134 -18
- package/dist/tui/cockpit/log-view.d.ts +28 -0
- package/dist/tui/cockpit/log-view.js +60 -0
- package/dist/tui/cockpit/run-view.js +4 -0
- package/dist/tui/cockpit/shell.d.ts +2 -1
- package/dist/tui/cockpit/shell.js +6 -4
- package/dist/tui/ink/fleet-app.d.ts +12 -6
- package/dist/tui/ink/fleet-app.js +17 -11
- package/package.json +1 -1
- package/skills/tickmarkr-auto/SKILL.md +33 -3
- package/skills/tickmarkr-loop/SKILL.md +55 -5
- package/skills/tickmarkr-overseer/SKILL.md +83 -8
|
@@ -111,7 +111,7 @@ Before \`tickmarkr compile\` or \`tickmarkr run\`: run \`tickmarkr version\`, re
|
|
|
111
111
|
|
|
112
112
|
### Tip-verify-before-green
|
|
113
113
|
|
|
114
|
-
A run is green only when ALL of these hold: the run-end event exists in the journal, tip verify is not "failed",
|
|
114
|
+
A run is green only when ALL of these hold: the run-end event exists in the journal, tip verify is not "failed", the run-end summary's \`failed\`, \`human\`, \`blocked\` and \`pending\` buckets are every one of them empty, AND the owed checks in CURRENT \`tickmarkr status <runId>\` are outstanding empty AND known (\`outstanding 0\`; \`outstanding unknown\` is never empty). The shorter "run-end plus tip verify" form is NOT sufficient: a run ending \`done=[T1,T3,T4] human=[T2] tipVerify=passed\` is a closed PARTIAL run — three of four delivered, one parked — and calling it green is how a park becomes invisible. Empty execution buckets are not sufficient either (D-660): a run ending with every execution bucket empty and \`tipVerify=passed\` after an operator waived one review still owes that accepted-risk review check — status reads \`outstanding 1 (T7 review)\`, and \`tickmarkr run\` and \`resume\` exit 0 on execution alone — so it is execution complete, not green. Read the debt from CURRENT status, never from the run-end record: a \`tickmarkr verify --record <runId>\` discharge that lands after run-end moves current status to \`outstanding 0\` while the historical run-end row still carries 1, and only then is that run green; a legacy waiver without a recorded obligation reads \`outstanding unknown\` and is never green. Never report green to the operator, tab titles, or records until every clause holds; when one does not, name the bucket that is not empty, the outstanding checks, or the unknown debt.
|
|
115
115
|
|
|
116
116
|
### Verified handoffs
|
|
117
117
|
|
|
@@ -132,7 +132,11 @@ you were handed the whole picture — list the repository root, open every guida
|
|
|
132
132
|
- **The gates are the product.** Seven, defined in \`src/graph/schema.ts\`:
|
|
133
133
|
\`build test lint evidence scope acceptance review\`. **That is DECLARATION order, not execution order** —
|
|
134
134
|
the first five run as a battery that stops at its first red, then \`acceptance\` and \`review\` run
|
|
135
|
-
CONCURRENTLY (\`run-gates.ts:39\`, *"judge ‖ review"*).
|
|
135
|
+
CONCURRENTLY (\`run-gates.ts:39\`, *"judge ‖ review"*). One repair reorders them: a repair the REVIEW sent
|
|
136
|
+
back (carried \`review:material\`, test selection on, no required repair test) runs build, lint, evidence and
|
|
137
|
+
scope, then \`acceptance\` ‖ \`review\`, then its test screen, so a review that re-reds an unchanged subject
|
|
138
|
+
starts no test at all; a test-red repair keeps the battery first. Either way nothing merges until the FULL
|
|
139
|
+
suite has passed on the merge-candidate commit. The first five are MANDATORY; only \`acceptance\` and
|
|
136
140
|
\`review\` may be omitted per task. Implementations are in \`src/gates/\` — \`baseline.ts\` (build/test/lint,
|
|
137
141
|
diffed against a recorded baseline so pre-existing failures are forgiven), \`evidence.ts\`, \`scope.ts\`,
|
|
138
142
|
\`acceptance.ts\` (its judge reads the DIFF and every criterion must cite a changed hunk), \`review.ts\`
|
|
@@ -634,7 +638,8 @@ export async function init(argv, cwd = process.cwd(), io = {}) {
|
|
|
634
638
|
const output = io.output ?? process.stdout;
|
|
635
639
|
const interactive = input.isTTY === true && output.isTTY === true && !(values.yes ?? false);
|
|
636
640
|
// Act 1 — preferences. Only when the repo config does not exist yet: init never rewrites an
|
|
637
|
-
// operator's config; re-tuning is `tickmarkr fleet` (act 3 below still runs
|
|
641
|
+
// operator's config; re-tuning is `tickmarkr fleet` (act 3 below still runs). Act 1 writes only
|
|
642
|
+
// project execution preferences; model choices are act 3's, saved to the USER overlay (B2).
|
|
638
643
|
if (!repoConfigExists) {
|
|
639
644
|
if (interactive) {
|
|
640
645
|
emitBanner();
|
|
@@ -671,7 +676,8 @@ export async function init(argv, cwd = process.cwd(), io = {}) {
|
|
|
671
676
|
: await doctor([], cwd, undefined, { banner: false, compact: interactive });
|
|
672
677
|
// Act 3 — fleet. The compact discovery surface prints BETWEEN the acts so the operator reads
|
|
673
678
|
// what the fleet browser (and its presets overlay, raised on the first Shapes entry) ranks
|
|
674
|
-
// with; the final summary then carries a pointer, not a repeat.
|
|
679
|
+
// with; the final summary then carries a pointer, not a repeat. Its confirmation is the shared
|
|
680
|
+
// Fleet actuator's: the reviewed choices land in the user overlay (gdir), never the repo's.
|
|
675
681
|
if (interactive) {
|
|
676
682
|
output.write(`${doc}\n`);
|
|
677
683
|
doc = "discovery shown above — full matrix any time with `tickmarkr doctor`";
|
|
@@ -2,6 +2,7 @@ import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { parseArgs } from "node:util";
|
|
4
4
|
import { allAdapters, doctorAgeMs, modelAuthExclusions, probeAll, readDoctor, rolePools, servableExclusions, servabilityLine } from "../../adapters/registry.js";
|
|
5
|
+
import { aliasIdentityHeadlines } from "../../adapters/model-discovery-check.js";
|
|
5
6
|
import { formatModelAuthLine, contextWindowLints, modelLints, preferEntryLints, ttyVisual } from "../../adapters/model-lints.js";
|
|
6
7
|
import { GLYPHS, dim, rule, title, warn } from "../../brand.js";
|
|
7
8
|
import { collateralLints, sourceScopeLints } from "../../compile/collateral.js";
|
|
@@ -15,9 +16,10 @@ import { disallowedBy, excludedChannels, exclusionLine, observedSeat, routingEnt
|
|
|
15
16
|
import { decayWeight, HALF_LIFE_RUNS, staffLedEvidence } from "../../route/profile.js";
|
|
16
17
|
import { resolvedFloor, route, RoutingError } from "../../route/router.js";
|
|
17
18
|
import { auditNamedTestOracles, listVitestTests } from "../../gates/acceptance.js";
|
|
18
|
-
import { modelId, modelProvider, pickReviewer } from "../../gates/review.js";
|
|
19
|
+
import { carriedAuthorVendors, gateReviewerFloor, modelId, modelProvider, pickReviewer } from "../../gates/review.js";
|
|
19
20
|
import { Journal, loadRoutingProfile, readProfileCursor, recordedGraphDefinitionHash, RUNS_WINDOW } from "../../run/journal.js";
|
|
20
21
|
import { harnessLine, resolveHarness } from "../harness.js";
|
|
22
|
+
import { codexCommitHeadline } from "../../adapters/codex-commit-check.js";
|
|
21
23
|
import { channelKey, shq } from "../../adapters/types.js";
|
|
22
24
|
import { shGit } from "../../run/git.js";
|
|
23
25
|
import { runLockRunId, runStatusLine } from "../../run/lock.js";
|
|
@@ -135,6 +137,13 @@ async function taskInputFindings(tasks, cwd) {
|
|
|
135
137
|
}
|
|
136
138
|
return findings;
|
|
137
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* v2.6.5 T9 (D-862): a task the run never dispatched gates under the fresh route's author, so plan can
|
|
142
|
+
* mirror its review eligibility exactly. Once dispatched, resume state only the daemon decides (a restored
|
|
143
|
+
* or failed-over author, a tier climb, carried commits, a parked recheck) picks the author — fail closed.
|
|
144
|
+
* ponytail: unknown, not exact; call production's own resume functions once daemon.ts can export them (POOL).
|
|
145
|
+
*/
|
|
146
|
+
const resumeDecidesAuthor = (events, taskId) => events.some((row) => row.taskId === taskId && row.event === "task-dispatch");
|
|
138
147
|
// v1.89 T4: harnessFrom is the resolver's INPUT — a caller (the byte-pinned goldens) fixes the location
|
|
139
148
|
// and keeps this machine's absolute paths out of a fixture. The default is the INVOKED entrypoint,
|
|
140
149
|
// `process.argv[1]`: the bin symlink a global install puts on PATH, which resolves to dist/cli/index.js.
|
|
@@ -183,6 +192,26 @@ export async function plan(argv, cwd = process.cwd(), adapters = allAdapters(),
|
|
|
183
192
|
.filter((event) => event.event === "gate-result" && event.data.gate === "review" && typeof event.data.reviewer === "string")
|
|
184
193
|
.map((event) => event.data.reviewer)
|
|
185
194
|
: [];
|
|
195
|
+
// daemon.ts replayedReviewerExclusions: a journaled review-pool-demotion is excluded from every review
|
|
196
|
+
// pick of the resumed run this plan previews (reviewer role only).
|
|
197
|
+
const replayedReviewerExclusions = [...new Set((matchingEvents ?? [])
|
|
198
|
+
.filter((event) => event.event === "review-pool-demotion" && typeof event.data.reviewer === "string")
|
|
199
|
+
.map((event) => event.data.reviewer))];
|
|
200
|
+
const demotedReviewers = new Set(replayedReviewerExclusions);
|
|
201
|
+
// daemon.ts demotedChannels: the resumed run's worker exclusions (journal replayExcludedChannels).
|
|
202
|
+
const runJournal = matchingRunId ? Journal.open(cwd, matchingRunId) : undefined;
|
|
203
|
+
const demotedWorkers = runJournal?.replayExcludedChannels() ?? new Set();
|
|
204
|
+
// run-gates.ts retired (OBS-1025 add.2): seats with two no-verdicts this run are out of every pick, tallied
|
|
205
|
+
// as daemon.ts seeds reviewNoVerdicts — noVerdict rows only, and a checkout-proof timeout never strikes.
|
|
206
|
+
const noVerdicts = new Map();
|
|
207
|
+
for (const event of matchingEvents ?? []) {
|
|
208
|
+
if (event.event !== "review-no-verdict" || event.data.noVerdict !== true || typeof event.data.reviewer !== "string")
|
|
209
|
+
continue;
|
|
210
|
+
if (event.data.cause === "seat-launch-failed" && event.data.launchCause === "checkout-proof-timeout")
|
|
211
|
+
continue;
|
|
212
|
+
noVerdicts.set(event.data.reviewer, (noVerdicts.get(event.data.reviewer) ?? 0) + 1);
|
|
213
|
+
}
|
|
214
|
+
const retiredReviewers = [...noVerdicts].filter(([, n]) => n >= 2).map(([seat]) => seat);
|
|
186
215
|
// OBS-947 repair: a resumed run re-dispatches only tasks whose replayed status is pending (or that the
|
|
187
216
|
// journal never mentions). done, failed and human are all skipped alike, so rendering their preview
|
|
188
217
|
// picks must not advance the shared reviewHistory — a phantom draw wraps the LRU and makes a later
|
|
@@ -243,6 +272,10 @@ export async function plan(argv, cwd = process.cwd(), adapters = allAdapters(),
|
|
|
243
272
|
if (judgeDeny?.by === "deny") {
|
|
244
273
|
lines.push(`REFUSAL OBS-576: judge seat ${cfg.judge.adapter}:${cfg.judge.model} is removed by flat routing.deny (${judgeDeny.entry}) — move worker-only policy to routing.deny.workers`, "");
|
|
245
274
|
}
|
|
275
|
+
// v2.6.5 T9 (H): alias drift is read from the identity doctor persisted, never re-probed here.
|
|
276
|
+
const aliasHeadlines = aliasIdentityHeadlines(cfg, health);
|
|
277
|
+
if (aliasHeadlines.length)
|
|
278
|
+
lines.push(...aliasHeadlines, "");
|
|
246
279
|
const entrySeats = routingEntrySeatLines(cfg);
|
|
247
280
|
if (entrySeats.length)
|
|
248
281
|
lines.push(...entrySeats, "");
|
|
@@ -359,9 +392,36 @@ export async function plan(argv, cwd = process.cwd(), adapters = allAdapters(),
|
|
|
359
392
|
return { line: "none — reviewPolicy judge-only: declared leaf paths", cost: 0 };
|
|
360
393
|
let rotationSeat = 1;
|
|
361
394
|
let rotationCount = 0;
|
|
362
|
-
|
|
395
|
+
// Dispatched before: no count, no single-seat claim, no previewed reviewer (and so no rotation draw this
|
|
396
|
+
// plan could name). Never a guess.
|
|
397
|
+
if (resumeDecidesAuthor(matchingEvents ?? [], task.id)) {
|
|
398
|
+
lints.push(`${task.id}: eligible cross-vendor review seat count unknown — resume state decides the author`);
|
|
399
|
+
return { line: "unknown — resume state decides the author", cost: 0 };
|
|
400
|
+
}
|
|
401
|
+
// Never dispatched, daemon.ts subjectAuthors is just the fresh author; its vendor stays off the review.
|
|
402
|
+
const authors = [channelKey(author)];
|
|
403
|
+
const authorVendors = carriedAuthorVendors(pools.review, authors);
|
|
404
|
+
// Production's inputs as the task's first review round sees them: excludeReviewers = daemon badReviewers =
|
|
405
|
+
// replayedReviewerExclusions. run-gates.ts:1303 floors on THIS task's journaled reviewers at their dispatched
|
|
406
|
+
// tiers (daemon taskReviewers) ∪ excludeReviewers, so a replayed exclusion RAISES the floor; :1305 picks
|
|
407
|
+
// with excludeReviewers ∪ retired. prefer only reorders (the seat count below passes none).
|
|
408
|
+
const priorReviewers = [...(matchingEvents ?? []).filter((e) => e.taskId === task.id && typeof e.data.reviewer === "string"
|
|
409
|
+
&& ((e.event === "gate-result" && e.data.gate === "review") || e.event === "review-no-verdict"))
|
|
410
|
+
.map((e) => ({ reviewer: e.data.reviewer, tier: e.data.reviewerTier })), ...replayedReviewerExclusions];
|
|
411
|
+
const reviewFloor = gateReviewerFloor(task, cfg, author, pools.review, priorReviewers).floor;
|
|
412
|
+
const exclusions = [...replayedReviewerExclusions, ...retiredReviewers];
|
|
413
|
+
const reviewer = pickReviewer(author, pools.review, exclusions, cfg.review.prefer ?? [], reviewFloor, reviewHistory, (seat, count) => { rotationSeat = seat; rotationCount = count; }, demotedReviewers, authorVendors, authors);
|
|
363
414
|
if (!reviewer)
|
|
364
415
|
return { line: "none — no cross-vendor reviewer available", cost: 0 };
|
|
416
|
+
// v2.6.5 T9 (H): one flaked or demoted seat away from no reviewer. Enumerate the production filter
|
|
417
|
+
// (pickReviewer at the gate's floor) and count served identities, so two aliases of one model count once.
|
|
418
|
+
const seats = [];
|
|
419
|
+
for (let s; (s = pickReviewer(author, pools.review, [...exclusions, ...seats.map(channelKey)], [], reviewFloor, [], undefined, demotedReviewers, authorVendors, authors));)
|
|
420
|
+
seats.push(s);
|
|
421
|
+
const identities = new Set(seats.map((c) => c.identity ?? modelId(c.model)));
|
|
422
|
+
if (identities.size === 1) {
|
|
423
|
+
lints.push(`${task.id}: exactly one eligible cross-vendor review seat remains — identity ${[...identities][0]} via ${seats.map(channelKey).join(", ")}; one flake or demotion leaves no reviewer`);
|
|
424
|
+
}
|
|
365
425
|
// Advance the simulated rotation only for tasks the run would actually dispatch next — the same
|
|
366
426
|
// rule as readyTasks (src/graph/graph.ts): pending, with every dependency already done. A pending
|
|
367
427
|
// task behind a parked/failed/pending dependency cannot run, so it consumes no draw (RULING-229-14).
|
|
@@ -402,7 +462,8 @@ export async function plan(argv, cwd = process.cwd(), adapters = allAdapters(),
|
|
|
402
462
|
continue;
|
|
403
463
|
}
|
|
404
464
|
try {
|
|
405
|
-
|
|
465
|
+
// daemon.ts:3322: the resumed run routes around its replayed worker exclusions.
|
|
466
|
+
const r = route(t, cfg, channels, dispatchProfile, undefined, demotedWorkers);
|
|
406
467
|
routed.push({ taskId: t.id, adapter: r.assignment.adapter, model: r.assignment.model });
|
|
407
468
|
for (const l of r.lints) {
|
|
408
469
|
const suf = exclusionReason(l);
|
|
@@ -412,7 +473,7 @@ export async function plan(argv, cwd = process.cwd(), adapters = allAdapters(),
|
|
|
412
473
|
const judge = judgeSeat(t);
|
|
413
474
|
const review = reviewSeat(t, r.assignment);
|
|
414
475
|
cost += est + judge.cost + review.cost;
|
|
415
|
-
lines.push(` ${t.id.padEnd(6)} ${t.shape.padEnd(10)} c${String(t.complexity).padEnd(3)}→ ${r.assignment.adapter}:${r.assignment.model} [${r.assignment.channel}/${r.assignment.tier}]${t.timeoutMinutes !== undefined ? ` (timeout ${t.timeoutMinutes}m)` : ""}${t.humanGate ? " (human gate)" : ""}${est ? ` ~$${est.toFixed(2)}` : ""} — ${r.provenance}`, ` judge: ${judge.line}${judge.cost ? ` ~$${judge.cost.toFixed(2)}` : ""}`, ` review: ${review.line}${review.cost ? ` ~$${review.cost.toFixed(2)}` : ""}`, chainLine(t));
|
|
476
|
+
lines.push(` ${t.id.padEnd(6)} ${t.shape.padEnd(10)} c${String(t.complexity).padEnd(3)}→ ${r.assignment.adapter}:${r.assignment.model} [${r.assignment.channel}/${r.assignment.tier}]${t.timeoutMinutes !== undefined ? ` (timeout ${t.timeoutMinutes}m)` : ""}${t.humanGate ? " (human gate)" : ""}${est ? ` ~$${est.toFixed(2)}` : ""} — ${r.provenance}`, ...[codexCommitHeadline(r.assignment.adapter, adapters, health)].filter((l) => l !== null), ` judge: ${judge.line}${judge.cost ? ` ~$${judge.cost.toFixed(2)}` : ""}`, ` review: ${review.line}${review.cost ? ` ~$${review.cost.toFixed(2)}` : ""}`, chainLine(t));
|
|
416
477
|
const d = derivation(t);
|
|
417
478
|
if (d)
|
|
418
479
|
lines.push(d);
|
|
@@ -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;
|