tickmarkr 2.3.0 → 2.4.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 (57) hide show
  1. package/dist/adapters/catalog-remote.d.ts +1 -4
  2. package/dist/adapters/catalog-remote.js +52 -42
  3. package/dist/adapters/catalog.js +5 -3
  4. package/dist/adapters/claude-code.d.ts +1 -1
  5. package/dist/adapters/claude-code.js +8 -5
  6. package/dist/adapters/model-lints.d.ts +9 -5
  7. package/dist/adapters/model-lints.js +56 -15
  8. package/dist/adapters/model-windows.js +11 -0
  9. package/dist/adapters/prompt.js +1 -0
  10. package/dist/adapters/qwen.d.ts +5 -0
  11. package/dist/adapters/qwen.js +153 -0
  12. package/dist/adapters/types.d.ts +1 -0
  13. package/dist/adapters/types.js +1 -0
  14. package/dist/cli/commands/compile.js +32 -6
  15. package/dist/cli/commands/doctor.d.ts +4 -3
  16. package/dist/cli/commands/doctor.js +20 -6
  17. package/dist/cli/commands/fleet.d.ts +4 -0
  18. package/dist/cli/commands/fleet.js +53 -14
  19. package/dist/cli/commands/plan.js +29 -5
  20. package/dist/cli/commands/status.d.ts +1 -0
  21. package/dist/cli/commands/status.js +45 -1
  22. package/dist/cli/commands/verify.d.ts +1 -0
  23. package/dist/cli/commands/verify.js +5 -0
  24. package/dist/cli/index.d.ts +1 -1
  25. package/dist/cli/index.js +2 -2
  26. package/dist/compile/collateral.d.ts +2 -9
  27. package/dist/compile/collateral.js +2 -9
  28. package/dist/compile/index.d.ts +4 -1
  29. package/dist/compile/index.js +41 -7
  30. package/dist/compile/native.d.ts +4 -2
  31. package/dist/compile/native.js +55 -6
  32. package/dist/compile/ownership.js +34 -9
  33. package/dist/config/config.d.ts +1 -0
  34. package/dist/config/config.js +51 -5
  35. package/dist/drivers/herdr.d.ts +1 -0
  36. package/dist/drivers/herdr.js +11 -1
  37. package/dist/drivers/orca.d.ts +14 -1
  38. package/dist/drivers/orca.js +114 -15
  39. package/dist/drivers/types.d.ts +10 -0
  40. package/dist/gates/baseline.js +26 -7
  41. package/dist/gates/review.d.ts +4 -2
  42. package/dist/gates/review.js +24 -13
  43. package/dist/gates/run-gates.d.ts +5 -2
  44. package/dist/gates/run-gates.js +34 -17
  45. package/dist/route/preference.d.ts +4 -0
  46. package/dist/route/preference.js +40 -0
  47. package/dist/route/router.js +15 -2
  48. package/dist/run/consult.d.ts +1 -0
  49. package/dist/run/consult.js +34 -7
  50. package/dist/run/daemon.d.ts +4 -0
  51. package/dist/run/daemon.js +128 -50
  52. package/dist/run/git.d.ts +2 -0
  53. package/dist/run/git.js +36 -5
  54. package/dist/run/journal.js +4 -1
  55. package/dist/tui/ink/fleet-app.d.ts +4 -0
  56. package/dist/tui/ink/fleet-app.js +45 -16
  57. package/package.json +59 -1
@@ -68,6 +68,10 @@ export type FleetModelGroup = {
68
68
  detectedAt?: string;
69
69
  suggestion?: FleetModelSuggestion;
70
70
  evidence?: FleetModelEvidence;
71
+ classifyModel?: string;
72
+ variants?: string[];
73
+ foldedModels?: string[];
74
+ score?: number;
71
75
  }>;
72
76
  };
73
77
  export type FleetClassification = {
@@ -96,7 +96,8 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
96
96
  };
