tickmarkr 1.85.0 → 1.86.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +4 -2
  2. package/dist/adapters/catalog-remote.d.ts +64 -0
  3. package/dist/adapters/catalog-remote.js +287 -0
  4. package/dist/adapters/catalog.d.ts +96 -0
  5. package/dist/adapters/catalog.js +176 -0
  6. package/dist/adapters/claude-code.js +5 -3
  7. package/dist/adapters/fake.js +33 -4
  8. package/dist/adapters/model-lints.d.ts +25 -5
  9. package/dist/adapters/model-lints.js +184 -50
  10. package/dist/adapters/model-windows.d.ts +31 -0
  11. package/dist/adapters/model-windows.js +69 -0
  12. package/dist/adapters/prompt.d.ts +5 -1
  13. package/dist/adapters/prompt.js +13 -4
  14. package/dist/adapters/registry.d.ts +25 -26
  15. package/dist/adapters/registry.js +173 -110
  16. package/dist/adapters/types.js +15 -3
  17. package/dist/brand.d.ts +5 -1
  18. package/dist/brand.js +18 -2
  19. package/dist/cli/commands/doctor.d.ts +3 -0
  20. package/dist/cli/commands/doctor.js +43 -21
  21. package/dist/cli/commands/fleet.d.ts +7 -0
  22. package/dist/cli/commands/fleet.js +94 -74
  23. package/dist/cli/commands/init.js +118 -5
  24. package/dist/cli/commands/status.js +44 -18
  25. package/dist/compile/gsd.d.ts +2 -1
  26. package/dist/compile/gsd.js +68 -2
  27. package/dist/compile/native.d.ts +14 -0
  28. package/dist/compile/native.js +161 -12
  29. package/dist/config/config.d.ts +20 -5
  30. package/dist/config/config.js +96 -64
  31. package/dist/config/fleet-overlay.d.ts +25 -20
  32. package/dist/config/fleet-overlay.js +195 -77
  33. package/dist/config/fleet-why.d.ts +23 -0
  34. package/dist/config/fleet-why.js +42 -0
  35. package/dist/drivers/herdr.d.ts +1 -0
  36. package/dist/drivers/herdr.js +70 -19
  37. package/dist/gates/acceptance.js +7 -2
  38. package/dist/gates/llm.d.ts +0 -1
  39. package/dist/gates/llm.js +5 -30
  40. package/dist/gates/review.d.ts +2 -1
  41. package/dist/gates/review.js +9 -7
  42. package/dist/gates/verdict-cause.d.ts +4 -0
  43. package/dist/gates/verdict-cause.js +63 -0
  44. package/dist/graph/schema.d.ts +6 -0
  45. package/dist/graph/schema.js +8 -5
  46. package/dist/route/router.d.ts +0 -5
  47. package/dist/route/router.js +16 -20
  48. package/dist/run/consult.d.ts +6 -0
  49. package/dist/run/consult.js +35 -25
  50. package/dist/run/daemon.js +71 -15
  51. package/dist/run/journal.js +87 -7
  52. package/dist/tui/cockpit/capture.d.ts +12 -0
  53. package/dist/tui/cockpit/capture.js +37 -1
  54. package/dist/tui/cockpit/components.js +8 -8
  55. package/dist/tui/cockpit/theme.d.ts +32 -26
  56. package/dist/tui/cockpit/theme.js +11 -5
  57. package/dist/tui/ink/components.d.ts +0 -15
  58. package/dist/tui/ink/components.js +0 -17
  59. package/dist/tui/ink/fleet-app.d.ts +4 -1
  60. package/dist/tui/ink/fleet-app.js +134 -13
  61. package/fixtures/sample.native.md +1 -1
  62. package/package.json +1 -1
  63. package/skills/tickmarkr-overseer/SKILL.md +354 -34
  64. package/skills/tickmarkr-overseer/scripts/watch-artifacts.sh +70 -0
  65. package/skills/tickmarkr-overseer/scripts/watch-panes.sh +1 -1
  66. package/dist/tui/ink/studio-app.d.ts +0 -59
  67. package/dist/tui/ink/studio-app.js +0 -320
  68. package/dist/tui/save.d.ts +0 -38
  69. package/dist/tui/save.js +0 -96
  70. package/dist/tui/staging.d.ts +0 -29
  71. package/dist/tui/staging.js +0 -78
