tickmarkr 2.6.1 → 2.6.3

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 (105) hide show
  1. package/README.md +16 -3
  2. package/dist/adapters/catalog-remote.js +89 -47
  3. package/dist/adapters/claude-code.js +9 -6
  4. package/dist/adapters/codex.js +7 -4
  5. package/dist/adapters/prompt.d.ts +1 -0
  6. package/dist/adapters/prompt.js +14 -6
  7. package/dist/adapters/registry.js +3 -3
  8. package/dist/adapters/types.d.ts +12 -4
  9. package/dist/adapters/types.js +6 -0
  10. package/dist/cli/commands/approve.d.ts +11 -4
  11. package/dist/cli/commands/approve.js +82 -27
  12. package/dist/cli/commands/compile.js +13 -3
  13. package/dist/cli/commands/doctor.d.ts +8 -2
  14. package/dist/cli/commands/doctor.js +11 -3
  15. package/dist/cli/commands/fleet.js +87 -11
  16. package/dist/cli/commands/plan.js +13 -8
  17. package/dist/cli/commands/report.d.ts +2 -1
  18. package/dist/cli/commands/report.js +74 -8
  19. package/dist/cli/commands/resume.js +4 -2
  20. package/dist/cli/commands/status.js +43 -20
  21. package/dist/cli/help.d.ts +2 -0
  22. package/dist/cli/help.js +9 -2
  23. package/dist/compile/native.js +7 -0
  24. package/dist/config/config.d.ts +35 -2
  25. package/dist/config/config.js +86 -10
  26. package/dist/config/fleet-overlay.d.ts +13 -2
  27. package/dist/config/fleet-overlay.js +60 -0
  28. package/dist/drivers/herdr.d.ts +12 -0
  29. package/dist/drivers/herdr.js +51 -0
  30. package/dist/drivers/orca.d.ts +35 -2
  31. package/dist/drivers/orca.js +222 -67
  32. package/dist/drivers/types.d.ts +2 -0
  33. package/dist/drivers/types.js +2 -2
  34. package/dist/eval/canary.d.ts +2 -1
  35. package/dist/eval/canary.js +2 -2
  36. package/dist/eval/dispatch.js +1 -0
  37. package/dist/gates/acceptance.d.ts +9 -1
  38. package/dist/gates/acceptance.js +31 -4
  39. package/dist/gates/baseline.d.ts +32 -2
  40. package/dist/gates/baseline.js +111 -24
  41. package/dist/gates/cache.d.ts +8 -0
  42. package/dist/gates/cache.js +12 -2
  43. package/dist/gates/llm.d.ts +11 -4
  44. package/dist/gates/llm.js +40 -21
  45. package/dist/gates/review.d.ts +14 -1
  46. package/dist/gates/review.js +160 -34
  47. package/dist/gates/run-gates.d.ts +56 -4
  48. package/dist/gates/run-gates.js +358 -58
  49. package/dist/gates/test-manifest.d.ts +45 -1
  50. package/dist/gates/test-manifest.js +78 -12
  51. package/dist/graph/schema.d.ts +2 -0
  52. package/dist/graph/schema.js +2 -0
  53. package/dist/plan/scope.js +2 -2
  54. package/dist/route/preference.d.ts +20 -2
  55. package/dist/route/preference.js +48 -13
  56. package/dist/route/router.d.ts +12 -1
  57. package/dist/route/router.js +56 -24
  58. package/dist/run/consult.d.ts +15 -1
  59. package/dist/run/consult.js +18 -7
  60. package/dist/run/daemon.d.ts +38 -2
  61. package/dist/run/daemon.js +895 -192
  62. package/dist/run/git.d.ts +8 -0
  63. package/dist/run/git.js +14 -0
  64. package/dist/run/interactive-seed.d.ts +4 -0
  65. package/dist/run/interactive-seed.js +35 -9
  66. package/dist/run/journal.d.ts +152 -3
  67. package/dist/run/journal.js +551 -50
  68. package/dist/run/lease.d.ts +13 -0
  69. package/dist/run/lease.js +45 -0
  70. package/dist/run/merge.d.ts +3 -1
  71. package/dist/run/merge.js +3 -2
  72. package/dist/run/operator-summary.d.ts +3 -0
  73. package/dist/run/operator-summary.js +3 -1
  74. package/dist/run/protocol.d.ts +46 -1
  75. package/dist/run/protocol.js +14 -2
  76. package/dist/run/receipt-resolver.d.ts +22 -0
  77. package/dist/run/receipt-resolver.js +40 -1
  78. package/dist/run/repair-selection.d.ts +11 -1
  79. package/dist/run/repair-selection.js +17 -9
  80. package/dist/run/supervision.d.ts +7 -1
  81. package/dist/run/supervision.js +5 -2
  82. package/dist/run/wall-budget.d.ts +48 -0
  83. package/dist/run/wall-budget.js +280 -0
  84. package/dist/tui/cockpit/board.js +3 -3
  85. package/dist/tui/cockpit/decision-actions.d.ts +8 -5
  86. package/dist/tui/cockpit/decision-actions.js +55 -32
  87. package/dist/tui/cockpit/derive.js +13 -2
  88. package/dist/tui/cockpit/live-runtime.d.ts +10 -0
  89. package/dist/tui/cockpit/live-runtime.js +50 -3
  90. package/dist/tui/cockpit/run-cockpit.d.ts +3 -0
  91. package/dist/tui/cockpit/run-cockpit.js +27 -2
  92. package/dist/tui/cockpit/run-view.d.ts +9 -2
  93. package/dist/tui/cockpit/run-view.js +66 -9
  94. package/dist/tui/cockpit/setup-cockpit.d.ts +6 -0
  95. package/dist/tui/cockpit/setup-cockpit.js +10 -3
  96. package/dist/tui/ink/fleet-app.d.ts +15 -3
  97. package/dist/tui/ink/fleet-app.js +91 -22
  98. package/package.json +3 -1
  99. package/schema/config.schema.json +825 -0
  100. package/skills/tickmarkr-loop/SKILL.md +15 -3
  101. package/skills/tickmarkr-overseer/SKILL.md +42 -0
  102. package/skills/tickmarkr-overseer/scripts/classify-vitest-log.sh +91 -0
  103. package/skills/tickmarkr-overseer/scripts/context-statusline.sh +81 -0
  104. package/skills/tickmarkr-overseer/scripts/grade-ci.sh +36 -34
  105. package/skills/tickmarkr-overseer/scripts/watch-journal.sh +6 -4
