tickmarkr 1.85.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 (71) 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.js +5 -3
  7. package/dist/adapters/fake.js +33 -4
  8. package/dist/adapters/model-lints.d.ts +25 -5
  9. package/dist/adapters/model-lints.js +184 -50
  10. package/dist/adapters/model-windows.d.ts +31 -0
  11. package/dist/adapters/model-windows.js +69 -0
  12. package/dist/adapters/prompt.d.ts +5 -1
  13. package/dist/adapters/prompt.js +13 -4
  14. package/dist/adapters/registry.d.ts +25 -26
  15. package/dist/adapters/registry.js +173 -110
  16. package/dist/adapters/types.js +15 -3
  17. package/dist/brand.d.ts +5 -1
  18. package/dist/brand.js +18 -2
  19. package/dist/cli/commands/doctor.d.ts +3 -0
  20. package/dist/cli/commands/doctor.js +43 -21
  21. package/dist/cli/commands/fleet.d.ts +7 -0
  22. package/dist/cli/commands/fleet.js +94 -74
  23. package/dist/cli/commands/init.js +118 -5
  24. package/dist/cli/commands/status.js +44 -18
  25. package/dist/compile/gsd.d.ts +2 -1
  26. package/dist/compile/gsd.js +68 -2
  27. package/dist/compile/native.d.ts +14 -0
  28. package/dist/compile/native.js +161 -12
  29. package/dist/config/config.d.ts +20 -5
  30. package/dist/config/config.js +96 -64
  31. package/dist/config/fleet-overlay.d.ts +25 -20
  32. package/dist/config/fleet-overlay.js +195 -77
  33. package/dist/config/fleet-why.d.ts +23 -0
  34. package/dist/config/fleet-why.js +42 -0
  35. package/dist/drivers/herdr.d.ts +1 -0
  36. package/dist/drivers/herdr.js +70 -19
  37. package/dist/gates/acceptance.js +7 -2
  38. package/dist/gates/llm.d.ts +0 -1
  39. package/dist/gates/llm.js +5 -30
  40. package/dist/gates/review.d.ts +2 -1
  41. package/dist/gates/review.js +9 -7
  42. package/dist/gates/verdict-cause.d.ts +4 -0
  43. package/dist/gates/verdict-cause.js +63 -0
  44. package/dist/graph/schema.d.ts +6 -0
  45. package/dist/graph/schema.js +8 -5
  46. package/dist/route/router.d.ts +0 -5
  47. package/dist/route/router.js +16 -20
  48. package/dist/run/consult.d.ts +6 -0
  49. package/dist/run/consult.js +35 -25
  50. package/dist/run/daemon.js +71 -15
  51. package/dist/run/journal.js +87 -7
  52. package/dist/tui/cockpit/capture.d.ts +12 -0
  53. package/dist/tui/cockpit/capture.js +37 -1
  54. package/dist/tui/cockpit/components.js +8 -8
  55. package/dist/tui/cockpit/theme.d.ts +32 -26
  56. package/dist/tui/cockpit/theme.js +11 -5
  57. package/dist/tui/ink/components.d.ts +0 -15
  58. package/dist/tui/ink/components.js +0 -17
  59. package/dist/tui/ink/fleet-app.d.ts +4 -1
  60. package/dist/tui/ink/fleet-app.js +134 -13
  61. package/fixtures/sample.native.md +1 -1
  62. package/package.json +1 -1
  63. package/skills/tickmarkr-overseer/SKILL.md +354 -34
  64. package/skills/tickmarkr-overseer/scripts/watch-artifacts.sh +70 -0
  65. package/skills/tickmarkr-overseer/scripts/watch-panes.sh +1 -1
  66. package/dist/tui/ink/studio-app.d.ts +0 -59
  67. package/dist/tui/ink/studio-app.js +0 -320
  68. package/dist/tui/save.d.ts +0 -38
  69. package/dist/tui/save.js +0 -96
  70. package/dist/tui/staging.d.ts +0 -29
  71. package/dist/tui/staging.js +0 -78