@@ -195,7 +195,7 @@ export function structuredFindings(gate, details, _scopeFiles = []) {
195
195
  const VOLATILE_TOKENS = [
196
196
  [/\u001b\[[0-9;]*[a-zA-Z]/g, ""], // ANSI styling
197
197
  [/\b\d{4}-\d{2}-\d{2}[T ][\d:]+(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?\b/g, "<ts>"], // timestamps
198
- [/\brun-\d{8}-\d{6}\b/g, "<run>"], // run identifiers
198
+ [/\brun-\d{8}-\d{6}(?:-\d{16})?\b/g, "<run>"], // run identifiers
199
199
  [/\b0x[0-9a-fA-F]+\b/g, "<addr>"], // memory addresses
200
200
  // An absolute path INTO the repo keeps its repo-relative tail — that tail IS identity (a defect in
201
201
  // daemon.ts is not a defect in journal.ts); only the machine/worktree prefix ahead of it is volatile.
@@ -227,15 +227,67 @@ const eraseVolatile = (text) => VOLATILE_TOKENS.reduce((out, [re, replacement])
227
227
  // difference and banned a retry that was never redundant. Newlines survive (payload spans cannot
228
228
  // cross one) and the line-wise trim below finishes the job.
229
229
  const collapseRuns = (text) => text.replace(/[^\S\n]+/g, " ");
230
+ // v1.85 T34: the runner's TALLY moves with the base, not with the defect. When another task merges
231
+ // ahead and adds test files, vitest re-counts the suite — `192 passed (197)` becomes `193 passed
232
+ // (198)` — while the FAIL headlines name the same defect in the same order, so a substantively
233
+ // identical gate failure re-fingerprinted and gate-fingerprint-cap never counted the repeat
234
+ // (measured on T21 in run-20260805-164546: details at 17:33:02 and 18:11:01 differ only so).
235
+ //
236
+ // Why provenance AND shape, and why not digits as a class: a blanket \d+ collapse also erases counts
237
+ // that ARE identity — the failed-assertion count, the failing-suite count, an exit status, an error
238
+ // code — and the rule above governs: a missed cap costs one extra round, a false cap bans a
239
+ // legitimate retry. So the mask is gated on PROVENANCE first: only lines inside the `failing tests:`
240
+ // block baseline.ts:209-219 emits (up to its blank-line/`new failure fingerprints` boundary) are
241
+ // eligible, which keeps review prose, quoted diffs and assertion payloads structurally unreachable —
242
+ // and that block's own `FAIL … > test name` headlines are excluded by SHAPE: only a line that starts
243
+ // with the runner's own `Tests`/`Test Files` summary token is a tally line.
244
+ //
245
+ // Masked tally FIELDS, exactly: the passed count, the skipped and todo counts, and the derived
246
+ // parenthesized total immediately following the tally sequence. Deliberately KEPT (a reader uses
247
+ // each of them to tell two failures apart): every `N failed` count on the line (which suites and how
248
+ // many assertions actually broke — the defect's identity), everything after the derived total (an
249
+ // appended `| exit 1`, an appended `(404)` code), and every other number anywhere.
250
+ const RUNNER_TALLY_LINE = /^([ \t]*(?:Test Files|Tests)[ \t]+)((?:\d+ (?:failed|passed|skipped|todo)[ \t]*\|[ \t]*)*\d+ (?:failed|passed|skipped|todo))([ \t]*\(\d+\))?(.*)$/;
251
+ const TALLY_MOVED_FIELD = /\d+ (?=passed|skipped|todo)/g;
252
+ const maskTallyLine = (line) => {
253
+ const m = RUNNER_TALLY_LINE.exec(line);
254
+ if (!m)
255
+ return line;
256
+ const fields = m[2].replace(TALLY_MOVED_FIELD, "#");
257
+ const total = (m[3] ?? "").replace(/\d+/, "#");
258
+ return m[1] + fields + total + m[4];
259
+ };
260
+ // Computed on the FULL details text BEFORE any payload split: every recorded tally-bearing detail
261
+ // carries the `failing tests:` header, so gating on it closes the false-line-start axis (the mask
262
+ // never sees a slice boundary) and the wrong-provenance axis in one place.
263
+ const maskRunnerTallies = (text) => {
264
+ let inBlock = false;
265
+ return text
266
+ .split("\n")
267
+ .map((line) => {
268
+ if (!inBlock) {
269
+ if (line.trim() === "failing tests:")
270
+ inBlock = true;
271
+ return line;
272
+ }
273
+ if (line.trim() === "" || line.startsWith("new failure fingerprints")) {
274
+ inBlock = false;
275
+ return line;
276
+ }
277
+ return maskTallyLine(line);
278
+ })
279
+ .join("\n");
280
+ };
230
281
  /** Normalized identity of a gate failure: the same defect, seen twice, normalizes to the same bytes. */
231
282
  export function normalizeGateFailure(details) {
283
+ const masked = maskRunnerTallies(details);
232
284
  let out = "";
233
285
  let last = 0;
234
- for (const m of details.matchAll(PAYLOAD_SPAN)) {
235
- out += collapseRuns(eraseVolatile(details.slice(last, m.index))) + m[0];
286
+ for (const m of masked.matchAll(PAYLOAD_SPAN)) {
287
+ out += collapseRuns(eraseVolatile(masked.slice(last, m.index))) + m[0];
236
288
  last = m.index + m[0].length;
237
289
  }
238
- out += collapseRuns(eraseVolatile(details.slice(last)));
290
+ out += collapseRuns(eraseVolatile(masked.slice(last)));
239
291
  return out.split("\n").map((l) => l.trim()).filter(Boolean).join("\n");
240
292
  }
241
293
  // Two normalized-identical failures of one gate on one task buy no more rounds (the ladder cannot fix
@@ -538,9 +590,37 @@ export function engagementComparable(events, loadedHash) {
538
590
  return { comparable: false, reason: "unbound" };
539
591
  return recorded === loadedHash ? { comparable: true, recorded } : { comparable: false, reason: "mismatch", recorded };
540
592
  }
593
+ const RUN_SEQUENCE_WIDTH = 16;
594
+ // The daemon mints before acquiring graph.lock, and each CLI invocation has fresh module state. Claim
595
+ // a repository-state ticket with mkdir's atomic EEXIST boundary instead: a losing process advances and
596
+ // retries, while the winning directory remains as the durable high-water evidence for later invocations.
597
+ function claimRunSequence(repoRoot = process.cwd()) {
598
+ const dir = join(tickmarkrDir(repoRoot), "run-id-sequence");
599
+ mkdirSync(dir, { recursive: true });
600
+ const allocated = readdirSync(dir).filter((name) => /^\d{16}$/.test(name));
601
+ let candidate = allocated.reduce((max, name) => {
602
+ const value = BigInt(name);
603
+ return value > max ? value : max;
604
+ }, 0n) + 1n;
605
+ while (true) {
606
+ const suffix = candidate.toString().padStart(RUN_SEQUENCE_WIDTH, "0");
607
+ if (suffix.length > RUN_SEQUENCE_WIDTH)
608
+ throw new Error("run id sequence exhausted");
609
+ try {
610
+ mkdirSync(join(dir, suffix));
611
+ return suffix;
612
+ }
613
+ catch (error) {
614
+ if (error.code !== "EEXIST")
615
+ throw error;
616
+ candidate += 1n;
617
+ }
618
+ }
619
+ }
541
620
  export function newRunId(now = new Date()) {
542
621
  const p = (n, w = 2) => String(n).padStart(w, "0");
543
- return `run-${now.getFullYear()}${p(now.getMonth() + 1)}${p(now.getDate())}-${p(now.getHours())}${p(now.getMinutes())}${p(now.getSeconds())}`;
622
+ const instant = `${now.getUTCFullYear()}${p(now.getUTCMonth() + 1)}${p(now.getUTCDate())}-${p(now.getUTCHours())}${p(now.getUTCMinutes())}${p(now.getUTCSeconds())}`;
623
+ return `run-${instant}-${claimRunSequence()}`;
544
624
  }
545
625
  // Sol #4: one strict parser for every journal open/create path — generated run-… ids plus test
546
626
  // suffix chars only; forbid path separators, dot-segments, and empty ids.
@@ -578,7 +658,7 @@ function readJsonl(path) {
578
658
  return out;
579
659
  }
580
660
  // Cross-run telemetry for Phase-12 profile derivation: the last K runs' rows, each
581
- // tagged with its runId (runIds are zero-padded run-YYYYMMDD-HHMMSS ⇒ plain .sort() is
661
+ // tagged with its runId (runIds are zero-padded run-UTCYYYYMMDD-HHMMSS-sequence ⇒ plain .sort() is
582
662
  // chronological, same as latestRunId). Rows are facts, not classifications — Phase 12
583
663
  // owns the quality-denominator/reward policy. A safeParse failure drops that one row
584
664
  // (same posture as a torn line); a garbage row must never crash profile derivation.
@@ -589,7 +669,7 @@ export function readAllTelemetry(repoRoot, lastK, opts = {}) {
589
669
  if (!existsSync(dir))
590
670
  return [];
591
671
  let runIds = readdirSync(dir).filter((d) => d.startsWith("run-")).sort();
592
- // VIS-03 reset cursor: runIds are zero-padded run-YYYYMMDD-HHMMSS ⇒ string > is chronological
672
+ // VIS-03 reset cursor: UTC clock fields and a fixed-width sequence make string > chronological
593
673
  if (opts.after)
594
674
  runIds = runIds.filter((id) => id > opts.after);
595
675
  runIds = runIds.slice(-lastK);
@@ -209,6 +209,18 @@ export type RegeneratedGoldenFrame = GoldenFrameCase & {
209
209
  readonly emitted: string;
210
210
  };
211
211
  export declare function regenerateGoldenFrames(binaryVersion?: string): Promise<readonly RegeneratedGoldenFrame[]>;
212
+ /**
213
+ * Canonicalize the one release-owned token in an ACTIVE golden frame.
214
+ *
215
+ * The grammar is deliberately positional: plain output owns the whole first
216
+ * line, while a setup frame owns the right-anchored `v<semver> · binary ✓`
217
+ * suffix. The latter preserves the suffix's right edge, so a longer version
218
+ * does not turn renderer padding into drift while any actual alignment change
219
+ * still does. Retired rendered frames never enter this exception.
220
+ */
221
+ export declare function normalizeGoldenFrameVersionForComparison(frame: GoldenFrameCase, bytes: string): string;
222
+ /** An absent committed frame is a mismatch; it is never an empty agreement. */
223
+ export declare function goldenFrameMatchesCommitted(frame: RegeneratedGoldenFrame, committed: string | undefined): boolean;
212
224
  export declare function findGoldenFrameMismatches(regenerated: readonly RegeneratedGoldenFrame[], committed: ReadonlyMap<string, string>): string[];
213
225
  export type RegeneratedColourFrame = ColourFrameCase & {
214
226
  readonly output: string;
@@ -564,13 +564,49 @@ export async function regenerateGoldenFrames(binaryVersion) {
564
564
  }
565
565
  return regenerated;
566
566
  }
567
+ const SEMVER_TOKEN = String.raw `\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?`;
568
+ const PLAIN_VERSION_HEADER = new RegExp(`^(tickmarkr v)(${SEMVER_TOKEN})$`, "u");
569
+ const FRAME_VERSION_HEADER = new RegExp(`^(.*\\S)( +)v(${SEMVER_TOKEN})( · binary ✓)$`, "u");
570
+ const COMPARISON_VERSION = "<binary-version>";
571
+ /**
572
+ * Canonicalize the one release-owned token in an ACTIVE golden frame.
573
+ *
574
+ * The grammar is deliberately positional: plain output owns the whole first
575
+ * line, while a setup frame owns the right-anchored `v<semver> · binary ✓`
576
+ * suffix. The latter preserves the suffix's right edge, so a longer version
577
+ * does not turn renderer padding into drift while any actual alignment change
578
+ * still does. Retired rendered frames never enter this exception.
579
+ */
580
+ export function normalizeGoldenFrameVersionForComparison(frame, bytes) {
581
+ if (isRetiredGoldenFrame(frame))
582
+ return bytes;
583
+ const lineBreak = bytes.indexOf("\n");
584
+ const header = lineBreak === -1 ? bytes : bytes.slice(0, lineBreak);
585
+ const rest = lineBreak === -1 ? "" : bytes.slice(lineBreak);
586
+ let normalized = header;
587
+ if (!frame.interactive || frame.ci) {
588
+ normalized = header.replace(PLAIN_VERSION_HEADER, `$1${COMPARISON_VERSION}`);
589
+ }
590
+ else {
591
+ normalized = header.replace(FRAME_VERSION_HEADER, (_match, prefix, gap, versionToken, suffix) => `${prefix}${" ".repeat(gap.length + versionToken.length)}v${COMPARISON_VERSION}${suffix}`);
592
+ }
593
+ return normalized === header ? bytes : `${normalized}${rest}`;
594
+ }
595
+ /** An absent committed frame is a mismatch; it is never an empty agreement. */
596
+ export function goldenFrameMatchesCommitted(frame, committed) {
597
+ return committed !== undefined
598
+ && normalizeGoldenFrameVersionForComparison(frame, committed)
599
+ === normalizeGoldenFrameVersionForComparison(frame, frame.output);
600
+ }
567
601
  export function findGoldenFrameMismatches(regenerated, committed) {
568
602
  // Membership is judged against the whole shipped manifest, retired cases
569
603
  // included: a retired fixture is claimed, so only its bytes are exempt.
570
604
  // Anything committed that no case claims is still named.
571
605
  const expected = new Set(GOLDEN_FRAME_CASES.map((frame) => frame.fixture));
572
606
  return [
573
- ...regenerated.flatMap((frame) => committed.get(frame.fixture) === frame.output ? [] : [frame.fixture]),
607
+ ...regenerated.flatMap((frame) => goldenFrameMatchesCommitted(frame, committed.get(frame.fixture))
608
+ ? []
609
+ : [frame.fixture]),
574
610
  ...[...committed.keys()].filter((fixture) => !expected.has(fixture)),
575
611
  ].sort();
576
612
  }
@@ -170,24 +170,24 @@ function statePresentation(state) {
170
170
  case "active":
171
171
  return {
172
172
  glyph: GLYPHS.toggleActive,
173
- color: `ansi256(${COCKPIT_DATA_RAMP[1].xterm})`,
173
+ color: COCKPIT_DATA_RAMP[1].hex,
174
174
  };
175
175
  case "inactive":
176
176
  return { glyph: GLYPHS.toggleInactive, dimmed: true };
177
177
  case "pass":
178
178
  return {
179
179
  glyph: GLYPHS.pass,
180
- color: `ansi256(${COCKPIT_DATA_RAMP[1].xterm})`,
180
+ color: COCKPIT_DATA_RAMP[1].hex,
181
181
  };
182
182
  case "fail":
183
183
  return {
184
184
  glyph: GLYPHS.fail,
185
- color: `ansi256(${COCKPIT_INKS.failure.xterm})`,
185
+ color: COCKPIT_INKS.failure.hex,
186
186
  };
187
187
  case "warn":
188
188
  return {
189
189
  glyph: GLYPHS.attention,
190
- color: `ansi256(${COCKPIT_INKS.warning.xterm})`,
190
+ color: COCKPIT_INKS.warning.hex,
191
191
  };
192
192
  case "neutral":
193
193
  return { glyph: GLYPHS.neutral, dimmed: true };
@@ -201,7 +201,7 @@ export function Panel({ title, focused = false, width, flexGrow, children, }) {
201
201
  const content = Children.map(children, (child) => typeof child === "string" || typeof child === "number"
202
202
  ? _jsx(BodyText, { children: child })
203
203
  : child);
204
- return (_jsxs(Box, { flexDirection: "column", flexGrow: flexGrow, width: width, paddingX: 1, borderStyle: focused ? "double" : "round", borderColor: `ansi256(${focused ? COCKPIT_DATA_RAMP[0].xterm : COCKPIT_DATA_RAMP[3].xterm})`, borderDimColor: !focused, children: [_jsx(Box, { children: focused ? (_jsxs(_Fragment, { children: [_jsx(Text, { color: `ansi256(${COCKPIT_DATA_RAMP[0].xterm})`, children: GLYPHS.pointer }), _jsxs(BodyText, { emphasis: "strong", children: [" ", title] })] })) : (_jsx(BodyText, { emphasis: "strong", children: title })) }), _jsx(Box, { flexDirection: "column", children: content })] }));
204
+ return (_jsxs(Box, { flexDirection: "column", flexGrow: flexGrow, width: width, paddingX: 1, borderStyle: focused ? "double" : "round", borderColor: focused ? COCKPIT_DATA_RAMP[0].hex : COCKPIT_DATA_RAMP[3].hex, borderDimColor: !focused, children: [_jsx(Box, { children: focused ? (_jsxs(_Fragment, { children: [_jsx(Text, { color: COCKPIT_DATA_RAMP[0].hex, children: GLYPHS.pointer }), _jsxs(BodyText, { emphasis: "strong", children: [" ", title] })] })) : (_jsx(BodyText, { emphasis: "strong", children: title })) }), _jsx(Box, { flexDirection: "column", children: content })] }));
205
205
  }
206
206
  export function CockpitGrid({ children, columns, columnContents, allocation, }) {
207
207
  const items = Children.toArray(children);
@@ -229,7 +229,7 @@ export function Sparkline({ samples, }) {
229
229
  const level = sparklineLevel(sample, minimum, maximum);
230
230
  const rampIndex = COCKPIT_DATA_RAMP.length - 1 - Math.round((level / (SPARKLINE_GLYPHS.length - 1)) * (COCKPIT_DATA_RAMP.length - 1));
231
231
  const ink = COCKPIT_DATA_RAMP[rampIndex] ?? COCKPIT_DATA_RAMP[0];
232
- return (_jsx(Text, { color: `ansi256(${ink.xterm})`, children: SPARKLINE_GLYPHS[level] }, `${index}:${sample}`));
232
+ return (_jsx(Text, { color: ink.hex, children: SPARKLINE_GLYPHS[level] }, `${index}:${sample}`));
233
233
  }) }));
234
234
  }
235
235
  export function StatTile({ label, value, samples, focused, width, }) {
@@ -242,7 +242,7 @@ export function ProgressMeter({ value, width = 24, }) {
242
242
  const valueLabel = Number.isInteger(percentage)
243
243
  ? percentage.toFixed(0)
244
244
  : percentage.toFixed(1);
245
- return (_jsxs(Box, { flexDirection: "row", flexWrap: "nowrap", height: 1, overflow: "hidden", "aria-role": "progressbar", "aria-label": `${valueLabel}% complete`, children: [_jsx(Text, { color: `ansi256(${COCKPIT_DATA_RAMP[0].xterm})`, children: "█".repeat(filled) }), _jsx(Text, { dimColor: true, children: "░".repeat(safeWidth - filled) }), _jsxs(BodyText, { emphasis: "strong", children: [" ", valueLabel, "%"] })] }));
245
+ return (_jsxs(Box, { flexDirection: "row", flexWrap: "nowrap", height: 1, overflow: "hidden", "aria-role": "progressbar", "aria-label": `${valueLabel}% complete`, children: [_jsx(Text, { color: COCKPIT_DATA_RAMP[0].hex, children: "█".repeat(filled) }), _jsx(Text, { dimColor: true, children: "░".repeat(safeWidth - filled) }), _jsxs(BodyText, { emphasis: "strong", children: [" ", valueLabel, "%"] })] }));
246
246
  }
247
247
  function OneLineItems({ children, width, label, }) {
248
248
  return (_jsx(Box, { flexDirection: "row", flexWrap: "nowrap", width: width, height: 1, overflow: "hidden", "aria-label": label, children: children }));
@@ -285,6 +285,6 @@ export function StatusStrip({ items, width, }) {
285
285
  }
286
286
  export function JournalRowPanel({ rows, width, focused, title = "JOURNAL", selection, hover, }) {
287
287
  return (_jsx(Panel, { title: title, focused: focused, width: width, children: rows.map((row, index) => (_jsxs(Box, { flexDirection: "row", flexWrap: "nowrap", height: 1, overflow: "hidden", children: [_jsx(BodyText, { emphasis: "dim", children: row.time }), _jsx(BodyText, { children: " " }), _jsx(StateGlyph, { state: row.state }), _jsx(BodyText, { emphasis: index === hover ? "strong" : "normal", children: index === hover ? HOVER_MARKER : " " }), index === selection && (_jsxs(BodyText, { emphasis: "strong", children: [GLYPHS.pointer, " "] })), index === hover
288
- ? (_jsx(Text, { inverse: true, color: `ansi256(${COCKPIT_DATA_RAMP[0].xterm})`, children: row.text }))
288
+ ? (_jsx(Text, { inverse: true, color: COCKPIT_DATA_RAMP[0].hex, children: row.text }))
289
289
  : _jsx(BodyText, { children: row.text })] }, row.id))) }));
290
290
  }
@@ -7,19 +7,25 @@ export type ColourValue = {
7
7
  export declare const LOGO_RAMP: readonly [84, 78, 41, 35];
8
8
  /** Near-black panel fill against which every data shade must remain legible. */
9
9
  export declare const COCKPIT_SURFACE: "#1a1a19";
10
- /** Bright-to-deep, single-hue data ink. Depth communicates magnitude, never tile identity. */
10
+ /**
11
+ * Bright-to-deep, single-hue data ink. Depth communicates magnitude, never tile identity.
12
+ * Anchored on xterm 114 (#87d787) — operator-chosen 2026-08-03 — with the hue (OKLab 144.2°)
13
+ * held constant down the ramp; steps descend in even OKLab lightness (0.094) and were searched
14
+ * and verified through guardDataRamp below. hex is the rendered ink (truecolor terminals);
15
+ * xterm is the nearest-distinct 256-colour fallback, documentation not render source.
16
+ */
11
17
  export declare const COCKPIT_DATA_RAMP: readonly [{
12
- readonly xterm: 84;
13
- readonly hex: "#5fff5f";
18
+ readonly xterm: 114;
19
+ readonly hex: "#87d787";
14
20
  }, {
15
- readonly xterm: 41;
16
- readonly hex: "#00d75f";
21
+ readonly xterm: 71;
22
+ readonly hex: "#5bbc5e";
17
23
  }, {
18
- readonly xterm: 35;
19
- readonly hex: "#00af5f";
24
+ readonly xterm: 65;
25
+ readonly hex: "#4f9a51";
20
26
  }, {
21
27
  readonly xterm: 29;
22
- readonly hex: "#00875f";
28
+ readonly hex: "#437944";
23
29
  }];
24
30
  /** Documented near-misses retained as guard fixtures, not special cases in the guard. */
25
31
  export declare const EXCLUDED_DATA_SHADES: {
@@ -38,17 +44,17 @@ export declare const EXCLUDED_DATA_SHADES: {
38
44
  */
39
45
  export declare const COCKPIT_INKS: {
40
46
  readonly dataRamp: readonly [{
41
- readonly xterm: 84;
42
- readonly hex: "#5fff5f";
47
+ readonly xterm: 114;
48
+ readonly hex: "#87d787";
43
49
  }, {
44
- readonly xterm: 41;
45
- readonly hex: "#00d75f";
50
+ readonly xterm: 71;
51
+ readonly hex: "#5bbc5e";
46
52
  }, {
47
- readonly xterm: 35;
48
- readonly hex: "#00af5f";
53
+ readonly xterm: 65;
54
+ readonly hex: "#4f9a51";
49
55
  }, {
50
56
  readonly xterm: 29;
51
- readonly hex: "#00875f";
57
+ readonly hex: "#437944";
52
58
  }];
53
59
  readonly warning: {
54
60
  readonly xterm: 214;
@@ -65,17 +71,17 @@ export declare const COCKPIT_INKS: {
65
71
  };
66
72
  /** Every drawable cockpit ink; the panel surface and text emphasis are not inks. */
67
73
  export declare const COCKPIT_INK_VOCABULARY: readonly [{
68
- readonly xterm: 84;
69
- readonly hex: "#5fff5f";
74
+ readonly xterm: 114;
75
+ readonly hex: "#87d787";
70
76
  }, {
71
- readonly xterm: 41;
72
- readonly hex: "#00d75f";
77
+ readonly xterm: 71;
78
+ readonly hex: "#5bbc5e";
73
79
  }, {
74
- readonly xterm: 35;
75
- readonly hex: "#00af5f";
80
+ readonly xterm: 65;
81
+ readonly hex: "#4f9a51";
76
82
  }, {
77
83
  readonly xterm: 29;
78
- readonly hex: "#00875f";
84
+ readonly hex: "#437944";
79
85
  }, {
80
86
  readonly xterm: 214;
81
87
  readonly hex: "#ffaf00";
@@ -105,16 +111,16 @@ export declare const TEXT_EMPHASIS_TOKENS: {
105
111
  /** Deliberate 16-colour choices. Renderers select this map; they do not infer one. */
106
112
  export declare const REDUCED_COLOUR_MAPPING: {
107
113
  readonly dataRamp: readonly [{
108
- readonly from: "#5fff5f";
114
+ readonly from: "#87d787";
109
115
  readonly to: "bright-green";
110
116
  }, {
111
- readonly from: "#00d75f";
117
+ readonly from: "#5bbc5e";
112
118
  readonly to: "green";
113
119
  }, {
114
- readonly from: "#00af5f";
120
+ readonly from: "#4f9a51";
115
121
  readonly to: "green";
116
122
  }, {
117
- readonly from: "#00875f";
123
+ readonly from: "#437944";
118
124
  readonly to: "dim-green";
119
125
  }];
120
126
  readonly warning: {
@@ -3,12 +3,18 @@ import { BRAND_RAMP, GLYPHS } from "../../brand.js";
3
3
  export const LOGO_RAMP = BRAND_RAMP;
4
4
  /** Near-black panel fill against which every data shade must remain legible. */
5
5
  export const COCKPIT_SURFACE = "#1a1a19";
6
- /** Bright-to-deep, single-hue data ink. Depth communicates magnitude, never tile identity. */
6
+ /**
7
+ * Bright-to-deep, single-hue data ink. Depth communicates magnitude, never tile identity.
8
+ * Anchored on xterm 114 (#87d787) — operator-chosen 2026-08-03 — with the hue (OKLab 144.2°)
9
+ * held constant down the ramp; steps descend in even OKLab lightness (0.094) and were searched
10
+ * and verified through guardDataRamp below. hex is the rendered ink (truecolor terminals);
11
+ * xterm is the nearest-distinct 256-colour fallback, documentation not render source.
12
+ */
7
13
  export const COCKPIT_DATA_RAMP = [
8
- { xterm: 84, hex: "#5fff5f" },
9
- { xterm: 41, hex: "#00d75f" },
10
- { xterm: 35, hex: "#00af5f" },
11
- { xterm: 29, hex: "#00875f" },
14
+ { xterm: 114, hex: "#87d787" },
15
+ { xterm: 71, hex: "#5bbc5e" },
16
+ { xterm: 65, hex: "#4f9a51" },
17
+ { xterm: 29, hex: "#437944" },
12
18
  ];
13
19
  /** Documented near-misses retained as guard fixtures, not special cases in the guard. */
14
20
  export const EXCLUDED_DATA_SHADES = {
@@ -1,5 +1,4 @@
1
1
  import type { ReactNode } from "react";
2
- import { type SaveTarget } from "../save.js";
3
2
  export type FleetListRow = {
4
3
  id: string;
5
4
  content: ReactNode;
@@ -22,17 +21,3 @@ export declare function FleetReviewScreen({ title, legend, diff, }: {
22
21
  legend: string;
23
22
  diff: string;
24
23
  }): import("react").JSX.Element;
25
- export type DiffConfirmationProps = {
26
- target: SaveTarget;
27
- targetPath: string;
28
- diff: string;
29
- liveRun: boolean;
30
- };
31
- export declare function DiffConfirmationScreen(props: DiffConfirmationProps): import("react").JSX.Element;
32
- export declare function StudioScreen({ tabs, active, lines, status, children, }: {
33
- tabs: string[];
34
- active: number;
35
- lines: string[];
36
- status?: string;
37
- children?: ReactNode;
38
- }): import("react").JSX.Element;
@@ -1,7 +1,6 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from "ink";
3
3
  import { GLYPHS } from "../../brand.js";
4
- import { RELOAD_GUARD_NOTICE } from "../save.js";
5
4
  function keyedLines(lines) {
6
5
  const occurrences = new Map();
7
6
  return lines.map((text) => {
@@ -24,19 +23,3 @@ export function FleetListScreen({ title, legend, rows, cursor, details = [], })
24
23
  export function FleetReviewScreen({ title, legend, diff, }) {
25
24
  return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: title }), _jsx(Text, { dimColor: true, children: legend }), _jsx(Text, { children: diff })] }));
26
25
  }
27
- export function DiffConfirmationScreen(props) {
28
- return _jsx(TextLines, { lines: [
29
- `Save overlay — ${props.targetPath} (${props.target === "repo" ? "repository" : "global"} target)`,
30
- "t toggle target · y confirm · n/esc cancel",
31
- ...(props.liveRun ? [RELOAD_GUARD_NOTICE] : []),
32
- "─ diff ─",
33
- props.diff || "(no overlay changes)",
34
- ] });
35
- }
36
- export function StudioScreen({ tabs, active, lines, status = "no staged changes", children, }) {
37
- const tabBar = [
38
- "tickmarkr ui ──",
39
- ...tabs.map((label, index) => `[${index + 1}]${index === active ? "*" : ""}${label}`),
40
- ].join(" ");
41
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: tabBar }), children ?? _jsx(TextLines, { lines: lines }), _jsx(Text, { dimColor: true, children: status })] }));
42
- }
@@ -1,4 +1,4 @@
1
- import type { AuthHealth, WorkerAdapter } from "../../adapters/types.js";
1
+ import { type AuthHealth, type WorkerAdapter } from "../../adapters/types.js";
2
2
  import type { MapEntry, RoutingMode, Tier } from "../../config/config.js";
3
3
  import { type Shape } from "../../graph/schema.js";
4
4
  export type FleetSteeringKey = "review" | "consult";
@@ -40,6 +40,7 @@ type AgentCli = {
40
40
  };
41
41
  export type FleetModelGroup = {
42
42
  adapter: string;
43
+ vendor?: string;
43
44
  rows: Array<{
44
45
  model: string;
45
46
  tier?: Tier;
@@ -51,6 +52,8 @@ export type FleetClassification = {
51
52
  model: string;
52
53
  tier: Tier;
53
54
  note: string;
55
+ vendor?: string;
56
+ channel?: "sub" | "api";
54
57
  };
55
58
  export type FleetModeOption = {
56
59
  id: RoutingMode;