tickmarkr 1.84.0 → 1.86.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/adapters/catalog-remote.d.ts +64 -0
- package/dist/adapters/catalog-remote.js +287 -0
- package/dist/adapters/catalog.d.ts +96 -0
- package/dist/adapters/catalog.js +176 -0
- package/dist/adapters/claude-code.d.ts +1 -0
- package/dist/adapters/claude-code.js +59 -1
- package/dist/adapters/fake.js +42 -4
- package/dist/adapters/model-lints.d.ts +25 -5
- package/dist/adapters/model-lints.js +184 -50
- package/dist/adapters/model-windows.d.ts +31 -0
- package/dist/adapters/model-windows.js +69 -0
- package/dist/adapters/prompt.d.ts +5 -1
- package/dist/adapters/prompt.js +13 -4
- package/dist/adapters/registry.d.ts +25 -26
- package/dist/adapters/registry.js +173 -110
- package/dist/adapters/types.d.ts +3 -0
- package/dist/adapters/types.js +36 -3
- package/dist/brand.d.ts +5 -1
- package/dist/brand.js +18 -2
- package/dist/cli/commands/doctor.d.ts +3 -0
- package/dist/cli/commands/doctor.js +43 -21
- package/dist/cli/commands/fleet.d.ts +7 -0
- package/dist/cli/commands/fleet.js +94 -74
- package/dist/cli/commands/init.js +118 -5
- package/dist/cli/commands/status.js +202 -46
- package/dist/compile/collateral.d.ts +86 -2
- package/dist/compile/collateral.js +294 -3
- package/dist/compile/gsd.d.ts +2 -1
- package/dist/compile/gsd.js +68 -2
- package/dist/compile/native.d.ts +14 -0
- package/dist/compile/native.js +161 -12
- package/dist/config/config.d.ts +82 -5
- package/dist/config/config.js +253 -66
- package/dist/config/fleet-overlay.d.ts +25 -20
- package/dist/config/fleet-overlay.js +195 -77
- package/dist/config/fleet-why.d.ts +23 -0
- package/dist/config/fleet-why.js +42 -0
- package/dist/drivers/herdr.d.ts +21 -3
- package/dist/drivers/herdr.js +344 -110
- package/dist/gates/acceptance.js +7 -2
- package/dist/gates/baseline.d.ts +1 -0
- package/dist/gates/baseline.js +91 -13
- package/dist/gates/llm.d.ts +0 -1
- package/dist/gates/llm.js +5 -30
- package/dist/gates/review.d.ts +9 -1
- package/dist/gates/review.js +105 -10
- package/dist/gates/run-gates.d.ts +9 -0
- package/dist/gates/run-gates.js +285 -41
- package/dist/gates/verdict-cause.d.ts +4 -0
- package/dist/gates/verdict-cause.js +63 -0
- package/dist/graph/schema.d.ts +6 -0
- package/dist/graph/schema.js +8 -5
- package/dist/route/router.d.ts +0 -5
- package/dist/route/router.js +16 -20
- package/dist/run/consult.d.ts +6 -0
- package/dist/run/consult.js +35 -25
- package/dist/run/daemon.d.ts +48 -2
- package/dist/run/daemon.js +1488 -330
- package/dist/run/journal.d.ts +56 -3
- package/dist/run/journal.js +358 -4
- package/dist/run/stall.d.ts +35 -1
- package/dist/run/stall.js +118 -8
- package/dist/tui/cockpit/capture.d.ts +12 -0
- package/dist/tui/cockpit/capture.js +37 -1
- package/dist/tui/cockpit/components.d.ts +2 -0
- package/dist/tui/cockpit/components.js +8 -8
- package/dist/tui/cockpit/derive.d.ts +29 -2
- package/dist/tui/cockpit/derive.js +219 -23
- package/dist/tui/cockpit/run-cockpit.js +128 -27
- package/dist/tui/cockpit/theme.d.ts +32 -26
- package/dist/tui/cockpit/theme.js +11 -5
- package/dist/tui/ink/components.d.ts +0 -15
- package/dist/tui/ink/components.js +0 -17
- package/dist/tui/ink/fleet-app.d.ts +4 -1
- package/dist/tui/ink/fleet-app.js +134 -13
- package/fixtures/sample.native.md +1 -1
- package/package.json +1 -1
- package/skills/tickmarkr-overseer/SKILL.md +354 -34
- package/skills/tickmarkr-overseer/scripts/watch-artifacts.sh +70 -0
- package/skills/tickmarkr-overseer/scripts/watch-panes.sh +1 -1
- package/dist/tui/ink/studio-app.d.ts +0 -59
- package/dist/tui/ink/studio-app.js +0 -320
- package/dist/tui/save.d.ts +0 -38
- package/dist/tui/save.js +0 -96
- package/dist/tui/staging.d.ts +0 -29
- package/dist/tui/staging.js +0 -78
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
// Fleet-overlay
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
export function
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
+
}
|
package/dist/drivers/herdr.d.ts
CHANGED
|
@@ -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
|
|
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;
|