tickmarkr 1.90.7 → 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.
- package/dist/adapters/catalog-remote.d.ts +11 -1
- package/dist/adapters/catalog-remote.js +169 -19
- package/dist/adapters/catalog.js +59 -0
- package/dist/adapters/model-lints.d.ts +36 -2
- package/dist/adapters/model-lints.js +151 -35
- package/dist/adapters/registry.js +32 -6
- package/dist/cli/commands/doctor.d.ts +15 -0
- package/dist/cli/commands/doctor.js +61 -14
- package/dist/cli/commands/fleet.js +31 -3
- package/dist/cli/commands/init.js +18 -16
- package/dist/compile/native.js +5 -4
- package/dist/config/config.js +5 -0
- package/dist/config/fleet-overlay.js +20 -4
- package/dist/gates/acceptance.js +13 -4
- package/dist/run/daemon.js +1 -1
- package/dist/tui/ink/components.d.ts +16 -1
- package/dist/tui/ink/components.js +16 -2
- package/dist/tui/ink/fleet-app.d.ts +8 -1
- package/dist/tui/ink/fleet-app.js +194 -55
- package/dist/tui/ink/init-app.js +2 -2
- package/package.json +1 -1
|
@@ -362,21 +362,20 @@ export async function init(argv, cwd = process.cwd(), io = {}) {
|
|
|
362
362
|
const interactive = input.isTTY === true && output.isTTY === true && !(values.yes ?? false);
|
|
363
363
|
// Act 1 — preferences. Only when the repo config does not exist yet: init never rewrites an
|
|
364
364
|
// operator's config; re-tuning is `tickmarkr fleet` (act 3 below still runs and writes overlays).
|
|
365
|
-
let wizardQuit = false;
|
|
366
365
|
if (!repoConfigExists) {
|
|
367
366
|
if (interactive) {
|
|
368
367
|
emitBanner();
|
|
369
368
|
const wizard = await runInitWizard(cwd, input, output);
|
|
370
369
|
if (wizard === null) {
|
|
371
|
-
|
|
372
|
-
|
|
370
|
+
// Operator field report 2026-08-13: quit means QUIT — no probe, no report wall, no fleet
|
|
371
|
+
// act, one line out. Scaffolding above already happened (spec/global config), and saying
|
|
372
|
+
// so costs one clause; everything else stops here. Esc and ctrl+c both land here.
|
|
373
|
+
return `init: wizard quit — nothing further run (scaffolding kept: ${notes.filter((n) => n.startsWith("wrote")).length} file(s) written above). Re-run tickmarkr init to continue.`;
|
|
373
374
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
await installAgentFiles(cwd, values.force ?? false, wizard.installDocs || (values.docs ?? false), notes);
|
|
379
|
-
}
|
|
375
|
+
writeFileSync(repoConfigPath, configTemplate(wizard.overlay));
|
|
376
|
+
notes.push(`wrote ${repoConfigPath}`);
|
|
377
|
+
if (wizard.installSkills) {
|
|
378
|
+
await installAgentFiles(cwd, values.force ?? false, wizard.installDocs || (values.docs ?? false), notes);
|
|
380
379
|
}
|
|
381
380
|
}
|
|
382
381
|
else {
|
|
@@ -386,17 +385,20 @@ export async function init(argv, cwd = process.cwd(), io = {}) {
|
|
|
386
385
|
}
|
|
387
386
|
// Act 2 — discovery. Doctor is the sensor (fleet never re-probes); runs after preferences so the
|
|
388
387
|
// probe reads the config the operator just chose. Reuse keeps init fast; --fresh forces the probe.
|
|
388
|
+
// Interactive journeys get the COMPACT surface (status rows + pointer) — the full matrix between
|
|
389
|
+
// two TUIs was the operator's clutter report; non-TTY keeps the complete machine surface.
|
|
389
390
|
const fresh = values.fresh ?? false;
|
|
390
391
|
const { reuse, ageMs, health } = initDoctorReuse(cwd, fresh);
|
|
391
392
|
let doc = reuse && health && ageMs !== null
|
|
392
|
-
?
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
//
|
|
397
|
-
|
|
393
|
+
? interactive
|
|
394
|
+
? `using probe results from ${formatDoctorAgeForInit(ageMs)} ago — full matrix: tickmarkr doctor · refresh: init --fresh`
|
|
395
|
+
: `using probe results from ${formatDoctorAgeForInit(ageMs)} ago — run tickmarkr doctor to refresh (or init --fresh)\n${formatDoctorReport(cwd, loadConfig(cwd), health, allAdapters(), { wrote: false })}`
|
|
396
|
+
: await doctor([], cwd, undefined, { banner: false, compact: interactive });
|
|
397
|
+
// Act 3 — fleet. The compact discovery surface prints BETWEEN the acts so the operator reads
|
|
398
|
+
// what the presets screen ranks with; the final summary then carries a pointer, not a repeat.
|
|
399
|
+
if (interactive) {
|
|
398
400
|
output.write(`${doc}\n`);
|
|
399
|
-
doc = "discovery
|
|
401
|
+
doc = "discovery shown above — full matrix any time with `tickmarkr doctor`";
|
|
400
402
|
// Same lazy-Ink seam as fleet.ts: load the editor runtime up front so the assembler's
|
|
401
403
|
// returned props need no startup-input capture window on this path.
|
|
402
404
|
const { runFleetInkEditor } = await import("../../tui/ink/fleet-app.js");
|
package/dist/compile/native.js
CHANGED
|
@@ -697,10 +697,11 @@ acceptance is required on every task (a nested list of observable outcomes).
|
|
|
697
697
|
WHAT MAKES A CRITERION REAL:
|
|
698
698
|
- "test:" must name a real test asserting on recorded state, journal lines, or drawn frames, and its
|
|
699
699
|
title must match the criterion string verbatim. It also needs a collectable test path in files[].
|
|
700
|
-
- A "test:" oracle must
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
tests and the gate parks even though the suite is green.
|
|
700
|
+
- A "test:" oracle's criterion must be the test's OWN title — the leaf — verbatim. Nesting under
|
|
701
|
+
describe() is allowed (OBS-511: the gate matches the criterion as the trailing segment of the
|
|
702
|
+
runner-visible full name), but the leaf title itself must equal the criterion: a shortened,
|
|
703
|
+
decorated, or paraphrased leaf selects ZERO tests and the gate parks even though the suite is green.
|
|
704
|
+
Measured before the widening: two parks across two runs, one full attempt lost in each.
|
|
704
705
|
- NO criterion may be satisfiable by an absence, a rename, a source-text grep, or an empty collection.
|
|
705
706
|
"no file references X" is not a criterion — it passes in a repo where the feature was never built.
|
|
706
707
|
- "goal:" is NEVER verification. Prose in the goal enforces nothing: every obligation needs an
|
package/dist/config/config.js
CHANGED
|
@@ -705,6 +705,11 @@ export function configTemplate(overlay) {
|
|
|
705
705
|
# tiers: # model → capability band; extend when new models ship
|
|
706
706
|
# claude-code: { vendor: anthropic, channel: sub, models: { fable: frontier } }
|
|
707
707
|
# codex: { models: { gpt-5.2-codex: null } } # null = tombstone: removes a stale seed id
|
|
708
|
+
# tier evidence: \`tickmarkr doctor --refresh-catalog\` fetches the catalogs behind tier suggestions.
|
|
709
|
+
# models.dev and LiveBench need no key. The Artificial Analysis leg runs ONLY when the environment
|
|
710
|
+
# carries ARTIFICIAL_ANALYSIS_API_KEY (free key: artificialanalysis.ai) — unset, AA contributes
|
|
711
|
+
# nothing and suggestions fall back to LiveBench's agentic-coding rank, then price. Not a config
|
|
712
|
+
# key: an API credential belongs in the environment, never in a tracked overlay.
|
|
708
713
|
# visibility:
|
|
709
714
|
# llm: headless # headless (default): judge/review/consult run silently | pane: visible agents
|
|
710
715
|
# keepPanes: run # run (default): ephemeral judge/review/consult panes close when read; a merged task's worker pane closes on done; other worker panes persist until run end | attempt | forever (keep everything for debugging)
|
|
@@ -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
|
-
//
|
|
193
|
-
//
|
|
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.
|
|
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. */
|
package/dist/gates/acceptance.js
CHANGED
|
@@ -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;
|
|
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
|
|
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
|
}
|
package/dist/run/daemon.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
21
|
-
|
|
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;
|