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.
- package/README.md +16 -3
- package/dist/adapters/catalog-remote.js +89 -47
- package/dist/adapters/claude-code.js +9 -6
- package/dist/adapters/codex.js +7 -4
- package/dist/adapters/prompt.d.ts +1 -0
- package/dist/adapters/prompt.js +14 -6
- package/dist/adapters/registry.js +3 -3
- package/dist/adapters/types.d.ts +12 -4
- package/dist/adapters/types.js +6 -0
- package/dist/cli/commands/approve.d.ts +11 -4
- package/dist/cli/commands/approve.js +82 -27
- package/dist/cli/commands/compile.js +13 -3
- package/dist/cli/commands/doctor.d.ts +8 -2
- package/dist/cli/commands/doctor.js +11 -3
- package/dist/cli/commands/fleet.js +87 -11
- package/dist/cli/commands/plan.js +13 -8
- package/dist/cli/commands/report.d.ts +2 -1
- package/dist/cli/commands/report.js +74 -8
- package/dist/cli/commands/resume.js +4 -2
- package/dist/cli/commands/status.js +43 -20
- package/dist/cli/help.d.ts +2 -0
- package/dist/cli/help.js +9 -2
- package/dist/compile/native.js +7 -0
- package/dist/config/config.d.ts +35 -2
- package/dist/config/config.js +86 -10
- package/dist/config/fleet-overlay.d.ts +13 -2
- package/dist/config/fleet-overlay.js +60 -0
- package/dist/drivers/herdr.d.ts +12 -0
- package/dist/drivers/herdr.js +51 -0
- package/dist/drivers/orca.d.ts +35 -2
- package/dist/drivers/orca.js +222 -67
- package/dist/drivers/types.d.ts +2 -0
- package/dist/drivers/types.js +2 -2
- package/dist/eval/canary.d.ts +2 -1
- package/dist/eval/canary.js +2 -2
- package/dist/eval/dispatch.js +1 -0
- package/dist/gates/acceptance.d.ts +9 -1
- package/dist/gates/acceptance.js +31 -4
- package/dist/gates/baseline.d.ts +32 -2
- package/dist/gates/baseline.js +111 -24
- package/dist/gates/cache.d.ts +8 -0
- package/dist/gates/cache.js +12 -2
- package/dist/gates/llm.d.ts +11 -4
- package/dist/gates/llm.js +40 -21
- package/dist/gates/review.d.ts +14 -1
- package/dist/gates/review.js +160 -34
- package/dist/gates/run-gates.d.ts +56 -4
- package/dist/gates/run-gates.js +358 -58
- package/dist/gates/test-manifest.d.ts +45 -1
- package/dist/gates/test-manifest.js +78 -12
- package/dist/graph/schema.d.ts +2 -0
- package/dist/graph/schema.js +2 -0
- package/dist/plan/scope.js +2 -2
- package/dist/route/preference.d.ts +20 -2
- package/dist/route/preference.js +48 -13
- package/dist/route/router.d.ts +12 -1
- package/dist/route/router.js +56 -24
- package/dist/run/consult.d.ts +15 -1
- package/dist/run/consult.js +18 -7
- package/dist/run/daemon.d.ts +38 -2
- package/dist/run/daemon.js +895 -192
- package/dist/run/git.d.ts +8 -0
- package/dist/run/git.js +14 -0
- package/dist/run/interactive-seed.d.ts +4 -0
- package/dist/run/interactive-seed.js +35 -9
- package/dist/run/journal.d.ts +152 -3
- package/dist/run/journal.js +551 -50
- package/dist/run/lease.d.ts +13 -0
- package/dist/run/lease.js +45 -0
- package/dist/run/merge.d.ts +3 -1
- package/dist/run/merge.js +3 -2
- package/dist/run/operator-summary.d.ts +3 -0
- package/dist/run/operator-summary.js +3 -1
- package/dist/run/protocol.d.ts +46 -1
- package/dist/run/protocol.js +14 -2
- package/dist/run/receipt-resolver.d.ts +22 -0
- package/dist/run/receipt-resolver.js +40 -1
- package/dist/run/repair-selection.d.ts +11 -1
- package/dist/run/repair-selection.js +17 -9
- package/dist/run/supervision.d.ts +7 -1
- package/dist/run/supervision.js +5 -2
- package/dist/run/wall-budget.d.ts +48 -0
- package/dist/run/wall-budget.js +280 -0
- package/dist/tui/cockpit/board.js +3 -3
- package/dist/tui/cockpit/decision-actions.d.ts +8 -5
- package/dist/tui/cockpit/decision-actions.js +55 -32
- package/dist/tui/cockpit/derive.js +13 -2
- package/dist/tui/cockpit/live-runtime.d.ts +10 -0
- package/dist/tui/cockpit/live-runtime.js +50 -3
- package/dist/tui/cockpit/run-cockpit.d.ts +3 -0
- package/dist/tui/cockpit/run-cockpit.js +27 -2
- package/dist/tui/cockpit/run-view.d.ts +9 -2
- package/dist/tui/cockpit/run-view.js +66 -9
- package/dist/tui/cockpit/setup-cockpit.d.ts +6 -0
- package/dist/tui/cockpit/setup-cockpit.js +10 -3
- package/dist/tui/ink/fleet-app.d.ts +15 -3
- package/dist/tui/ink/fleet-app.js +91 -22
- package/package.json +3 -1
- package/schema/config.schema.json +825 -0
- package/skills/tickmarkr-loop/SKILL.md +15 -3
- package/skills/tickmarkr-overseer/SKILL.md +42 -0
- package/skills/tickmarkr-overseer/scripts/classify-vitest-log.sh +91 -0
- package/skills/tickmarkr-overseer/scripts/context-statusline.sh +81 -0
- package/skills/tickmarkr-overseer/scripts/grade-ci.sh +36 -34
- package/skills/tickmarkr-overseer/scripts/watch-journal.sh +6 -4
|
@@ -11,9 +11,9 @@ import { batteryPriority, chainDepth, dispatchWaves, graphDefinitionHash, loadGr
|
|
|
11
11
|
import { filesGlob } from "../../graph/files-glob.js";
|
|
12
12
|
import { renderAcceptanceItem } from "../../graph/schema.js";
|
|
13
13
|
import { pendingDaemonApprovalActions, resolveRunMode } from "../../run/daemon.js";
|
|
14
|
-
import { disallowedBy, excludedChannels, exclusionLine, routingEntrySeatLines } from "../../route/preference.js";
|
|
14
|
+
import { disallowedBy, excludedChannels, exclusionLine, observedSeat, routingEntrySeatLines } from "../../route/preference.js";
|
|
15
15
|
import { decayWeight, HALF_LIFE_RUNS, staffLedEvidence } from "../../route/profile.js";
|
|
16
|
-
import { route, RoutingError } from "../../route/router.js";
|
|
16
|
+
import { resolvedFloor, route, RoutingError } from "../../route/router.js";
|
|
17
17
|
import { auditNamedTestOracles, listVitestTests } from "../../gates/acceptance.js";
|
|
18
18
|
import { modelId, modelProvider, pickReviewer } from "../../gates/review.js";
|
|
19
19
|
import { Journal, loadRoutingProfile, readProfileCursor, recordedGraphDefinitionHash, RUNS_WINDOW } from "../../run/journal.js";
|
|
@@ -237,16 +237,21 @@ export async function plan(argv, cwd = process.cwd(), adapters = allAdapters(),
|
|
|
237
237
|
...(runLine ? [runLine] : []),
|
|
238
238
|
"",
|
|
239
239
|
];
|
|
240
|
-
|
|
240
|
+
// OBS-1186: the judge seat is judged under doctor's cached identity for that exact channel — the
|
|
241
|
+
// runtime judge's rule; unknown stays conservative (alias-family deny).
|
|
242
|
+
const judgeDeny = disallowedBy(observedSeat(health, cfg.judge.adapter, cfg.judge.model), cfg.routing, "judge");
|
|
241
243
|
if (judgeDeny?.by === "deny") {
|
|
242
244
|
lines.push(`REFUSAL OBS-576: judge seat ${cfg.judge.adapter}:${cfg.judge.model} is removed by flat routing.deny (${judgeDeny.entry}) — move worker-only policy to routing.deny.workers`, "");
|
|
243
245
|
}
|
|
244
246
|
const entrySeats = routingEntrySeatLines(cfg);
|
|
245
247
|
if (entrySeats.length)
|
|
246
248
|
lines.push(...entrySeats, "");
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
249
|
+
// OBS-1185: the floor route() resolved for THIS task — a task hint over the configured/mode floor.
|
|
250
|
+
const derivation = (t) => {
|
|
251
|
+
const floor = resolvedFloor(t, cfg);
|
|
252
|
+
if (!floor)
|
|
253
|
+
return null;
|
|
254
|
+
return ` floor ${floor.tier} ← ${floor.source === "task" ? "task hint" : mode.provenance[t.shape] ?? "config floors"}`;
|
|
250
255
|
};
|
|
251
256
|
const excluded = excludedChannels(cfg, adapters, health);
|
|
252
257
|
if (excluded.length)
|
|
@@ -408,7 +413,7 @@ export async function plan(argv, cwd = process.cwd(), adapters = allAdapters(),
|
|
|
408
413
|
const review = reviewSeat(t, r.assignment);
|
|
409
414
|
cost += est + judge.cost + review.cost;
|
|
410
415
|
lines.push(` ${t.id.padEnd(6)} ${t.shape.padEnd(10)} c${String(t.complexity).padEnd(3)}→ ${r.assignment.adapter}:${r.assignment.model} [${r.assignment.channel}/${r.assignment.tier}]${t.timeoutMinutes !== undefined ? ` (timeout ${t.timeoutMinutes}m)` : ""}${t.humanGate ? " (human gate)" : ""}${est ? ` ~$${est.toFixed(2)}` : ""} — ${r.provenance}`, ` judge: ${judge.line}${judge.cost ? ` ~$${judge.cost.toFixed(2)}` : ""}`, ` review: ${review.line}${review.cost ? ` ~$${review.cost.toFixed(2)}` : ""}`, chainLine(t));
|
|
411
|
-
const d = derivation(t
|
|
416
|
+
const d = derivation(t);
|
|
412
417
|
if (d)
|
|
413
418
|
lines.push(d);
|
|
414
419
|
if (r.deviation) {
|
|
@@ -432,7 +437,7 @@ export async function plan(argv, cwd = process.cwd(), adapters = allAdapters(),
|
|
|
432
437
|
throw e;
|
|
433
438
|
const msg = `${e.message}${exclusionReason(e.message)}`;
|
|
434
439
|
lines.push(` ${t.id.padEnd(6)} ${t.shape.padEnd(10)} !! ${msg}`, chainLine(t));
|
|
435
|
-
const d = derivation(t
|
|
440
|
+
const d = derivation(t);
|
|
436
441
|
if (d)
|
|
437
442
|
lines.push(d);
|
|
438
443
|
lints.push(`${t.id}: unroutable — ${msg}`);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
+
import type { BaselineCommand } from "../../gates/baseline.js";
|
|
1
2
|
import { type ChannelCost } from "../../report/cost.js";
|
|
2
3
|
import { type JournalEvent, type TelemetryRow } from "../../run/journal.js";
|
|
3
|
-
export declare function renderMarkdownRecord(runId: string, events: JournalEvent[], prices?: ChannelCost[], rows?: TelemetryRow[]): string;
|
|
4
|
+
export declare function renderMarkdownRecord(runId: string, events: JournalEvent[], prices?: ChannelCost[], rows?: TelemetryRow[], suite?: BaselineCommand): string;
|
|
4
5
|
export declare function report(argv: string[], cwd?: string): Promise<string>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { writeFileSync } from "node:fs";
|
|
1
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
2
3
|
import { parseArgs } from "node:util";
|
|
3
4
|
import { ttyVisual } from "../../adapters/model-lints.js";
|
|
4
5
|
import { addUsage } from "../../adapters/types.js";
|
|
@@ -11,7 +12,8 @@ import { estimateCosts } from "../../report/cost.js";
|
|
|
11
12
|
import { assignmentChannel, buildOperatorRecord, formatChannelMoney, formatChannelTokens, formatTokenUsage as fields, totalTokens as total, } from "../../report/operator-record.js";
|
|
12
13
|
import { cellsOf, cellSummary } from "../../route/profile.js";
|
|
13
14
|
import { Journal, loadRoutingProfile } from "../../run/journal.js";
|
|
14
|
-
import { formatTipProof, runEndTipProof } from "../../run/daemon.js";
|
|
15
|
+
import { baselineProvenanceOf, fingerprintsOf, forgivenFingerprints, forgivenGateRow, formatBaselineProvenance, formatFingerprints, formatForgiven, formatTipProof, runEndTipProof, } from "../../run/daemon.js";
|
|
16
|
+
import { formatSpan, WALL_PRIORITY_TEXT, wallBudget, wallBudgetFacts } from "../../run/wall-budget.js";
|
|
15
17
|
import { deriveRunCockpitData } from "../../tui/cockpit/derive.js";
|
|
16
18
|
const n = (x) => x.toLocaleString("en-US"); // explicit locale — CI/darwin flake guard
|
|
17
19
|
const EM = "—";
|
|
@@ -75,6 +77,44 @@ const wallClock = (start, end) => {
|
|
|
75
77
|
const minutes = Math.floor(seconds / 60);
|
|
76
78
|
return minutes ? `${minutes}m ${seconds % 60}s` : `${seconds}s`;
|
|
77
79
|
};
|
|
80
|
+
// OBS-1201: the disjoint wall budget, or why the journal cannot give one — never a zeroed table.
|
|
81
|
+
const wallFacts = (events) => {
|
|
82
|
+
const budget = wallBudget(events);
|
|
83
|
+
if (!budget)
|
|
84
|
+
return [["window", "not measurable — the journal names no run-start with a readable timestamp"]];
|
|
85
|
+
return [["window", `${formatSpan(budget.wallMs)} — each instant counted once, by priority ${WALL_PRIORITY_TEXT}; task-time sums concurrent spans`], ...wallBudgetFacts(budget)];
|
|
86
|
+
};
|
|
87
|
+
// OBS-634: the four numbers the baseline capture already measured for the test command. A capture that
|
|
88
|
+
// measured nothing says so; a missing number is not measurable, never a zero.
|
|
89
|
+
const suiteTelemetry = (entry) => {
|
|
90
|
+
if (!entry)
|
|
91
|
+
return "not recorded — this run's baseline holds no test capture";
|
|
92
|
+
if (entry.infra)
|
|
93
|
+
return `not measurable — the baseline capture returned no verdict (${entry.invalidCause ?? "infra"})`;
|
|
94
|
+
if (typeof entry.durationMs !== "number" || !Number.isFinite(entry.durationMs))
|
|
95
|
+
return "not recorded — the baseline predates capture timing";
|
|
96
|
+
const unmeasured = "not measurable (the runner named no per-file durations)";
|
|
97
|
+
const sum = entry.fileDurationSumMs;
|
|
98
|
+
const parallelism = entry.impliedParallelism;
|
|
99
|
+
const longest = entry.longestFile;
|
|
100
|
+
return [
|
|
101
|
+
`wall ${formatSpan(entry.durationMs)}`,
|
|
102
|
+
`file-sum ${typeof sum === "number" ? formatSpan(sum) : unmeasured}`,
|
|
103
|
+
`implied parallelism ${typeof parallelism === "number" ? parallelism.toFixed(2) : unmeasured}`,
|
|
104
|
+
`longest file ${longest ? `${longest.file} ${formatSpan(longest.durationMs)}` : unmeasured}`,
|
|
105
|
+
...(typeof entry.fileCount === "number" ? [`${entry.fileCount} files`] : []),
|
|
106
|
+
].join(" · ");
|
|
107
|
+
};
|
|
108
|
+
/** The run's own baseline.json test entry; absent or unreadable reads as not recorded. */
|
|
109
|
+
const baselineTestEntry = (runDir) => {
|
|
110
|
+
try {
|
|
111
|
+
const baseline = JSON.parse(readFileSync(join(runDir, "baseline.json"), "utf8"));
|
|
112
|
+
return baseline.commands?.test;
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
78
118
|
const detail = (value) => typeof value === "string" || typeof value === "number" ? String(value) : EM;
|
|
79
119
|
// T11: a gate that declined never ran. Drop those before the comparison metrics so the pass rate
|
|
80
120
|
// counts only gates that actually ran — a declined gate is never a pass and never pads the base.
|
|
@@ -170,6 +210,10 @@ const VERIFICATION_READING = {
|
|
|
170
210
|
failed: "FAILED — the run did not verify its own tip",
|
|
171
211
|
absent: "absent — no tip verification recorded: neither passed nor failed",
|
|
172
212
|
};
|
|
213
|
+
function forgivenLines(events) {
|
|
214
|
+
const forgiven = forgivenFingerprints(closedCycle(events));
|
|
215
|
+
return forgiven.length ? ["- **forgiven vs baseline:**", ...forgiven.map((f) => ` - ${formatForgiven(f)}`)] : [];
|
|
216
|
+
}
|
|
173
217
|
function verificationReading(runId, events) {
|
|
174
218
|
const state = verificationOf(runId, events);
|
|
175
219
|
const proof = runEndTipProof(closedCycle(events));
|
|
@@ -179,8 +223,18 @@ function verificationReading(runId, events) {
|
|
|
179
223
|
}
|
|
180
224
|
return VERIFICATION_READING[state];
|
|
181
225
|
}
|
|
226
|
+
// OBS-1123: a battery row that carried baseline reds names them apart from any red it introduced, so a
|
|
227
|
+
// new regression can never read as forgiven. Rows without the structured fields render as before.
|
|
228
|
+
const fingerprintClauses = (data) => {
|
|
229
|
+
const fresh = fingerprintsOf(data.freshFingerprints) ?? [];
|
|
230
|
+
const forgiven = fingerprintsOf(data.forgivenFingerprints);
|
|
231
|
+
if (!forgiven && !forgivenGateRow(data))
|
|
232
|
+
return "";
|
|
233
|
+
return `${fresh.length ? `; new red (not in baseline): ${formatFingerprints(fresh)}` : ""}`
|
|
234
|
+
+ `; forgiven vs baseline: ${formatFingerprints(forgiven)} — ${formatBaselineProvenance(baselineProvenanceOf(data.baselineProvenance))}`;
|
|
235
|
+
};
|
|
182
236
|
// VIS-07 / REC-01: derived only from the run journal, telemetry, and local configuration.
|
|
183
|
-
export function renderMarkdownRecord(runId, events, prices = [], rows = []) {
|
|
237
|
+
export function renderMarkdownRecord(runId, events, prices = [], rows = [], suite) {
|
|
184
238
|
const runStart = events.find((e) => e.event === "run-start");
|
|
185
239
|
const runEnd = [...events].reverse().find((e) => e.event === "run-end");
|
|
186
240
|
const baseRef = typeof runStart?.data.baseRef === "string" ? runStart.data.baseRef : EM;
|
|
@@ -216,6 +270,8 @@ export function renderMarkdownRecord(runId, events, prices = [], rows = []) {
|
|
|
216
270
|
`- **failed:** ${count("failed")}`,
|
|
217
271
|
`- **human:** ${count("human")}`,
|
|
218
272
|
`- **verification:** ${verificationReading(runId, events)}`,
|
|
273
|
+
// OBS-1123: the same fold the run-end record states, over the same closed cycle as verification.
|
|
274
|
+
...forgivenLines(events),
|
|
219
275
|
"",
|
|
220
276
|
"## Usage & efficiency",
|
|
221
277
|
"",
|
|
@@ -226,6 +282,11 @@ export function renderMarkdownRecord(runId, events, prices = [], rows = []) {
|
|
|
226
282
|
`- **consults:** ${events.filter((e) => e.event === "consult-verdict").length}`,
|
|
227
283
|
`- **escalations:** ${events.filter((e) => e.event === "escalation").length}`,
|
|
228
284
|
"",
|
|
285
|
+
"## Wall budget",
|
|
286
|
+
"",
|
|
287
|
+
...wallFacts(events).map(([label, fact]) => `- **${label}:** ${fact}`),
|
|
288
|
+
`- **suite telemetry (baseline test capture):** ${suiteTelemetry(suite)}`,
|
|
289
|
+
"",
|
|
229
290
|
];
|
|
230
291
|
lines.push("## Channels", "");
|
|
231
292
|
const operatorRecords = buildOperatorRecord(events, prices);
|
|
@@ -280,7 +341,7 @@ export function renderMarkdownRecord(runId, events, prices = [], rows = []) {
|
|
|
280
341
|
}
|
|
281
342
|
const resolved = gate === "review" && g.data.pass === true && Array.isArray(g.data.resolved)
|
|
282
343
|
? g.data.resolved.filter((id) => typeof id === "string") : [];
|
|
283
|
-
lines.push(` - ${gate}: ${pass} — ${firstLine(g.data.details)}${resolved.length ? `; resolved: ${resolved.join(", ")}` : ""}`);
|
|
344
|
+
lines.push(` - ${gate}: ${pass} — ${firstLine(g.data.details)}${resolved.length ? `; resolved: ${resolved.join(", ")}` : ""}${fingerprintClauses(g.data)}`);
|
|
284
345
|
}
|
|
285
346
|
for (const row of leg2) {
|
|
286
347
|
const pass = row.data.pass === true ? "pass" : row.data.pass === false ? "fail" : EM;
|
|
@@ -310,7 +371,7 @@ export function renderMarkdownRecord(runId, events, prices = [], rows = []) {
|
|
|
310
371
|
}
|
|
311
372
|
return lines.join("\n").trimEnd() + "\n";
|
|
312
373
|
}
|
|
313
|
-
function textReport(runId, events, rows, cwd) {
|
|
374
|
+
function textReport(runId, events, rows, cwd, suite) {
|
|
314
375
|
// one group per adapter:model, carrying channel + the folded usage across its rows
|
|
315
376
|
const groups = new Map();
|
|
316
377
|
for (const r of rows) {
|
|
@@ -372,6 +433,10 @@ function textReport(runId, events, rows, cwd) {
|
|
|
372
433
|
`tickmark rate: ${gateRan ? Math.round((100 * gatePass) / gateRan) : 0}% (${gatePass}/${gateRan})${declinedCount ? ` · declined: ${declinedCount}` : ""}`,
|
|
373
434
|
`escalations: ${escalations} · National Office consults: ${consults} · quota failovers: ${failovers}`,
|
|
374
435
|
"",
|
|
436
|
+
"wall budget — exposed wall, disjoint:",
|
|
437
|
+
...wallFacts(events).map(([label, fact]) => ` ${label.padEnd(14)} ${fact}`),
|
|
438
|
+
` ${"suite".padEnd(14)} ${suiteTelemetry(suite)}`,
|
|
439
|
+
"",
|
|
375
440
|
"spend — tokens (measured where observed):",
|
|
376
441
|
...tokenLines,
|
|
377
442
|
"spend — money:",
|
|
@@ -391,7 +456,7 @@ const stylizeReport = (out) => {
|
|
|
391
456
|
return out;
|
|
392
457
|
return out
|
|
393
458
|
.replace(/^.*$/m, (first) => `${title(first)}\n${rule()}`) // non-global /m ⇒ first line only
|
|
394
|
-
.replace(/^(engagement summary — audit trail:|spend — tokens[^\n]*|spend — money:|learning \([^\n]*)$/gm, (l) => dim(l));
|
|
459
|
+
.replace(/^(engagement summary — audit trail:|wall budget — [^\n]*|spend — tokens[^\n]*|spend — money:|learning \([^\n]*)$/gm, (l) => dim(l));
|
|
395
460
|
};
|
|
396
461
|
export async function report(argv, cwd = process.cwd()) {
|
|
397
462
|
const { values, positionals } = parseArgs({
|
|
@@ -412,6 +477,7 @@ export async function report(argv, cwd = process.cwd()) {
|
|
|
412
477
|
const events = j.read();
|
|
413
478
|
const rows = j.readTelemetry();
|
|
414
479
|
const cfg = loadConfig(cwd);
|
|
480
|
+
const suite = baselineTestEntry(j.dir);
|
|
415
481
|
let bundleNote = "";
|
|
416
482
|
if (values.bundle) {
|
|
417
483
|
// Local-only write of the pure proof packet — no network path exists in buildProofBundle.
|
|
@@ -438,7 +504,7 @@ export async function report(argv, cwd = process.cwd()) {
|
|
|
438
504
|
comparison = "\n" + outcome.text;
|
|
439
505
|
}
|
|
440
506
|
if (values.md) {
|
|
441
|
-
return bundleNote + renderMarkdownRecord(runId, events, estimateCosts(rows, cfg.cost), rows) + comparison;
|
|
507
|
+
return bundleNote + renderMarkdownRecord(runId, events, estimateCosts(rows, cfg.cost), rows, suite) + comparison;
|
|
442
508
|
}
|
|
443
|
-
return bundleNote + stylizeReport(textReport(runId, events, rows, cwd)) + comparison;
|
|
509
|
+
return bundleNote + stylizeReport(textReport(runId, events, rows, cwd, suite)) + comparison;
|
|
444
510
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
|
+
import { readDoctor } from "../../adapters/registry.js";
|
|
2
3
|
import { loadConfig } from "../../config/config.js";
|
|
3
4
|
import { classifyHost, parseDriverOverride, pickDriver, preflightHostDriver } from "../../drivers/index.js";
|
|
4
5
|
import { loadGraph } from "../../graph/graph.js";
|
|
@@ -31,9 +32,10 @@ export async function resume(argv, cwd = process.cwd()) {
|
|
|
31
32
|
// v1.87 T3 (OBS-162, twice-carried workaround): the preflight runs AFTER the graph is read and
|
|
32
33
|
// sees only the shapes the resumed graph carries. A deny∩prefer collision on a shape no resumed
|
|
33
34
|
// task uses is a config fact the run would never resolve — it must not refuse the only
|
|
34
|
-
// crash-recovery path. doctor still walks the whole map.
|
|
35
|
+
// crash-recovery path. doctor still walks the whole map. OBS-1143: aliases are judged by the
|
|
36
|
+
// identity doctor cached — the same one the daemon's discovery routes with.
|
|
35
37
|
const graph = loadGraph(cwd);
|
|
36
|
-
const collisions = denyPreferCollisions(cfg, graph.tasks.map((t) => t.shape));
|
|
38
|
+
const collisions = denyPreferCollisions(cfg, graph.tasks.map((t) => t.shape), readDoctor(cwd));
|
|
37
39
|
if (collisions.length) {
|
|
38
40
|
throw new Error(collisions.map(denyPreferCollisionLine).join("; "));
|
|
39
41
|
}
|
|
@@ -12,11 +12,12 @@ import { authorsNote, doneAuthors } from "../../run/operator-state.js";
|
|
|
12
12
|
import { projectOperatorSummary } from "../../run/operator-summary.js";
|
|
13
13
|
import { trackJournalRows } from "../../run/protocol.js";
|
|
14
14
|
import { newestPark, permittedDecisionVerbs } from "./approve.js";
|
|
15
|
-
import { Journal, formatJournalNarration, engagementComparable, isQualityFailureParkKind, parseRunId, preservedRefsByTask, recordedTaskFailureKind, runHasEnded, upheldFeedbackByTask, } from "../../run/journal.js";
|
|
15
|
+
import { Journal, bindingToken, effectiveEvents, formatJournalNarration, parseJournalText, physicalLine, engagementComparable, isQualityFailureParkKind, parseRunId, preservedRefsByTask, recordedTaskFailureKind, runHasEnded, upheldFeedbackByTask, } from "../../run/journal.js";
|
|
16
16
|
import { isPidLive, runLockRunId, runStatusLine } from "../../run/lock.js";
|
|
17
17
|
import { normalizeGateOutcome } from "../../run/outcome.js";
|
|
18
18
|
import { desiredPanes } from "../../run/reconcile.js";
|
|
19
19
|
import { normalizeStallSnapshot } from "../../run/stall.js";
|
|
20
|
+
import { formatSpan, wallBudget, wallBudgetFacts } from "../../run/wall-budget.js";
|
|
20
21
|
import { armWatchSupervision, observeNamedRun, WATCH_OWNER_ENV, readSupervision, supervisionText } from "../../run/supervision.js";
|
|
21
22
|
import { deriveRunCockpitData, } from "../../tui/cockpit/derive.js";
|
|
22
23
|
import { cellWidth, fitCells, wrapCells } from "../../tui/cockpit/width.js";
|
|
@@ -167,7 +168,7 @@ export const decisionEventsFromJournal = (events, runId, stateDir = ".tickmarkr"
|
|
|
167
168
|
...base,
|
|
168
169
|
type: "human-decision-required",
|
|
169
170
|
tier: "decision",
|
|
170
|
-
approvalCommand: `tickmarkr approve ${runId} ${event.taskId}`,
|
|
171
|
+
approvalCommand: `tickmarkr approve ${runId} ${event.taskId} --park ${bindingToken({ line: physicalLine(events, index), ts: event.ts })}`,
|
|
171
172
|
...(typeof event.data.kind === "string" ? { kind: event.data.kind } : {}),
|
|
172
173
|
...(typeof event.data.reason === "string" ? { reason: event.data.reason } : {}),
|
|
173
174
|
}];
|
|
@@ -841,18 +842,6 @@ const statusEngagement = (events, loadedHash) => {
|
|
|
841
842
|
}
|
|
842
843
|
return { comparable: true };
|
|
843
844
|
};
|
|
844
|
-
// The journal's own reader rule (src/run/journal.ts readJsonl), applied to bytes already in hand:
|
|
845
|
-
// skip blanks, drop a line that will not parse (a torn trailing write after a crash), keep the rest.
|
|
846
|
-
const parseJournalSnapshot = (raw) => raw.split("\n").flatMap((line) => {
|
|
847
|
-
if (!line.trim())
|
|
848
|
-
return [];
|
|
849
|
-
try {
|
|
850
|
-
return [JSON.parse(line)];
|
|
851
|
-
}
|
|
852
|
-
catch {
|
|
853
|
-
return [];
|
|
854
|
-
}
|
|
855
|
-
});
|
|
856
845
|
// runLockOwner is the single lock payload/liveness reader. Its current path helper also initializes
|
|
857
846
|
// .tickmarkr metadata; an engine-written lock necessarily passed through that initializer already, so
|
|
858
847
|
// status avoids invoking it in stripped reader-purity fixtures where no engine lock can exist.
|
|
@@ -892,7 +881,7 @@ const readRunRecord = (cwd, graph, namedRunId) => {
|
|
|
892
881
|
if (!runId)
|
|
893
882
|
return undefined;
|
|
894
883
|
const raw = readRunJournalRaw(cwd, runId, lockedRunId === runId);
|
|
895
|
-
const events =
|
|
884
|
+
const events = parseJournalText(raw);
|
|
896
885
|
// The resume comparator is the fail-closed baseline; a matching graph-rehash is the daemon's
|
|
897
886
|
// append-only audit that authorizes this status replay after stop-amend-resume.
|
|
898
887
|
const engagement = statusEngagement(events, graphDefinitionHash(graph));
|
|
@@ -918,7 +907,8 @@ const recordTaskRows = (record, graph, isDaemonAlive) => record.comparable && re
|
|
|
918
907
|
const recordProjection = (record, graph, isDaemonAlive) => {
|
|
919
908
|
const rows = record ? recordTaskRows(record, graph, isDaemonAlive) : new Map();
|
|
920
909
|
const events = record?.comparable ? record.events : [];
|
|
921
|
-
|
|
910
|
+
// OBS-1178: a refused or unsound decision released nothing, so the activity fold never reads it.
|
|
911
|
+
const activity = projectActivity(record?.runId ?? "", trackJournalRows(record?.runId ?? "", effectiveEvents(events).map((raw, sourceIndex) => ({ raw, sourceIndex }))), graph.tasks);
|
|
922
912
|
const tasks = graph.tasks.map(task => {
|
|
923
913
|
const row = rows.get(task.id);
|
|
924
914
|
const evidence = events.filter(event => event.taskId === task.id);
|
|
@@ -1068,12 +1058,28 @@ binaryVersion, now = Date.now(), animationFrame = 0, workerLiveness = new Map(),
|
|
|
1068
1058
|
// OBS-738: recovery facts stay on the journal's two established reducers. The prefix fold keeps an
|
|
1069
1059
|
// older journal equally readable by asking upheldFeedbackByTask what was active at that exact
|
|
1070
1060
|
// restore boundary; current resume-restore rows additionally carry the same task for narration.
|
|
1061
|
+
// OBS-1178: both folds read decisions through the one decision fold over the whole journal.
|
|
1062
|
+
const decided = effectiveEvents(events);
|
|
1071
1063
|
const preservedRefs = preservedRefsByTask(events);
|
|
1072
|
-
const restoredUpheldFeedback = new Set(
|
|
1073
|
-
&& upheldFeedbackByTask(
|
|
1064
|
+
const restoredUpheldFeedback = new Set(decided.flatMap((event, index) => event.event === "resume-restore" && event.taskId
|
|
1065
|
+
&& upheldFeedbackByTask(decided.slice(0, index)).has(event.taskId)
|
|
1074
1066
|
? [event.taskId]
|
|
1075
1067
|
: []));
|
|
1068
|
+
// OBS-1178: a failed task's newest task-failed row is the token its recheck binds to. Only an
|
|
1069
|
+
// effective decision consumes it — a refused or unsound recheck leaves the task failed and the token shown.
|
|
1070
|
+
const failureTokens = new Map();
|
|
1071
|
+
decided.forEach((event, index) => {
|
|
1072
|
+
if (!event.taskId || !["task-dispatch", "task-done", "task-failed", "task-human", "task-approved"].includes(event.event))
|
|
1073
|
+
return;
|
|
1074
|
+
if (event.event === "task-failed")
|
|
1075
|
+
failureTokens.set(event.taskId, bindingToken({ line: physicalLine(decided, index), ts: event.ts }));
|
|
1076
|
+
else
|
|
1077
|
+
failureTokens.delete(event.taskId);
|
|
1078
|
+
});
|
|
1076
1079
|
const recoveryLinesForTask = (taskId) => [
|
|
1080
|
+
...(failureTokens.has(taskId) && runId
|
|
1081
|
+
? [`failed — ${taskId} — failure ${failureTokens.get(taskId)} — re-gate landed work with \`tickmarkr approve ${runId} ${taskId} --recheck --park ${failureTokens.get(taskId)}\``]
|
|
1082
|
+
: []),
|
|
1077
1083
|
...(preservedRefs.get(taskId) ?? []).flatMap(({ ref, diffCommand }) => [
|
|
1078
1084
|
`preserved worktree — ${taskId} — ${ref}`,
|
|
1079
1085
|
` ${diffCommand}`,
|
|
@@ -1150,6 +1156,7 @@ binaryVersion, now = Date.now(), animationFrame = 0, workerLiveness = new Map(),
|
|
|
1150
1156
|
...(hotPhase ? { hotPhase } : {}),
|
|
1151
1157
|
...(runId ? { runId } : {}),
|
|
1152
1158
|
workerPhases: [...phases.values()].filter((phase) => phase.phase === "worker"),
|
|
1159
|
+
events,
|
|
1153
1160
|
};
|
|
1154
1161
|
}
|
|
1155
1162
|
// TTY: the operator-approved task table. The daemon gives this surface a ~110-column pane first;
|
|
@@ -1359,8 +1366,22 @@ binaryVersion, now = Date.now(), animationFrame = 0, workerLiveness = new Map(),
|
|
|
1359
1366
|
...(hotPhase ? { hotPhase } : {}),
|
|
1360
1367
|
...(runId ? { runId } : {}),
|
|
1361
1368
|
workerPhases: [...phases.values()].filter((phase) => phase.phase === "worker"),
|
|
1369
|
+
events,
|
|
1362
1370
|
};
|
|
1363
1371
|
};
|
|
1372
|
+
// OBS-1201: the one-shot print closes with where the run's wall went (src/run/wall-budget.ts). A
|
|
1373
|
+
// window of zero length measured nothing, so it prints nothing; watch frames are untouched. Rows wrap
|
|
1374
|
+
// to the board width, so narrowing costs rows, never columns.
|
|
1375
|
+
const wallBudgetLines = (events) => {
|
|
1376
|
+
const budget = wallBudget(events);
|
|
1377
|
+
if (!budget || budget.wallMs <= 0)
|
|
1378
|
+
return [];
|
|
1379
|
+
const columns = Math.max(40, process.stdout.columns ?? 120);
|
|
1380
|
+
return [
|
|
1381
|
+
` wall budget ${formatSpan(budget.wallMs)} · each instant once, by priority`,
|
|
1382
|
+
...wallBudgetFacts(budget).map(([label, fact]) => ` ${label} ${fact}`),
|
|
1383
|
+
].flatMap((line) => wrapCells(line, columns, { continuationPrefix: " " }));
|
|
1384
|
+
};
|
|
1364
1385
|
/**
|
|
1365
1386
|
* The compact form a statusline calls, so journal interpretation happens ONCE, inside the product.
|
|
1366
1387
|
* (An operator-local bash reimplementation counted `tip-verify` events instead of reading the
|
|
@@ -1396,7 +1417,9 @@ export async function status(argv, cwd = process.cwd(), opts = {}) {
|
|
|
1396
1417
|
// The task-table frame owns its own two-row brand lockup; the four-row global banner would
|
|
1397
1418
|
// create the second header the approved replacement removes.
|
|
1398
1419
|
if (!argv.includes("--watch")) {
|
|
1399
|
-
|
|
1420
|
+
const frame = renderFrame(cwd, binaryVersion, opts.now?.() ?? Date.now(), 0, undefined, false, namedRunId);
|
|
1421
|
+
const wall = wallBudgetLines(frame.events);
|
|
1422
|
+
return wall.length ? [frame.content, "", ...wall].join("\n") : frame.content;
|
|
1400
1423
|
}
|
|
1401
1424
|
const eventStream = argv.some((arg) => arg === "--events" || arg === "--jsonl" || arg === "--decision-events");
|
|
1402
1425
|
const webhookUrl = opts.webhookUrl
|
|
@@ -1501,7 +1524,7 @@ export async function status(argv, cwd = process.cwd(), opts = {}) {
|
|
|
1501
1524
|
decisionRunId = runId;
|
|
1502
1525
|
journalCursor = 0;
|
|
1503
1526
|
}
|
|
1504
|
-
const journalEvents =
|
|
1527
|
+
const journalEvents = parseJournalText(readRunJournalRaw(cwd, runId, lockedRunId === runId));
|
|
1505
1528
|
if (journalEvents.length < journalCursor)
|
|
1506
1529
|
journalCursor = 0;
|
|
1507
1530
|
const fresh = decisionEventsFromJournal(journalEvents, runId, stateDirName(cwd))
|
package/dist/cli/help.d.ts
CHANGED
|
@@ -163,6 +163,8 @@ export declare const COMMAND_HELP: {
|
|
|
163
163
|
usage: string;
|
|
164
164
|
description: string;
|
|
165
165
|
options: {
|
|
166
|
+
"--park <line>@<ts>": string;
|
|
167
|
+
"--gate <gate>": string;
|
|
166
168
|
"--files <glob,\u2026>": string;
|
|
167
169
|
"--by <name>": string;
|
|
168
170
|
"--reason <text>": string;
|
package/dist/cli/help.js
CHANGED
|
@@ -131,14 +131,21 @@ export const COMMAND_HELP = {
|
|
|
131
131
|
approve: {
|
|
132
132
|
usage: "approve <run-id> <task-id> [options]", description: "Append a validated park decision. A running owner may enact it; a closed run requires a separate resume. Decisions cannot be undone.",
|
|
133
133
|
options: {
|
|
134
|
+
"--park <line>@<ts>": "Bind the decision to the park token status prints (a failed task's recheck binds its failure token); refused once a newer park opens.",
|
|
135
|
+
"--gate <gate>": "Bind a waive to the park's failed gate; refused when the park failed another gate.",
|
|
134
136
|
"--files <glob,…>": "Extend files[] for a scope-request park with comma-separated repository-relative globs; required for scope approval.",
|
|
135
137
|
"--by <name>": "Name the actor (default: current OS user).",
|
|
136
138
|
"--reason <text>": "Record the decision reason.",
|
|
137
139
|
"--waive": "Waive only the identified failed gate.",
|
|
138
140
|
"--uphold": "Uphold a review failure and fund a fixed attempt.",
|
|
139
141
|
"--recheck": "Request rechecking an infra or failed-gate park; satisfies no gate.",
|
|
140
|
-
"--review-rounds <N>": "Set a positive integer review-round ceiling
|
|
141
|
-
}, examples: [
|
|
142
|
+
"--review-rounds <N>": "Set a positive integer review-round ceiling that binds only this approval engagement; a later approval without --review-rounds restores the built-in default.",
|
|
143
|
+
}, examples: [
|
|
144
|
+
"approve run-example T1 --park 42@2026-09-26T08:00:00.000Z --by operator --reason 'ready to proceed'",
|
|
145
|
+
"approve run-example T1 --waive --park 42@2026-09-26T08:00:00.000Z --gate review",
|
|
146
|
+
"approve run-example T1 --recheck --park 42@2026-09-26T08:00:00.000Z --reason 'infra recovered'",
|
|
147
|
+
"approve run-example T3 --recheck --park 57@2026-09-26T08:05:00.000Z --reason 'failed task: re-gate its landed commits'",
|
|
148
|
+
],
|
|
142
149
|
},
|
|
143
150
|
beat: {
|
|
144
151
|
usage: "beat <orchestrator|orchestrator-context|overseer|overseer-context|watch> --seat <identity> [options]",
|
package/dist/compile/native.js
CHANGED
|
@@ -911,6 +911,13 @@ acceptance is required on every task (a nested list of observable outcomes).
|
|
|
911
911
|
- Enumerating one axis exhaustively is what hides the others. A spec that guards PARTIAL coverage
|
|
912
912
|
site-by-site, member-by-member, can be defeated wholesale by CONDITIONAL coverage, which leaves
|
|
913
913
|
every enumeration satisfied. After you enumerate, ask what a single flag would do to the whole set.
|
|
914
|
+
- A GOAL STATING AN INVARIANT OVER "every", "never" OR "any" CARRIES ITS CLOSED CASE TABLE AS A
|
|
915
|
+
CRITERION: the in-scope consumers, bridges, operations and event sequences it ranges over, each a
|
|
916
|
+
row a test exercises. An unenumerated invariant is repaired one edge per review round while every
|
|
917
|
+
round re-judges the accumulated diff (OBS-1019 add.2). The table binds author and reviewer, never a
|
|
918
|
+
worker's claim: each review material binds its declared class to the goal clause or criterion it
|
|
919
|
+
violates through input-to-consequence evidence marked executed, static or blocked. A worker's own
|
|
920
|
+
table alone never silences a real finding, and blocked evidence is never a pass.
|
|
914
921
|
- EVERY CRITERION NAMES THE PAIR IT DISCRIMINATES: the correct case that MUST PASS, and the
|
|
915
922
|
neighbouring plausible-wrong or false-clean case that MUST FAIL. Two easy examples that both pass are
|
|
916
923
|
not discrimination — they are two ways of being green, and the wrong half is the whole point: it is
|
package/dist/config/config.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { type Effort } from "../graph/schema.js";
|
|
2
3
|
declare const TierEnum: z.ZodEnum<{
|
|
3
4
|
cheap: "cheap";
|
|
4
5
|
mid: "mid";
|
|
@@ -41,6 +42,7 @@ export declare const MapEntrySchema: z.ZodObject<{
|
|
|
41
42
|
escalate: z.ZodOptional<z.ZodBoolean>;
|
|
42
43
|
}, z.core.$strip>;
|
|
43
44
|
export type MapEntry = z.infer<typeof MapEntrySchema>;
|
|
45
|
+
export declare const EFFORT_ADAPTERS: readonly ["claude-code", "codex"];
|
|
44
46
|
export declare const TierEntrySchema: z.ZodObject<{
|
|
45
47
|
vendor: z.ZodNullable<z.ZodString>;
|
|
46
48
|
channel: z.ZodEnum<{
|
|
@@ -58,6 +60,11 @@ export declare const TierEntrySchema: z.ZodObject<{
|
|
|
58
60
|
sub: "sub";
|
|
59
61
|
api: "api";
|
|
60
62
|
}>>;
|
|
63
|
+
effort: z.ZodOptional<z.ZodEnum<{
|
|
64
|
+
low: "low";
|
|
65
|
+
medium: "medium";
|
|
66
|
+
high: "high";
|
|
67
|
+
}>>;
|
|
61
68
|
}, z.core.$strip>>>;
|
|
62
69
|
windows: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
|
|
63
70
|
}, z.core.$strip>;
|
|
@@ -174,13 +181,13 @@ export declare function effectiveReviewPolicy(files: ReadonlyArray<string>, revi
|
|
|
174
181
|
export declare function criticalPathHits(files: ReadonlyArray<string>, criticalPaths?: ReadonlyArray<string>): string[];
|
|
175
182
|
export declare const ExecutionPolicySchema: z.ZodObject<{
|
|
176
183
|
boundedInfrastructure: z.ZodDefault<z.ZodBoolean>;
|
|
177
|
-
repairSelection: z.
|
|
184
|
+
repairSelection: z.ZodOptional<z.ZodBoolean>;
|
|
178
185
|
taskExecutionLimitMs: z.ZodNumber;
|
|
179
186
|
}, z.core.$strict>;
|
|
180
187
|
export declare const TickmarkrConfigSchema: z.ZodObject<{
|
|
181
188
|
executionPolicy: z.ZodOptional<z.ZodObject<{
|
|
182
189
|
boundedInfrastructure: z.ZodDefault<z.ZodBoolean>;
|
|
183
|
-
repairSelection: z.
|
|
190
|
+
repairSelection: z.ZodOptional<z.ZodBoolean>;
|
|
184
191
|
taskExecutionLimitMs: z.ZodNumber;
|
|
185
192
|
}, z.core.$strict>>;
|
|
186
193
|
concurrency: z.ZodNumber;
|
|
@@ -274,6 +281,11 @@ export declare const TickmarkrConfigSchema: z.ZodObject<{
|
|
|
274
281
|
sub: "sub";
|
|
275
282
|
api: "api";
|
|
276
283
|
}>>;
|
|
284
|
+
effort: z.ZodOptional<z.ZodEnum<{
|
|
285
|
+
low: "low";
|
|
286
|
+
medium: "medium";
|
|
287
|
+
high: "high";
|
|
288
|
+
}>>;
|
|
277
289
|
}, z.core.$strip>>>;
|
|
278
290
|
windows: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
|
|
279
291
|
}, z.core.$strip>>;
|
|
@@ -297,6 +309,8 @@ export declare const TickmarkrConfigSchema: z.ZodObject<{
|
|
|
297
309
|
tipTest: z.ZodOptional<z.ZodString>;
|
|
298
310
|
lint: z.ZodOptional<z.ZodString>;
|
|
299
311
|
diffCap: z.ZodOptional<z.ZodNumber>;
|
|
312
|
+
evidenceQuotaBytes: z.ZodOptional<z.ZodNumber>;
|
|
313
|
+
repairSelection: z.ZodOptional<z.ZodBoolean>;
|
|
300
314
|
byShape: z.ZodOptional<z.ZodOptional<z.ZodRecord<z.ZodEnum<{
|
|
301
315
|
plan: "plan";
|
|
302
316
|
spec: "spec";
|
|
@@ -362,6 +376,7 @@ export type TickmarkrConfig = z.infer<typeof TickmarkrConfigSchema>;
|
|
|
362
376
|
export declare class ConfigError extends Error {
|
|
363
377
|
constructor(message: string);
|
|
364
378
|
}
|
|
379
|
+
export declare const DEFAULT_EVIDENCE_QUOTA_BYTES: number;
|
|
365
380
|
export declare const DEFAULT_CONFIG: TickmarkrConfig;
|
|
366
381
|
export declare function globalConfigDir(): string;
|
|
367
382
|
export declare function overlayPreferShapes(repoRoot: string, opts?: {
|
|
@@ -383,6 +398,22 @@ export declare function loadConfigWithMode(repoRoot: string, opts?: {
|
|
|
383
398
|
cfg: TickmarkrConfig;
|
|
384
399
|
mode: ModeResolution;
|
|
385
400
|
};
|
|
401
|
+
/** OBS-1182: tiers.<adapter>.modelOverrides as the layers under the repo overlay resolve them. */
|
|
402
|
+
export type LowerLayerModelOverrides = Record<string, Record<string, Record<string, unknown>>>;
|
|
403
|
+
/** OBS-1182: tiers.<adapter>.modelOverrides as the layers under the repo overlay (defaults + global)
|
|
404
|
+
* merge them. Read raw, never schema-validated: a lower layer may only validate beside repo fields
|
|
405
|
+
* (global `vendor: null` completed by a repo vendor) and its override metadata is still inherited.
|
|
406
|
+
* OBS-1188: an unparseable global file, or a non-map where this path expects a map, is an error —
|
|
407
|
+
* never an empty lower layer, which would silently drop the metadata a write must re-mask. */
|
|
408
|
+
export declare function lowerLayerModelOverrides(opts?: {
|
|
409
|
+
globalDir?: string;
|
|
410
|
+
}): {
|
|
411
|
+
ok: true;
|
|
412
|
+
overrides: LowerLayerModelOverrides;
|
|
413
|
+
} | {
|
|
414
|
+
ok: false;
|
|
415
|
+
error: string;
|
|
416
|
+
};
|
|
386
417
|
export declare function loadConfig(repoRoot: string, opts?: {
|
|
387
418
|
globalDir?: string;
|
|
388
419
|
}): TickmarkrConfig;
|
|
@@ -416,6 +447,8 @@ export type FleetEditable = Partial<Record<DenyScopeKey, string[]>> & {
|
|
|
416
447
|
denyModels: string[];
|
|
417
448
|
allowOut?: string[];
|
|
418
449
|
tiers: Record<string, Record<string, FleetTierAssignment | null>>;
|
|
450
|
+
/** OBS-1182: tiers.<adapter>.modelOverrides.<model>.effort — absent = CLI default; present only when one is set */
|
|
451
|
+
efforts?: Record<string, Record<string, Effort>>;
|
|
419
452
|
map: Record<string, MapEntry>;
|
|
420
453
|
floors: Record<string, Tier>;
|
|
421
454
|
};
|