tickmarkr 1.90.8 → 1.91.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.
@@ -1,5 +1,5 @@
1
1
  // Fleet-overlay mutation, serialization, and diff rendering for the `tickmarkr fleet` write path.
2
- import { isMap, isScalar, isSeq, parseDocument, stringify } from "yaml";
2
+ import { isMap, isScalar, isSeq, parseDocument, stringify, visit } from "yaml";
3
3
  /** Fleet-owned overlay keys — the only config surface `tickmarkr fleet` may write. */
4
4
  export const FLEET_OVERLAY_KEYS = ["routing", "tiers"];
5
5
  function fleetSubset(obj) {
@@ -18,6 +18,9 @@ function sortedUnique(xs) {
18
18
  // private provenance envelope until this module writes the YAML. The envelope never reaches disk.
19
19
  const FIRST_TOUCH_OPEN = "\uE000tickmarkr-fleet-first-touch:";
20
20
  const FIRST_TOUCH_CLOSE = "\uE001";
21
+ // OBS-505: marks a scalar-trailing single-line comment for the two-space inline note style at
22
+ // stringify time; applied and consumed inside renderFleetOverlayWrite, never written to disk.
23
+ const INLINE_COMMENT_SENTINEL = "\uE002";
21
24
  export function fleetFirstTouchProvenance(note, firstTouch) {
22
25
  return `${FIRST_TOUCH_OPEN}${encodeURIComponent(firstTouch.vendor)}:${firstTouch.channel}${FIRST_TOUCH_CLOSE}${note}`;
23
26
  }
@@ -189,10 +192,23 @@ export function renderFleetOverlayWrite(priorBytes, write) {
189
192
  }
190
193
  }
191
194
  }
192
- // Fleet's established note style uses two spaces before `#`; the Document remains the sole
193
- // comment writer while preserving that byte-level convention for old and newly-authored notes.
195
+ // OBS-505: fleet's two-space note style (`tier: mid # note`) applies to INLINE comments only.
196
+ // The previous commentString prefixed " #" onto EVERY comment line, so block comments — the
197
+ // whole init scaffold at column 0, and indented operator essays — each gained a stray leading
198
+ // space, turning a one-key write into a whole-file diff on the one confirmation surface an
199
+ // operator reviews. commentString has no position context, but this writer owns the document:
200
+ // scalar-trailing single-line comments (the only inline form fleet emits) are marked with a
201
+ // private-use sentinel (the FIRST_TOUCH envelope precedent above), everything else renders
202
+ // byte-identical to yaml's own stringifyComment.
203
+ visit(doc, (_key, node) => {
204
+ if (isScalar(node) && typeof node.comment === "string" && !node.comment.includes("\n")) {
205
+ node.comment = `${INLINE_COMMENT_SENTINEL}${node.comment}`;
206
+ }
207
+ });
194
208
  return doc.toString({
195
- commentString: (comment) => comment.replace(/^(?!$)(?: $)?/gm, " #"),
209
+ commentString: (comment) => comment.startsWith(INLINE_COMMENT_SENTINEL)
210
+ ? ` #${comment.slice(1)}`
211
+ : comment.replace(/^(?!$)(?: $)?/gm, "#"),
196
212
  });
197
213
  }
198
214
  /** Build the repo overlay fragment fleet would write for edits since session start. */
@@ -75,10 +75,17 @@ function escapeRegExp(s) {
75
75
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
76
76
  }
77
77
  // v1.89 T6: Vitest applies -t to the runner-visible full name (enclosing describe titles and the test
78
- // title, space-joined). Escaping preserves literal criteria; anchoring makes equality, rather than
79
- // substring containment, the named-test contract.
78
+ // title, space-joined). Escaping preserves literal criteria; the leaf anchor makes trailing-title
79
+ // equality, rather than substring containment, the named-test contract.
80
+ // OBS-511 (runs 1013+1027, one full attempt lost in EACH): two independent workers nested the verbatim
81
+ // criterion under a describe, the old `^…$` full-name equality matched zero tests, and the gate parked a
82
+ // green suite. `(^| )` accepts any describe prefix while keeping the criterion as the complete trailing
83
+ // segment — equality still matches (empty prefix), and a title merely CONTAINING the criterion still
84
+ // selects zero. ponytail: a leaf title that itself ENDS with " <criterion>" also matches — vitest -t sees
85
+ // only the space-joined full name, so true leaf-boundary precision needs the runner's --list JSON
86
+ // (auditAcceptanceCorpus has it at doctor time); upgrade there if this window is ever exploited.
80
87
  export function testFilterPattern(name) {
81
- return `^${escapeRegExp(name)}$`;
88
+ return `(^| )${escapeRegExp(name)}$`;
82
89
  }