97
97
  const groupRows = (group) => {
98
98
  const rows = group.rows.map((row) => {
99
- const staged = ui.classifications.find((classification) => classification.adapter === group.adapter && classification.model === row.model);
99
+ const staged = ui.classifications.find((classification) => classification.adapter === group.adapter
100
+ && (classification.model === row.model || classification.model === row.classifyModel));
100
101
  return {
101
102
  adapter: group.adapter,
102
103
  model: row.model,
@@ -105,10 +106,14 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
105
106
  suggestion: row.suggestion,
106
107
  evidence: row.evidence,
107
108
  channel: group.channel,
109
+ classifyModel: row.classifyModel,
110
+ variants: row.variants,
111
+ foldedModels: row.foldedModels,
112
+ score: row.score,
108
113
  denied: ui.denyModels.has(`${group.adapter}:${row.model}`),
109
114
  };
110
115
  });
111
- const known = new Set(rows.map((row) => row.model));
116
+ const known = new Set(rows.flatMap((row) => row.classifyModel ? [row.model, row.classifyModel] : [row.model]));
112
117
  for (const staged of ui.classifications) {
113
118
  if (staged.adapter === group.adapter && !known.has(staged.model)) {
114
119
  rows.push({
@@ -125,10 +130,21 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
125
130
  const matches = (label, f) => f === "" || label.toLowerCase().includes(f.toLowerCase());
126
131
  // Operator directive 2026-08-13: retired shapes (dated snapshots, previews, non-worker SKUs,
127
132
  // legacy families) hide by DEFAULT. `a` reveals them; a CLASSIFIED row is never hidden.
128
- const modelRows = (f = ui.filter) => scopedGroups().flatMap(groupRows).filter((row) => matches(`${row.adapter}/${row.model}`, f)
129
- && (ui.showAll || row.tier !== undefined || retiredModelReason(row.model) === null));
130
- const hiddenModelCount = (f = ui.filter) => scopedGroups().flatMap(groupRows).filter((row) => matches(`${row.adapter}/${row.model}`, f)).length
133
+ const modelRows = (f = ui.filter) => {
134
+ const rows = scopedGroups().flatMap(groupRows).filter((row) => matches(`${row.adapter}/${row.model} ${(row.foldedModels ?? []).join(" ")}`, f)
135
+ && (ui.showAll || row.tier !== undefined || retiredModelReason(row.model) === null));
136
+ const classified = rows.filter((row) => row.tier !== undefined);
137
+ const unclassified = rows.filter((row) => row.tier === undefined).sort((a, b) => Number(!!b.suggestion) - Number(!!a.suggestion)
138
+ || (b.detectedAt ?? "").localeCompare(a.detectedAt ?? "")
139
+ || (b.score ?? Number.NEGATIVE_INFINITY) - (a.score ?? Number.NEGATIVE_INFINITY)
140
+ || `${a.adapter}/${a.model}`.localeCompare(`${b.adapter}/${b.model}`));
141
+ return [...classified, ...unclassified];
142
+ };
143
+ const hiddenModelCount = (f = ui.filter) => scopedGroups().flatMap(groupRows)
144
+ .filter((row) => matches(`${row.adapter}/${row.model} ${(row.foldedModels ?? []).join(" ")}`, f)).length
131
145
  - modelRows(f).length;
146
+ const foldedModelCount = (f = ui.filter) => modelRows(f)
147
+ .reduce((count, row) => count + Math.max((row.foldedModels?.length ?? 1) - 1, 0), 0);
132
148
  /** rail counts mirror the list's retired-hide (never the text filter) so numbers agree on screen */
133
149
  const visibleCount = (scope) => {
134
150
  const groups = scope === -1 ? enabledGroups() : (modelGroups[scope] ? [modelGroups[scope]] : []);
@@ -232,14 +248,17 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
232
248
  return;
233
249
  // OBS-508: catalog evidence prefills the flow — the tier cursor lands on the suggested band and
234
250
  // (when the operator keeps that band) the provenance note arrives pre-typed. Free to override.
235
- const suggestion = group.rows.find((row) => row.model === model)?.suggestion ?? null;
251
+ const source = group.rows.find((row) => row.model === model);
252
+ const suggestion = source?.suggestion ?? null;
253
+ const classifyModel = source?.classifyModel ?? model;
236
254
  const tierAt = suggestion ? Math.max(TIERS.indexOf(suggestion.tier), 0) : 0;
237
255
  const firstTouch = !group.rows.some((row) => row.tier !== undefined)
238
256
  && !ui.classifications.some((c) => c.adapter === adapter);
239
257
  const base = {
240
258
  kind: "classify",
241
259
  adapter,
242
- model,
260
+ model: classifyModel,
261
+ ...(classifyModel !== model ? { displayModel: model } : {}),
243
262
  channelAt: 0,
244
263
  tierAt,
245
264
  note: "",
@@ -966,7 +985,7 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
966
985
  const group = modelGroups[ui.adapterAt];
967
986
  const suggested = rows
968
987
  .filter((candidate) => candidate.adapter === group.adapter && !candidate.tier && candidate.suggestion !== undefined)
969
- .map((candidate) => ({ model: candidate.model, suggestion: candidate.suggestion }));
988
+ .map((candidate) => ({ model: candidate.classifyModel ?? candidate.model, suggestion: candidate.suggestion }));
970
989
  if (!suggested.length) {
971
990
  ui.notice = "no catalog tier suggestions among the visible unclassified models — evidence comes from the cached catalogs (AA index / API pricing)";
972
991
  bump();
@@ -1101,6 +1120,9 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
1101
1120
  const parts = [
1102
1121
  `${row.adapter}:${row.model}`,
1103
1122
  row.tier ?? "unclassified",
1123
+ row.variants?.length ? `variants ${row.variants.join(", ")}` : "",
1124
+ row.classifyModel ? `classify writes ${row.classifyModel}` : "",
1125
+ row.foldedModels?.length ? `${row.foldedModels.length} gateway ids folded: ${row.foldedModels.join(", ")}` : "",
1104
1126
  row.evidence?.unauthed !== undefined ? `UNAUTHED — ${row.evidence.unauthed}` : "",
1105
1127
  row.evidence?.contextWindow !== undefined ? `${fmtCtx(row.evidence.contextWindow)} ctx` : "",
1106
1128
  row.evidence?.outputWindow !== undefined ? `${fmtCtx(row.evidence.outputWindow)} out` : "",
@@ -1120,7 +1142,8 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
1120
1142
  const nameW = bodyW - 4 - 11 - 6 - (showPrice ? 12 : 0) - (showProbe ? 7 : 0);
1121
1143
  // OBS-531: deep router ids (omp/prime-agent) clip tail-preserving — the LAST segment is the
1122
1144
  // distinguishing half; end-clipping rendered ten identical "prime-agent/anthropic/claude-…" rows.
1123
- const name = clipPathTail(`${row.adapter}/${row.model}`, nameW);
1145
+ const folded = row.foldedModels?.length ?? 1;
1146
+ const name = clipPathTail(`${row.adapter}/${row.model}${folded > 1 ? ` ×${folded}` : ""}`, nameW);
1124
1147
  const prefixLen = Math.min(name.length, row.adapter.length + 1);
1125
1148
  return (_jsxs(Text, { wrap: "truncate", children: [_jsx(Pointer, { on: selected }), row.denied
1126
1149
  ? _jsx(Glyph, { kind: "off" })
@@ -1174,7 +1197,9 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
1174
1197
  if (overlay.kind === "classify") {
1175
1198
  const subject = overlay.bulk
1176
1199
  ? `${overlay.adapter} · ${overlay.bulk.rows.length} suggested models`
1177
- : `${overlay.adapter}:${overlay.model}`;
1200
+ : overlay.displayModel
1201
+ ? `${overlay.adapter}:${overlay.displayModel} · writes ${overlay.model}`
1202
+ : `${overlay.adapter}:${overlay.model}`;
1178
1203
  return (_jsxs(OverlayPanel, { title: `classify · ${subject}`, width: bodyW, children: [overlay.stage === "channel" && (_jsxs(_Fragment, { children: [_jsxs(Text, { dimColor: true, children: ["first touch for ", overlay.adapter, " \u2014 how is this CLI billed?"] }), _jsx(Text, { children: " " }), CHANNELS.map((channel, index) => (_jsxs(Text, { children: [_jsx(Pointer, { on: overlay.channelAt === index }), _jsx(Text, { bold: overlay.channelAt === index, children: padCell(channel, 5) }), _jsx(Text, { dimColor: true, children: channel === "sub" ? "flat-rate subscription quota" : "metered API billing" })] }, channel)))] })), overlay.stage === "tier" && (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: overlay.suggestion
1179
1204
  ? `catalog suggests ${overlay.suggestion.tier} — keep it and the provenance note arrives pre-typed`
1180
1205
  : "pick the capability band this model routes as" }), _jsx(Text, { children: " " }), TIERS.map((tier, index) => (_jsxs(Text, { children: [_jsx(Pointer, { on: overlay.tierAt === index }), overlay.suggestion?.tier === tier ? _jsx(Glyph, { kind: "on" }) : _jsx(Text, { children: " " }), _jsx(Text, { bold: overlay.tierAt === index, children: ` ${padCell(tier, 9)}` }), _jsx(Text, { dimColor: true, children: tier === "frontier" ? "strongest band — integrity shapes" : tier === "mid" ? "capable daily driver" : "fast + cheap" })] }, tier)))] })), overlay.stage === "note" && (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: "benchmark provenance (required) \u2014 where does this tier claim come from?" }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [_jsx(Text, { color: INK.brand, children: "> " }), _jsx(Text, { children: clip(overlay.note, bodyW - 6) || "" }), _jsx(Text, { color: INK.brand, children: "\u2588" })] })] }))] }));
@@ -1220,6 +1245,15 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
1220
1245
  const selectedSeat = ui.judgeSeat ?? judgeKeepRow;
1221
1246
  return (_jsxs(OverlayPanel, { title: "pick \u00B7 judge", width: bodyW, children: [_jsx(Text, { dimColor: true, children: "one seat judges acceptance criteria \u2014 failover stays runtime (GATE-09)" }), _jsx(SearchRow, { filter: ui.filter, active: true }), above > 0 && _jsx(ElisionMark, { count: above, side: "above" }), visible.map((label, index) => (_jsxs(Text, { children: [_jsx(Pointer, { on: start + index === overlay.at }), label === selectedSeat ? _jsx(Glyph, { kind: "on" }) : _jsx(Text, { children: " " }), _jsx(Text, { bold: start + index === overlay.at, children: ` ${clip(label, bodyW - 10)}` })] }, label))), below > 0 && _jsx(ElisionMark, { count: below, side: "below" })] }));
1222
1247
  })();
1248
+ const modelVisibilityLine = () => {
1249
+ const hidden = hiddenModelCount();
1250
+ const folded = foldedModelCount();
1251
+ return [
1252
+ ...(folded ? [`${folded} same-model gateway id${folded === 1 ? "" : "s"} folded`] : []),
1253
+ ...(hidden ? [`${hidden} retired/preview/non-worker hidden — a shows all`] : []),
1254
+ ...(ui.showAll ? ["showing retired models — a hides them again"] : []),
1255
+ ].join(" · ");
1256
+ };
1223
1257
  const listNode = (() => {
1224
1258
  if (overlayNode)
1225
1259
  return overlayNode;
@@ -1230,7 +1264,7 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
1230
1264
  ? "All models"
1231
1265
  : modelGroups[ui.adapterAt]?.adapter ?? "";
1232
1266
  const scopeDenied = ui.adapterAt !== -1 && ui.deny.has(modelGroups[ui.adapterAt]?.adapter ?? "");
1233
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: scopeLabel }), _jsx(Text, { dimColor: true, children: ` ${rows.length}` })] }), _jsx(SearchRow, { filter: ui.filter, active: ui.searching, hint: "/ to search" }), scopeDenied && _jsx(Text, { color: INK.warn, children: `${modelGroups[ui.adapterAt]?.adapter} is out of the fleet — Space on its rail row adds it back` }), above > 0 && _jsx(ElisionMark, { count: above, side: "above" }), visible.map((row, index) => renderModelRow(row, ui.focus === "list" && start + index === ui.listAt)), below > 0 && _jsx(ElisionMark, { count: below, side: "below", hint: "/ to search" }), rows.length === 0 && !scopeDenied && _jsx(Text, { dimColor: true, children: " no models match" })] }));
1267
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: scopeLabel }), _jsx(Text, { dimColor: true, children: ` ${rows.length}` })] }), _jsx(SearchRow, { filter: ui.filter, active: ui.searching, hint: "/ to search" }), modelVisibilityLine() && _jsx(Text, { dimColor: true, children: ` ${modelVisibilityLine()}` }), scopeDenied && _jsx(Text, { color: INK.warn, children: `${modelGroups[ui.adapterAt]?.adapter} is out of the fleet — Space on its rail row adds it back` }), above > 0 && _jsx(ElisionMark, { count: above, side: "above" }), visible.map((row, index) => renderModelRow(row, ui.focus === "list" && start + index === ui.listAt)), below > 0 && _jsx(ElisionMark, { count: below, side: "below", hint: "/ to search" }), rows.length === 0 && !scopeDenied && _jsx(Text, { dimColor: true, children: " no models match" })] }));
1234
1268
  }
