tickmarkr 1.84.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.
- package/README.md +4 -2
- package/dist/adapters/catalog-remote.d.ts +64 -0
- package/dist/adapters/catalog-remote.js +287 -0
- package/dist/adapters/catalog.d.ts +96 -0
- package/dist/adapters/catalog.js +176 -0
- package/dist/adapters/claude-code.d.ts +1 -0
- package/dist/adapters/claude-code.js +59 -1
- package/dist/adapters/fake.js +42 -4
- package/dist/adapters/model-lints.d.ts +25 -5
- package/dist/adapters/model-lints.js +184 -50
- package/dist/adapters/model-windows.d.ts +31 -0
- package/dist/adapters/model-windows.js +69 -0
- package/dist/adapters/prompt.d.ts +5 -1
- package/dist/adapters/prompt.js +13 -4
- package/dist/adapters/registry.d.ts +25 -26
- package/dist/adapters/registry.js +173 -110
- package/dist/adapters/types.d.ts +3 -0
- package/dist/adapters/types.js +36 -3
- package/dist/brand.d.ts +5 -1
- package/dist/brand.js +18 -2
- package/dist/cli/commands/doctor.d.ts +3 -0
- package/dist/cli/commands/doctor.js +43 -21
- package/dist/cli/commands/fleet.d.ts +7 -0
- package/dist/cli/commands/fleet.js +94 -74
- package/dist/cli/commands/init.js +118 -5
- package/dist/cli/commands/status.js +202 -46
- package/dist/compile/collateral.d.ts +86 -2
- package/dist/compile/collateral.js +294 -3
- package/dist/compile/gsd.d.ts +2 -1
- package/dist/compile/gsd.js +68 -2
- package/dist/compile/native.d.ts +14 -0
- package/dist/compile/native.js +161 -12
- package/dist/config/config.d.ts +82 -5
- package/dist/config/config.js +253 -66
- package/dist/config/fleet-overlay.d.ts +25 -20
- package/dist/config/fleet-overlay.js +195 -77
- package/dist/config/fleet-why.d.ts +23 -0
- package/dist/config/fleet-why.js +42 -0
- package/dist/drivers/herdr.d.ts +21 -3
- package/dist/drivers/herdr.js +344 -110
- package/dist/gates/acceptance.js +7 -2
- package/dist/gates/baseline.d.ts +1 -0
- package/dist/gates/baseline.js +91 -13
- package/dist/gates/llm.d.ts +0 -1
- package/dist/gates/llm.js +5 -30
- package/dist/gates/review.d.ts +9 -1
- package/dist/gates/review.js +105 -10
- package/dist/gates/run-gates.d.ts +9 -0
- package/dist/gates/run-gates.js +285 -41
- package/dist/gates/verdict-cause.d.ts +4 -0
- package/dist/gates/verdict-cause.js +63 -0
- package/dist/graph/schema.d.ts +6 -0
- package/dist/graph/schema.js +8 -5
- package/dist/route/router.d.ts +0 -5
- package/dist/route/router.js +16 -20
- package/dist/run/consult.d.ts +6 -0
- package/dist/run/consult.js +35 -25
- package/dist/run/daemon.d.ts +48 -2
- package/dist/run/daemon.js +1488 -330
- package/dist/run/journal.d.ts +56 -3
- package/dist/run/journal.js +358 -4
- package/dist/run/stall.d.ts +35 -1
- package/dist/run/stall.js +118 -8
- package/dist/tui/cockpit/capture.d.ts +12 -0
- package/dist/tui/cockpit/capture.js +37 -1
- package/dist/tui/cockpit/components.d.ts +2 -0
- package/dist/tui/cockpit/components.js +8 -8
- package/dist/tui/cockpit/derive.d.ts +29 -2
- package/dist/tui/cockpit/derive.js +219 -23
- package/dist/tui/cockpit/run-cockpit.js +128 -27
- package/dist/tui/cockpit/theme.d.ts +32 -26
- package/dist/tui/cockpit/theme.js +11 -5
- package/dist/tui/ink/components.d.ts +0 -15
- package/dist/tui/ink/components.js +0 -17
- package/dist/tui/ink/fleet-app.d.ts +4 -1
- package/dist/tui/ink/fleet-app.js +134 -13
- package/fixtures/sample.native.md +1 -1
- package/package.json +1 -1
- package/skills/tickmarkr-overseer/SKILL.md +354 -34
- package/skills/tickmarkr-overseer/scripts/watch-artifacts.sh +70 -0
- package/skills/tickmarkr-overseer/scripts/watch-panes.sh +1 -1
- package/dist/tui/ink/studio-app.d.ts +0 -59
- package/dist/tui/ink/studio-app.js +0 -320
- package/dist/tui/save.d.ts +0 -38
- package/dist/tui/save.js +0 -96
- package/dist/tui/staging.d.ts +0 -29
- package/dist/tui/staging.js +0 -78
package/dist/config/config.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import
|
|
4
|
+
import picomatch from "picomatch";
|
|
5
|
+
import { Document, parse } from "yaml";
|
|
5
6
|
import { z } from "zod";
|
|
7
|
+
import { CITED_MODEL_WINDOWS } from "../adapters/model-windows.js";
|
|
6
8
|
import { stateDirName } from "../graph/graph.js";
|
|
7
9
|
import { SHAPES, TIERS } from "../graph/schema.js";
|
|
8
10
|
const TierEnum = z.enum(TIERS, {
|
|
@@ -10,6 +12,8 @@ const TierEnum = z.enum(TIERS, {
|
|
|
10
12
|
});
|
|
11
13
|
export const TIER_RANK = { cheap: 0, mid: 1, frontier: 2 };
|
|
12
14
|
export const DEFAULT_DIFF_CAP = 60_000;
|
|
15
|
+
// This is deliberately only a rejection guard. T20's cited table, not this floor, supplies each claim.
|
|
16
|
+
export const MODEL_WINDOW_PLAUSIBILITY_FLOOR = 1_024;
|
|
13
17
|
// v1.51 T1: routing.mode — a preset COMPILED INTO FLOORS at config load. The router never sees the
|
|
14
18
|
// mode; it receives resolved floors only (the structural defense against the quality-silently-loses
|
|
15
19
|
// class — no fourth runtime authority, no new precedence key).
|
|
@@ -38,12 +42,36 @@ export const MapEntrySchema = z.object({
|
|
|
38
42
|
prefer: z.array(z.string()).optional(),
|
|
39
43
|
escalate: z.boolean().optional(),
|
|
40
44
|
});
|
|
45
|
+
const DeclaredVendorSchema = z.string().trim().min(1, "vendor must be nonempty");
|
|
46
|
+
const ModelOverrideSchema = z
|
|
47
|
+
.object({
|
|
48
|
+
vendor: DeclaredVendorSchema.optional(),
|
|
49
|
+
channel: z.enum(["sub", "api"]).optional(),
|
|
50
|
+
})
|
|
51
|
+
.refine((override) => override.vendor !== undefined || override.channel !== undefined, {
|
|
52
|
+
message: "a model override must declare vendor, channel, or both",
|
|
53
|
+
});
|
|
41
54
|
export const TierEntrySchema = z.object({
|
|
42
|
-
vendor:
|
|
55
|
+
vendor: DeclaredVendorSchema.nullable(),
|
|
43
56
|
channel: z.enum(["sub", "api"]),
|
|
44
57
|
models: z.record(z.string(), TierEnum),
|
|
58
|
+
// T19: keep model values scalar for existing consumers. Optional sibling metadata resolves before
|
|
59
|
+
// parent metadata in channelsFromConfig; neither provider prefixes nor adapter ids manufacture it.
|
|
60
|
+
modelOverrides: z.record(z.string(), ModelOverrideSchema).optional(),
|
|
45
61
|
// v1.47 T3: optional per-model context-window sizes (tokens). Absent block ⇒ no doctor column, no plan lint.
|
|
46
62
|
windows: z.record(z.string(), z.number().int().positive()).optional(),
|
|
63
|
+
}).superRefine((entry, ctx) => {
|
|
64
|
+
if (entry.vendor !== null)
|
|
65
|
+
return;
|
|
66
|
+
for (const model of Object.keys(entry.models)) {
|
|
67
|
+
if (entry.modelOverrides?.[model]?.vendor)
|
|
68
|
+
continue;
|
|
69
|
+
ctx.addIssue({
|
|
70
|
+
code: "custom",
|
|
71
|
+
path: ["modelOverrides", model, "vendor"],
|
|
72
|
+
message: "vendor: null requires every configured model to declare a nonempty vendor override",
|
|
73
|
+
});
|
|
74
|
+
}
|
|
47
75
|
});
|
|
48
76
|
// v1.10 FLEET-06: optional routing.allow/deny fleet preference; absent blocks ⇒ byte-identical routing/discovery.
|
|
49
77
|
// deny wins on conflict; presence of allow (even {} / empty arrays) activates allowlist (fail-closed).
|
|
@@ -73,6 +101,143 @@ export const SubPricingSchema = z
|
|
|
73
101
|
windowsPerMonthHigh: z.number().int().positive(),
|
|
74
102
|
})
|
|
75
103
|
.refine((s) => s.windowsPerMonthLow <= s.windowsPerMonthHigh, "windowsPerMonthLow must be ≤ windowsPerMonthHigh");
|
|
104
|
+
// ── Review participation (R3 of OVERSEER-RULING-20260731-velocity; OBS-186) ────────────────────
|
|
105
|
+
// Participation is keyed on PATHS — what a task DECLARES at compile time and what its diff ACTUALLY
|
|
106
|
+
// touches at gate time — never on complexity. `complexityThreshold` capped participation at a number
|
|
107
|
+
// the authoring law forbade tasks from reaching, so cross-vendor review was unreachable by
|
|
108
|
+
// construction while `required: true` claimed the opposite. Paths cannot drift out from under the
|
|
109
|
+
// gate that way: the same predicate decides both ends.
|
|
110
|
+
export const REVIEW_POLICIES = ["full", "judge-only"];
|
|
111
|
+
/**
|
|
112
|
+
* The leaf class: work a judge alone can carry because there is no behaviour to review. It is CLOSED
|
|
113
|
+
* and enumerated here — documentation pages, the changelog, the release runbook, and the version
|
|
114
|
+
* mirrors a release bump rewrites. Everything else, including tests and scripts, is full-review work.
|
|
115
|
+
*/
|
|
116
|
+
export const REVIEW_LEAF_GLOBS = [
|
|
117
|
+
"docs/**", "CHANGELOG.md", "RELEASING.md", "package.json", "package-lock.json",
|
|
118
|
+
];
|
|
119
|
+
/**
|
|
120
|
+
* Membership is a POSITIVE rule, not a blacklist. A blacklist of source extensions is a list of the
|
|
121
|
+
* formats someone thought of, and everything nobody thought of — `docs/widget.vue`, `docs/site.svelte`,
|
|
122
|
+
* `docs/Makefile`, `docs/package.json`, an extensionless script — falls into the leaf class and skips
|
|
123
|
+
* its review. That direction of failure is the whole defect OBS-186 names, so the rule is inverted:
|
|
124
|
+
* under `docs/` a path is leaf work only when its extension is a DOCUMENTATION format. An unknown
|
|
125
|
+
* format is behaviour until proven otherwise, which is the fail-closed direction.
|
|
126
|
+
*/
|
|
127
|
+
const REVIEW_DOC_EXT_RE = /\.(?:md|mdx|markdown|txt|rst|adoc|asciidoc)$/i;
|
|
128
|
+
/**
|
|
129
|
+
* The version mirrors a release bump rewrites — and ONLY at the repo root: `docs/package.json` is a
|
|
130
|
+
* real manifest for something, not a mirror. Membership here is still a claim about paths, and a
|
|
131
|
+
* manifest diff that moves more than the version field leaves the class; the gate re-tests that
|
|
132
|
+
* against the actual diff content (src/gates/review.ts) because a path cannot say it by itself.
|
|
133
|
+
*/
|
|
134
|
+
export const REVIEW_VERSION_MIRRORS = new Set(["package.json", "package-lock.json"]);
|
|
135
|
+
/** The changelog and the release runbook, at the repo root, by exact name. */
|
|
136
|
+
const REVIEW_LEAF_FILES = new Set(["CHANGELOG.md", "RELEASING.md"]);
|
|
137
|
+
/**
|
|
138
|
+
* Paths whose review may never be skipped (R3): the gate/run/driver/adapter surface — lifecycle,
|
|
139
|
+
* demux, and everything that reaches a shell. Shipped as the default so the compile lint that
|
|
140
|
+
* enforces them is live in a repo that never configures anything.
|
|
141
|
+
*/
|
|
142
|
+
export const DEFAULT_REVIEW_CRITICAL_PATHS = [
|
|
143
|
+
"src/gates/**", "src/run/**", "src/drivers/**", "src/adapters/**",
|
|
144
|
+
];
|
|
145
|
+
const GLOB_META_RE = /[*?[\]{}]/;
|
|
146
|
+
const normPath = (p) => p.replace(/^\.\//, "").trim();
|
|
147
|
+
/**
|
|
148
|
+
* True when `path` — an ACTUAL path a diff touched — is provably leaf-class work. Enumerated, closed,
|
|
149
|
+
* and positive: the two named root files, the two root version mirrors, and documentation formats
|
|
150
|
+
* under `docs/`. Everything else, including a `.vue`/`.svelte`/`Makefile`/`package.json` parked under
|
|
151
|
+
* `docs/`, is behaviour and draws a full review.
|
|
152
|
+
*/
|
|
153
|
+
export function isReviewLeafPath(path) {
|
|
154
|
+
const p = normPath(path);
|
|
155
|
+
if (!p.length)
|
|
156
|
+
return false;
|
|
157
|
+
if (REVIEW_LEAF_FILES.has(p) || REVIEW_VERSION_MIRRORS.has(p))
|
|
158
|
+
return true;
|
|
159
|
+
return p.startsWith("docs/") && REVIEW_DOC_EXT_RE.test(p);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* True when a DECLARED pattern may claim leaf work. A declaration is a claim about a directory, not
|
|
163
|
+
* a file: `docs/**` is how a documentation task declares itself and has to stay claimable, so a glob
|
|
164
|
+
* rooted at `docs/` reads leaf here while `docs/tool.ts` does not read leaf above. The claim is never
|
|
165
|
+
* the last word — reviewGate re-tests it against what the diff actually touched and promotes when the
|
|
166
|
+
* diff leaves the class. A glob rooted anywhere else can claim nothing.
|
|
167
|
+
*/
|
|
168
|
+
export function isReviewLeafDeclaration(pattern) {
|
|
169
|
+
const p = normPath(pattern);
|
|
170
|
+
if (!p.length)
|
|
171
|
+
return false;
|
|
172
|
+
return isReviewLeafPath(p) || (GLOB_META_RE.test(p) && p.startsWith("docs/"));
|
|
173
|
+
}
|
|
174
|
+
/** full beats judge-only. The join is monotone by construction: nothing here can LOWER a policy. */
|
|
175
|
+
export function raiseReviewPolicy(...policies) {
|
|
176
|
+
return policies.includes("full") ? "full" : "judge-only";
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* The compiler's assignment, from the task's DECLARED files[]. judge-only only when EVERY declared
|
|
180
|
+
* path is provably leaf work — one non-leaf pattern (or an empty files[]) makes it full.
|
|
181
|
+
*/
|
|
182
|
+
export function declaredReviewPolicy(files) {
|
|
183
|
+
const declared = files.map(normPath).filter((f) => f.length > 0);
|
|
184
|
+
return declared.length > 0 && declared.every(isReviewLeafDeclaration) ? "judge-only" : "full";
|
|
185
|
+
}
|
|
186
|
+
/** The declared assignment after the operator's floor is applied — config may raise it, never lower it. */
|
|
187
|
+
export function effectiveReviewPolicy(files, review) {
|
|
188
|
+
return raiseReviewPolicy(declaredReviewPolicy(files), review.policy);
|
|
189
|
+
}
|
|
190
|
+
/** The literal head of a pattern — everything before its first glob metacharacter. */
|
|
191
|
+
const literalPrefix = (p) => {
|
|
192
|
+
const i = p.search(GLOB_META_RE);
|
|
193
|
+
return i < 0 ? p : p.slice(0, i);
|
|
194
|
+
};
|
|
195
|
+
/** The literal tail of a pattern — everything after its last glob metacharacter. */
|
|
196
|
+
const literalSuffix = (p) => p.replace(/^.*[*?[\]{}]/s, "");
|
|
197
|
+
const eitherWay = (a, b, rel) => rel(a, b) || rel(b, a);
|
|
198
|
+
/**
|
|
199
|
+
* Do two GLOBS have a path in common? Exact glob-vs-glob intersection is undecidable in general, so
|
|
200
|
+
* this fails CLOSED: the pair is treated as intersecting unless their literal heads or their literal
|
|
201
|
+
* tails prove they cannot meet. `docs/**\/README.md` vs `docs/security/*.md` share the head `docs/`
|
|
202
|
+
* and the tail `.md` is a tail of `/README.md`, so `docs/security/README.md` is correctly flagged;
|
|
203
|
+
* `src/**\/*.ts` vs `src/**\/*.md` share a head but neither tail contains the other, so they are not.
|
|
204
|
+
*/
|
|
205
|
+
const globsMayIntersect = (a, b) => eitherWay(literalPrefix(a), literalPrefix(b), (x, y) => x.startsWith(y))
|
|
206
|
+
&& eitherWay(literalSuffix(a), literalSuffix(b), (x, y) => x.endsWith(y));
|
|
207
|
+
/**
|
|
208
|
+
* Declared patterns that intersect a critical path. Directional containment is the certain half — a
|
|
209
|
+
* critical `src/gates/**` contains a declared `src/gates/review.ts`, and a declared `docs/**` contains
|
|
210
|
+
* a critical `docs/security/**`. It is NOT intersection: two globs can overlap without either matching
|
|
211
|
+
* the other as a literal string, which is how a judge-only task declaring `docs/**\/README.md` slipped
|
|
212
|
+
* past a critical `docs/security/*.md`. When BOTH sides are globs the conservative overlap test above
|
|
213
|
+
* decides, and it decides toward flagging — an over-flagged task is narrowed by its author, an
|
|
214
|
+
* under-flagged one skips the only gate that exists for it.
|
|
215
|
+
*/
|
|
216
|
+
export function criticalPathHits(files, criticalPaths = []) {
|
|
217
|
+
const hits = new Set();
|
|
218
|
+
for (const raw of files) {
|
|
219
|
+
const f = normPath(raw);
|
|
220
|
+
if (!f)
|
|
221
|
+
continue;
|
|
222
|
+
for (const rawCritical of criticalPaths) {
|
|
223
|
+
const critical = normPath(rawCritical);
|
|
224
|
+
if (!critical)
|
|
225
|
+
continue;
|
|
226
|
+
if (f === critical || picomatch(critical, { dot: true })(f) || picomatch(f, { dot: true })(critical)
|
|
227
|
+
|| (GLOB_META_RE.test(f) && GLOB_META_RE.test(critical) && globsMayIntersect(f, critical))) {
|
|
228
|
+
hits.add(f);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return [...hits].sort();
|
|
233
|
+
}
|
|
234
|
+
// R3 (OBS-186): `review: false` here is a SECOND participation switch, and unlike review.policy it is
|
|
235
|
+
// not monotone — it can omit the review gate from a task the path rules assigned `full`. Participation
|
|
236
|
+
// is decided by paths, so the two are JOINED at the one seam that can still refuse the graph:
|
|
237
|
+
// reviewParticipationErrors (src/compile/collateral.ts) fails compile when a shape's `review: false`
|
|
238
|
+
// would demote a full-review task or silence a critical path. The key itself stays parseable (a run's
|
|
239
|
+
// own config, the setup cockpit and the shipped template all round-trip it); what it can no longer do
|
|
240
|
+
// is take effect behind the policy's back.
|
|
76
241
|
const ShapeGateParticipationSchema = z
|
|
77
242
|
.object({ acceptance: z.boolean().optional(), review: z.boolean().optional() })
|
|
78
243
|
.passthrough()
|
|
@@ -157,7 +322,22 @@ export const TickmarkrConfigSchema = z.object({
|
|
|
157
322
|
judge: z.object({ adapter: z.string(), model: z.string() }),
|
|
158
323
|
// v1.53 T2: prefer — ordered reviewer preference (entry grammar: adapter | adapter:model, same as
|
|
159
324
|
// routing.map.prefer). Reorders diversity-eligible channels only; never widens or narrows eligibility.
|
|
160
|
-
review: z.object({
|
|
325
|
+
review: z.object({
|
|
326
|
+
// RETIRED as a participation switch (R3 / OBS-186, v1.85): review participation is decided by the
|
|
327
|
+
// paths a task declares and the paths its diff touches — never by complexity. The key survives
|
|
328
|
+
// because routing hints and the setup cockpit still read it as a complexity landmark; NOTHING in
|
|
329
|
+
// src/gates/review.ts reads it any more, and a value here can no longer disable a review.
|
|
330
|
+
complexityThreshold: z.number(),
|
|
331
|
+
required: z.boolean(),
|
|
332
|
+
prefer: z.array(z.string()).optional(),
|
|
333
|
+
// R3: the operator's participation floor. "full" forces a cross-vendor review on every task;
|
|
334
|
+
// "judge-only" is the neutral floor (it leaves the compiler's per-task assignment standing).
|
|
335
|
+
// Config may RAISE a task's policy and can never lower one — see raiseReviewPolicy.
|
|
336
|
+
policy: z.enum(REVIEW_POLICIES).optional(),
|
|
337
|
+
// R3: globs no task may skip review on (input/demux/lifecycle, gates/run/drivers/adapters, anything
|
|
338
|
+
// reaching a shell). A judge-only task intersecting one of these fails COMPILE, never silently skips.
|
|
339
|
+
criticalPaths: z.array(z.string()).optional(),
|
|
340
|
+
}),
|
|
161
341
|
// v1.54 T1: prefer — ranked consult seat failover. Entries MUST be adapter:model (unlike
|
|
162
342
|
// review.prefer's adapter|adapter:model grammar): a consult seat has no channel to inherit a
|
|
163
343
|
// model from, so a bare adapter is meaningless and fails config load fail-closed. The pinned
|
|
@@ -188,6 +368,24 @@ export class ConfigError extends Error {
|
|
|
188
368
|
this.name = "ConfigError";
|
|
189
369
|
}
|
|
190
370
|
}
|
|
371
|
+
function assertSeededModelWindows() {
|
|
372
|
+
const cited = new Map(CITED_MODEL_WINDOWS.map((claim) => [claim.modelId, claim.window]));
|
|
373
|
+
for (const [adapter, entry] of Object.entries(DEFAULT_CONFIG.tiers)) {
|
|
374
|
+
for (const [model, window] of Object.entries(entry.windows ?? {})) {
|
|
375
|
+
const path = `tiers.${adapter}.windows.${model}`;
|
|
376
|
+
if (window < MODEL_WINDOW_PLAUSIBILITY_FLOOR) {
|
|
377
|
+
throw new ConfigError(`${path} declares window ${window}, below plausibility floor ${MODEL_WINDOW_PLAUSIBILITY_FLOOR} (non-authoritative rejection guard)`);
|
|
378
|
+
}
|
|
379
|
+
const citedWindow = cited.get(model);
|
|
380
|
+
if (citedWindow === undefined) {
|
|
381
|
+
throw new ConfigError(`${path} has no cited model-window entry in T20's vendored table`);
|
|
382
|
+
}
|
|
383
|
+
if (window !== citedWindow) {
|
|
384
|
+
throw new ConfigError(`${path} declares ${window} but does not match cited window ${citedWindow} in T20's vendored table`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
191
389
|
export const DEFAULT_CONFIG = {
|
|
192
390
|
concurrency: 3,
|
|
193
391
|
driver: "auto",
|
|
@@ -221,10 +419,13 @@ export const DEFAULT_CONFIG = {
|
|
|
221
419
|
allowUnverifiedModels: false,
|
|
222
420
|
},
|
|
223
421
|
// Seed table (spec §13). New models = edit this (or your config.yaml), never code.
|
|
422
|
+
// Windows are copied rather than derived from T20's table so config load can reject disagreement
|
|
423
|
+
// between two independently owned artifacts instead of letting one side certify itself.
|
|
224
424
|
tiers: {
|
|
225
425
|
"claude-code": {
|
|
226
426
|
vendor: "anthropic", channel: "sub",
|
|
227
427
|
models: { fable: "frontier", opus: "frontier", sonnet: "mid", haiku: "cheap" },
|
|
428
|
+
windows: { fable: 1_000_000, opus: 1_000_000, sonnet: 1_000_000, haiku: 200_000 },
|
|
228
429
|
},
|
|
229
430
|
// ids verified against installed CLI models_cache.json (codex 0.144.0, fetched 2026-07-09);
|
|
230
431
|
// gpt-5.4 / gpt-5.4-mini removed — OpenAI retirement 2026-07-23; sol=frontier / terra=mid / luna=cheap per OpenAI tier framing.
|
|
@@ -236,6 +437,7 @@ export const DEFAULT_CONFIG = {
|
|
|
236
437
|
codex: {
|
|
237
438
|
vendor: "openai", channel: "sub",
|
|
238
439
|
models: { "gpt-5.6-sol": "frontier", "gpt-5.5": "frontier", "gpt-5.6-terra": "mid", "gpt-5.6-luna": "cheap" },
|
|
440
|
+
windows: { "gpt-5.6-sol": 1_050_000, "gpt-5.5": 1_050_000, "gpt-5.6-terra": 1_050_000, "gpt-5.6-luna": 1_050_000 },
|
|
239
441
|
},
|
|
240
442
|
// grok-4.5 (xAI, released 2026-07-08) → mid: AA Intelligence 54 (#4), Terminal-Bench 2.1 83.3%
|
|
241
443
|
// (≈ GPT-5.5 83.4, Fable 5 84.3), SWE-bench Pro 64.7%, ~4.2× more token-efficient than Opus 4.8
|
|
@@ -256,6 +458,7 @@ export const DEFAULT_CONFIG = {
|
|
|
256
458
|
// stop burning its mid. Operator-approved 2026-07-16.
|
|
257
459
|
"composer-2.5-fast": "cheap",
|
|
258
460
|
},
|
|
461
|
+
windows: { "composer-2.5": 200_000, "composer-2.5-fast": 200_000 },
|
|
259
462
|
},
|
|
260
463
|
// GLM-5.2 → mid per benchmark policy (2026-07): SWE-bench Pro 62.1 (> GPT-5.5 58.6), FrontierSWE 74.4 ≈ Opus 4.8;
|
|
261
464
|
// no independent Terminal-Bench score → conservative mid, overlays may raise.
|
|
@@ -266,6 +469,7 @@ export const DEFAULT_CONFIG = {
|
|
|
266
469
|
opencode: {
|
|
267
470
|
vendor: "mixed", channel: "sub",
|
|
268
471
|
models: { "zai-coding-plan/glm-5.2": "mid" },
|
|
472
|
+
windows: { "zai-coding-plan/glm-5.2": 1_000_000 },
|
|
269
473
|
},
|
|
270
474
|
// GLM-5.2 via pi/ZAI Coding Plan (sub, flat-rate). mid per benchmark policy (2026-07):
|
|
271
475
|
// SWE-bench Pro 62.1, FrontierSWE 74.4 — same rationale as the opencode glm-5.2 seed above.
|
|
@@ -277,6 +481,7 @@ export const DEFAULT_CONFIG = {
|
|
|
277
481
|
pi: {
|
|
278
482
|
vendor: "zhipu", channel: "sub",
|
|
279
483
|
models: { "zai/glm-5.2": "mid" },
|
|
484
|
+
windows: { "zai/glm-5.2": 1_000_000 },
|
|
280
485
|
},
|
|
281
486
|
// Native grok CLI (Phase 40). grok-4.5 → mid: same benchmark provenance as the retired cursor-agent
|
|
282
487
|
// grok-4.5-xhigh seed above (AA 54, TB2.1 83.3%, SWE-b Pro 64.7% — researched 2026-07-10).
|
|
@@ -286,6 +491,7 @@ export const DEFAULT_CONFIG = {
|
|
|
286
491
|
grok: {
|
|
287
492
|
vendor: "xai", channel: "sub",
|
|
288
493
|
models: { "grok-4.5": "mid", "grok-composer-2.5-fast": "cheap" },
|
|
494
|
+
windows: { "grok-4.5": 500_000, "grok-composer-2.5-fast": 200_000 },
|
|
289
495
|
},
|
|
290
496
|
// Native kimi CLI (Phase 48). kimi-code/k3 → frontier: 81.2 FrontierSWE, 88.3 Terminal-Bench 2.1,
|
|
291
497
|
// top-3 across six coding benchmarks (research 2026-07-17, K3 released 2026-07-16).
|
|
@@ -298,18 +504,25 @@ export const DEFAULT_CONFIG = {
|
|
|
298
504
|
"kimi-code/kimi-for-coding": "mid",
|
|
299
505
|
"kimi-code/kimi-for-coding-highspeed": "cheap",
|
|
300
506
|
},
|
|
507
|
+
windows: {
|
|
508
|
+
"kimi-code/k3": 1_048_576,
|
|
509
|
+
"kimi-code/kimi-for-coding": 262_144,
|
|
510
|
+
"kimi-code/kimi-for-coding-highspeed": 262_144,
|
|
511
|
+
},
|
|
301
512
|
},
|
|
302
513
|
},
|
|
303
514
|
pricing: { cheap: 0.1, mid: 0.5, frontier: 2.5 },
|
|
304
515
|
gates: { diffCap: DEFAULT_DIFF_CAP },
|
|
305
516
|
judge: { adapter: "claude-code", model: "fable" },
|
|
306
|
-
|
|
517
|
+
// R3: no `policy` floor — the neutral floor leaves the compiler's per-task assignment standing, so
|
|
518
|
+
// the path-keyed rule is reachable out of the box rather than raised to full by construction.
|
|
519
|
+
review: { complexityThreshold: 7, required: true, criticalPaths: [...DEFAULT_REVIEW_CRITICAL_PATHS] },
|
|
307
520
|
consult: { adapter: "claude-code", model: "fable", stallMinutes: 15 },
|
|
308
521
|
// v1.4: gate LLM calls (judge/review/consult) run headless by default; pane opts back into visible agents.
|
|
309
522
|
// v1.2: workers are the real agent TUI in the pane; "print" restores the -p-rendered-in-pane path.
|
|
310
523
|
visibility: { llm: "headless", keepPanes: "run", worker: "interactive", workersPerTab: 3 },
|
|
311
524
|
};
|
|
312
|
-
function deepMerge(base, over) {
|
|
525
|
+
function deepMerge(base, over, path = []) {
|
|
313
526
|
if (over === undefined || over === null)
|
|
314
527
|
return base;
|
|
315
528
|
if (Array.isArray(base) || Array.isArray(over) || typeof base !== "object" || typeof over !== "object" || base === null) {
|
|
@@ -318,12 +531,18 @@ function deepMerge(base, over) {
|
|
|
318
531
|
const out = { ...base };
|
|
319
532
|
for (const [k, v] of Object.entries(over)) {
|
|
320
533
|
if (v === null) {
|
|
321
|
-
|
|
534
|
+
// T19: null is a declared multi-vendor state only at tiers.<adapter>.vendor. Every other null
|
|
535
|
+
// remains the v1.1 overlay tombstone (for example a stale tiers model id).
|
|
536
|
+
if (path.length === 2 && path[0] === "tiers" && k === "vendor")
|
|
537
|
+
out[k] = null;
|
|
538
|
+
else
|
|
539
|
+
delete out[k];
|
|
322
540
|
continue;
|
|
323
541
|
}
|
|
324
542
|
// OBS-75 class: merge fresh-landing objects onto {} so nested tombstone nulls are pruned even when
|
|
325
543
|
// no lower layer set the key (a fleet-cleared deny writes {adapters: null} — the schema rejects raw null)
|
|
326
|
-
|
|
544
|
+
const childPath = [...path, k];
|
|
545
|
+
out[k] = k in out ? deepMerge(out[k], v, childPath) : deepMerge({}, v, childPath);
|
|
327
546
|
}
|
|
328
547
|
return out;
|
|
329
548
|
}
|
|
@@ -401,7 +620,7 @@ function resolveRoutingMode(cfg, layers) {
|
|
|
401
620
|
lints.push(`floors.${shape}: ${val} (config floors) overrides mode ${mode} — shadowed delta: ${shape} ${dflt}→${moded}`);
|
|
402
621
|
}
|
|
403
622
|
if (TIER_RANK[val] < TIER_RANK[integrityMin(shape)]) {
|
|
404
|
-
lints.push(`floors.${shape}: ${val} is below integrity minimum frontier — integrity class ${INTEGRITY_FLOOR_SHAPES.join("/")}
|
|
623
|
+
lints.push(`floors.${shape}: ${val} is below integrity minimum frontier — integrity class ${INTEGRITY_FLOOR_SHAPES.join("/")} is advisory to explicit floors and map pins; map pins are supreme`);
|
|
405
624
|
}
|
|
406
625
|
continue;
|
|
407
626
|
}
|
|
@@ -431,6 +650,9 @@ export function loadConfigWithMode(repoRoot, opts = {}) {
|
|
|
431
650
|
const r = TickmarkrConfigSchema.safeParse(merged);
|
|
432
651
|
if (!r.success)
|
|
433
652
|
throw new ConfigError(z.prettifyError(r.error));
|
|
653
|
+
// The vendored table attests Tickmarkr's built-in seed artifact. Operator overlays remain free to
|
|
654
|
+
// declare private/future models and deliberately smaller test windows without pretending T20 cited them.
|
|
655
|
+
assertSeededModelWindows();
|
|
434
656
|
return { cfg: r.data, mode: resolveRoutingMode(r.data, [globalCfg, repoCfg]) };
|
|
435
657
|
}
|
|
436
658
|
export function loadConfig(repoRoot, opts = {}) {
|
|
@@ -487,7 +709,7 @@ export function configTemplate(overlay) {
|
|
|
487
709
|
# llm: headless # headless (default): judge/review/consult run silently | pane: visible agents
|
|
488
710
|
# 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)
|
|
489
711
|
# worker: interactive # interactive (default): workers run the real agent TUI | print
|
|
490
|
-
# workersPerTab: 3 # cap
|
|
712
|
+
# workersPerTab: 3 # cap worker panes sharing one tab; gate panes ride with their task and never count. Default 3
|
|
491
713
|
# scope:
|
|
492
714
|
# allowDeviations: [] # globs an operator permits out-of-scope edits into, e.g. ["package-lock.json"]
|
|
493
715
|
# gates: # override auto-detected commands; per-shape participation may only skip LLM gates
|
|
@@ -541,9 +763,8 @@ export function configTemplate(overlay) {
|
|
|
541
763
|
const nl = base.indexOf("\n");
|
|
542
764
|
return `${base.slice(0, nl + 1)}${lines.join("\n")}\n${base.slice(nl + 1)}`;
|
|
543
765
|
}
|
|
544
|
-
//
|
|
545
|
-
|
|
546
|
-
export { FLEET_OVERLAY_KEYS, fleetEditableEquals, fleetRepoOverlayFromDelta, harvestFleetProvenance, repoOverlayYaml, serializeFleetOverlay, unifiedYamlDiff, } from "./fleet-overlay.js";
|
|
766
|
+
// Fleet overlay mutation, legacy serialization, and diff rendering live together in fleet-overlay.ts.
|
|
767
|
+
export { FLEET_OVERLAY_KEYS, fleetEditableEquals, fleetRepoOverlayFromDelta, renderFleetOverlayWrite, repoOverlayYaml, serializeFleetOverlay, unifiedYamlDiff, } from "./fleet-overlay.js";
|
|
547
768
|
export function repoOverlayPath(repoRoot) {
|
|
548
769
|
return join(repoRoot, stateDirName(repoRoot), "config.yaml");
|
|
549
770
|
}
|
|
@@ -555,15 +776,12 @@ export function readOverlayFile(path) {
|
|
|
555
776
|
throw new ConfigError(`invalid overlay at ${path}`);
|
|
556
777
|
return raw;
|
|
557
778
|
}
|
|
558
|
-
export function fleetEditableFromConfig(cfg
|
|
779
|
+
export function fleetEditableFromConfig(cfg) {
|
|
559
780
|
const tiers = {};
|
|
560
781
|
for (const [adapter, entry] of Object.entries(cfg.tiers)) {
|
|
561
782
|
tiers[adapter] = {};
|
|
562
783
|
for (const [model, tier] of Object.entries(entry.models)) {
|
|
563
|
-
|
|
564
|
-
// write knows about every existing note — not only the ones this session typed itself
|
|
565
|
-
const note = provenance[adapter]?.[model];
|
|
566
|
-
tiers[adapter][model] = note ? { tier, provenance: note } : { tier };
|
|
784
|
+
tiers[adapter][model] = { tier };
|
|
567
785
|
}
|
|
568
786
|
}
|
|
569
787
|
return {
|
|
@@ -599,55 +817,24 @@ export function fleetKeyLayer(repoRoot, dotted, opts = {}) {
|
|
|
599
817
|
export function formatFleetPrint(repoRoot, opts = {}) {
|
|
600
818
|
const gdir = opts.globalDir ?? globalConfigDir();
|
|
601
819
|
const effective = loadConfig(repoRoot, { globalDir: gdir });
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
const annotate = (
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
lines.push("routing:");
|
|
609
|
-
lines.push(" deny:");
|
|
610
|
-
if (denyAdapters.length)
|
|
611
|
-
lines.push(annotate("routing.deny.adapters", ` adapters: ${stringify(denyAdapters).trim()}`));
|
|
612
|
-
if (denyModels.length)
|
|
613
|
-
lines.push(annotate("routing.deny.models", ` models: ${stringify(denyModels).trim()}`));
|
|
614
|
-
}
|
|
615
|
-
const mapKeys = Object.keys(editable.map).sort();
|
|
616
|
-
if (mapKeys.length) {
|
|
617
|
-
if (!lines.some((l) => l === "routing:"))
|
|
618
|
-
lines.push("routing:");
|
|
619
|
-
lines.push(" map:");
|
|
620
|
-
for (const shape of mapKeys) {
|
|
621
|
-
const entry = editable.map[shape];
|
|
622
|
-
const body = stringify(entry, { indent: 4 }).trim().split("\n").map((l) => ` ${l}`).join("\n");
|
|
623
|
-
lines.push(annotate(`routing.map.${shape}`, ` ${shape}:`));
|
|
624
|
-
lines.push(body.split("\n").slice(1).join("\n"));
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
const floorKeys = Object.keys(editable.floors).sort();
|
|
628
|
-
if (floorKeys.length) {
|
|
629
|
-
if (!lines.some((l) => l === "routing:"))
|
|
630
|
-
lines.push("routing:");
|
|
631
|
-
lines.push(annotate("routing.floors", ` floors: ${stringify(Object.fromEntries(floorKeys.map((k) => [k, editable.floors[k]]))).trim().replace(/^/gm, " ")}`));
|
|
632
|
-
}
|
|
633
|
-
const tierAdapters = Object.keys(editable.tiers).sort();
|
|
634
|
-
if (tierAdapters.length) {
|
|
635
|
-
lines.push("tiers:");
|
|
636
|
-
for (const adapter of tierAdapters) {
|
|
637
|
-
const models = editable.tiers[adapter];
|
|
638
|
-
const modelIds = Object.keys(models).sort();
|
|
639
|
-
if (!modelIds.length)
|
|
640
|
-
continue;
|
|
641
|
-
lines.push(` ${adapter}:`);
|
|
642
|
-
lines.push(" models:");
|
|
643
|
-
for (const model of modelIds) {
|
|
644
|
-
const v = models[model];
|
|
645
|
-
if (v === null)
|
|
646
|
-
lines.push(` ${model}: null`);
|
|
647
|
-
else
|
|
648
|
-
lines.push(annotate(`tiers.${adapter}.models.${model}`, ` ${model}: ${v.tier}`));
|
|
649
|
-
}
|
|
820
|
+
const doc = new Document({ routing: effective.routing, tiers: effective.tiers });
|
|
821
|
+
doc.commentBefore = " tickmarkr fleet — effective state (repo > global > defaults)";
|
|
822
|
+
const annotate = (path, dotted = path.join(".")) => {
|
|
823
|
+
const node = doc.getIn(path, true);
|
|
824
|
+
if (node && typeof node === "object" && "comment" in node) {
|
|
825
|
+
node.comment = ` ${fleetKeyLayer(repoRoot, dotted, opts)}`;
|
|
650
826
|
}
|
|
827
|
+
};
|
|
828
|
+
if (effective.routing.deny?.adapters !== undefined)
|
|
829
|
+
annotate(["routing", "deny", "adapters"]);
|
|
830
|
+
if (effective.routing.deny?.models !== undefined)
|
|
831
|
+
annotate(["routing", "deny", "models"]);
|
|
832
|
+
for (const shape of Object.keys(effective.routing.map))
|
|
833
|
+
annotate(["routing", "map", shape]);
|
|
834
|
+
annotate(["routing", "floors"]);
|
|
835
|
+
for (const [adapter, entry] of Object.entries(effective.tiers)) {
|
|
836
|
+
for (const model of Object.keys(entry.models))
|
|
837
|
+
annotate(["tiers", adapter, "models", model]);
|
|
651
838
|
}
|
|
652
|
-
return
|
|
839
|
+
return String(doc);
|
|
653
840
|
}
|
|
@@ -1,27 +1,32 @@
|
|
|
1
|
-
import type { FleetEditable } from "./config.js";
|
|
1
|
+
import type { FleetEditable, RoutingMode } from "./config.js";
|
|
2
2
|
/** Fleet-owned overlay keys — the only config surface `tickmarkr fleet` may write. */
|
|
3
3
|
export declare const FLEET_OVERLAY_KEYS: readonly ["routing", "tiers"];
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
export type FleetOverlayWrite = {
|
|
5
|
+
initial: FleetEditable;
|
|
6
|
+
edited: FleetEditable;
|
|
7
|
+
mode?: RoutingMode;
|
|
8
|
+
steering?: {
|
|
9
|
+
initial: {
|
|
10
|
+
review?: string[];
|
|
11
|
+
consult?: string[];
|
|
12
|
+
};
|
|
13
|
+
edited: {
|
|
14
|
+
review?: string[];
|
|
15
|
+
consult?: string[];
|
|
16
|
+
};
|
|
17
|
+
};
|
|
10
18
|
};
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
* benchmark-provenance note survived exactly one write. Fail-open to empty: an unreadable
|
|
15
|
-
* overlay is the loader's problem to reject, never the harvester's. */
|
|
16
|
-
export declare function harvestFleetProvenance(overlayText: string): HarvestedProvenance;
|
|
17
|
-
/** Harvested deny-entry notes keyed by the exact entry string, re-attached at serialize time. */
|
|
18
|
-
export type FleetDenyNotes = {
|
|
19
|
-
adapters?: Record<string, string>;
|
|
20
|
-
models?: Record<string, string>;
|
|
19
|
+
export type FleetFirstTouch = {
|
|
20
|
+
vendor: string;
|
|
21
|
+
channel: "sub" | "api";
|
|
21
22
|
};
|
|
23
|
+
export declare function fleetFirstTouchProvenance(note: string, firstTouch: FleetFirstTouch): string;
|
|
24
|
+
/** Apply only fields fleet authored to the parsed YAML document. Untouched nodes retain their
|
|
25
|
+
* keys, ordering, scalar style, and comments; fresh provenance is written directly on its tier node. */
|
|
26
|
+
export declare function renderFleetOverlayWrite(priorBytes: string, write: FleetOverlayWrite): string;
|
|
22
27
|
/** Build the repo overlay fragment fleet would write for edits since session start. */
|
|
23
|
-
export declare function fleetRepoOverlayFromDelta(initial: FleetEditable, edited: FleetEditable, existingRepo?: Record<string, unknown
|
|
24
|
-
export declare function repoOverlayYaml(overlay: Record<string, unknown
|
|
25
|
-
export declare function serializeFleetOverlay(overlay: Record<string, unknown
|
|
28
|
+
export declare function fleetRepoOverlayFromDelta(initial: FleetEditable, edited: FleetEditable, existingRepo?: Record<string, unknown>, firstTouches?: Readonly<Record<string, FleetFirstTouch>>): Record<string, unknown>;
|
|
29
|
+
export declare function repoOverlayYaml(overlay: Record<string, unknown>): string;
|
|
30
|
+
export declare function serializeFleetOverlay(overlay: Record<string, unknown>): string;
|
|
26
31
|
export declare function unifiedYamlDiff(before: string, after: string, label?: string): string;
|
|
27
32
|
export declare function fleetEditableEquals(a: FleetEditable, b: FleetEditable): boolean;
|