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,6 +1,4 @@
1
- // Fleet-overlay provenance cluster (v1.61 seed 8): harvesting, serialization, and diff rendering
2
- // for the `tickmarkr fleet` write path. Pure move out of config.ts — prior import paths preserved
3
- // via re-exports there.
1
+ // Fleet-overlay mutation, serialization, and diff rendering for the `tickmarkr fleet` write path.
4
2
  import { isMap, isScalar, isSeq, parseDocument, stringify } from "yaml";
5
3
  /** Fleet-owned overlay keys — the only config surface `tickmarkr fleet` may write. */
6
4
  export const FLEET_OVERLAY_KEYS = ["routing", "tiers"];
@@ -15,49 +13,186 @@ function fleetSubset(obj) {
15
13
  function sortedUnique(xs) {
16
14
  return [...new Set(xs)].sort();
17
15
  }
18
- /** OBS-88: harvest existing provenance comments from raw repo-overlay bytes at fleet-session
19
- * load. yaml.parse discards comments, so before this every fleet write re-serialized the file
20
- * knowing only the current session's own notes and silently stripped all prior ones — a typed
21
- * benchmark-provenance note survived exactly one write. Fail-open to empty: an unreadable
22
- * overlay is the loader's problem to reject, never the harvester's. */
23
- export function harvestFleetProvenance(overlayText) {
24
- const out = { tiers: {}, denyAdapters: {}, denyModels: {} };
25
- if (!overlayText.trim())
26
- return out;
27
- const doc = parseDocument(overlayText);
28
- const note = (n) => {
29
- const c = isScalar(n) ? n.comment : undefined;
30
- return typeof c === "string" && c.trim() ? c.trim() : undefined;
31
- };
32
- const tiers = doc.getIn(["tiers"]);
33
- if (isMap(tiers)) {
34
- for (const ap of tiers.items) {
35
- if (!isScalar(ap.key))
36
- continue;
37
- const models = isMap(ap.value) ? ap.value.get("models") : undefined;
38
- if (!isMap(models))
39
- continue;
40
- for (const mp of models.items) {
41
- const n = note(mp.value);
42
- if (isScalar(mp.key) && n)
43
- (out.tiers[String(ap.key.value)] ??= {})[String(mp.key.value)] = n;
16
+ // fleet.ts deliberately remains the sole overlay builder and writer. Its established classification
17
+ // seam copies only `tier` and `note` into FleetEditable, so first-touch entry metadata rides inside a
18
+ // private provenance envelope until this module writes the YAML. The envelope never reaches disk.
19
+ const FIRST_TOUCH_OPEN = "\uE000tickmarkr-fleet-first-touch:";
20
+ const FIRST_TOUCH_CLOSE = "\uE001";
21
+ export function fleetFirstTouchProvenance(note, firstTouch) {
22
+ return `${FIRST_TOUCH_OPEN}${encodeURIComponent(firstTouch.vendor)}:${firstTouch.channel}${FIRST_TOUCH_CLOSE}${note}`;
23
+ }
24
+ function unpackFleetProvenance(provenance) {
25
+ if (!provenance?.startsWith(FIRST_TOUCH_OPEN))
26
+ return { provenance };
27
+ const close = provenance.indexOf(FIRST_TOUCH_CLOSE, FIRST_TOUCH_OPEN.length);
28
+ if (close === -1)
29
+ return { provenance };
30
+ const metadata = provenance.slice(FIRST_TOUCH_OPEN.length, close);
31
+ const colon = metadata.lastIndexOf(":");
32
+ if (colon === -1)
33
+ return { provenance };
34
+ const channel = metadata.slice(colon + 1);
35
+ if (channel !== "sub" && channel !== "api")
36
+ return { provenance };
37
+ try {
38
+ return {
39
+ provenance: provenance.slice(close + FIRST_TOUCH_CLOSE.length),
40
+ firstTouch: { vendor: decodeURIComponent(metadata.slice(0, colon)), channel },
41
+ };
42
+ }
43
+ catch {
44
+ return { provenance };
45
+ }
46
+ }
47
+ function setScalarPreservingComment(doc, path, value, authoredComment) {
48
+ const existing = doc.getIn(path, true);
49
+ if (isScalar(existing))
50
+ existing.value = value;
51
+ else
52
+ doc.setIn(path, doc.createNode(value));
53
+ if (authoredComment !== undefined) {
54
+ const written = doc.getIn(path, true);
55
+ if (isScalar(written))
56
+ written.comment = ` ${authoredComment}`;
57
+ }
58
+ }
59
+ function setStringSequencePreservingComments(doc, path, values) {
60
+ const existing = doc.getIn(path, true);
61
+ if (values === null) {
62
+ const tombstone = doc.createNode(null);
63
+ if (isScalar(tombstone) && existing && typeof existing === "object") {
64
+ // A block sequence's key-line note is stored as commentBefore; on a scalar it must be
65
+ // inline comment content or YAML expands `key: null` into a nested null value.
66
+ const comments = [];
67
+ if ("commentBefore" in existing && typeof existing.commentBefore === "string") {
68
+ comments.push(existing.commentBefore);
69
+ }
70
+ if ("comment" in existing && typeof existing.comment === "string") {
71
+ comments.push(existing.comment);
44
72
  }
73
+ if (comments.length)
74
+ tombstone.comment = comments.join("\n");
45
75
  }
76
+ doc.setIn(path, tombstone);
77
+ return;
46
78
  }
47
- for (const [key, dest] of [["adapters", out.denyAdapters], ["models", out.denyModels]]) {
48
- const seq = doc.getIn(["routing", "deny", key]);
49
- if (!isSeq(seq))
79
+ if (!isSeq(existing)) {
80
+ doc.setIn(path, doc.createNode(values));
81
+ return;
82
+ }
83
+ const available = [...existing.items];
84
+ existing.items = values.map((value) => {
85
+ const at = available.findIndex((item) => isScalar(item) && String(item.value) === value);
86
+ if (at >= 0)
87
+ return available.splice(at, 1)[0];
88
+ return doc.createNode(value);
89
+ });
90
+ }
91
+ function deleteEmptyMap(doc, path) {
92
+ const node = doc.getIn(path, true);
93
+ if (isMap(node) && node.items.length === 0)
94
+ doc.deleteIn(path);
95
+ }
96
+ /** Apply only fields fleet authored to the parsed YAML document. Untouched nodes retain their
97
+ * keys, ordering, scalar style, and comments; fresh provenance is written directly on its tier node. */
98
+ export function renderFleetOverlayWrite(priorBytes, write) {
99
+ const doc = parseDocument(priorBytes);
100
+ if (doc.errors.length)
101
+ throw doc.errors[0];
102
+ const { initial, edited } = write;
103
+ const denyChanged = sortedUnique(initial.denyAdapters).join() !== sortedUnique(edited.denyAdapters).join()
104
+ || sortedUnique(initial.denyModels).join() !== sortedUnique(edited.denyModels).join();
105
+ if (denyChanged) {
106
+ setStringSequencePreservingComments(doc, ["routing", "deny", "adapters"], edited.denyAdapters.length ? sortedUnique(edited.denyAdapters) : null);
107
+ setStringSequencePreservingComments(doc, ["routing", "deny", "models"], edited.denyModels.length ? sortedUnique(edited.denyModels) : null);
108
+ }
109
+ for (const shape of new Set([...Object.keys(initial.map), ...Object.keys(edited.map)])) {
110
+ const before = initial.map[shape];
111
+ const after = edited.map[shape];
112
+ if (JSON.stringify(before?.pin) !== JSON.stringify(after?.pin)) {
113
+ if (after?.pin === undefined)
114
+ doc.deleteIn(["routing", "map", shape, "pin"]);
115
+ else
116
+ doc.setIn(["routing", "map", shape, "pin"], doc.createNode(after.pin));
117
+ }
118
+ if (JSON.stringify(before?.prefer) !== JSON.stringify(after?.prefer)) {
119
+ // Clearing a resolved list must mask the lower layer with [], not delete the key and inherit it again.
120
+ setStringSequencePreservingComments(doc, ["routing", "map", shape, "prefer"], after?.prefer ?? []);
121
+ }
122
+ if (before?.escalate !== after?.escalate) {
123
+ if (after?.escalate === undefined)
124
+ doc.deleteIn(["routing", "map", shape, "escalate"]);
125
+ else
126
+ setScalarPreservingComment(doc, ["routing", "map", shape, "escalate"], after.escalate);
127
+ }
128
+ }
129
+ for (const shape of new Set([...Object.keys(initial.floors), ...Object.keys(edited.floors)])) {
130
+ if (initial.floors[shape] === edited.floors[shape])
50
131
  continue;
51
- for (const item of seq.items) {
52
- const n = note(item);
53
- if (isScalar(item) && n)
54
- dest[String(item.value)] = n;
132
+ const tier = edited.floors[shape];
133
+ if (tier === undefined)
134
+ doc.deleteIn(["routing", "floors", shape]);
135
+ else
136
+ setScalarPreservingComment(doc, ["routing", "floors", shape], tier);
137
+ }
138
+ for (const adapter of new Set([...Object.keys(initial.tiers), ...Object.keys(edited.tiers)])) {
139
+ const beforeModels = initial.tiers[adapter] ?? {};
140
+ const afterModels = edited.tiers[adapter] ?? {};
141
+ let firstTouch;
142
+ for (const model of new Set([...Object.keys(beforeModels), ...Object.keys(afterModels)])) {
143
+ const before = beforeModels[model];
144
+ const after = afterModels[model];
145
+ if (JSON.stringify(before) === JSON.stringify(after) || after === null || after === undefined)
146
+ continue;
147
+ firstTouch ??= unpackFleetProvenance(after.provenance).firstTouch;
148
+ }
149
+ const ft = firstTouch;
150
+ if (ft) {
151
+ if (doc.getIn(["tiers", adapter, "vendor"]) === undefined) {
152
+ setScalarPreservingComment(doc, ["tiers", adapter, "vendor"], ft.vendor);
153
+ }
154
+ if (doc.getIn(["tiers", adapter, "channel"]) === undefined) {
155
+ setScalarPreservingComment(doc, ["tiers", adapter, "channel"], ft.channel);
156
+ }
157
+ }
158
+ for (const model of new Set([...Object.keys(beforeModels), ...Object.keys(afterModels)])) {
159
+ const before = beforeModels[model];
160
+ const after = afterModels[model];
161
+ if (JSON.stringify(before) === JSON.stringify(after))
162
+ continue;
163
+ const path = ["tiers", adapter, "models", model];
164
+ if (after === null || after === undefined)
165
+ setScalarPreservingComment(doc, path, null);
166
+ else
167
+ setScalarPreservingComment(doc, path, after.tier, unpackFleetProvenance(after.provenance).provenance);
55
168
  }
56
169
  }
57
- return out;
170
+ if (write.mode !== undefined) {
171
+ setScalarPreservingComment(doc, ["routing", "mode"], write.mode);
172
+ }
173
+ if (write.steering) {
174
+ for (const key of ["review", "consult"]) {
175
+ const before = write.steering.initial[key];
176
+ const after = write.steering.edited[key];
177
+ if (JSON.stringify(before) === JSON.stringify(after))
178
+ continue;
179
+ if (after === undefined) {
180
+ doc.deleteIn([key, "prefer"]);
181
+ deleteEmptyMap(doc, [key]);
182
+ }
183
+ else {
184
+ setStringSequencePreservingComments(doc, [key, "prefer"], after);
185
+ }
186
+ }
187
+ }
188
+ // Fleet's established note style uses two spaces before `#`; the Document remains the sole
189
+ // comment writer while preserving that byte-level convention for old and newly-authored notes.
190
+ return doc.toString({
191
+ commentString: (comment) => comment.replace(/^(?!$)(?: $)?/gm, " #"),
192
+ });
58
193
  }
59
194
  /** Build the repo overlay fragment fleet would write for edits since session start. */
60
- export function fleetRepoOverlayFromDelta(initial, edited, existingRepo = {}) {
195
+ export function fleetRepoOverlayFromDelta(initial, edited, existingRepo = {}, firstTouches = {}) {
61
196
  if (fleetEditableEquals(initial, edited))
62
197
  return {};
63
198
  const out = structuredClone(existingRepo);
@@ -74,8 +209,13 @@ export function fleetRepoOverlayFromDelta(initial, edited, existingRepo = {}) {
74
209
  }
75
210
  const mapDelta = {};
76
211
  for (const shape of new Set([...Object.keys(initial.map), ...Object.keys(edited.map)])) {
77
- if (JSON.stringify(initial.map[shape]) !== JSON.stringify(edited.map[shape]))
78
- mapDelta[shape] = edited.map[shape];
212
+ if (JSON.stringify(initial.map[shape]) !== JSON.stringify(edited.map[shape])) {
213
+ const next = { ...edited.map[shape] };
214
+ if (initial.map[shape]?.prefer !== undefined && edited.map[shape]?.prefer === undefined) {
215
+ next.prefer = [];
216
+ }
217
+ mapDelta[shape] = next;
218
+ }
79
219
  }
80
220
  if (Object.keys(mapDelta).length) {
81
221
  routing.map = { ...routing.map, ...mapDelta };
@@ -111,9 +251,17 @@ export function fleetRepoOverlayFromDelta(initial, edited, existingRepo = {}) {
111
251
  }
112
252
  }
113
253
  if (Object.keys(modelDelta).length) {
114
- // spread the existing entry so vendor/channel/windows survive the rewrite — dropping them
115
- // makes the overlay unloadable for any adapter without a default seed (reload-guard class)
116
- tiersOut[adapter] = { ...tiersOut[adapter], models: { ...tiersOut[adapter]?.models, ...modelDelta } };
254
+ const existingEntry = tiersOut[adapter] ?? {};
255
+ const firstTouch = firstTouches[adapter];
256
+ // Spread the whole entry rather than a known-key projection: vendor/channel/windows and sibling
257
+ // keys introduced by newer schemas all survive. A genuinely new entry receives only facts the
258
+ // adapter/operator declared; channel is never inferred from the binary.
259
+ tiersOut[adapter] = {
260
+ ...existingEntry,
261
+ ...(existingEntry.vendor === undefined && firstTouch ? { vendor: firstTouch.vendor } : {}),
262
+ ...(existingEntry.channel === undefined && firstTouch ? { channel: firstTouch.channel } : {}),
263
+ models: { ...existingEntry.models, ...modelDelta },
264
+ };
117
265
  tiersTouched = true;
118
266
  }
119
267
  }
@@ -121,11 +269,11 @@ export function fleetRepoOverlayFromDelta(initial, edited, existingRepo = {}) {
121
269
  out.tiers = tiersOut;
122
270
  return out;
123
271
  }
124
- export function repoOverlayYaml(overlay, provenance = {}, denyNotes = {}) {
272
+ export function repoOverlayYaml(overlay) {
125
273
  if (!Object.keys(overlay).length)
126
274
  return "";
127
275
  const fleet = fleetSubset(overlay);
128
- const fleetBody = serializeFleetOverlay(fleet, provenance, denyNotes);
276
+ const fleetBody = serializeFleetOverlay(fleet);
129
277
  const rest = { ...overlay };
130
278
  for (const k of FLEET_OVERLAY_KEYS)
131
279
  delete rest[k];
@@ -134,7 +282,7 @@ export function repoOverlayYaml(overlay, provenance = {}, denyNotes = {}) {
134
282
  const head = stringify(rest).trimEnd();
135
283
  return fleetBody ? `${head}\n${fleetBody}` : `${head}\n`;
136
284
  }
137
- export function serializeFleetOverlay(overlay, provenance = {}, denyNotes = {}) {
285
+ export function serializeFleetOverlay(overlay) {
138
286
  if (!Object.keys(overlay).length)
139
287
  return "";
140
288
  const lines = [];
@@ -142,8 +290,6 @@ export function serializeFleetOverlay(overlay, provenance = {}, denyNotes = {})
142
290
  // re-indent the whole emitted block, so sequences/nested maps nest correctly and null
143
291
  // tombstones/empty collections survive the serialize→parse round-trip.
144
292
  const block = (obj, pad) => stringify(obj).trimEnd().split("\n").map((l) => `${pad}${l}`);
145
- // deny lists emit item-by-item through the same stringify quoting rules as block(), so a
146
- // harvested `# reason` can re-attach to its exact entry (multi-line emissions never take one)
147
293
  const denySeq = (key, v) => {
148
294
  if (v === undefined)
149
295
  return [];
@@ -152,9 +298,6 @@ export function serializeFleetOverlay(overlay, provenance = {}, denyNotes = {})
152
298
  const out = [` ${key}:`];
153
299
  for (const item of v) {
154
300
  const emitted = stringify([item]).trimEnd().split("\n");
155
- const n = denyNotes[key]?.[item];
156
- if (n && emitted.length === 1)
157
- emitted[0] += ` # ${n}`;
158
301
  out.push(...emitted.map((l) => ` ${l}`));
159
302
  }
160
303
  return out;
@@ -177,32 +320,7 @@ export function serializeFleetOverlay(overlay, provenance = {}, denyNotes = {})
177
320
  if (tiers && Object.keys(tiers).length) {
178
321
  lines.push("tiers:");
179
322
  for (const [adapter, entry] of Object.entries(tiers)) {
180
- const body = [];
181
- if (entry.vendor)
182
- body.push(` vendor: ${entry.vendor}`);
183
- if (entry.channel)
184
- body.push(` channel: ${entry.channel}`);
185
- if (entry.windows)
186
- body.push(...block({ windows: entry.windows }, " "));
187
- const models = Object.entries(entry.models ?? {});
188
- if (models.length) {
189
- body.push(" models:");
190
- for (const [model, tier] of models) {
191
- // OBS-88: notes serialize verbatim (fresh session notes arrive pre-stamped with their
192
- // "— fleet <date>" suffix), so a harvested note round-trips byte-for-byte every write
193
- const note = provenance[adapter]?.[model];
194
- const suffix = note ? ` # ${note}` : "";
195
- body.push(` ${model}: ${tier === null ? "null" : tier}${suffix}`);
196
- }
197
- }
198
- else if (entry.models) {
199
- body.push(" models: {}"); // present-but-empty: explicit {} — a childless header parses as null and the loader rejects it
200
- }
201
- // a bare `adapter:` line parses as a null tombstone and would DELETE the adapter's default seeds on merge
202
- if (body.length)
203
- lines.push(` ${adapter}:`, ...body);
204
- else
205
- lines.push(` ${adapter}: {}`);
323
+ lines.push(...block({ [adapter]: entry }, " "));
206
324
  }
207
325
  }
208
326
  return `${lines.join("\n")}\n`;
@@ -0,0 +1,23 @@
1
+ export type FleetWhySource = "repo-overlay" | "global-config" | "seed-default" | "operator-pinned" | "defaulted";
2
+ export type FleetWhyValue<Id extends string = string> = {
3
+ id: Id;
4
+ effective: string;
5
+ /** Dotted config path whose winning declaration produced this value. */
6
+ declaredAt?: string;
7
+ /** A live Shapes-screen pin outranks every persisted declaration. */
8
+ operatorPinned?: boolean;
9
+ /** Paste-ready only: projection and renderers never execute setup actions. */
10
+ setupCommand?: string;
11
+ };
12
+ export type FleetWhyRow<Id extends string = string> = FleetWhyValue<Id> & {
13
+ source: FleetWhySource;
14
+ label: string;
15
+ };
16
+ export type FleetWhyOptions = {
17
+ repoRoot: string;
18
+ globalDir?: string;
19
+ };
20
+ /** The single fleet projection: every consumer receives the same value/source/setup label. */
21
+ export declare function projectFleetWhy<Id extends string>(values: readonly FleetWhyValue<Id>[], options: FleetWhyOptions): FleetWhyRow<Id>[];
22
+ /** Plain line-mode twin of the Shapes rows; labels are projected, never reconstructed here. */
23
+ export declare function renderFleetWhy(rows: readonly FleetWhyRow[]): string;
@@ -0,0 +1,42 @@
1
+ import { DEFAULT_CONFIG, fleetKeyLayer } from "./config.js";
2
+ function valueAt(root, dotted) {
3
+ let value = root;
4
+ for (const part of dotted.split(".")) {
5
+ if (value === null || typeof value !== "object" || Array.isArray(value))
6
+ return undefined;
7
+ value = value[part];
8
+ }
9
+ return value;
10
+ }
11
+ function sourceOf(value, options) {
12
+ if (value.operatorPinned)
13
+ return "operator-pinned";
14
+ if (!value.declaredAt)
15
+ return "defaulted";
16
+ const layer = fleetKeyLayer(options.repoRoot, value.declaredAt, {
17
+ globalDir: options.globalDir,
18
+ });
19
+ if (layer === "repo")
20
+ return "repo-overlay";
21
+ if (layer === "global")
22
+ return "global-config";
23
+ return valueAt(DEFAULT_CONFIG, value.declaredAt) === undefined
24
+ ? "defaulted"
25
+ : "seed-default";
26
+ }
27
+ /** The single fleet projection: every consumer receives the same value/source/setup label. */
28
+ export function projectFleetWhy(values, options) {
29
+ return values.map((value) => {
30
+ const source = sourceOf(value, options);
31
+ const setup = value.setupCommand ? `\n setup: ${value.setupCommand}` : "";
32
+ return {
33
+ ...value,
34
+ source,
35
+ label: `${value.id} → ${value.effective} · source: ${source}${setup}`,
36
+ };
37
+ });
38
+ }
39
+ /** Plain line-mode twin of the Shapes rows; labels are projected, never reconstructed here. */
40
+ export function renderFleetWhy(rows) {
41
+ return ["tickmarkr fleet --why — effective shape routing", ...rows.map((row) => row.label)].join("\n");
42
+ }
@@ -2,6 +2,7 @@ import { type ExecutorDriver, type NotifyOpts, type Slot, type SlotOpts } from "
2
2
  export declare const TRAILER_SAFE_FLOOR_COLS = 108;
3
3
  export declare const TRAILER_WIDTH_MARGIN = 2;
4
4
  export declare const DELIVERY_ATTEMPTS = 3;
5
+ export declare const DISPATCH_START_PREFIX = "TICKMARKR_START_";
5
6
  export interface HerdrTimeSource {
6
7
  now: () => number;
7
8
  sleep: (ms: number) => Promise<void>;
@@ -12,12 +13,21 @@ export declare class DeliveryReadinessError extends Error {
12
13
  readonly phase = "READINESS";
13
14
  constructor(waitedMs: number, transcript: string);
14
15
  }
16
+ export declare class DeliveryCorruptedError extends Error {
17
+ readonly pane: string;
18
+ readonly transcript: string;
19
+ readonly phase = "DISPATCH";
20
+ constructor(pane: string, reason: string, transcript: string);
21
+ }
22
+ export type DriverJournal = (event: string, slotName: string, data: Record<string, unknown>) => void;
15
23
  /** First-generation join direction from measured trailer-safe floor (43-MEASUREMENT.md). */
16
24
  export declare function workerSplitDirection(paneCols: number | null, safeFloor?: number, margin?: number): "right" | "down";
25
+ export declare function taskGroupOf(name: string): string | undefined;
17
26
  export declare class HerdrDriver implements ExecutorDriver {
18
27
  private bin;
19
28
  private workersPerTab;
20
29
  private time;
30
+ private journal?;
21
31
  id: string;
22
32
  interactive: boolean;
23
33
  private groups;
@@ -26,16 +36,18 @@ export declare class HerdrDriver implements ExecutorDriver {
26
36
  private dispatchLeases;
27
37
  private deliveredPanes;
28
38
  private inputBoxes;
39
+ private bootstraps;
40
+ private journalRoots;
29
41
  private ws;
30
42
  private callerPane;
31
43
  private watches;
32
- constructor(bin?: string, workersPerTab?: number, time?: HerdrTimeSource);
44
+ constructor(bin?: string, workersPerTab?: number, time?: HerdrTimeSource, journal?: DriverJournal | undefined);
45
+ private appendDispatchRetry;
33
46
  private serial;
34
47
  private deliveryQueue;
35
48
  private reserveDispatch;
36
49
  private verifyPaneIdentityBinding;
37
50
  private deliveryMatches;
38
- private shellExecutionEchoed;
39
51
  private submissionRegistered;
40
52
  static available(): boolean;
41
53
  private herdr;
@@ -52,7 +64,13 @@ export declare class HerdrDriver implements ExecutorDriver {
52
64
  private glyphFor;
53
65
  private joinGroup;
54
66
  run(slot: Slot, cmd: string): Promise<void>;
55
- private deliver;
67
+ private dispatchWithRetry;
68
+ private isTuiTurn;
69
+ private dispatch;
70
+ private deliverAtomic;
71
+ private awaitDispatchAck;
72
+ private freshPane;
73
+ private deliverTyped;
56
74
  nudge(slot: Slot, message: string): Promise<boolean>;
57
75
  private deliverPersistentShellCommand;
58
76
  private submitVerifiedDelivery;