1235
1269
  if (ui.view === "shapes") {
1236
1270
  const rows = shapeList();
@@ -1266,11 +1300,6 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
1266
1300
  : "unclassified — Space/Enter classifies; unclassified models are never routed");
1267
1301
  }
1268
1302
  }
1269
- const hidden = hiddenModelCount();
1270
- if (hidden > 0 && lines.length < 2)
1271
- lines.push(`${hidden} retired/preview/non-worker hidden — a shows all`);
1272
- if (ui.showAll && lines.length < 2)
1273
- lines.push("showing retired models — a hides them again");
1274
1303
  return lines;
1275
1304
  }
1276
1305
  if (ui.view === "shapes") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tickmarkr",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "description": "Spec in, verified work out.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -57,5 +57,63 @@
57
57
  "tsx": "^4.19.0",
58
58
  "typescript": "^5.6.0",
59
59
  "vitest": "^3.0.0"
60
+ },
61
+ "tickmarkrExport": {
62
+ "publicPaths": {
63
+ "exact": [
64
+ ".gitignore",
65
+ ".oxlintrc.json",
66
+ "CHANGELOG.md",
67
+ "CODE_OF_CONDUCT.md",
68
+ "CONTRIBUTING.md",
69
+ "FLEET.md",
70
+ "LICENSE",
71
+ "README.md",
72
+ "RELEASING.md",
73
+ "SECURITY.md",
74
+ "package-lock.json",
75
+ "package.json",
76
+ "tickmarkr.spec.md",
77
+ "tsconfig.json",
78
+ "vitest.config.ts",
79
+ ".github/pull_request_template.md",
80
+ "scripts/assert-test-file-count.sh",
81
+ "scripts/emit-schema.ts",
82
+ "scripts/probe-rig.mjs",
83
+ "specs/export-selftest.spec.md"
84
+ ],
85
+ "prefixes": [
86
+ ".github/ISSUE_TEMPLATE/",
87
+ ".github/workflows/",
88
+ "assets/",
89
+ "docs/codebase/",
90
+ "fixtures/",
91
+ "schema/",
92
+ "skills/tickmarkr-auto/",
93
+ "skills/tickmarkr-loop/",
94
+ "skills/tickmarkr-overseer/",
95
+ "src/",
96
+ "tests/"
97
+ ]
98
+ },
99
+ "excludedPaths": [
100
+ ".planning",
101
+ "specs",
102
+ ".tickmarkr",
103
+ ".overseer",
104
+ ".claude",
105
+ "docs",
106
+ "ASSESSMENT-*.md",
107
+ ".gitignore",
108
+ "scripts/measure-trailer-width.mjs",
109
+ "scripts/scan-scope-corpus.mjs",
110
+ "scripts/repro-obs96.mjs",
111
+ "CLAUDE.md",
112
+ "scripts/export-public.sh",
113
+ "scripts/verify-export.sh",
114
+ "tests/scripts/verify-export.test.ts",
115
+ ".github/workflows/ci.yml",
116
+ "**/*.local.*"
117
+ ]
60
118
  }
61
119
  }