@@ -3,12 +3,13 @@ import { Box, render, Text, useApp, useInput } from "ink";
3
3
  import { useRef, useState } from "react";
4
4
  import { MODEL_ID_RE } from "../../adapters/types.js";
5
5
  import { retiredModelReason } from "../../adapters/model-lints.js";
6
- import { DENY_SCOPES, denyBlockFrom, denyEntriesAt, initialDenyPropOf, stagedDenyKeyOf } from "../../config/config.js";
6
+ import { DENY_SCOPES, EFFORT_ADAPTERS, denyBlockFrom, denyEntriesAt, initialDenyPropOf, stagedDenyKeyOf } from "../../config/config.js";
7
7
  import { fleetFirstTouchProvenance } from "../../config/fleet-overlay.js";
8
8
  import { exclusionReason } from "../../config/fleet-why.js";
9
9
  import { entryMatchesChannel, exclusionCollector } from "../../route/preference.js";
10
- import { TIERS } from "../../graph/schema.js";
10
+ import { EFFORTS, TIERS } from "../../graph/schema.js";
11
11
  import { windowRows } from "./components.js";
12
+ import { wrapCells } from "../cockpit/width.js";
12
13
  import { clip, clipPathTail, ElisionMark, enterAltScreen, fmtCtx, fmtMs, fmtUsdPair, Glyph, INK, inkInput, inkOutput, KeyBar, OverlayPanel, padCell, padCellStart, Pointer, SearchRow, } from "./frame.js";
13
14
  const STEERING_KEYS = ["review", "consult"];
14
15
  /** every scope's staged list, empty — the "every policy scope open" probe the picker lifts against */
@@ -31,12 +32,18 @@ function splitSeat(seat) {
31
32
  const at = seat.indexOf(":");
32
33
  return { adapter: seat.slice(0, at), model: seat.slice(at + 1) };
33
34
  }