83
90
  // OBS-55: when the base command already contains `--`, append -t after forwarded args — a second `--`
84
91
  // makes vitest treat -t as a positional file filter and the name filter is dropped.
@@ -126,8 +133,10 @@ export function auditAcceptanceCorpus(corpusRoot, listedTests) {
126
133
  ? {
127
134
  namedTest: {
128
135
  criterion: item.test,
136
+ // OBS-511: mirror the gate's leaf-anchored suffix rule — the audit's denominator
137
+ // must count exactly the tests the -t filter would select, or doctor and gate disagree.
129
138
  matches: runnerNames
130
- .filter(({ fullName }) => fullName === item.test)
139
+ .filter(({ fullName }) => fullName === item.test || fullName.endsWith(` ${item.test}`))
131
140
  .map(({ listed }) => listed),
132
141
  },
133
142
  }
@@ -1944,7 +1944,7 @@ export async function runDaemon(repoRoot, opts = {}) {
1944
1944
  const promptFile = writePrompt(journal.dir, t, attempt, feedback, nonce);
1945
1945
  // OBS-56: state the non-interactive, one-pass finish contract and the OBS-54 stall budget in every
1946
1946
  // worker prompt, not only consult retry guidance. Prepended so prompt.ts's completion trailer stays last.
1947
- const workerContract = `## Harness contract\n- This harness is non-interactive: make one continuous pass; do not stop for questions or follow-up input.\n- You have a ${taskTimeoutMinutes} minute stall window. Budget the full suite once, then commit and emit the completion trailer before it expires.\n- Each test: acceptance criterion must exist as a vitest test whose title matches the criterion string verbatim.`; // OBS-64
1947
+ const workerContract = `## Harness contract\n- This harness is non-interactive: make one continuous pass; do not stop for questions or follow-up input.\n- You have a ${taskTimeoutMinutes} minute stall window. Budget the full suite once, then commit and emit the completion trailer before it expires.\n- Each test: acceptance criterion must exist as a vitest test whose OWN title (the leaf, not counting enclosing describe titles) is the criterion string verbatim — never shortened, never decorated. Nesting under describe() is allowed.`; // OBS-64; OBS-511: leaf-title rule stated where the worker reads it
1948
1948
  // OBS-47: state the worktree layout contract in the worker prompt (cheap-tier workers were
1949
1949
  // committing/deleting node_modules and tripping the scope gate). The harness re-asserts the link
1950
1950
  // itself before gates regardless of what the worker does with it.
@@ -9,7 +9,20 @@ export declare function TextLines({ lines }: {
9
9
  export declare function ToggleMark({ active }: {
10
10
  active: boolean;
11
11
  }): import("react").JSX.Element;
12
- export declare function FleetListScreen({ title, legend, rows, cursor, details, filter, }: {
12
+ /**
13
+ * v1.90.9 (operator field report: "can't choose models for omp"): a list longer than the
14
+ * terminal rendered EVERY row, so the title, legend, and cursor scrolled off the top — omp's
15
+ * 218 unclassified models left the operator staring at a tail of unselectable rows. The window
16
+ * is cursor-centered and clamped; the markers say exactly what is elided and that the list is
17
+ * still fully reachable (arrows walk it, type-to-search narrows it).
18
+ */
19
+ export declare function windowRows<T>(rows: T[], cursor: number, capacity: number): {
20
+ visible: T[];
21
+ start: number;
22
+ above: number;
23
+ below: number;
24
+ };
25
+ export declare function FleetListScreen({ title, legend, rows, cursor, details, filter, viewRows, }: {
13
26
  title: string;
14
27
  legend: string;
15
28
  rows: FleetListRow[];
@@ -17,6 +30,8 @@ export declare function FleetListScreen({ title, legend, rows, cursor, details,
17
30
  details?: string[];
18
31
  /** active type-to-search string; rendered on the legend line so the operator sees the narrowing */
19
32
  filter?: string;
33
+ /** list viewport capacity; rows beyond it window around the cursor with elision markers */
34
+ viewRows?: number;
20
35
  }): import("react").JSX.Element;
21
36
  export declare function FleetReviewScreen({ title, legend, diff, }: {
22
37
  title: string;
@@ -17,8 +17,22 @@ export function ToggleMark({ active }) {
17
17
  ? _jsx(Text, { color: "ansi256(41)", children: GLYPHS.toggleActive })
18
18
  : _jsx(Text, { dimColor: true, children: GLYPHS.toggleInactive });
19
19
  }
20
- export function FleetListScreen({ title, legend, rows, cursor, details = [], filter, }) {
21
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: title }), _jsx(Text, { dimColor: true, children: filter ? `${legend} · search: ${filter}` : legend }), rows.map((row, index) => (_jsxs(Text, { bold: index === cursor, children: [index === cursor ? `${GLYPHS.pointer} ` : " ", row.content] }, row.id))), _jsx(TextLines, { lines: details })] }));
20
+ /**
21
+ * v1.90.9 (operator field report: "can't choose models for omp"): a list longer than the
22
+ * terminal rendered EVERY row, so the title, legend, and cursor scrolled off the top — omp's
23
+ * 218 unclassified models left the operator staring at a tail of unselectable rows. The window
24
+ * is cursor-centered and clamped; the markers say exactly what is elided and that the list is
25
+ * still fully reachable (arrows walk it, type-to-search narrows it).
26
+ */
27
+ export function windowRows(rows, cursor, capacity) {
28
+ if (capacity <= 0 || rows.length <= capacity)
29
+ return { visible: rows, start: 0, above: 0, below: 0 };
30
+ const start = Math.min(Math.max(cursor - Math.floor(capacity / 2), 0), rows.length - capacity);
31
+ return { visible: rows.slice(start, start + capacity), start, above: start, below: rows.length - start - capacity };
32
+ }
33
+ export function FleetListScreen({ title, legend, rows, cursor, details = [], filter, viewRows, }) {
34
+ const { visible, start, above, below } = windowRows(rows, cursor, viewRows ?? Number.POSITIVE_INFINITY);
35
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: title }), _jsx(Text, { dimColor: true, children: filter ? `${legend} · search: ${filter}` : legend }), above > 0 && _jsxs(Text, { dimColor: true, children: [" \u2026 ", above, " above"] }), visible.map((row, index) => (_jsxs(Text, { bold: start + index === cursor, children: [start + index === cursor ? `${GLYPHS.pointer} ` : " ", row.content] }, row.id))), below > 0 && _jsxs(Text, { dimColor: true, children: [" \u2026 ", below, " below \u2014 type to search"] }), _jsx(TextLines, { lines: details })] }));
22
36
  }
