tickmarkr 1.65.0 → 1.67.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/dist/cli/commands/run.js +9 -0
- package/dist/cli/commands/status.js +32 -18
- package/dist/cli/commands/ui.d.ts +5 -0
- package/dist/cli/commands/ui.js +13 -0
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +3 -1
- package/dist/compile/collateral.d.ts +1 -0
- package/dist/compile/collateral.js +52 -3
- package/dist/gates/run-gates.js +13 -6
- package/dist/tui/app.d.ts +69 -0
- package/dist/tui/app.js +322 -0
- package/dist/tui/engine.d.ts +52 -0
- package/dist/tui/engine.js +92 -0
- package/dist/tui/frame.d.ts +22 -0
- package/dist/tui/frame.js +56 -0
- package/dist/tui/input.d.ts +41 -0
- package/dist/tui/input.js +100 -0
- package/dist/tui/save.d.ts +38 -0
- package/dist/tui/save.js +96 -0
- package/dist/tui/staging.d.ts +29 -0
- package/dist/tui/staging.js +78 -0
- package/dist/tui/views/diff-modal.d.ts +9 -0
- package/dist/tui/views/diff-modal.js +19 -0
- package/dist/tui/views/fleet-view.d.ts +91 -0
- package/dist/tui/views/fleet-view.js +337 -0
- package/dist/tui/views/preview-view.d.ts +7 -0
- package/dist/tui/views/preview-view.js +80 -0
- package/dist/tui/views/profile-view.d.ts +15 -0
- package/dist/tui/views/profile-view.js +93 -0
- package/dist/tui/views/routing-view.d.ts +48 -0
- package/dist/tui/views/routing-view.js +234 -0
- package/package.json +1 -1
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { channelsFromConfig } from "../../adapters/types.js";
|
|
2
|
+
import { candidateRow, shapeCandidates } from "../../cli/commands/fleet-picker.js";
|
|
3
|
+
import { DEFAULT_CONFIG, fleetEditableFromConfig, INTEGRITY_FLOOR_SHAPES, loadConfigWithMode, ROUTING_MODES, TIER_RANK, } from "../../config/config.js";
|
|
4
|
+
import { SHAPES } from "../../graph/schema.js";
|
|
5
|
+
import { FleetStaging } from "../staging.js";
|
|
6
|
+
// Same preview task fleet.ts's picker ranks (one per shape, complexity 3): the inspector must
|
|
7
|
+
// rank the exact inputs the fleet editor ranks, or the two surfaces could disagree.
|
|
8
|
+
export function routingPreviewTask(shape) {
|
|
9
|
+
return {
|
|
10
|
+
id: "routing-preview",
|
|
11
|
+
title: "routing preview",
|
|
12
|
+
goal: "preview",
|
|
13
|
+
shape,
|
|
14
|
+
complexity: 3,
|
|
15
|
+
acceptance: ["done"],
|
|
16
|
+
deps: [],
|
|
17
|
+
files: [],
|
|
18
|
+
context: [],
|
|
19
|
+
gates: ["build", "test", "lint", "evidence", "scope", "acceptance", "review"],
|
|
20
|
+
humanGate: false,
|
|
21
|
+
status: "pending",
|
|
22
|
+
evidence: { commits: [], artifacts: [], gateResults: [] },
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const isIntegrity = (shape) => INTEGRITY_FLOOR_SHAPES.includes(shape);
|
|
26
|
+
// Staged-cell glyph — the same ● the shell status bar counts staged changes with (app.ts).
|
|
27
|
+
const STAGED_MARK = "●";
|
|
28
|
+
const lowerTier = (t) => (t === "frontier" ? "mid" : "cheap");
|
|
29
|
+
const maxTier = (a, b) => (TIER_RANK[a] >= TIER_RANK[b] ? a : b);
|
|
30
|
+
// config.ts presetFloor mirrored (module-private there): a mode compiles into floors at load,
|
|
31
|
+
// so staging a mode stages exactly this table.
|
|
32
|
+
function presetFloor(mode, shape) {
|
|
33
|
+
const dflt = DEFAULT_CONFIG.routing.floors[shape] ?? "cheap";
|
|
34
|
+
if (mode === "partner-led")
|
|
35
|
+
return "frontier";
|
|
36
|
+
if (mode === "staff-led")
|
|
37
|
+
return maxTier(lowerTier(dflt), isIntegrity(shape) ? "frontier" : "cheap");
|
|
38
|
+
return dflt;
|
|
39
|
+
}
|
|
40
|
+
function floorsEqual(a, b) {
|
|
41
|
+
const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
42
|
+
for (const k of keys)
|
|
43
|
+
if (a[k] !== b[k])
|
|
44
|
+
return false;
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
/** The header mode is DERIVED from the buffer, never stored: clean floors show the loaded mode;
|
|
48
|
+
* floors matching a preset show that mode (staged); anything else reads "custom". */
|
|
49
|
+
function deriveMode(loadedMode, loaded, buffer) {
|
|
50
|
+
if (floorsEqual(loaded.floors, buffer.floors))
|
|
51
|
+
return { mode: loadedMode, staged: false };
|
|
52
|
+
for (const mode of ROUTING_MODES) {
|
|
53
|
+
if (SHAPES.every((s) => buffer.floors[s] === presetFloor(mode, s)))
|
|
54
|
+
return { mode, staged: true };
|
|
55
|
+
}
|
|
56
|
+
return { mode: "custom", staged: true };
|
|
57
|
+
}
|
|
58
|
+
/** The config the router would see with the staged policy applied: map + floors ride the buffer
|
|
59
|
+
* (deny/tier edits are the fleet view's lane, T3). The buffer snapshot is already detached. */
|
|
60
|
+
function effectiveConfig(cfg, buffer) {
|
|
61
|
+
return { ...cfg, routing: { ...cfg.routing, map: buffer.map, floors: buffer.floors } };
|
|
62
|
+
}
|
|
63
|
+
// No-arg shell path (app.ts constructs views without deps): load the operator's resolved config
|
|
64
|
+
// and derive channels from the configured tier table. Best-effort — a load failure degrades the
|
|
65
|
+
// view to an explanatory note instead of breaking the studio.
|
|
66
|
+
function loadDefaultData(repoRoot) {
|
|
67
|
+
try {
|
|
68
|
+
const { cfg, mode } = loadConfigWithMode(repoRoot);
|
|
69
|
+
const channels = Object.keys(cfg.tiers).flatMap((id) => channelsFromConfig(id, cfg));
|
|
70
|
+
return { cfg, channels, mode: mode.mode };
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export function createRoutingView(deps = {}) {
|
|
77
|
+
const data = deps.data ?? loadDefaultData(deps.repoRoot ?? process.cwd());
|
|
78
|
+
// The one edit authority: every mutating method below is a staging.apply()/revert() over this
|
|
79
|
+
// buffer, so a staged change can never hide in private view state.
|
|
80
|
+
const staging = deps.staging ??
|
|
81
|
+
new FleetStaging(data ? fleetEditableFromConfig(data.cfg) : { denyAdapters: [], denyModels: [], tiers: {}, map: {}, floors: {} });
|
|
82
|
+
let cursor = 0;
|
|
83
|
+
let inspector = null;
|
|
84
|
+
const rankInspector = (shape) => {
|
|
85
|
+
if (!data)
|
|
86
|
+
return [];
|
|
87
|
+
// THE parity seam: rank through the same shared picker glue the fleet editor uses — no
|
|
88
|
+
// comparator lives here. Clean buffer ⇒ the injected config by reference (the delegation
|
|
89
|
+
// tests pin that identity); staged edits ⇒ the effective config, what the router would
|
|
90
|
+
// choose given the staged inputs.
|
|
91
|
+
const cfgNow = staging.isDirty ? effectiveConfig(data.cfg, staging.current) : data.cfg;
|
|
92
|
+
return shapeCandidates(routingPreviewTask(shape), cfgNow, data.channels, data.profile);
|
|
93
|
+
};
|
|
94
|
+
const rowOf = (c) => candidateRow(c, data?.cfg.pricing ?? {});
|
|
95
|
+
// Re-rank the open inspector after a staging mutation changed the ranking inputs.
|
|
96
|
+
const refreshInspector = () => {
|
|
97
|
+
if (!inspector)
|
|
98
|
+
return;
|
|
99
|
+
inspector.ranked = rankInspector(inspector.shape);
|
|
100
|
+
inspector.cursor = Math.min(inspector.cursor, Math.max(inspector.ranked.length - 1, 0));
|
|
101
|
+
};
|
|
102
|
+
return {
|
|
103
|
+
id: "routing",
|
|
104
|
+
label: "Routing",
|
|
105
|
+
get selectedShape() {
|
|
106
|
+
return SHAPES[cursor];
|
|
107
|
+
},
|
|
108
|
+
get selectedMode() {
|
|
109
|
+
const loadedMode = data?.mode ?? data?.cfg.routing.mode ?? "risk-based";
|
|
110
|
+
return deriveMode(loadedMode, staging.loadedState, staging.current).mode;
|
|
111
|
+
},
|
|
112
|
+
get staging() {
|
|
113
|
+
return staging;
|
|
114
|
+
},
|
|
115
|
+
moveCursor(delta) {
|
|
116
|
+
cursor = (cursor + delta + SHAPES.length) % SHAPES.length;
|
|
117
|
+
},
|
|
118
|
+
moveInspectorCursor(delta) {
|
|
119
|
+
if (!inspector || inspector.ranked.length === 0)
|
|
120
|
+
return;
|
|
121
|
+
const n = inspector.ranked.length;
|
|
122
|
+
inspector.cursor = (inspector.cursor + delta + n) % n;
|
|
123
|
+
},
|
|
124
|
+
openInspector(shape) {
|
|
125
|
+
if (!data)
|
|
126
|
+
return;
|
|
127
|
+
const target = shape ?? SHAPES[cursor];
|
|
128
|
+
inspector = { shape: target, cursor: 0, ranked: rankInspector(target) };
|
|
129
|
+
},
|
|
130
|
+
closeInspector() {
|
|
131
|
+
inspector = null;
|
|
132
|
+
},
|
|
133
|
+
stageInspectorPin() {
|
|
134
|
+
if (!data || !inspector)
|
|
135
|
+
return;
|
|
136
|
+
const target = inspector.shape;
|
|
137
|
+
const chosen = inspector.ranked[inspector.cursor];
|
|
138
|
+
if (!chosen)
|
|
139
|
+
return;
|
|
140
|
+
const { adapter, model } = chosen.assignment;
|
|
141
|
+
staging.apply((buffer) => {
|
|
142
|
+
buffer.map[target] = { ...buffer.map[target], pin: { via: adapter, model } };
|
|
143
|
+
});
|
|
144
|
+
// No re-rank: shapeCandidates strips the shape's own pin from the ranking inputs.
|
|
145
|
+
},
|
|
146
|
+
stageFloor(tier) {
|
|
147
|
+
if (!data)
|
|
148
|
+
return;
|
|
149
|
+
const target = SHAPES[cursor];
|
|
150
|
+
staging.apply((buffer) => {
|
|
151
|
+
buffer.floors[target] = tier;
|
|
152
|
+
});
|
|
153
|
+
refreshInspector();
|
|
154
|
+
},
|
|
155
|
+
stagePrefer(chain) {
|
|
156
|
+
if (!data)
|
|
157
|
+
return;
|
|
158
|
+
const target = SHAPES[cursor];
|
|
159
|
+
staging.apply((buffer) => {
|
|
160
|
+
const entry = { ...buffer.map[target] };
|
|
161
|
+
if (chain.length)
|
|
162
|
+
entry.prefer = [...chain];
|
|
163
|
+
else
|
|
164
|
+
delete entry.prefer;
|
|
165
|
+
// an emptied entry leaves no phantom delta behind
|
|
166
|
+
if (Object.keys(entry).length)
|
|
167
|
+
buffer.map[target] = entry;
|
|
168
|
+
else
|
|
169
|
+
delete buffer.map[target];
|
|
170
|
+
});
|
|
171
|
+
refreshInspector();
|
|
172
|
+
},
|
|
173
|
+
stageMode(mode) {
|
|
174
|
+
if (!data)
|
|
175
|
+
return;
|
|
176
|
+
staging.apply((buffer) => {
|
|
177
|
+
for (const shape of SHAPES)
|
|
178
|
+
buffer.floors[shape] = presetFloor(mode, shape);
|
|
179
|
+
});
|
|
180
|
+
refreshInspector();
|
|
181
|
+
},
|
|
182
|
+
revert() {
|
|
183
|
+
staging.revert();
|
|
184
|
+
refreshInspector();
|
|
185
|
+
},
|
|
186
|
+
inspectorRows() {
|
|
187
|
+
return inspector ? inspector.ranked.map(rowOf) : null;
|
|
188
|
+
},
|
|
189
|
+
render: () => {
|
|
190
|
+
const lines = ["Routing view — per-shape policy grid"];
|
|
191
|
+
if (!data) {
|
|
192
|
+
lines.push("routing data unavailable — see `tickmarkr fleet --print` for the line-mode surface");
|
|
193
|
+
return lines;
|
|
194
|
+
}
|
|
195
|
+
const { cfg } = data;
|
|
196
|
+
const buffer = staging.current;
|
|
197
|
+
const loaded = staging.loadedState;
|
|
198
|
+
const loadedMode = data.mode ?? cfg.routing.mode ?? "risk-based";
|
|
199
|
+
const { mode, staged: modeStaged } = deriveMode(loadedMode, loaded, buffer);
|
|
200
|
+
lines.push(`mode: ${mode}${modeStaged ? ` ${STAGED_MARK} (staged)` : ""}`);
|
|
201
|
+
lines.push(` ${"shape".padEnd(10)}${"floor".padEnd(10)}${"pin".padEnd(20)}prefer`);
|
|
202
|
+
for (const [i, shape] of SHAPES.entries()) {
|
|
203
|
+
const entry = buffer.map[shape];
|
|
204
|
+
const loadedEntry = loaded.map[shape];
|
|
205
|
+
const floor = buffer.floors[shape] ?? "—";
|
|
206
|
+
const floorStaged = loaded.floors[shape] !== buffer.floors[shape];
|
|
207
|
+
const marker = isIntegrity(shape) ? "*" : "";
|
|
208
|
+
const pin = entry?.pin ? `${entry.pin.via}:${entry.pin.model}` : "—";
|
|
209
|
+
const prefer = entry?.prefer?.length ? entry.prefer.join(", ") : "—";
|
|
210
|
+
const pinStaged = JSON.stringify(loadedEntry?.pin ?? null) !== JSON.stringify(entry?.pin ?? null);
|
|
211
|
+
const preferStaged = JSON.stringify(loadedEntry?.prefer ?? null) !== JSON.stringify(entry?.prefer ?? null);
|
|
212
|
+
const sel = i === cursor ? "❯" : " ";
|
|
213
|
+
const floorCell = `${floor}${marker}${floorStaged ? STAGED_MARK : ""}`.padEnd(10);
|
|
214
|
+
const pinCell = `${pin}${pinStaged ? STAGED_MARK : ""}`.padEnd(20);
|
|
215
|
+
const preferCell = `${prefer}${preferStaged ? STAGED_MARK : ""}`;
|
|
216
|
+
lines.push(`${sel} ${shape.padEnd(10)}${floorCell}${pinCell}${preferCell}`);
|
|
217
|
+
}
|
|
218
|
+
lines.push("* integrity set: never below frontier");
|
|
219
|
+
if (staging.isDirty)
|
|
220
|
+
lines.push(`${STAGED_MARK} staged — not saved`);
|
|
221
|
+
lines.push(`pick: ${SHAPES[cursor]} — enter opens the candidate inspector`);
|
|
222
|
+
if (inspector) {
|
|
223
|
+
lines.push(`─ candidates: ${inspector.shape} — the fleet picker ranking for the same inputs ──`);
|
|
224
|
+
if (inspector.ranked.length === 0)
|
|
225
|
+
lines.push(" (no routable candidates)");
|
|
226
|
+
for (const [j, c] of inspector.ranked.entries()) {
|
|
227
|
+
lines.push(`${j === inspector.cursor ? "❯ " : " "}${rowOf(c)}`);
|
|
228
|
+
}
|
|
229
|
+
lines.push("select stages the highlighted candidate as this shape's pin");
|
|
230
|
+
}
|
|
231
|
+
return lines;
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
}
|