@@ -2,8 +2,9 @@ import { existsSync, readFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import picomatch from "picomatch";
5
- import { parse, stringify } from "yaml";
5
+ import { Document, parse } from "yaml";
6
6
  import { z } from "zod";
7
+ import { CITED_MODEL_WINDOWS } from "../adapters/model-windows.js";
7
8
  import { stateDirName } from "../graph/graph.js";
8
9
  import { SHAPES, TIERS } from "../graph/schema.js";
9
10
  const TierEnum = z.enum(TIERS, {
@@ -11,6 +12,8 @@ const TierEnum = z.enum(TIERS, {
11
12
  });
12
13
  export const TIER_RANK = { cheap: 0, mid: 1, frontier: 2 };
13
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;
14
17
  // v1.51 T1: routing.mode — a preset COMPILED INTO FLOORS at config load. The router never sees the
15
18
  // mode; it receives resolved floors only (the structural defense against the quality-silently-loses
16
19
  // class — no fourth runtime authority, no new precedence key).
@@ -39,12 +42,36 @@ export const MapEntrySchema = z.object({
39
42
  prefer: z.array(z.string()).optional(),
40
43
  escalate: z.boolean().optional(),
41
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
+ });
42
54
  export const TierEntrySchema = z.object({
43
- vendor: z.string(),
55
+ vendor: DeclaredVendorSchema.nullable(),
44
56
  channel: z.enum(["sub", "api"]),
45
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(),
46
61
  // v1.47 T3: optional per-model context-window sizes (tokens). Absent block ⇒ no doctor column, no plan lint.
47
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
+ }
48
75
  });
49
76
  // v1.10 FLEET-06: optional routing.allow/deny fleet preference; absent blocks ⇒ byte-identical routing/discovery.
50
77
  // deny wins on conflict; presence of allow (even {} / empty arrays) activates allowlist (fail-closed).
@@ -341,6 +368,24 @@ export class ConfigError extends Error {
341
368
  this.name = "ConfigError";
342
369
  }
343
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
+ }
344
389
  export const DEFAULT_CONFIG = {
345
390
  concurrency: 3,
346
391
  driver: "auto",
@@ -374,10 +419,13 @@ export const DEFAULT_CONFIG = {
374
419
  allowUnverifiedModels: false,
375
420
  },
376
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.
377
424
  tiers: {
378
425
  "claude-code": {
379
426
  vendor: "anthropic", channel: "sub",
380
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 },
381
429
  },
382
430
  // ids verified against installed CLI models_cache.json (codex 0.144.0, fetched 2026-07-09);
383
431
  // gpt-5.4 / gpt-5.4-mini removed — OpenAI retirement 2026-07-23; sol=frontier / terra=mid / luna=cheap per OpenAI tier framing.
@@ -389,6 +437,7 @@ export const DEFAULT_CONFIG = {
389
437
  codex: {
390
438
  vendor: "openai", channel: "sub",
391
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 },
392
441
  },
393
442
  // grok-4.5 (xAI, released 2026-07-08) → mid: AA Intelligence 54 (#4), Terminal-Bench 2.1 83.3%
394
443
  // (≈ GPT-5.5 83.4, Fable 5 84.3), SWE-bench Pro 64.7%, ~4.2× more token-efficient than Opus 4.8
@@ -409,6 +458,7 @@ export const DEFAULT_CONFIG = {
409
458
  // stop burning its mid. Operator-approved 2026-07-16.
410
459
  "composer-2.5-fast": "cheap",
411
460
  },
461
+ windows: { "composer-2.5": 200_000, "composer-2.5-fast": 200_000 },
412
462
  },
413
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;
414
464
  // no independent Terminal-Bench score → conservative mid, overlays may raise.
@@ -419,6 +469,7 @@ export const DEFAULT_CONFIG = {
419
469
  opencode: {
420
470
  vendor: "mixed", channel: "sub",
421
471
  models: { "zai-coding-plan/glm-5.2": "mid" },
472
+ windows: { "zai-coding-plan/glm-5.2": 1_000_000 },
422
473
  },
423
474
  // GLM-5.2 via pi/ZAI Coding Plan (sub, flat-rate). mid per benchmark policy (2026-07):
424
475
  // SWE-bench Pro 62.1, FrontierSWE 74.4 — same rationale as the opencode glm-5.2 seed above.
@@ -430,6 +481,7 @@ export const DEFAULT_CONFIG = {
430
481
  pi: {
431
482
  vendor: "zhipu", channel: "sub",
432
483
  models: { "zai/glm-5.2": "mid" },
484
+ windows: { "zai/glm-5.2": 1_000_000 },
433
485
  },
434
486
  // Native grok CLI (Phase 40). grok-4.5 → mid: same benchmark provenance as the retired cursor-agent
435
487
  // grok-4.5-xhigh seed above (AA 54, TB2.1 83.3%, SWE-b Pro 64.7% — researched 2026-07-10).
@@ -439,6 +491,7 @@ export const DEFAULT_CONFIG = {
439
491
  grok: {
440
492
  vendor: "xai", channel: "sub",
441
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 },
442
495
  },
443
496
  // Native kimi CLI (Phase 48). kimi-code/k3 → frontier: 81.2 FrontierSWE, 88.3 Terminal-Bench 2.1,
444
497
  // top-3 across six coding benchmarks (research 2026-07-17, K3 released 2026-07-16).
@@ -451,6 +504,11 @@ export const DEFAULT_CONFIG = {
451
504
  "kimi-code/kimi-for-coding": "mid",
452
505
  "kimi-code/kimi-for-coding-highspeed": "cheap",
453
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
+ },
454
512
  },
455
513
  },
456
514
  pricing: { cheap: 0.1, mid: 0.5, frontier: 2.5 },
@@ -464,7 +522,7 @@ export const DEFAULT_CONFIG = {
464
522
  // v1.2: workers are the real agent TUI in the pane; "print" restores the -p-rendered-in-pane path.
465
523
  visibility: { llm: "headless", keepPanes: "run", worker: "interactive", workersPerTab: 3 },
466
524
  };
467
- function deepMerge(base, over) {
525
+ function deepMerge(base, over, path = []) {
468
526
  if (over === undefined || over === null)
469
527
  return base;
470
528
  if (Array.isArray(base) || Array.isArray(over) || typeof base !== "object" || typeof over !== "object" || base === null) {
@@ -473,12 +531,18 @@ function deepMerge(base, over) {
473
531
  const out = { ...base };
474
532
  for (const [k, v] of Object.entries(over)) {
475
533
  if (v === null) {
476
- delete out[k]; // v1.1 tombstone: an explicit null in an overlay removes the key (e.g. stale tiers model ids)
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];
477
540
  continue;
478
541
  }
479
542
  // OBS-75 class: merge fresh-landing objects onto {} so nested tombstone nulls are pruned even when
480
543
  // no lower layer set the key (a fleet-cleared deny writes {adapters: null} — the schema rejects raw null)
481
- out[k] = k in out ? deepMerge(out[k], v) : deepMerge({}, v);
544
+ const childPath = [...path, k];
545
+ out[k] = k in out ? deepMerge(out[k], v, childPath) : deepMerge({}, v, childPath);
482
546
  }
483
547
  return out;
484
548
  }
@@ -556,7 +620,7 @@ function resolveRoutingMode(cfg, layers) {
556
620
  lints.push(`floors.${shape}: ${val} (config floors) overrides mode ${mode} — shadowed delta: ${shape} ${dflt}→${moded}`);
557
621
  }
558
622
  if (TIER_RANK[val] < TIER_RANK[integrityMin(shape)]) {
559
- lints.push(`floors.${shape}: ${val} is below integrity minimum frontier — integrity class ${INTEGRITY_FLOOR_SHAPES.join("/")} holds regardless of mode`);
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`);
560
624
  }
561
625
  continue;
562
626
  }
@@ -586,6 +650,9 @@ export function loadConfigWithMode(repoRoot, opts = {}) {
586
650
  const r = TickmarkrConfigSchema.safeParse(merged);
587
651
  if (!r.success)
588
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();
589
656
  return { cfg: r.data, mode: resolveRoutingMode(r.data, [globalCfg, repoCfg]) };
590
657
  }
591
658
  export function loadConfig(repoRoot, opts = {}) {
@@ -642,7 +709,7 @@ export function configTemplate(overlay) {
642
709
  # llm: headless # headless (default): judge/review/consult run silently | pane: visible agents
643
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)
644
711
  # worker: interactive # interactive (default): workers run the real agent TUI | print
645
- # workersPerTab: 3 # cap concurrent worker panes per WORKERS tab; overflow opens WORKERS-2 (VIS-09). Default 3
712
+ # workersPerTab: 3 # cap worker panes sharing one tab; gate panes ride with their task and never count. Default 3
646
713
  # scope:
647
714
  # allowDeviations: [] # globs an operator permits out-of-scope edits into, e.g. ["package-lock.json"]
648
715
  # gates: # override auto-detected commands; per-shape participation may only skip LLM gates
@@ -696,9 +763,8 @@ export function configTemplate(overlay) {
696
763
  const nl = base.indexOf("\n");
697
764
  return `${base.slice(0, nl + 1)}${lines.join("\n")}\n${base.slice(nl + 1)}`;
698
765
  }
699
- // v1.61 seed 8: the fleet-overlay provenance cluster (harvesting, serialization, diff rendering)
700
- // moved to fleet-overlay.ts as a pure move — re-exported here so prior import paths keep working.
701
- 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";
702
768
  export function repoOverlayPath(repoRoot) {
703
769
  return join(repoRoot, stateDirName(repoRoot), "config.yaml");
704
770
  }
@@ -710,15 +776,12 @@ export function readOverlayFile(path) {
710
776
  throw new ConfigError(`invalid overlay at ${path}`);
711
777
  return raw;
712
778
  }
713
- export function fleetEditableFromConfig(cfg, provenance = {}) {
779
+ export function fleetEditableFromConfig(cfg) {
714
780
  const tiers = {};
715
781
  for (const [adapter, entry] of Object.entries(cfg.tiers)) {
716
782
  tiers[adapter] = {};
717
783
  for (const [model, tier] of Object.entries(entry.models)) {
718
- // OBS-88: notes harvested from the overlay ride the session state from load, so a later
719
- // write knows about every existing note — not only the ones this session typed itself
720
- const note = provenance[adapter]?.[model];
721
- tiers[adapter][model] = note ? { tier, provenance: note } : { tier };
784
+ tiers[adapter][model] = { tier };
722
785
  }
723
786
  }
724
787
  return {
@@ -754,55 +817,24 @@ export function fleetKeyLayer(repoRoot, dotted, opts = {}) {
754
817
  export function formatFleetPrint(repoRoot, opts = {}) {
755
818
  const gdir = opts.globalDir ?? globalConfigDir();
756
819
  const effective = loadConfig(repoRoot, { globalDir: gdir });
757
- const editable = fleetEditableFromConfig(effective);
758
- const lines = ["# tickmarkr fleet — effective state (repo > global > defaults)"];
759
- const annotate = (dotted, yaml) => `${yaml} # ${fleetKeyLayer(repoRoot, dotted, opts)}`;
760
- const denyAdapters = editable.denyAdapters;
761
- const denyModels = editable.denyModels;
762
- if (denyAdapters.length || denyModels.length) {
763
- lines.push("routing:");
764
- lines.push(" deny:");
765
- if (denyAdapters.length)
766
- lines.push(annotate("routing.deny.adapters", ` adapters: ${stringify(denyAdapters).trim()}`));
767
- if (denyModels.length)
768
- lines.push(annotate("routing.deny.models", ` models: ${stringify(denyModels).trim()}`));
769
- }
770
- const mapKeys = Object.keys(editable.map).sort();
771
- if (mapKeys.length) {
772
- if (!lines.some((l) => l === "routing:"))
773
- lines.push("routing:");
774
- lines.push(" map:");
775
- for (const shape of mapKeys) {
776
- const entry = editable.map[shape];
777
- const body = stringify(entry, { indent: 4 }).trim().split("\n").map((l) => ` ${l}`).join("\n");
778
- lines.push(annotate(`routing.map.${shape}`, ` ${shape}:`));
779
- lines.push(body.split("\n").slice(1).join("\n"));
780
- }
781
- }
782
- const floorKeys = Object.keys(editable.floors).sort();
783
- if (floorKeys.length) {
784
- if (!lines.some((l) => l === "routing:"))
785
- lines.push("routing:");
786
- lines.push(annotate("routing.floors", ` floors: ${stringify(Object.fromEntries(floorKeys.map((k) => [k, editable.floors[k]]))).trim().replace(/^/gm, " ")}`));
787
- }
788
- const tierAdapters = Object.keys(editable.tiers).sort();
789
- if (tierAdapters.length) {
790
- lines.push("tiers:");
791
- for (const adapter of tierAdapters) {
792
- const models = editable.tiers[adapter];
793
- const modelIds = Object.keys(models).sort();
794
- if (!modelIds.length)
795
- continue;
796
- lines.push(` ${adapter}:`);
797
- lines.push(" models:");
798
- for (const model of modelIds) {
799
- const v = models[model];
800
- if (v === null)
801
- lines.push(` ${model}: null`);
802
- else
803
- lines.push(annotate(`tiers.${adapter}.models.${model}`, ` ${model}: ${v.tier}`));
804
- }
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)}`;
805
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]);
806
838
  }
807
- return `${lines.join("\n")}\n`;
839
+ return String(doc);
808
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
- /** Trailing `# note` comments on per-entry lines an operator may have hand-written or a prior
5
- * fleet write stamped: model tier lines (including null tombstones) and deny list items. */
6
- export type HarvestedProvenance = {
7
- tiers: Record<string, Record<string, string>>;
8
- denyAdapters: Record<string, string>;
9
- denyModels: Record<string, string>;
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
- /** OBS-88: harvest existing provenance comments from raw repo-overlay bytes at fleet-session
12
- * load. yaml.parse discards comments, so before this every fleet write re-serialized the file
13
- * knowing only the current session's own notes and silently stripped all prior ones — a typed
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>): Record<string, unknown>;
24
- export declare function repoOverlayYaml(overlay: Record<string, unknown>, provenance?: Record<string, Record<string, string>>, denyNotes?: FleetDenyNotes): string;
25
- export declare function serializeFleetOverlay(overlay: Record<string, unknown>, provenance?: Record<string, Record<string, string>>, denyNotes?: FleetDenyNotes): string;
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;