23
37
  export function FleetReviewScreen({ title, legend, diff, }) {
24
38
  return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: title }), _jsx(Text, { dimColor: true, children: legend }), _jsx(Text, { children: diff })] }));
@@ -42,6 +42,10 @@ type AgentCli = {
42
42
  version: string;
43
43
  authed: boolean;
44
44
  };
45
+ export type FleetModelSuggestion = {
46
+ tier: Tier;
47
+ note: string;
48
+ };
45
49
  export type FleetModelGroup = {
46
50
  adapter: string;
47
51
  vendor?: string;
@@ -49,6 +53,7 @@ export type FleetModelGroup = {
49
53
  model: string;
50
54
  tier?: Tier;
51
55
  detectedAt?: string;
56
+ suggestion?: FleetModelSuggestion;
52
57
  }>;
53
58
  };
54
59
  export type FleetClassification = {
@@ -76,7 +81,7 @@ export type FleetCandidateOption = {
76
81
  };
77
82
  };
78
83
  export declare function formatDoctorAge(ageMs: number | null): string;
79
- export declare function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewOverlay, reloadGuard, entry, initialJudge, judgeSeats, }: {
84
+ export declare function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewOverlay, reloadGuard, entry, initialJudge, judgeSeats, viewRows, }: {
80
85
  ageMs: number | null;
81
86
  agents: AgentCli[];
82
87
  initialDenyAdapters: string[];
@@ -98,6 +103,8 @@ export declare function FleetApp({ ageMs, agents, initialDenyAdapters, initialDe
98
103
  initialJudge?: string;
99
104
  /** discovered adapter:model seats — the judge picker universe */
100
105
  judgeSeats?: string[];
106
+ /** list viewport capacity (terminal rows minus chrome) — long lists window around the cursor */
107
+ viewRows?: number;
101
108
  }): import("react").JSX.Element;
102
109
  export declare function runFleetInkEditor({ ageMs, adapters, health, initialDenyAdapters, initialDenyModels, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewOverlay, reloadGuard, entry, initialJudge, judgeSeats, initialInput, input, output, debug, }: {
103
110
  ageMs: number | null;