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.
Files changed (87) hide show
  1. package/README.md +4 -2
  2. package/dist/adapters/catalog-remote.d.ts +64 -0
  3. package/dist/adapters/catalog-remote.js +287 -0
  4. package/dist/adapters/catalog.d.ts +96 -0
  5. package/dist/adapters/catalog.js +176 -0
  6. package/dist/adapters/claude-code.d.ts +1 -0
  7. package/dist/adapters/claude-code.js +59 -1
  8. package/dist/adapters/fake.js +42 -4
  9. package/dist/adapters/model-lints.d.ts +25 -5
  10. package/dist/adapters/model-lints.js +184 -50
  11. package/dist/adapters/model-windows.d.ts +31 -0
  12. package/dist/adapters/model-windows.js +69 -0
  13. package/dist/adapters/prompt.d.ts +5 -1
  14. package/dist/adapters/prompt.js +13 -4
  15. package/dist/adapters/registry.d.ts +25 -26
  16. package/dist/adapters/registry.js +173 -110
  17. package/dist/adapters/types.d.ts +3 -0
  18. package/dist/adapters/types.js +36 -3
  19. package/dist/brand.d.ts +5 -1
  20. package/dist/brand.js +18 -2
  21. package/dist/cli/commands/doctor.d.ts +3 -0
  22. package/dist/cli/commands/doctor.js +43 -21
  23. package/dist/cli/commands/fleet.d.ts +7 -0
  24. package/dist/cli/commands/fleet.js +94 -74
  25. package/dist/cli/commands/init.js +118 -5
  26. package/dist/cli/commands/status.js +202 -46
  27. package/dist/compile/collateral.d.ts +86 -2
  28. package/dist/compile/collateral.js +294 -3
  29. package/dist/compile/gsd.d.ts +2 -1
  30. package/dist/compile/gsd.js +68 -2
  31. package/dist/compile/native.d.ts +14 -0
  32. package/dist/compile/native.js +161 -12
  33. package/dist/config/config.d.ts +82 -5
  34. package/dist/config/config.js +253 -66
  35. package/dist/config/fleet-overlay.d.ts +25 -20
  36. package/dist/config/fleet-overlay.js +195 -77
  37. package/dist/config/fleet-why.d.ts +23 -0
  38. package/dist/config/fleet-why.js +42 -0
  39. package/dist/drivers/herdr.d.ts +21 -3
  40. package/dist/drivers/herdr.js +344 -110
  41. package/dist/gates/acceptance.js +7 -2
  42. package/dist/gates/baseline.d.ts +1 -0
  43. package/dist/gates/baseline.js +91 -13
  44. package/dist/gates/llm.d.ts +0 -1
  45. package/dist/gates/llm.js +5 -30
  46. package/dist/gates/review.d.ts +9 -1
  47. package/dist/gates/review.js +105 -10
  48. package/dist/gates/run-gates.d.ts +9 -0
  49. package/dist/gates/run-gates.js +285 -41
  50. package/dist/gates/verdict-cause.d.ts +4 -0
  51. package/dist/gates/verdict-cause.js +63 -0
  52. package/dist/graph/schema.d.ts +6 -0
  53. package/dist/graph/schema.js +8 -5
  54. package/dist/route/router.d.ts +0 -5
  55. package/dist/route/router.js +16 -20
  56. package/dist/run/consult.d.ts +6 -0
  57. package/dist/run/consult.js +35 -25
  58. package/dist/run/daemon.d.ts +48 -2
  59. package/dist/run/daemon.js +1488 -330
  60. package/dist/run/journal.d.ts +56 -3
  61. package/dist/run/journal.js +358 -4
  62. package/dist/run/stall.d.ts +35 -1
  63. package/dist/run/stall.js +118 -8
  64. package/dist/tui/cockpit/capture.d.ts +12 -0
  65. package/dist/tui/cockpit/capture.js +37 -1
  66. package/dist/tui/cockpit/components.d.ts +2 -0
  67. package/dist/tui/cockpit/components.js +8 -8
  68. package/dist/tui/cockpit/derive.d.ts +29 -2
  69. package/dist/tui/cockpit/derive.js +219 -23
  70. package/dist/tui/cockpit/run-cockpit.js +128 -27
  71. package/dist/tui/cockpit/theme.d.ts +32 -26
  72. package/dist/tui/cockpit/theme.js +11 -5
  73. package/dist/tui/ink/components.d.ts +0 -15
  74. package/dist/tui/ink/components.js +0 -17
  75. package/dist/tui/ink/fleet-app.d.ts +4 -1
  76. package/dist/tui/ink/fleet-app.js +134 -13
  77. package/fixtures/sample.native.md +1 -1
  78. package/package.json +1 -1
  79. package/skills/tickmarkr-overseer/SKILL.md +354 -34
  80. package/skills/tickmarkr-overseer/scripts/watch-artifacts.sh +70 -0
  81. package/skills/tickmarkr-overseer/scripts/watch-panes.sh +1 -1
  82. package/dist/tui/ink/studio-app.d.ts +0 -59
  83. package/dist/tui/ink/studio-app.js +0 -320
  84. package/dist/tui/save.d.ts +0 -38
  85. package/dist/tui/save.js +0 -96
  86. package/dist/tui/staging.d.ts +0 -29
  87. package/dist/tui/staging.js +0 -78