35
+ // a picker ledger row is labelled "adapter/model — …" (fleet.ts channelLedger); this is its seat id
36
+ function ledgerChannelId(line) {
37
+ const head = line.split(" — ")[0];
38
+ const slash = head.indexOf("/");
39
+ return slash === -1 ? undefined : `${head.slice(0, slash)}:${head.slice(slash + 1)}`;
40
+ }
34
41
  const REACH_CHOICES = [
35
42
  { id: "in", label: "in", gloss: "every seat may route here" },
36
43
  { id: "out-workers", label: "out · workers", gloss: "workers skip it; judge/review/consult unaffected" },
37
44
  { id: "out-all", label: "out · all seats", gloss: "no seat routes here" },
38
45
  ];
39
- export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewOverlay, reloadGuard, stagedRouting, entry = "probe", initialJudge = "", judgeSeats = [], viewRows = Number.POSITIVE_INFINITY, frameColumns = 100, frameRows, ...initialDenyProps }) {
46
+ export function FleetApp({ ageMs, agents, initialAllowOut = [], initialEfforts = {}, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewAdvisories = [], reviewOverlay, reloadGuard, stagedRouting, entry = "probe", initialJudge = "", judgeSeats = [], viewRows = Number.POSITIVE_INFINITY, frameColumns = 100, frameRows, ...initialDenyProps }) {
40
47
  const { exit } = useApp();
41
48
  const [, setRevision] = useState(0);
42
49
  // the props are the fleet command's contract, one per schema-enumerated scope (initialDeny…),
@@ -60,6 +67,7 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
60
67
  allowOut: new Set(initialAllowOut),
61
68
  ...Object.fromEntries(DENY_SCOPES.map((scope) => [scope.key, new Set(initialDeny[scope.key])])),
62
69
  classifications: [],
70
+ efforts: structuredClone(initialEfforts),
63
71
  selectedMode: initialMode,
64
72
  map: structuredClone(initialMap),
65
73
  steering: structuredClone(initialSteering),
@@ -100,6 +108,12 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
100
108
  if (JSON.stringify(ui.steering[key]) !== JSON.stringify(initialSteering[key]))
101
109
  n += 1;
102
110
  }
111
+ for (const adapter of new Set([...Object.keys(initialEfforts), ...Object.keys(ui.efforts)])) {
112
+ for (const model of new Set([...Object.keys(initialEfforts[adapter] ?? {}), ...Object.keys(ui.efforts[adapter] ?? {})])) {
113
+ if (initialEfforts[adapter]?.[model] !== ui.efforts[adapter]?.[model])
114
+ n += 1;
115
+ }
116
+ }
103
117
  return n;
104
118
  };
105
119
  // ── derived data ───────────────────────────────────────────────────────────
@@ -367,6 +381,7 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
367
381
  }),
368
382
  }
369
383
  : classification),
384
+ efforts: structuredClone(ui.efforts),
370
385
  selectedMode: ui.selectedMode,
371
386
  map: ui.map,
372
387
  ...(ui.judgeSeat ? { judgeSeat: splitSeat(ui.judgeSeat) } : {}),
@@ -704,10 +719,9 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
704
719
  // same picker reopens over the recomputed policy — a shared or adapter-wide entry is refused by
705
720
  // name and the row stays greyed