@@ -1,7 +1,61 @@
1
+ import { spawnSync } from "node:child_process";
1
2
  import { existsSync, readFileSync } from "node:fs";
3
+ import { basename, dirname } from "node:path";
2
4
  import picomatch from "picomatch";
3
5
  import { GATE_NAMES, GRAPH_ROUTING_MODES, ORACLES, SHAPES, TIERS, validateGraph } from "../graph/schema.js";
4
6
  import { CompileError, inferShape, sha256 } from "./common.js";
7
+ // OBS-170/OBS-184: `context:` is a promise to the worker, and nothing ever checked it could be kept.
8
+ // Workers run in `git worktree add <baseRef>` (run/git.ts:169-176), which materialises the base
9
+ // TREE and copies nothing in — so the oracle is that tree, and NEVER two things that look like it:
10
+ // · existsSync reads the AUTHOR's checkout. Measured on v1.85: 10 of 16 unreachable entries are
11
+ // plainly present there (.overseer/ is 21 tracked files out of 480 on disk).
12
+ // · `git ls-files` reads the INDEX. `git add -f` writes the index, so a staged-uncommitted file
13
+ // reports present while still being invisible to every worker — the lint would then certify
14
+ // exactly the failure it exists to catch (RULING 2026-08-03 rider; the absent-vs-empty shape
15
+ // again, cf. fileBytes). Hence the opt-in is "add AND commit", and the resolver reads HEAD.
16
+ // `ls-tree -r HEAD` enumerates that tree once; per-path `cat-file -e HEAD:<path>` is the same oracle
17
+ // one path at a time, and one spawn beats N.
18
+ function trackedAtHead(dir) {
19
+ const git = (...args) => spawnSync("git", ["-C", dir, ...args], { encoding: "utf8", maxBuffer: 1 << 28 });
20
+ const top = git("rev-parse", "--show-toplevel");
21
+ // --full-tree is load-bearing: without it `ls-tree` scopes to the CWD's subtree and prints paths
22
+ // relative to it, so a spec in specs/ would see only specs/-relative names and misjudge every
23
+ // path outside it. Caught only by a fixture whose spec is NOT at the repo root.
24
+ const r = git("ls-tree", "--full-tree", "-r", "--name-only", "HEAD");
25
+ // not a repo, or no commits yet — fail open, so non-repo fixtures and fresh inits stay silent
26
+ if (top.status !== 0 || r.status !== 0 || typeof r.stdout !== "string")
27
+ return undefined;
28
+ return { root: top.stdout.trim(), tracked: new Set(r.stdout.split("\n").filter(Boolean)) };
29
+ }
30
+ const GLOB_CHARS = /[*?{[]/;
31
+ /**
32
+ * Classify one `context:` entry against the tree a worker's worktree is built from.
33
+ * ok — reachable
34
+ * untracked — in the author's checkout, invisible to every worker. WARNS, permanently: per-file
35
+ * force-add is the deliberate opt-in, so this is a standing prompt, not a migration.
36
+ * missing — absent from both. FAILS compile; carries a repair when exactly one tracked file
37
+ * shares the basename (ambiguous matches suggest nothing rather than guess).
38
+ * RULING 2026-08-03 (.overseer/OVERSEER-RULING-20260803-context-visibility.md): OBS-170's
39
+ * fail-closed-on-untracked is retired — oracle upheld, policy overruled (it blocked 11 of 13).
40
+ */
41
+ export function classifyContextPath(entry, tracked, repoDir) {
42
+ if (GLOB_CHARS.test(entry))
43
+ return { kind: "ok" }; // a glob is not a promise about one file
44
+ const q = entry.replace(/\/+$/, "");
45
+ if (tracked.has(q))
46
+ return { kind: "ok" };
47
+ for (const t of tracked)
48
+ if (t.startsWith(`${q}/`))
49
+ return { kind: "ok" }; // directory with tracked children
50
+ if (existsSync(`${repoDir}/${q}`))
51
+ return { kind: "untracked" };
52
+ // Measured: 5 of v1.85's 6 absent entries are a tracked file written without its directory
53
+ // (`V185-SEEDS.md` for `.overseer/V185-SEEDS.md`) — the opt-in had worked, only the check was
54
+ // missing. Suggest ONLY on an unambiguous basename: two candidates is a guess, not a repair.
55
+ const base = basename(q);
56
+ const hits = [...tracked].filter((t) => basename(t) === base);
57
+ return hits.length === 1 ? { kind: "missing", suggestion: hits[0] } : { kind: "missing" };
58
+ }
5
59
  // OBS-97: mirror of the vitest.config.ts suite include — the only path class vitest collects.
6
60
  export const COLLECTABLE_TESTS = "tests/**/*.test.ts";
7
61
  export const LEGACY_PREFIX = ["dro", "vr"].join("");
@@ -31,7 +85,7 @@ export function compileNative(file) {
31
85
  for (const line of content.split("\n")) {
32
86
  const heading = line.match(HEAD_RE);
33
87
  if (heading) {
34
- drafts.push({ id: heading[1], title: heading[2].trim(), fields: {}, acceptance: [], gates: [], hasGates: false, list: null, goalContinuation: false });
88
+ drafts.push({ id: heading[1], title: heading[2].trim(), fields: {}, acceptance: [], gates: [], hasGates: false, list: null, continuationField: null });
35
89
  continue;
36
90
  }
37
91
  const draft = drafts.at(-1);
@@ -59,29 +113,42 @@ export function compileNative(file) {
59
113
  draft.list = name;
60
114
  if (name === "gates")
61
115
  draft.hasGates = true;
62
- draft.goalContinuation = false;
116
+ draft.continuationField = null;
63
117
  }
64
118
  else {
65
119
  if (name === "goal" && !value)
66
120
  invalid(draft.id, field[1], "must not be empty");
67
121
  draft.fields[name] = value;
68
- // OBS-60: carry every indented continuation line after `- goal:` into the compiled goal.
69
- draft.goalContinuation = name === "goal";
122
+ // OBS-60/OBS-308: carry wrapped physical lines for prose and comma-separated fields. A
123
+ // worker must receive the whole logical field, regardless of the author's editor width.
124
+ draft.continuationField = name === "goal" || name === "deps" || name === "files" || name === "context"
125
+ ? name
126
+ : null;
70
127
  draft.list = null;
71
128
  }
72
129
  continue;
73
130
  }
74
- // OBS-60: multiline goals — indented prose after `- goal:` is appended until the next field.
75
- if (draft.goalContinuation) {
131
+ // OBS-60/OBS-308: indented physical lines continue the preceding logical field until the next
132
+ // field/list/heading. List continuations join with a space so the existing comma parser sees the
133
+ // exact same value it would have received on one line.
134
+ if (draft.continuationField) {
135
+ const continuation = draft.continuationField;
76
136
  if ((line.startsWith(" ") || line.startsWith("\t")) && !NESTED_RE.test(line)) {
77
- draft.fields.goal += `\n${line.trim()}`;
78
- continue;
137
+ const value = line.trim();
138
+ if (continuation === "goal") {
139
+ draft.fields.goal += `\n${value}`;
140
+ continue;
141
+ }
142
+ if (value) {
143
+ draft.fields[continuation] += ` ${value}`;
144
+ continue;
145
+ }
79
146
  }
80
- if (!line.trim()) {
147
+ if (continuation === "goal" && !line.trim()) {
81
148
  draft.fields.goal += "\n";
82
149
  continue;
83
150
  }
84
- draft.goalContinuation = false;
151
+ draft.continuationField = null;
85
152
  }
86
153
  const nested = line.match(NESTED_RE);
87
154
  if (nested && draft.list) {
@@ -124,12 +191,16 @@ export function compileNative(file) {
124
191
  }
125
192
  // v1.62 (OBS-97): commas inside {a,b} alternatives are part of one glob entry, not separators —
126
193
  // split only at brace depth 0 so a brace glob reaches the lint (and the scope gate) intact.
194
+ // OBS-170/OBS-184: the same is true of a citation annotation. `.planning/OBSERVATIONS.md (OBS-262,
195
+ // OBS-263)` split at the inner comma into two bullets, and workers were dispatched a context line
196
+ // reading `OBS-263)`. Track paren depth alongside brace depth for exactly the OBS-97 reason.
127
197
  const splitTop = (value) => {
128
198
  const parts = [];
129
199
  let depth = 0;
200
+ let paren = 0;
130
201
  let current = "";
131
202
  for (const ch of value) {
132
- if (ch === "," && depth === 0) {
203
+ if (ch === "," && depth === 0 && paren === 0) {
133
204
  parts.push(current);
134
205
  current = "";
135
206
  continue;
@@ -138,11 +209,34 @@ export function compileNative(file) {
138
209
  depth++;
139
210
  else if (ch === "}" && depth > 0)
140
211
  depth--;
212
+ else if (ch === "(")
213
+ paren++;
214
+ else if (ch === ")" && paren > 0)
215
+ paren--;
141
216
  current += ch;
142
217
  }
143
218
  return [...parts, current];
144
219
  };
145
- const csv = (value) => (value && value.toLowerCase() !== "none" ? splitTop(value).map((item) => item.trim()).filter(Boolean) : []);
220
+ // Keeping the comma is only half of it: the merged entry still carries the annotation, so it
221
+ // resolves no better than the fragments did (measured: 24 → 19 unresolvable, zero rescued).
222
+ // Strip one trailing parenthetical so the entry is the path its author meant.
223
+ const stripAnnotation = (item) => {
224
+ const trimmed = item.trim();
225
+ if (!trimmed.endsWith(")"))
226
+ return trimmed;
227
+ let paren = 0;
228
+ for (let i = trimmed.length - 1; i >= 0; i--) {
229
+ if (trimmed[i] === ")")
230
+ paren++;
231
+ else if (trimmed[i] === "(") {
232
+ paren--;
233
+ if (paren === 0)
234
+ return trimmed.slice(0, i).trim();
235
+ }
236
+ }
237
+ return trimmed;
238
+ };
239
+ const csv = (value) => value && value.toLowerCase() !== "none" ? splitTop(value).map((item) => stripAnnotation(item.trim())).filter(Boolean) : [];
146
240
  // OBS-97: a typed test: oracle needs a collectable home. vitest only collects COLLECTABLE_TESTS
147
241
  // paths, so a task whose non-empty files[] cannot host one makes scope-green and acceptance-green
148
242
  // mutually exclusive by construction — run-20260719-210434 burned two dispatch attempts before a
@@ -281,6 +375,31 @@ export function compileNative(file) {
281
375
  }
282
376
  }
283
377
  }
378
+ // OBS-170/OBS-184: warn per unreachable context: entry, classified by the action its author must
379
+ // take. Warn-only by operator ruling (2026-08-03) — fail-closed would have refused to compile the
380
+ // spec that shipped green. Fails open when git cannot answer, so non-repo fixtures stay silent.
381
+ const repo = trackedAtHead(dirname(file));
382
+ if (repo) {
383
+ const unreachable = [];
384
+ for (const t of tasks) {
385
+ for (const entry of t.context) {
386
+ const { kind, suggestion } = classifyContextPath(entry, repo.tracked, repo.root);
387
+ if (kind === "untracked") {
388
+ // Permanent, by ruling: the per-file force-add IS the opt-in, so this stays a standing
389
+ // prompt. "and commit" is load-bearing — staging alone leaves it out of the base tree.
390
+ console.warn(`tickmarkr: OBS-170: task ${t.id} context ${JSON.stringify(entry)} exists in your checkout but is NOT in a worker's worktree. To make it worker context: git add -f ${entry} && git commit. Staging alone is not enough.`);
391
+ }
392
+ else if (kind === "missing") {
393
+ unreachable.push(` ${t.id}: ${JSON.stringify(entry)}${suggestion ? `\n → did you mean ${suggestion} ?` : "\n → not found in the repository, and not a path."}`);
394
+ }
395
+ }
396
+ }
397
+ if (unreachable.length) {
398
+ throw new CompileError(`context: paths that do not exist in ${file}:\n${unreachable.join("\n")}\n\n` +
399
+ `A context: entry is a promise the worker can read it. These resolve to nothing in the repository,\n` +
400
+ `so the worker would be told to read a file that is not there. Fix the paths and recompile.`);
401
+ }
402
+ }
284
403
  return result;
285
404
  }
286
405
  // Commented native spec written to tickmarkr.spec.md by `tickmarkr init`. Documented via HTML comments (which the
@@ -319,6 +438,36 @@ acceptance is required on every task (a nested list of observable outcomes).
319
438
  - test: <name> (oracle: test — named test)
320
439
  - judge: <rubric> (oracle: judge — LLM-judged, free text)
321
440
  - <plain text> (compat: compiles as judge oracle, warns)
441
+
442
+ HARD BOUNDS — these FAIL the compile, they do not warn:
443
+ - at most 6 acceptance items per task (no exception path)
444
+ - at most 8 files[] patterns; a {a,b} brace group counts as ONE pattern
445
+ - acceptance items x files[] patterns must be <= 24 ("surface")
446
+ - at most 60 goal words per acceptance criterion ("density")
447
+ Density is goal words DIVIDED BY items, so REMOVING a criterion RAISES it. Compress the goal in the
448
+ same edit, or a task that was comfortably inside the bound breaches it while you are tidying up.
449
+
450
+ WHAT MAKES A CRITERION REAL:
451
+ - "test:" must name a real test asserting on recorded state, journal lines, or drawn frames, and its
452
+ title must match the criterion string verbatim. It also needs a collectable test path in files[].
453
+ - NO criterion may be satisfiable by an absence, a rename, a source-text grep, or an empty collection.
454
+ "no file references X" is not a criterion — it passes in a repo where the feature was never built.
455
+ - "goal:" is NEVER verification. Prose in the goal enforces nothing: every obligation needs an
456
+ acceptance item, or a NAMED independent gate that actually applies. A gate is not independent of a
457
+ task that owns both the thing it checks and the fixture it checks against.
458
+ - A source-only obligation (a comment or doc that a change makes false) has no lawful "test:" — verify
459
+ it with "judge:", which reads the DIFF and must cite a changed line in a file the task owns.
460
+
461
+ ORDERING AND OWNERSHIP:
462
+ - Every path has exactly ONE owning task. Two tasks writing one file must be ORDERED by deps, or the
463
+ loser's work is silently dropped when the integration tip advances.
464
+ - A file one task CREATES cannot be "context:" for another — only deps: carries it, and that extends to
465
+ the task that PRODUCES a value, not just the file's existence.
466
+ - Deleting or renaming a symbol is a cross-task contract. Sweep for consumers by symbol AND by what the
467
+ mechanism DOES for them; the most dangerous consumer is a file no task owns, because nothing fixes it
468
+ and no edge can order it.
469
+
470
+ "timeout:" IS A KILL CEILING, NOT AN ESTIMATE. Never read a sum of timeouts as a predicted duration.
322
471
  -->
323
472
 
324
473
  ## T1: Scaffold the feature
@@ -7,6 +7,7 @@ declare const TierEnum: z.ZodEnum<{
7
7
  export type Tier = z.infer<typeof TierEnum>;
8
8
  export declare const TIER_RANK: Record<Tier, number>;
9
9
  export declare const DEFAULT_DIFF_CAP = 60000;
10
+ export declare const MODEL_WINDOW_PLAUSIBILITY_FLOOR = 1024;
10
11
  export declare const ROUTING_MODES: readonly ["partner-led", "risk-based", "staff-led"];
11
12
  declare const ModeEnum: z.ZodEnum<{
12
13
  "partner-led": "partner-led";
@@ -26,7 +27,7 @@ export declare const MapEntrySchema: z.ZodObject<{
26
27
  }, z.core.$strip>;
27
28
  export type MapEntry = z.infer<typeof MapEntrySchema>;
28
29
  export declare const TierEntrySchema: z.ZodObject<{
29
- vendor: z.ZodString;
30
+ vendor: z.ZodNullable<z.ZodString>;
30
31
  channel: z.ZodEnum<{
31
32
  sub: "sub";
32
33
  api: "api";
@@ -36,6 +37,13 @@ export declare const TierEntrySchema: z.ZodObject<{
36
37
  mid: "mid";
37
38
  frontier: "frontier";
38
39
  }>>;
40
+ modelOverrides: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
41
+ vendor: z.ZodOptional<z.ZodString>;
42
+ channel: z.ZodOptional<z.ZodEnum<{
43
+ sub: "sub";
44
+ api: "api";
45
+ }>>;
46
+ }, z.core.$strip>>>;
39
47
  windows: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
40
48
  }, z.core.$strip>;
41
49
  export type TierEntry = z.infer<typeof TierEntrySchema>;
@@ -52,6 +60,63 @@ export declare const SubPricingSchema: z.ZodObject<{
52
60
  windowsPerMonthHigh: z.ZodNumber;
53
61
  }, z.core.$strip>;
54
62
  export type SubPricing = z.infer<typeof SubPricingSchema>;
63
+ export declare const REVIEW_POLICIES: readonly ["full", "judge-only"];
64
+ export type ReviewPolicy = (typeof REVIEW_POLICIES)[number];
65
+ /**
66
+ * The leaf class: work a judge alone can carry because there is no behaviour to review. It is CLOSED
67
+ * and enumerated here — documentation pages, the changelog, the release runbook, and the version
68
+ * mirrors a release bump rewrites. Everything else, including tests and scripts, is full-review work.
69
+ */
70
+ export declare const REVIEW_LEAF_GLOBS: readonly ["docs/**", "CHANGELOG.md", "RELEASING.md", "package.json", "package-lock.json"];
71
+ /**
72
+ * The version mirrors a release bump rewrites — and ONLY at the repo root: `docs/package.json` is a
73
+ * real manifest for something, not a mirror. Membership here is still a claim about paths, and a
74
+ * manifest diff that moves more than the version field leaves the class; the gate re-tests that
75
+ * against the actual diff content (src/gates/review.ts) because a path cannot say it by itself.
76
+ */
77
+ export declare const REVIEW_VERSION_MIRRORS: ReadonlySet<string>;
78
+ /**
79
+ * Paths whose review may never be skipped (R3): the gate/run/driver/adapter surface — lifecycle,
80
+ * demux, and everything that reaches a shell. Shipped as the default so the compile lint that
81
+ * enforces them is live in a repo that never configures anything.
82
+ */
83
+ export declare const DEFAULT_REVIEW_CRITICAL_PATHS: readonly ["src/gates/**", "src/run/**", "src/drivers/**", "src/adapters/**"];
84
+ /**
85
+ * True when `path` — an ACTUAL path a diff touched — is provably leaf-class work. Enumerated, closed,
86
+ * and positive: the two named root files, the two root version mirrors, and documentation formats
87
+ * under `docs/`. Everything else, including a `.vue`/`.svelte`/`Makefile`/`package.json` parked under
88
+ * `docs/`, is behaviour and draws a full review.
89
+ */
90
+ export declare function isReviewLeafPath(path: string): boolean;
91
+ /**
92
+ * True when a DECLARED pattern may claim leaf work. A declaration is a claim about a directory, not
93
+ * a file: `docs/**` is how a documentation task declares itself and has to stay claimable, so a glob
94
+ * rooted at `docs/` reads leaf here while `docs/tool.ts` does not read leaf above. The claim is never
95
+ * the last word — reviewGate re-tests it against what the diff actually touched and promotes when the
96
+ * diff leaves the class. A glob rooted anywhere else can claim nothing.
97
+ */
98
+ export declare function isReviewLeafDeclaration(pattern: string): boolean;
99
+ /** full beats judge-only. The join is monotone by construction: nothing here can LOWER a policy. */
100
+ export declare function raiseReviewPolicy(...policies: ReadonlyArray<ReviewPolicy | undefined>): ReviewPolicy;
101
+ /**
102
+ * The compiler's assignment, from the task's DECLARED files[]. judge-only only when EVERY declared
103
+ * path is provably leaf work — one non-leaf pattern (or an empty files[]) makes it full.
104
+ */
105
+ export declare function declaredReviewPolicy(files: ReadonlyArray<string>): ReviewPolicy;
106
+ /** The declared assignment after the operator's floor is applied — config may raise it, never lower it. */
107
+ export declare function effectiveReviewPolicy(files: ReadonlyArray<string>, review: {
108
+ policy?: ReviewPolicy;
109
+ }): ReviewPolicy;
110
+ /**
111
+ * Declared patterns that intersect a critical path. Directional containment is the certain half — a
112
+ * critical `src/gates/**` contains a declared `src/gates/review.ts`, and a declared `docs/**` contains
113
+ * a critical `docs/security/**`. It is NOT intersection: two globs can overlap without either matching
114
+ * the other as a literal string, which is how a judge-only task declaring `docs/**\/README.md` slipped
115
+ * past a critical `docs/security/*.md`. When BOTH sides are globs the conservative overlap test above
116
+ * decides, and it decides toward flagging — an over-flagged task is narrowed by its author, an
117
+ * under-flagged one skips the only gate that exists for it.
118
+ */
119
+ export declare function criticalPathHits(files: ReadonlyArray<string>, criticalPaths?: ReadonlyArray<string>): string[];
55
120
  export declare const TickmarkrConfigSchema: z.ZodObject<{
56
121
  concurrency: z.ZodNumber;
57
122
  driver: z.ZodEnum<{
@@ -116,7 +181,7 @@ export declare const TickmarkrConfigSchema: z.ZodObject<{
116
181
  }, z.core.$strip>>;
117
182
  }, z.core.$strip>;
118
183
  tiers: z.ZodRecord<z.ZodString, z.ZodObject<{
119
- vendor: z.ZodString;
184
+ vendor: z.ZodNullable<z.ZodString>;
120
185
  channel: z.ZodEnum<{
121
186
  sub: "sub";
122
187
  api: "api";
@@ -126,6 +191,13 @@ export declare const TickmarkrConfigSchema: z.ZodObject<{
126
191
  mid: "mid";
127
192
  frontier: "frontier";
128
193
  }>>;
194
+ modelOverrides: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
195
+ vendor: z.ZodOptional<z.ZodString>;
196
+ channel: z.ZodOptional<z.ZodEnum<{
197
+ sub: "sub";
198
+ api: "api";
199
+ }>>;
200
+ }, z.core.$strip>>>;
129
201
  windows: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
130
202
  }, z.core.$strip>>;
131
203
  pricing: z.ZodRecord<z.ZodString, z.ZodNumber>;
@@ -173,6 +245,11 @@ export declare const TickmarkrConfigSchema: z.ZodObject<{
173
245
  complexityThreshold: z.ZodNumber;
174
246
  required: z.ZodBoolean;
175
247
  prefer: z.ZodOptional<z.ZodArray<z.ZodString>>;
248
+ policy: z.ZodOptional<z.ZodEnum<{
249
+ full: "full";
250
+ "judge-only": "judge-only";
251
+ }>>;
252
+ criticalPaths: z.ZodOptional<z.ZodArray<z.ZodString>>;
176
253
  }, z.core.$strip>;
177
254
  consult: z.ZodObject<{
178
255
  adapter: z.ZodString;
@@ -239,8 +316,8 @@ export type InitConfigOverlay = {
239
316
  };
240
317
  };
241
318
  export declare function configTemplate(overlay?: InitConfigOverlay): string;
242
- export { FLEET_OVERLAY_KEYS, fleetEditableEquals, fleetRepoOverlayFromDelta, harvestFleetProvenance, repoOverlayYaml, serializeFleetOverlay, unifiedYamlDiff, } from "./fleet-overlay.js";
243
- export type { FleetDenyNotes, HarvestedProvenance } from "./fleet-overlay.js";
319
+ export { FLEET_OVERLAY_KEYS, fleetEditableEquals, fleetRepoOverlayFromDelta, renderFleetOverlayWrite, repoOverlayYaml, serializeFleetOverlay, unifiedYamlDiff, } from "./fleet-overlay.js";
320
+ export type { FleetOverlayWrite } from "./fleet-overlay.js";
244
321
  export type FleetTierAssignment = {
245
322
  tier: Tier;
246
323
  provenance?: string;
@@ -254,7 +331,7 @@ export type FleetEditable = {
254
331
  };
255
332
  export declare function repoOverlayPath(repoRoot: string): string;
256
333
  export declare function readOverlayFile(path: string): Record<string, unknown>;
257
- export declare function fleetEditableFromConfig(cfg: TickmarkrConfig, provenance?: Record<string, Record<string, string>>): FleetEditable;
334
+ export declare function fleetEditableFromConfig(cfg: TickmarkrConfig): FleetEditable;
258
335
  /** Resolve which layer last set a dotted fleet path (defaults < global < repo). */
259
336
  export declare function fleetKeyLayer(repoRoot: string, dotted: string, opts?: {
260
337
  globalDir?: string;