706
721
  const liftFromPicker = (overlay, line) => {
707
- const head = line.split(" — ")[0];
708
- const slash = head.indexOf("/");
709
- const self = slash === -1 ? undefined
710
- : displayedChannels().find((channel) => channel.adapter === head.slice(0, slash) && channel.model === head.slice(slash + 1));
722
+ const lineId = ledgerChannelId(line);
723
+ const self = lineId === undefined ? undefined
724
+ : displayedChannels().find((channel) => `${channel.adapter}:${channel.model}` === lineId);
711
725
  if (!self) {
712
726
  ui.notice = `nothing to lift on this row — ${line}`;
713
727
  bump();
@@ -747,7 +761,12 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
747
761
  bump();
748
762
  };
749
763
  const reviewWindow = (overlay) => {
750
- const lines = overlay.review.diff.split("\n");
764
+ // OBS-1144: notes are pre-wrapped to the diff's width and lead the SAME scrolled buffer, so a
765
+ // note never sits past the terminal's last row, unreachable, while y still saves.
766
+ const notes = (overlay.review.notes ?? [])
767
+ .flatMap((note) => wrapCells(`! ${note}`, bodyW - 6, { continuationPrefix: " " }))
768
+ .map((text) => ({ text, note: true }));
769
+ const lines = [...notes, ...overlay.review.diff.split("\n").map((text) => ({ text, note: false }))];
751
770
  const cap = reviewCapacity();
752
771
  const maxScroll = Math.max(lines.length - cap, 0);
753
772
  const scroll = Math.min(Math.max(overlay.scroll, 0), maxScroll);
@@ -1059,16 +1078,39 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
1059
1078
  bump();
1060
1079
  return;
1061
1080
  }
1062
- if (input === " " && rows[overlay.at]) {
1063
- const id = rows[overlay.at].id;
1081
+ if (input === " ") {
1082
+ const offered = rows[overlay.at];
1083
+ const greyed = offered ? undefined : ledger[overlay.at - rows.length];
1084
+ const id = offered?.id ?? (greyed === undefined ? undefined : ledgerChannelId(greyed));
1085
+ if (id === undefined)
1086
+ return;
1064
1087
  const at = overlay.chain.indexOf(id);
1065
- if (at === -1)
1088
+ // OBS-1145 add.1: Space drops ANY carried member, admitted or not, and leaves its deny
1089
+ // alone; only an offered row joins — a greyed one still enters through its reach (Enter)
1090
+ if (at !== -1)
1091
+ overlay.chain.splice(at, 1);
1092
+ else if (offered)
1066
1093
  overlay.chain.push(id);
1067
1094
  else
1068
- overlay.chain.splice(at, 1);
1095
+ ui.notice = `${id} is not offered — Enter lifts it first; Space pools offered rows only`;
1069
1096
  bump();
1070
1097
  return;
1071
1098
  }
1099
+ // a selection is a POOL, and its mode is a decision, not a default — the tiny
1100
+ // poolmode overlay asks it; Esc there restores this picker with the chain intact
1101
+ // OBS-525: an existing pool's mode seeds the cursor so a round-trip keeps it
1102
+ const askPoolMode = () => setOverlay({ kind: "poolmode", picker: overlay, at: ui.map[overlay.shape]?.pool?.mode === "ordered" ? 1 : 0 });
1103
+ // OBS-1145 add.1: Tab applies the chain from ANY row — in an all-greyed picker every row is
1104
+ // ✗ and Enter lifts it, so a Space drop there had no commit path; Tab lifts no deny
1105
+ if (key.tab) {
1106
+ if (overlay.chain.length)
1107
+ askPoolMode();
1108
+ else {
1109
+ ui.notice = `the ${overlay.shape} pool is empty — Esc keeps it; a on the shape reverts it to auto`;
1110
+ bump();
1111
+ }
1112
+ return;
1113
+ }
1072
1114
  if (key.return) {
1073
1115
  const greyed = ledger[overlay.at - rows.length];
1074
1116
  if (!rows[overlay.at] && greyed !== undefined) {
@@ -1076,10 +1118,7 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
1076
1118
  return;
1077
1119
  }
1078
1120
  if (overlay.chain.length) {
1079
- // a selection is a POOL, and its mode is a decision, not a default — the tiny
1080
- // poolmode overlay asks it; Esc there restores this picker with the chain intact
1081
- // OBS-525: an existing pool's mode seeds the cursor so a round-trip keeps it
1082
- setOverlay({ kind: "poolmode", picker: overlay, at: ui.map[overlay.shape]?.pool?.mode === "ordered" ? 1 : 0 });
1121
+ askPoolMode();
1083
1122
  return;
1084
1123
  }
1085
1124
  if (rows[overlay.at]) {
@@ -1448,6 +1487,29 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
1448
1487
  setOverlay({ kind: "lift", id: `${row.adapter}:${row.model}`, covering: row.covering });
1449
1488
  return;
1450
1489
  }
1490
+ if (hotkey === "e" && row) {
1491
+ // OBS-1182: e cycles launch effort default → low → medium → high → default; the seat's
1492
+ // identity (adapter:model) never changes, only tiers.<adapter>.modelOverrides.<model>.effort
1493
+ if (!EFFORT_ADAPTERS.includes(row.adapter)) {
1494
+ ui.notice = `${row.adapter} takes no effort level — effort is launch metadata only ${EFFORT_ADAPTERS.join(" and ")} accept`;
1495
+ }
1496
+ else if (!row.tier) {
1497
+ ui.notice = `${row.adapter}:${row.model} is unclassified — classify it before setting its effort`;
1498
+ }
1499
+ else {
1500
+ const current = ui.efforts[row.adapter]?.[row.model];
1501
+ const next = EFFORTS[current === undefined ? 0 : EFFORTS.indexOf(current) + 1];
1502
+ const models = { ...ui.efforts[row.adapter] };
1503
+ if (next === undefined)
1504
+ delete models[row.model];
1505
+ else
1506
+ models[row.model] = next;
1507
+ ui.efforts = { ...ui.efforts, [row.adapter]: models };
1508
+ ui.notice = `${row.adapter}:${row.model} effort → ${next ?? "CLI default"} — w writes tiers.${row.adapter}.modelOverrides.${row.model}.effort`;
1509
+ }
1510
+ bump();
1511
+ return;
1512
+ }
1451
1513
  if (hotkey === "t" && row) {
1452
1514
  if (row.tier) {
1453
1515
  ui.notice = "tier reassignment on classified models is not supported in v1 — edit config directly";
@@ -1657,6 +1719,7 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
1657
1719
  const parts = [
1658
1720
  `${row.adapter}:${row.model}`,
1659
1721
  row.tier ?? "unclassified",
1722
+ ui.efforts[row.adapter]?.[row.model] ? `effort ${ui.efforts[row.adapter][row.model]}` : "",
1660
1723
  ui.lastEdit?.id === `${row.adapter}:${row.model}` ? ui.lastEdit.text : "",
1661
1724
  identityDetail(row),
1662
1725
  row.tier ? reachDetail(row) : "",
@@ -1734,7 +1797,7 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
1734
1797
  if (overlay.kind === "review") {
1735
1798
  const { visible, scroll, cap, lines } = reviewWindow(overlay);
1736
1799
  const below = lines.length - scroll - visible.length;
1737
- return (_jsxs(OverlayPanel, { title: `review · ${overlay.review.path}`, width: bodyW, children: [_jsx(Text, { dimColor: true, wrap: "truncate", children: clip("everything staged lands in this one diff — y writes, n discards, ↑↓ scroll, Esc keeps editing", bodyW - 4) }), scroll > 0 && _jsx(ElisionMark, { count: scroll, side: "above" }), visible.map((line, index) => (_jsx(Text, { wrap: "truncate", color: line.startsWith("+") ? INK.brand : line.startsWith("-") ? INK.fail : undefined, dimColor: line.startsWith("@") || line.startsWith("---") || line.startsWith("+++"), children: clip(line, bodyW - 6) || " " }, `${scroll + index}:${line}`))), below > 0 && _jsx(ElisionMark, { count: below, side: "below", hint: `↓ scrolls · PgDn jumps ${cap} lines` })] }));
1800
+ return (_jsxs(OverlayPanel, { title: clipPathTail(`review · ${overlay.review.path}`, bodyW - 4), width: bodyW, children: [_jsx(Text, { dimColor: true, wrap: "truncate", children: clip("everything staged lands in this one diff — y writes, n discards, ↑↓ scroll, Esc keeps editing", bodyW - 4) }), scroll > 0 && _jsx(ElisionMark, { count: scroll, side: "above" }), visible.map(({ text: line, note }, index) => (_jsx(Text, { wrap: "truncate", color: note ? INK.warn : line.startsWith("+") ? INK.brand : line.startsWith("-") ? INK.fail : undefined, dimColor: !note && (line.startsWith("@") || line.startsWith("---") || line.startsWith("+++")), children: clip(line, bodyW - 6) || " " }, `${scroll + index}:${line}`))), below > 0 && _jsx(ElisionMark, { count: below, side: "below", hint: `↓ scrolls · PgDn jumps ${cap} lines` })] }));
1738
1801
  }
1739
1802
  if (overlay.kind === "classify") {
1740
1803
  const subject = overlay.bulk
@@ -1767,10 +1830,13 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
1767
1830
  ];
1768
1831
  const { visible, start, above, below } = windowRows(items, overlay.at, Math.max(1, capacity - noteLines.length + 1));
1769
1832
  const chained = overlay.chain.length > 0;
1770
- return (_jsxs(OverlayPanel, { title: chained ? `pool · ${overlay.shape}` : `pin · ${overlay.shape}`, width: bodyW, children: [_jsx(Text, { dimColor: true, wrap: "truncate", children: clip("Enter pins one channel · Space selects a pool in order — Enter then asks its mode (pool replaces pin)", bodyW - 4) }), _jsx(SearchRow, { filter: ui.filter, active: true }), noteLines.map((line, index) => _jsx(Text, { dimColor: true, wrap: "truncate", children: clip(line, bodyW - 4) }, `excluded-${index}`)), above > 0 && _jsx(ElisionMark, { count: above, side: "above" }), visible.map(({ candidate, line }, index) => {
1833
+ return (_jsxs(OverlayPanel, { title: chained ? `pool · ${overlay.shape}` : `pin · ${overlay.shape}`, width: bodyW, children: [_jsx(Text, { dimColor: true, wrap: "truncate", children: clip("Enter pins one channel · Space pools in order · Tab applies the pool from any row — Enter on an offered row too (pool replaces pin)", bodyW - 4) }), _jsx(SearchRow, { filter: ui.filter, active: true }), noteLines.map((line, index) => _jsx(Text, { dimColor: true, wrap: "truncate", children: clip(line, bodyW - 4) }, `excluded-${index}`)), above > 0 && _jsx(ElisionMark, { count: above, side: "above" }), visible.map(({ candidate, line }, index) => {
1771
1834
  const selected = start + index === overlay.at;
1772
1835
  if (candidate === undefined) {
1773
- return (_jsxs(Text, { wrap: "truncate", children: [_jsx(Pointer, { on: selected }), _jsx(Text, { dimColor: true, children: "✗ " }), _jsx(Text, { bold: selected, dimColor: !selected, children: clip(line, bodyW - 10) })] }, `ledger-${start + index}`));
1836
+ // OBS-1145 add.1: a carried chain member the picker cannot offer keeps its ordinal
1837
+ const id = ledgerChannelId(line);
1838
+ const at = id === undefined ? -1 : overlay.chain.indexOf(id);
1839
+ return (_jsxs(Text, { wrap: "truncate", children: [_jsx(Pointer, { on: selected }), at !== -1 && _jsx(Text, { color: INK.brand, children: `${at + 1} ` }), _jsx(Text, { dimColor: true, children: "✗ " }), _jsx(Text, { bold: selected, dimColor: !selected, children: clip(line, bodyW - (at === -1 ? 10 : 12)) })] }, `ledger-${start + index}`));
1774
1840
  }
1775
1841
  const at = overlay.chain.indexOf(candidate.id);
1776
1842
  return (_jsxs(Text, { wrap: "truncate", children: [_jsx(Pointer, { on: selected }), at === -1 ? _jsx(Text, { dimColor: true, children: "· " }) : _jsx(Text, { color: INK.brand, children: `${at + 1} ` }), _jsx(Text, { bold: selected, children: clip(candidate.label, bodyW - 10) })] }, candidate.id));
@@ -1840,7 +1906,8 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
1840
1906
  return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Shapes" }), _jsx(Text, { dimColor: true, children: ` routed under ${ui.selectedMode}` })] }), _jsx(Text, { children: " " }), rows.map((row, index) => renderShapeRow(row, ui.focus === "list" && index === ui.listAt))] }));
1841
1907
  }
1842
1908
  const rows = steeringList();
1843
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Steering" }), _jsx(Text, { dimColor: true, children: " review · consult · judge" })] }), _jsx(Text, { children: " " }), rows.map((row, index) => renderPlainRow(row.id, row.label, ui.focus === "list" && index === ui.listAt))] }));
1909
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Steering" }), _jsx(Text, { dimColor: true, children: " review · consult · judge" })] }), _jsx(Text, { children: " " }), rows.map((row, index) => renderPlainRow(row.id, row.label, ui.focus === "list" && index === ui.listAt)), reviewAdvisories.length ? _jsx(Text, { children: " " }) : null, reviewAdvisories.flatMap((line) => wrapCells(`! ${line}`, bodyW - 4, { continuationPrefix: " " }))
1910
+ .map((text, index) => _jsx(Text, { color: INK.warn, children: text }, `advisory-${index}`))] }));
1844
1911
  })();
1845
1912
  // ── footer ─────────────────────────────────────────────────────────────────
1846
1913
  const detailLines = () => {
@@ -1911,6 +1978,7 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
1911
1978
  if (overlay.kind === "candidates") {
1912
1979
  return [
1913
1980
  { key: "Enter", label: overlay.chain.length ? "apply pool" : "pin · lift greyed" },
1981
+ ...(overlay.chain.length ? [{ key: "Tab", label: "apply from any row" }] : []),
1914
1982
  { key: "Space", label: "pool in order" },
1915
1983
  { key: "type", label: "search" },
1916
1984
  { key: "Esc", label: "cancel" },
@@ -1955,6 +2023,7 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
1955
2023
  { key: "Enter", label: "assign/classify" },
1956
2024
  { key: "Space", label: "reach picker" },
1957
2025
  { key: "l", label: "lift entry" },
2026
+ { key: "e", label: "effort" },
1958
2027
  { key: "m", label: "presets" },
1959
2028
  { key: "w", label: "review + write" },
1960
2029
  { key: "←", label: "rail" },
@@ -1985,7 +2054,7 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
1985
2054
  ? _jsx(Text, { color: INK.warn, children: ` ! ${clip(ui.notice, width - 4)}` })
1986
2055
  : details.map((line) => _jsx(Text, { dimColor: true, children: ` ${clip(line, width - 2)}` }, line)), _jsx(KeyBar, { binds: keyBinds() })] }));
1987
2056
  }
1988
- export async function runFleetInkEditor({ ageMs, adapters, health, initialAllowOut = [], modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewOverlay, reloadGuard, stagedRouting, entry = "probe", initialJudge = "", judgeSeats = [], initialInput = [], input, output, debug = false, ...initialDenyProps }) {
2057
+ export async function runFleetInkEditor({ ageMs, adapters, health, initialAllowOut = [], initialEfforts, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewAdvisories = [], reviewOverlay, reloadGuard, stagedRouting, entry = "probe", initialJudge = "", judgeSeats = [], initialInput = [], input, output, debug = false, ...initialDenyProps }) {
1989
2058
  const agents = adapters.flatMap((adapter) => {
1990
2059
  const state = health[adapter.id];
1991
2060
  if (!state?.installed)
@@ -2009,7 +2078,7 @@ export async function runFleetInkEditor({ ageMs, adapters, health, initialAllowO
2009
2078
  // rebuilt from output.columns/rows so a pane resize re-renders at the new size. Same root
2010
2079
  // element type on every rerender ⇒ React keeps the component instance, so staged edits,
2011
2080
  // cursor, and overlays all survive the resize.
2012
- const editorElement = () => (_jsx(FleetApp, { ageMs: ageMs, agents: agents, ...initialDenyProps, initialAllowOut: initialAllowOut, modelGroups: declaredModelGroups, initialMode: initialMode, modeOptions: modeOptions, initialMap: initialMap, modePreview: modePreview, shapeRows: shapeRows, candidatesForShape: candidatesForShape, preferOptionsForShape: preferOptionsForShape, initialSteering: initialSteering, steeringOptionsFor: steeringOptionsFor, reviewOverlay: reviewOverlay, reloadGuard: reloadGuard, stagedRouting: stagedRouting, entry: entry, initialJudge: initialJudge, judgeSeats: judgeSeats,
2081
+ const editorElement = () => (_jsx(FleetApp, { ageMs: ageMs, agents: agents, ...initialDenyProps, initialAllowOut: initialAllowOut, initialEfforts: initialEfforts, modelGroups: declaredModelGroups, initialMode: initialMode, modeOptions: modeOptions, initialMap: initialMap, modePreview: modePreview, shapeRows: shapeRows, candidatesForShape: candidatesForShape, preferOptionsForShape: preferOptionsForShape, initialSteering: initialSteering, steeringOptionsFor: steeringOptionsFor, reviewAdvisories: reviewAdvisories, reviewOverlay: reviewOverlay, reloadGuard: reloadGuard, stagedRouting: stagedRouting, entry: entry, initialJudge: initialJudge, judgeSeats: judgeSeats,
2013
2082
  // v1.90.9: cap every list to the terminal minus chrome so the cursor and header can
2014
2083
  // never scroll off-screen (omp's 218-model list did exactly that pre-cap).
2015
2084
  // OBS-523: chrome is ≤12 rows worst case (header, borders, padding, scope+search,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tickmarkr",
3
- "version": "2.6.1",
3
+ "version": "2.6.3",
4
4
  "description": "Spec in, verified work out.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -80,6 +80,8 @@
80
80
  "scripts/assert-test-file-count.sh",
81
81
  "scripts/emit-schema.ts",
82
82
  "scripts/probe-rig.mjs",
83
+ "scripts/run-ci-vitest.sh",
84
+ "scripts/vitest-lease.ts",
83
85
  "specs/export-selftest.spec.md"
84
86
  ],
85
87
  "prefixes": [