@oevery/rsp 3.1.1 → 3.3.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 -3
- package/README.zh-CN.md +3 -2
- package/dist/{chunk-WJXKLLIF.mjs → chunk-7HXQNBIG.mjs} +447 -734
- package/dist/{chunk-3Z4IO2TD.mjs → chunk-QM2UVPQK.mjs} +47 -30
- package/dist/{chunk-MUNLLVVH.mjs → chunk-TSUDEX5Y.mjs} +1205 -117
- package/dist/cli.mjs +5336 -3045
- package/dist/entry-GIDNR7CJ.mjs +1846 -0
- package/dist/{entry-KAWA2AS4.mjs → entry-YAW3PYFA.mjs} +4 -4
- package/package.json +12 -7
- package/rules/rsp-rules.md +30 -58
- package/skills/rsp/SKILL.md +28 -45
- package/skills/rsp/references/control-outcome.md +31 -0
- package/skills/rsp/references/durable-review.md +23 -4
- package/skills/rsp/references/focus-continuation.md +9 -0
- package/skills/rsp/references/implementation-evidence.md +12 -0
- package/skills/rsp/references/managed-routing.md +23 -18
- package/skills/rsp/references/response-language.md +11 -0
- package/skills/rsp/references/setup-repair.md +3 -3
- package/skills/rsp-commit/SKILL.md +26 -9
- package/skills/rsp-design/SKILL.md +1 -1
- package/skills/rsp-diagnose/SKILL.md +4 -2
- package/skills/rsp-implement/SKILL.md +18 -6
- package/skills/rsp-manage/SKILL.md +61 -37
- package/skills/rsp-manage/references/closeout.md +13 -0
- package/skills/rsp-manage/references/delegation.md +47 -0
- package/skills/rsp-manage/references/interruption-recovery.md +19 -0
- package/skills/rsp-manage/references/review-convergence.md +9 -0
- package/skills/rsp-release-docs/SKILL.md +15 -13
- package/skills/rsp-release-docs/references/publication-lifecycle.md +4 -0
- package/skills/rsp-resolve-findings/SKILL.md +3 -3
- package/skills/rsp-review/SKILL.md +10 -36
- package/skills/rsp-review/references/code-review.md +15 -0
- package/skills/rsp-review/references/document-review.md +15 -0
- package/skills/rsp-shape/SKILL.md +14 -4
- package/skills/rsp-shape/references/deep-clarification.md +2 -0
- package/skills/rsp-structural-audit/SKILL.md +1 -1
- package/skills/rsp-tdd/SKILL.md +11 -3
- package/skills/rsp-verify/SKILL.md +40 -0
- package/dist/entry-IPHJBI53.mjs +0 -882
- package/skills/rsp/references/release-operations.md +0 -9
package/dist/entry-IPHJBI53.mjs
DELETED
|
@@ -1,882 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
ArchiveHistoryError,
|
|
4
|
-
historyInspectionComplete,
|
|
5
|
-
inspectArchiveHistory,
|
|
6
|
-
inspectProjectStatus,
|
|
7
|
-
projectDependencyForest,
|
|
8
|
-
queryArchiveHistory,
|
|
9
|
-
readArchiveHistoryDetail
|
|
10
|
-
} from "./chunk-MUNLLVVH.mjs";
|
|
11
|
-
import {
|
|
12
|
-
displayWidth,
|
|
13
|
-
openTerminalSession,
|
|
14
|
-
truncateDisplay,
|
|
15
|
-
wrapDisplay
|
|
16
|
-
} from "./chunk-JOMFFQRG.mjs";
|
|
17
|
-
import "./chunk-WJXKLLIF.mjs";
|
|
18
|
-
|
|
19
|
-
// src/tui/entry.tsx
|
|
20
|
-
import process from "process";
|
|
21
|
-
import { render } from "ink";
|
|
22
|
-
import React from "react";
|
|
23
|
-
|
|
24
|
-
// src/tui/app.tsx
|
|
25
|
-
import { Box, Text, useApp, useInput, useStdout } from "ink";
|
|
26
|
-
import { useCallback, useEffect, useReducer, useRef } from "react";
|
|
27
|
-
|
|
28
|
-
// src/tui/history-display.ts
|
|
29
|
-
function formatHistoryRow(record, width) {
|
|
30
|
-
const boundedWidth = Math.max(1, width);
|
|
31
|
-
if (boundedWidth < 24)
|
|
32
|
-
return truncateDisplay(`${record.date} ${record.workRef} [${record.kind}] ${record.summary}`, boundedWidth);
|
|
33
|
-
const date = truncateDisplay(record.date, Math.min(10, boundedWidth));
|
|
34
|
-
const remaining = Math.max(3, boundedWidth - displayWidth(date) - 4);
|
|
35
|
-
const workRefWidth = Math.ceil(remaining / 3);
|
|
36
|
-
const kindWidth = Math.ceil((remaining - workRefWidth) / 2);
|
|
37
|
-
const summaryWidth = Math.max(1, remaining - workRefWidth - kindWidth);
|
|
38
|
-
const kind = truncateDisplay(record.kind, Math.max(1, kindWidth - 2));
|
|
39
|
-
const summary = `${record.summary}${record.summaryTruncated ? "\u2026" : ""}`;
|
|
40
|
-
return `${date} ${truncateDisplay(record.workRef, workRefWidth)} [${kind}] ${truncateDisplay(summary, summaryWidth)}`;
|
|
41
|
-
}
|
|
42
|
-
function formatHistoryField(label, value, width) {
|
|
43
|
-
return truncateDisplay(`${label}: ${value}`, Math.max(1, width));
|
|
44
|
-
}
|
|
45
|
-
function formatHistoryDiagnostic(label, diagnostic, width) {
|
|
46
|
-
return truncateDisplay(`${label} ${diagnostic}`, Math.max(1, width));
|
|
47
|
-
}
|
|
48
|
-
var CHECKBOX_MARKERS = {
|
|
49
|
-
"x": "\u2713",
|
|
50
|
-
" ": "\u25CB",
|
|
51
|
-
"/": "\u25D0",
|
|
52
|
-
"-": "\u2212"
|
|
53
|
-
};
|
|
54
|
-
function projectEvidenceItem(value) {
|
|
55
|
-
const trimmed = value.trimStart();
|
|
56
|
-
const checkboxPrefix = trimmed.match(/^(?:[-*][ \t]+)?\[([x /-])\][ \t]*/i);
|
|
57
|
-
if (checkboxPrefix) {
|
|
58
|
-
const state = checkboxPrefix[1].toLowerCase();
|
|
59
|
-
return { marker: CHECKBOX_MARKERS[state], text: trimmed.slice(checkboxPrefix[0].length) };
|
|
60
|
-
}
|
|
61
|
-
const bulletPrefix = trimmed.match(/^[-*][ \t]+/);
|
|
62
|
-
return bulletPrefix ? { marker: "\u2022", text: trimmed.slice(bulletPrefix[0].length) } : { marker: "\u2022", text: value };
|
|
63
|
-
}
|
|
64
|
-
function formatHistoryEvidenceLines(items, none, width) {
|
|
65
|
-
if (items.length === 0)
|
|
66
|
-
return [truncateDisplay(` ${none}`, Math.max(1, width))];
|
|
67
|
-
return items.flatMap((value) => {
|
|
68
|
-
const item = projectEvidenceItem(value);
|
|
69
|
-
const prefix = ` ${item.marker} `;
|
|
70
|
-
return wrapDisplay(item.text, width, prefix, " ".repeat(displayWidth(prefix)));
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
function appendHistoryTruncationMarker(line, marker, width) {
|
|
74
|
-
const suffix = ` ${marker}`;
|
|
75
|
-
const contentWidth = Math.max(1, width - displayWidth(suffix));
|
|
76
|
-
return `${truncateDisplay(line, contentWidth)}${suffix}`;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// src/tui/history-detail.ts
|
|
80
|
-
var EVIDENCE_KEYS = ["tasks", "verify", "blockers"];
|
|
81
|
-
var HISTORY_DETAIL_FIXED_ROWS = 9;
|
|
82
|
-
var HISTORY_EVIDENCE_MIN_ROWS = EVIDENCE_KEYS.length * 2;
|
|
83
|
-
var HISTORY_DETAIL_MIN_HEIGHT = 12;
|
|
84
|
-
function projectHistoryEvidence(evidence, options) {
|
|
85
|
-
const { dynamicRows = 0, headings, height, none, truncationMarker, width } = options;
|
|
86
|
-
const rowBudget = Math.max(0, height - HISTORY_DETAIL_FIXED_ROWS - dynamicRows);
|
|
87
|
-
if (rowBudget < HISTORY_EVIDENCE_MIN_ROWS)
|
|
88
|
-
return [];
|
|
89
|
-
const formatted = new Map(EVIDENCE_KEYS.map((key) => [key, formatHistoryEvidenceLines(evidence[key].items, none, width)]));
|
|
90
|
-
const allocations = new Map(EVIDENCE_KEYS.map((key) => [key, 1]));
|
|
91
|
-
let remaining = rowBudget - HISTORY_EVIDENCE_MIN_ROWS;
|
|
92
|
-
let cursor = 0;
|
|
93
|
-
while (remaining > 0) {
|
|
94
|
-
const key = Array.from({ length: EVIDENCE_KEYS.length }, (_, offset) => EVIDENCE_KEYS[(cursor + offset) % EVIDENCE_KEYS.length]).find((candidate) => (allocations.get(candidate) ?? 1) < (formatted.get(candidate)?.length ?? 1));
|
|
95
|
-
if (!key)
|
|
96
|
-
break;
|
|
97
|
-
allocations.set(key, (allocations.get(key) ?? 1) + 1);
|
|
98
|
-
cursor = (EVIDENCE_KEYS.indexOf(key) + 1) % EVIDENCE_KEYS.length;
|
|
99
|
-
remaining -= 1;
|
|
100
|
-
}
|
|
101
|
-
return EVIDENCE_KEYS.map((key) => {
|
|
102
|
-
const value = evidence[key];
|
|
103
|
-
const allLines = formatted.get(key) ?? [];
|
|
104
|
-
const lines = allLines.slice(0, allocations.get(key));
|
|
105
|
-
const truncated = value.truncated || lines.length < allLines.length;
|
|
106
|
-
if (truncated && lines.length > 0)
|
|
107
|
-
lines[lines.length - 1] = appendHistoryTruncationMarker(lines[lines.length - 1], truncationMarker, width);
|
|
108
|
-
return { key, heading: headings[key], lines, truncated };
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// src/tui/projection.ts
|
|
113
|
-
function projectItemState(item, snapshot) {
|
|
114
|
-
const blocked = item.type === "change" ? item.record.output.isBlocked : item.type === "group" ? item.group.blockers : false;
|
|
115
|
-
const ready = snapshot.plan.ready.includes(item.workRef) || item.type === "group" && item.group.readyToClose;
|
|
116
|
-
return {
|
|
117
|
-
focused: item.type === "change" && item.record.output.isFocused,
|
|
118
|
-
execution: blocked ? "blocked" : ready ? "ready" : "waiting"
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
function projectItemDependencyForest(item, snapshot) {
|
|
122
|
-
if (item.type === "history")
|
|
123
|
-
return [];
|
|
124
|
-
const roots = item.type === "group" ? item.group.slices.map((slice) => slice.name) : [item.workRef];
|
|
125
|
-
const forest = projectDependencyForest(snapshot.plan, roots);
|
|
126
|
-
return item.type === "change" ? forest[0]?.children ?? [] : forest;
|
|
127
|
-
}
|
|
128
|
-
function projectExternalBlockers(item, snapshot) {
|
|
129
|
-
if (item.type !== "change")
|
|
130
|
-
return [];
|
|
131
|
-
const edges = snapshot.plan.edges.filter((edge) => edge.change === item.workRef);
|
|
132
|
-
return item.record.blockerEntries.filter((entry) => {
|
|
133
|
-
const dependency = entry.match(/^requires\s+`([^`]+)`:\s*(\S.*)$/i);
|
|
134
|
-
if (!dependency)
|
|
135
|
-
return true;
|
|
136
|
-
return !edges.some((edge) => edge.requires === dependency[1] && edge.reason === dependency[2]);
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
function projectNextAction(item, snapshot) {
|
|
140
|
-
if (item.type === "history")
|
|
141
|
-
return { kind: "brief", value: item.history.path };
|
|
142
|
-
if (item.type === "change" && snapshot.plan.blocked.some((blocked) => blocked.change === item.workRef && blocked.external))
|
|
143
|
-
return { kind: "blocked" };
|
|
144
|
-
if (item.type === "change")
|
|
145
|
-
return { kind: "command", value: `rsp show ${item.workRef}` };
|
|
146
|
-
if (item.group.readyToClose)
|
|
147
|
-
return { kind: "command", value: `rsp group close ${item.workRef}` };
|
|
148
|
-
const executableSlice = item.group.slices.find((slice) => slice.state === "open" && snapshot.plan.ready.includes(slice.name));
|
|
149
|
-
if (executableSlice)
|
|
150
|
-
return { kind: "command", value: `rsp show ${executableSlice.name}` };
|
|
151
|
-
return { kind: "brief", value: item.group.path };
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// src/tui/state.ts
|
|
155
|
-
function dashboardLayout(width, height) {
|
|
156
|
-
if (width < 40 || height < 8)
|
|
157
|
-
return "compact";
|
|
158
|
-
return width >= 96 ? "wide" : "narrow";
|
|
159
|
-
}
|
|
160
|
-
function dashboardListWidth(layout, width) {
|
|
161
|
-
if (layout !== "wide")
|
|
162
|
-
return width;
|
|
163
|
-
return Math.min(56, Math.max(36, Math.floor(width * 0.4)));
|
|
164
|
-
}
|
|
165
|
-
function unfilteredItems(state, scope = state.scope) {
|
|
166
|
-
if (scope === "changes") {
|
|
167
|
-
return state.snapshot.records.map((record) => ({
|
|
168
|
-
type: "change",
|
|
169
|
-
key: record.output.name,
|
|
170
|
-
workRef: record.output.name,
|
|
171
|
-
title: record.title,
|
|
172
|
-
searchable: `${record.output.name} ${record.title}`,
|
|
173
|
-
record
|
|
174
|
-
}));
|
|
175
|
-
}
|
|
176
|
-
if (scope === "groups") {
|
|
177
|
-
return state.snapshot.groups.map((group) => ({
|
|
178
|
-
type: "group",
|
|
179
|
-
key: group.name,
|
|
180
|
-
workRef: group.name,
|
|
181
|
-
title: group.name,
|
|
182
|
-
searchable: `${group.name} ${group.slices.map((slice) => slice.name).join(" ")}`,
|
|
183
|
-
group
|
|
184
|
-
}));
|
|
185
|
-
}
|
|
186
|
-
return state.history.records.map((record) => ({
|
|
187
|
-
type: "history",
|
|
188
|
-
key: record.path,
|
|
189
|
-
workRef: record.workRef,
|
|
190
|
-
title: record.summary,
|
|
191
|
-
searchable: `${record.date} ${record.workRef} ${record.kind} ${record.summary} ${record.path}`,
|
|
192
|
-
history: record
|
|
193
|
-
}));
|
|
194
|
-
}
|
|
195
|
-
function itemsFor(state, scope = state.scope) {
|
|
196
|
-
const query = state.navigation[scope].filter.trim().toLocaleLowerCase();
|
|
197
|
-
const items = unfilteredItems(state, scope);
|
|
198
|
-
return query ? items.filter((item) => item.searchable.toLocaleLowerCase().includes(query)) : items;
|
|
199
|
-
}
|
|
200
|
-
function viewportCapacity(state) {
|
|
201
|
-
return Math.max(1, state.height - 7);
|
|
202
|
-
}
|
|
203
|
-
function reconcileScope(state, scope, preferredKey, preferredIndex) {
|
|
204
|
-
const current = state.navigation[scope];
|
|
205
|
-
const items = itemsFor(state, scope);
|
|
206
|
-
if (items.length === 0) {
|
|
207
|
-
return {
|
|
208
|
-
...state,
|
|
209
|
-
navigation: { ...state.navigation, [scope]: { ...current, selectedKey: null, selectedIndex: 0, viewportStart: 0 } }
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
const retainedKey = preferredKey === void 0 ? current.selectedKey : preferredKey;
|
|
213
|
-
const retained = retainedKey ? items.findIndex((item) => item.key === retainedKey) : -1;
|
|
214
|
-
const candidateIndex = preferredIndex ?? current.selectedIndex;
|
|
215
|
-
const selectedIndex = retained >= 0 ? retained : Math.max(0, Math.min(candidateIndex, items.length - 1));
|
|
216
|
-
const capacity = viewportCapacity(state);
|
|
217
|
-
const viewportStart = selectedIndex < current.viewportStart ? selectedIndex : selectedIndex >= current.viewportStart + capacity ? selectedIndex - capacity + 1 : Math.min(current.viewportStart, Math.max(0, items.length - capacity));
|
|
218
|
-
return {
|
|
219
|
-
...state,
|
|
220
|
-
navigation: {
|
|
221
|
-
...state.navigation,
|
|
222
|
-
[scope]: { ...current, selectedKey: items[selectedIndex].key, selectedIndex, viewportStart }
|
|
223
|
-
}
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
function emptyNavigation(selectedKey = null) {
|
|
227
|
-
return { filter: "", selectedKey, selectedIndex: 0, viewportStart: 0 };
|
|
228
|
-
}
|
|
229
|
-
function initialDashboardState(snapshot, dimensions) {
|
|
230
|
-
return reconcileScope({
|
|
231
|
-
snapshot,
|
|
232
|
-
scope: "changes",
|
|
233
|
-
navigation: {
|
|
234
|
-
changes: emptyNavigation(snapshot.focused[0] ?? null),
|
|
235
|
-
groups: emptyNavigation(),
|
|
236
|
-
history: emptyNavigation()
|
|
237
|
-
},
|
|
238
|
-
history: {
|
|
239
|
-
status: "idle",
|
|
240
|
-
records: [],
|
|
241
|
-
summary: null,
|
|
242
|
-
error: null,
|
|
243
|
-
queued: false,
|
|
244
|
-
listRequestId: 0,
|
|
245
|
-
detail: { record: null, loadingPath: null, error: null, errorPath: null, requestId: 0 }
|
|
246
|
-
},
|
|
247
|
-
width: dimensions.width,
|
|
248
|
-
height: dimensions.height,
|
|
249
|
-
layout: dashboardLayout(dimensions.width, dimensions.height),
|
|
250
|
-
mode: "list",
|
|
251
|
-
refresh: { running: false, queued: false, error: null }
|
|
252
|
-
}, "changes");
|
|
253
|
-
}
|
|
254
|
-
function reduceDashboard(state, action) {
|
|
255
|
-
switch (action.type) {
|
|
256
|
-
case "move": {
|
|
257
|
-
const items = itemsFor(state);
|
|
258
|
-
const current = state.navigation[state.scope];
|
|
259
|
-
const index = Math.max(0, Math.min(current.selectedIndex + action.delta, Math.max(0, items.length - 1)));
|
|
260
|
-
return reconcileScope(state, state.scope, items[index]?.key ?? null, index);
|
|
261
|
-
}
|
|
262
|
-
case "filter": {
|
|
263
|
-
const current = state.navigation[state.scope];
|
|
264
|
-
const next = {
|
|
265
|
-
...state,
|
|
266
|
-
navigation: { ...state.navigation, [state.scope]: { ...current, filter: action.value, selectedIndex: 0, viewportStart: 0 } }
|
|
267
|
-
};
|
|
268
|
-
return reconcileScope(next, state.scope, null, 0);
|
|
269
|
-
}
|
|
270
|
-
case "scope":
|
|
271
|
-
return reconcileScope({ ...state, scope: action.scope, mode: "list" }, action.scope);
|
|
272
|
-
case "scope-next": {
|
|
273
|
-
const next = state.scope === "changes" ? "groups" : state.scope === "groups" ? "history" : "changes";
|
|
274
|
-
return reconcileScope({ ...state, scope: next, mode: "list" }, next);
|
|
275
|
-
}
|
|
276
|
-
case "resize": {
|
|
277
|
-
const next = { ...state, width: action.width, height: action.height, layout: dashboardLayout(action.width, action.height) };
|
|
278
|
-
return reconcileScope(next, state.scope);
|
|
279
|
-
}
|
|
280
|
-
case "mode":
|
|
281
|
-
return { ...state, mode: action.mode };
|
|
282
|
-
case "snapshot": {
|
|
283
|
-
const next = { ...state, snapshot: action.snapshot, refresh: { running: false, queued: false, error: null } };
|
|
284
|
-
return reconcileScope(reconcileScope(next, "changes"), "groups");
|
|
285
|
-
}
|
|
286
|
-
case "refresh-requested":
|
|
287
|
-
return { ...state, refresh: { running: true, queued: state.refresh.running, error: null } };
|
|
288
|
-
case "refresh-failed":
|
|
289
|
-
return { ...state, refresh: { running: false, queued: false, error: action.message } };
|
|
290
|
-
case "history-list-requested":
|
|
291
|
-
return { ...state, history: { ...state.history, status: "loading", error: null, queued: false, listRequestId: action.requestId } };
|
|
292
|
-
case "history-list-queued":
|
|
293
|
-
return { ...state, history: { ...state.history, queued: true } };
|
|
294
|
-
case "history-list-loaded": {
|
|
295
|
-
if (action.requestId !== state.history.listRequestId)
|
|
296
|
-
return state;
|
|
297
|
-
const next = {
|
|
298
|
-
...state,
|
|
299
|
-
history: { ...state.history, status: "ready", records: action.result.records, summary: action.result.summary, error: null }
|
|
300
|
-
};
|
|
301
|
-
return reconcileScope(next, "history");
|
|
302
|
-
}
|
|
303
|
-
case "history-list-failed":
|
|
304
|
-
return action.requestId === state.history.listRequestId ? { ...state, history: { ...state.history, status: "error", error: action.message } } : state;
|
|
305
|
-
case "history-detail-requested":
|
|
306
|
-
return {
|
|
307
|
-
...state,
|
|
308
|
-
history: { ...state.history, detail: { ...state.history.detail, loadingPath: action.path, error: null, errorPath: null, requestId: action.requestId } }
|
|
309
|
-
};
|
|
310
|
-
case "history-detail-loaded":
|
|
311
|
-
return action.requestId === state.history.detail.requestId ? { ...state, history: { ...state.history, detail: { ...state.history.detail, record: action.record, loadingPath: null, error: null, errorPath: null } } } : state;
|
|
312
|
-
case "history-detail-failed":
|
|
313
|
-
return action.requestId === state.history.detail.requestId ? { ...state, history: { ...state.history, detail: { ...state.history.detail, loadingPath: null, error: action.message, errorPath: state.history.detail.loadingPath } } } : state;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
function allItems(state) {
|
|
317
|
-
return itemsFor(state);
|
|
318
|
-
}
|
|
319
|
-
function visibleItems(state) {
|
|
320
|
-
const navigation = state.navigation[state.scope];
|
|
321
|
-
return itemsFor(state).slice(navigation.viewportStart, navigation.viewportStart + viewportCapacity(state));
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// src/tui/app.tsx
|
|
325
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
326
|
-
function stateLabel(item, snapshot, messages) {
|
|
327
|
-
if (item.type === "history")
|
|
328
|
-
return item.history.kind;
|
|
329
|
-
const state = projectItemState(item, snapshot);
|
|
330
|
-
const labels = state.focused ? [messages.focused, messages[state.execution]] : [messages[state.execution]];
|
|
331
|
-
return labels.join(" \xB7 ");
|
|
332
|
-
}
|
|
333
|
-
function dependencySymbol(node, snapshot) {
|
|
334
|
-
if (snapshot.focused.includes(node.name))
|
|
335
|
-
return "\u25CE";
|
|
336
|
-
if (node.state === "ready")
|
|
337
|
-
return "\u25CF";
|
|
338
|
-
if (node.state === "archived")
|
|
339
|
-
return "\u2713";
|
|
340
|
-
if (node.state === "blocked" || node.state === "missing")
|
|
341
|
-
return "!";
|
|
342
|
-
return "\u25CB";
|
|
343
|
-
}
|
|
344
|
-
function DependencyNode({ contentWidth, expanded, last, messages, node, prefix, snapshot }) {
|
|
345
|
-
const connector = last === null ? "" : last ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
346
|
-
const childPrefix = prefix + (last === null ? "" : last ? " " : "\u2502 ");
|
|
347
|
-
const state = node.state === "archived" ? messages.resolved : messages[node.state === "missing" ? "blocked" : node.state];
|
|
348
|
-
const reasonWidth = Math.max(8, contentWidth - displayWidth(childPrefix) - displayWidth(messages.reason) - 2);
|
|
349
|
-
const reason = node.reason && (expanded ? node.reason : truncateDisplay(node.reason, reasonWidth));
|
|
350
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
351
|
-
/* @__PURE__ */ jsxs(Text, { children: [
|
|
352
|
-
prefix,
|
|
353
|
-
connector,
|
|
354
|
-
dependencySymbol(node, snapshot),
|
|
355
|
-
" ",
|
|
356
|
-
node.name,
|
|
357
|
-
" ",
|
|
358
|
-
state,
|
|
359
|
-
node.shared ? ` \xB7 \u21A9 ${messages.shared}` : ""
|
|
360
|
-
] }),
|
|
361
|
-
reason && /* @__PURE__ */ jsx(Box, { paddingLeft: displayWidth(childPrefix), children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
362
|
-
messages.reason,
|
|
363
|
-
":",
|
|
364
|
-
" ",
|
|
365
|
-
reason
|
|
366
|
-
] }) }),
|
|
367
|
-
node.children.map((child, index) => /* @__PURE__ */ jsx(DependencyNode, { contentWidth, expanded, last: index === node.children.length - 1, messages, node: child, prefix: childPrefix, snapshot }, `${node.name}:${child.name}`))
|
|
368
|
-
] });
|
|
369
|
-
}
|
|
370
|
-
function CurrentDetail({ expanded, item, messages, snapshot, width }) {
|
|
371
|
-
if (!item || item.type === "history")
|
|
372
|
-
return /* @__PURE__ */ jsx(Text, { dimColor: true, children: messages.noWork });
|
|
373
|
-
const forest = projectItemDependencyForest(item, snapshot);
|
|
374
|
-
const externalBlockers = projectExternalBlockers(item, snapshot);
|
|
375
|
-
const nextAction = projectNextAction(item, snapshot);
|
|
376
|
-
const blockers = externalBlockers.length ? externalBlockers.join("; ") : item.type === "group" && item.group.blockers || snapshot.plan.blocked.some((blocked) => blocked.change === item.workRef && blocked.external) ? messages.yes : messages.none;
|
|
377
|
-
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingLeft: 1, width, children: [
|
|
378
|
-
/* @__PURE__ */ jsxs(Text, { bold: true, children: [
|
|
379
|
-
messages.detail,
|
|
380
|
-
":",
|
|
381
|
-
" ",
|
|
382
|
-
item.workRef
|
|
383
|
-
] }),
|
|
384
|
-
/* @__PURE__ */ jsxs(Text, { children: [
|
|
385
|
-
messages.progress,
|
|
386
|
-
":",
|
|
387
|
-
" ",
|
|
388
|
-
item.type === "change" ? `${item.record.output.progress.done}/${item.record.output.progress.total}` : `${item.group.completion.done}/${item.group.completion.total}`
|
|
389
|
-
] }),
|
|
390
|
-
/* @__PURE__ */ jsxs(Text, { children: [
|
|
391
|
-
messages.status,
|
|
392
|
-
":",
|
|
393
|
-
" ",
|
|
394
|
-
stateLabel(item, snapshot, messages)
|
|
395
|
-
] }),
|
|
396
|
-
/* @__PURE__ */ jsx(Text, { bold: true, children: messages.dependencies }),
|
|
397
|
-
forest.length ? forest.map((node, index) => /* @__PURE__ */ jsx(DependencyNode, { contentWidth: Math.max(8, width - 1), expanded, last: forest.length === 1 ? null : index === forest.length - 1, messages, node, prefix: "", snapshot }, node.name)) : /* @__PURE__ */ jsx(Text, { children: messages.none }),
|
|
398
|
-
item.type === "group" && /* @__PURE__ */ jsxs(Text, { children: [
|
|
399
|
-
messages.changes,
|
|
400
|
-
":",
|
|
401
|
-
" ",
|
|
402
|
-
item.group.slices.length ? item.group.slices.map((slice) => `${slice.name} (${slice.state})`).join(", ") : messages.none
|
|
403
|
-
] }),
|
|
404
|
-
/* @__PURE__ */ jsxs(Text, { children: [
|
|
405
|
-
messages.blockers,
|
|
406
|
-
":",
|
|
407
|
-
" ",
|
|
408
|
-
blockers
|
|
409
|
-
] }),
|
|
410
|
-
/* @__PURE__ */ jsxs(Text, { children: [
|
|
411
|
-
nextAction.kind === "command" ? messages.nextCommand : nextAction.kind === "blocked" ? messages.nextAction : messages.reviewGroupBrief,
|
|
412
|
-
":",
|
|
413
|
-
" ",
|
|
414
|
-
nextAction.kind === "blocked" ? messages.awaitingOwnerDecision : nextAction.value
|
|
415
|
-
] })
|
|
416
|
-
] });
|
|
417
|
-
}
|
|
418
|
-
function HistorySummary({ item, messages, width }) {
|
|
419
|
-
if (!item)
|
|
420
|
-
return /* @__PURE__ */ jsx(Text, { dimColor: true, children: messages.noHistory });
|
|
421
|
-
const contentWidth = Math.max(8, width - 1);
|
|
422
|
-
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingLeft: 1, width, children: [
|
|
423
|
-
/* @__PURE__ */ jsx(Text, { bold: true, children: formatHistoryField(messages.detail, item.workRef, contentWidth) }),
|
|
424
|
-
/* @__PURE__ */ jsx(Text, { children: formatHistoryField(messages.archiveDate, item.history.date, contentWidth) }),
|
|
425
|
-
/* @__PURE__ */ jsx(Text, { children: formatHistoryField(messages.kind, item.history.kind, contentWidth) }),
|
|
426
|
-
/* @__PURE__ */ jsx(Text, { children: truncateDisplay(`${messages.summary}: ${item.history.summary}${item.history.summaryTruncated ? "\u2026" : ""}`, Math.max(8, width - 1)) }),
|
|
427
|
-
/* @__PURE__ */ jsx(Text, { children: truncateDisplay(`${messages.path}: ${item.history.path}`, Math.max(8, width - 1)) }),
|
|
428
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: messages.enterHistoryDetail })
|
|
429
|
-
] });
|
|
430
|
-
}
|
|
431
|
-
function Evidence({ value }) {
|
|
432
|
-
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
433
|
-
/* @__PURE__ */ jsx(Text, { bold: true, children: value.heading }),
|
|
434
|
-
value.lines.map((line, index) => /* @__PURE__ */ jsx(Text, { children: line }, index))
|
|
435
|
-
] });
|
|
436
|
-
}
|
|
437
|
-
function HistoryDetail({ height, history, item, messages, width }) {
|
|
438
|
-
if (!item)
|
|
439
|
-
return /* @__PURE__ */ jsx(Text, { dimColor: true, children: messages.noHistory });
|
|
440
|
-
if (height < HISTORY_DETAIL_MIN_HEIGHT)
|
|
441
|
-
return /* @__PURE__ */ jsx(Text, { children: messages.historyDetailCompact });
|
|
442
|
-
const detail = history.detail.record?.path === item.history.path ? history.detail.record : null;
|
|
443
|
-
const contentWidth = Math.max(8, width - 1);
|
|
444
|
-
const listChromeRows = history.status === "loading" || history.status === "error" ? 1 : 0;
|
|
445
|
-
const detailChromeRows = history.detail.loadingPath === item.history.path || history.detail.error && history.detail.errorPath === item.history.path ? 1 : 0;
|
|
446
|
-
const evidence = detail ? projectHistoryEvidence(detail.evidence, {
|
|
447
|
-
height,
|
|
448
|
-
width: contentWidth,
|
|
449
|
-
dynamicRows: listChromeRows + detailChromeRows,
|
|
450
|
-
headings: {
|
|
451
|
-
tasks: `${messages.tasks}: ${detail.checkboxes.tasks.done}/${detail.checkboxes.tasks.total}`,
|
|
452
|
-
verify: `${messages.verify}: ${detail.checkboxes.verify.done}/${detail.checkboxes.verify.total}`,
|
|
453
|
-
blockers: `${messages.blockers}:`
|
|
454
|
-
},
|
|
455
|
-
none: messages.none,
|
|
456
|
-
truncationMarker: messages.truncated
|
|
457
|
-
}) : [];
|
|
458
|
-
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width, children: [
|
|
459
|
-
/* @__PURE__ */ jsx(Text, { bold: true, children: formatHistoryField(messages.detail, item.workRef, contentWidth) }),
|
|
460
|
-
/* @__PURE__ */ jsx(Text, { children: formatHistoryField(messages.archiveDate, item.history.date, contentWidth) }),
|
|
461
|
-
/* @__PURE__ */ jsx(Text, { children: formatHistoryField(messages.kind, item.history.kind, contentWidth) }),
|
|
462
|
-
/* @__PURE__ */ jsx(Text, { children: formatHistoryField(messages.path, item.history.path, contentWidth) }),
|
|
463
|
-
history.detail.loadingPath === item.history.path && /* @__PURE__ */ jsx(Text, { children: messages.historyDetailLoading }),
|
|
464
|
-
history.detail.error && history.detail.errorPath === item.history.path && /* @__PURE__ */ jsx(Text, { color: "red", children: formatHistoryDiagnostic(messages.historyDetailFailed, history.detail.error, contentWidth) }),
|
|
465
|
-
detail && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
466
|
-
height >= 12 && /* @__PURE__ */ jsx(Text, { children: truncateDisplay(`${messages.summary}: ${detail.summary}${detail.summaryTruncated ? "\u2026" : ""}`, contentWidth) }),
|
|
467
|
-
/* @__PURE__ */ jsxs(Text, { children: [
|
|
468
|
-
messages.scenarios,
|
|
469
|
-
":",
|
|
470
|
-
" ",
|
|
471
|
-
detail.scenarioCount
|
|
472
|
-
] }),
|
|
473
|
-
evidence.length > 0 ? /* @__PURE__ */ jsx(Fragment, { children: evidence.map((section) => /* @__PURE__ */ jsx(Evidence, { value: section }, section.key)) }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: messages.historyEvidenceHidden })
|
|
474
|
-
] })
|
|
475
|
-
] });
|
|
476
|
-
}
|
|
477
|
-
function scopeNavigation(scope, messages, width) {
|
|
478
|
-
const labels = [
|
|
479
|
-
["changes", messages.changes],
|
|
480
|
-
["groups", messages.groups],
|
|
481
|
-
["history", messages.history]
|
|
482
|
-
];
|
|
483
|
-
const navigation = labels.map(([candidate, label]) => candidate === scope ? `[${label}]` : label).join(" ");
|
|
484
|
-
return truncateDisplay(`${messages.title} ${navigation}`, width);
|
|
485
|
-
}
|
|
486
|
-
function DashboardApp({ initialDimensions, initialSnapshot, inspectHistory, inspectHistoryDetail, inspectStatus, messages }) {
|
|
487
|
-
const { exit } = useApp();
|
|
488
|
-
const { stdout } = useStdout();
|
|
489
|
-
const dimensions = initialDimensions ?? { width: stdout.columns ?? 80, height: stdout.rows ?? 24 };
|
|
490
|
-
const [state, dispatch] = useReducer(reduceDashboard, initialDashboardState(initialSnapshot, dimensions));
|
|
491
|
-
const filterRefs = useRef({ changes: "", groups: "", history: "" });
|
|
492
|
-
const refreshing = useRef(false);
|
|
493
|
-
const queuedRefresh = useRef(false);
|
|
494
|
-
const historyRequest = useRef(0);
|
|
495
|
-
const historyRefreshing = useRef(false);
|
|
496
|
-
const queuedHistoryRefresh = useRef(false);
|
|
497
|
-
const detailRequest = useRef(0);
|
|
498
|
-
const items = allItems(state);
|
|
499
|
-
const selectedKey = state.navigation[state.scope].selectedKey;
|
|
500
|
-
const selected = items.find((item) => item.key === selectedKey);
|
|
501
|
-
const loadHistory = useCallback(async () => {
|
|
502
|
-
if (historyRefreshing.current) {
|
|
503
|
-
queuedHistoryRefresh.current = true;
|
|
504
|
-
dispatch({ type: "history-list-queued" });
|
|
505
|
-
return;
|
|
506
|
-
}
|
|
507
|
-
historyRefreshing.current = true;
|
|
508
|
-
do {
|
|
509
|
-
queuedHistoryRefresh.current = false;
|
|
510
|
-
const requestId = ++historyRequest.current;
|
|
511
|
-
dispatch({ type: "history-list-requested", requestId });
|
|
512
|
-
try {
|
|
513
|
-
dispatch({ type: "history-list-loaded", requestId, result: await inspectHistory() });
|
|
514
|
-
} catch (error) {
|
|
515
|
-
dispatch({ type: "history-list-failed", requestId, message: error instanceof Error ? error.message : String(error) });
|
|
516
|
-
}
|
|
517
|
-
} while (queuedHistoryRefresh.current);
|
|
518
|
-
historyRefreshing.current = false;
|
|
519
|
-
}, [inspectHistory]);
|
|
520
|
-
const refreshStatus = useCallback(async () => {
|
|
521
|
-
if (refreshing.current) {
|
|
522
|
-
queuedRefresh.current = true;
|
|
523
|
-
dispatch({ type: "refresh-requested" });
|
|
524
|
-
return;
|
|
525
|
-
}
|
|
526
|
-
refreshing.current = true;
|
|
527
|
-
dispatch({ type: "refresh-requested" });
|
|
528
|
-
do {
|
|
529
|
-
queuedRefresh.current = false;
|
|
530
|
-
try {
|
|
531
|
-
dispatch({ type: "snapshot", snapshot: await inspectStatus() });
|
|
532
|
-
} catch (error) {
|
|
533
|
-
dispatch({ type: "refresh-failed", message: error instanceof Error ? error.message : String(error) });
|
|
534
|
-
}
|
|
535
|
-
} while (queuedRefresh.current);
|
|
536
|
-
refreshing.current = false;
|
|
537
|
-
}, [inspectStatus]);
|
|
538
|
-
const loadDetail = useCallback(async (path) => {
|
|
539
|
-
const requestId = ++detailRequest.current;
|
|
540
|
-
dispatch({ type: "history-detail-requested", requestId, path });
|
|
541
|
-
try {
|
|
542
|
-
dispatch({ type: "history-detail-loaded", requestId, record: await inspectHistoryDetail(path) });
|
|
543
|
-
} catch (error) {
|
|
544
|
-
dispatch({ type: "history-detail-failed", requestId, message: error instanceof Error ? error.message : String(error) });
|
|
545
|
-
}
|
|
546
|
-
}, [inspectHistoryDetail]);
|
|
547
|
-
useEffect(() => {
|
|
548
|
-
if (state.scope === "history" && state.history.status === "idle")
|
|
549
|
-
void loadHistory();
|
|
550
|
-
}, [loadHistory, state.history.status, state.scope]);
|
|
551
|
-
useEffect(() => {
|
|
552
|
-
const resize = () => dispatch({ type: "resize", width: stdout.columns ?? 80, height: stdout.rows ?? 24 });
|
|
553
|
-
stdout.on("resize", resize);
|
|
554
|
-
return () => {
|
|
555
|
-
stdout.off("resize", resize);
|
|
556
|
-
};
|
|
557
|
-
}, [stdout]);
|
|
558
|
-
useInput((input, key) => {
|
|
559
|
-
if (key.ctrl && input === "c") {
|
|
560
|
-
exit();
|
|
561
|
-
return;
|
|
562
|
-
}
|
|
563
|
-
if (state.mode === "search") {
|
|
564
|
-
if (key.escape || key.return) {
|
|
565
|
-
dispatch({ type: "mode", mode: "list" });
|
|
566
|
-
return;
|
|
567
|
-
}
|
|
568
|
-
if (key.backspace || key.delete)
|
|
569
|
-
filterRefs.current[state.scope] = filterRefs.current[state.scope].slice(0, -1);
|
|
570
|
-
else if (input && !key.ctrl && !key.meta)
|
|
571
|
-
filterRefs.current[state.scope] += input;
|
|
572
|
-
dispatch({ type: "filter", value: filterRefs.current[state.scope] });
|
|
573
|
-
return;
|
|
574
|
-
}
|
|
575
|
-
if (key.escape) {
|
|
576
|
-
if (state.mode !== "list")
|
|
577
|
-
dispatch({ type: "mode", mode: "list" });
|
|
578
|
-
else
|
|
579
|
-
exit();
|
|
580
|
-
} else if (input === "q") {
|
|
581
|
-
exit();
|
|
582
|
-
} else if (key.tab) {
|
|
583
|
-
dispatch({ type: "scope-next" });
|
|
584
|
-
} else if (key.upArrow || input === "k") {
|
|
585
|
-
dispatch({ type: "move", delta: -1 });
|
|
586
|
-
} else if (key.downArrow || input === "j") {
|
|
587
|
-
dispatch({ type: "move", delta: 1 });
|
|
588
|
-
} else if (input === "/") {
|
|
589
|
-
dispatch({ type: "mode", mode: "search" });
|
|
590
|
-
} else if (input === "?") {
|
|
591
|
-
dispatch({ type: "mode", mode: state.mode === "help" ? "list" : "help" });
|
|
592
|
-
} else if (input === "r") {
|
|
593
|
-
void (state.scope === "history" ? loadHistory() : refreshStatus());
|
|
594
|
-
} else if (key.return) {
|
|
595
|
-
dispatch({ type: "mode", mode: "detail" });
|
|
596
|
-
if (selected?.type === "history" && state.history.detail.record?.path !== selected.history.path)
|
|
597
|
-
void loadDetail(selected.history.path);
|
|
598
|
-
}
|
|
599
|
-
});
|
|
600
|
-
if (state.layout === "compact")
|
|
601
|
-
return /* @__PURE__ */ jsx(Text, { children: messages.compact });
|
|
602
|
-
if (state.mode === "help") {
|
|
603
|
-
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
604
|
-
/* @__PURE__ */ jsx(Text, { bold: true, children: messages.helpTitle }),
|
|
605
|
-
/* @__PURE__ */ jsx(Text, { children: messages.help }),
|
|
606
|
-
/* @__PURE__ */ jsx(Text, { children: messages.dependencyDirection }),
|
|
607
|
-
/* @__PURE__ */ jsx(Text, { children: messages.legend }),
|
|
608
|
-
/* @__PURE__ */ jsx(Text, { children: messages.legendMore }),
|
|
609
|
-
/* @__PURE__ */ jsx(Text, { children: messages.canonicalStates }),
|
|
610
|
-
/* @__PURE__ */ jsx(Text, { children: messages.closeHelp })
|
|
611
|
-
] });
|
|
612
|
-
}
|
|
613
|
-
const listWidth = dashboardListWidth(state.layout, state.width);
|
|
614
|
-
const detailWidth = Math.max(8, state.width - listWidth - 2);
|
|
615
|
-
const list = /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width: listWidth, marginRight: state.layout === "wide" ? 2 : 0, children: [
|
|
616
|
-
visibleItems(state).map((item) => {
|
|
617
|
-
const selectedItem = item.key === selectedKey;
|
|
618
|
-
if (item.type === "history") {
|
|
619
|
-
return /* @__PURE__ */ jsxs(Text, { inverse: selectedItem, children: [
|
|
620
|
-
selectedItem ? "\u203A" : " ",
|
|
621
|
-
" ",
|
|
622
|
-
formatHistoryRow(item.history, Math.max(1, listWidth - 2))
|
|
623
|
-
] }, item.key);
|
|
624
|
-
}
|
|
625
|
-
const label = stateLabel(item, state.snapshot, messages);
|
|
626
|
-
const identity = item.type === "group" && item.group.slices.length ? `${item.workRef}: ${item.group.slices.map((slice) => slice.name).join(", ")}` : item.workRef;
|
|
627
|
-
return /* @__PURE__ */ jsxs(Text, { inverse: selectedItem, children: [
|
|
628
|
-
selectedItem ? "\u203A" : " ",
|
|
629
|
-
" ",
|
|
630
|
-
truncateDisplay(identity, Math.max(8, listWidth - displayWidth(label) - 5)),
|
|
631
|
-
" ",
|
|
632
|
-
"[",
|
|
633
|
-
label,
|
|
634
|
-
"]"
|
|
635
|
-
] }, item.key);
|
|
636
|
-
}),
|
|
637
|
-
items.length === 0 && !(state.scope === "history" && state.history.status === "loading") && /* @__PURE__ */ jsx(Text, { dimColor: true, children: state.navigation[state.scope].filter ? messages.noMatches : state.scope === "history" ? messages.noHistory : messages.noWork })
|
|
638
|
-
] });
|
|
639
|
-
const selectedHistory = selected?.type === "history" ? selected : void 0;
|
|
640
|
-
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width: state.width, height: state.height, children: [
|
|
641
|
-
/* @__PURE__ */ jsx(Text, { bold: true, children: scopeNavigation(state.scope, messages, state.width) }),
|
|
642
|
-
state.mode === "search" && /* @__PURE__ */ jsxs(Text, { children: [
|
|
643
|
-
messages.filter,
|
|
644
|
-
":",
|
|
645
|
-
" ",
|
|
646
|
-
state.navigation[state.scope].filter,
|
|
647
|
-
"_"
|
|
648
|
-
] }),
|
|
649
|
-
state.scope === "history" && state.history.status === "loading" && /* @__PURE__ */ jsx(Text, { children: messages.historyLoading }),
|
|
650
|
-
state.scope === "history" && state.history.status === "error" && /* @__PURE__ */ jsx(Text, { color: "red", children: formatHistoryDiagnostic(messages.historyRefreshFailed, state.history.error ?? "", state.width) }),
|
|
651
|
-
state.scope === "history" && state.history.summary && /* @__PURE__ */ jsx(Text, { dimColor: true, children: truncateDisplay(`${state.history.summary.returned}/${state.history.summary.matched} ${messages.archivedChangesShown}${state.history.summary.hasMore ? ` \xB7 ${messages.historyBounded}` : ""}`, state.width) }),
|
|
652
|
-
state.scope !== "history" && state.refresh.running && /* @__PURE__ */ jsx(Text, { children: messages.refreshing }),
|
|
653
|
-
state.scope !== "history" && state.refresh.error && /* @__PURE__ */ jsxs(Text, { color: "red", children: [
|
|
654
|
-
messages.refreshFailed,
|
|
655
|
-
" ",
|
|
656
|
-
state.refresh.error
|
|
657
|
-
] }),
|
|
658
|
-
state.scope !== "history" && state.snapshot.diagnostics.length > 0 && /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
|
|
659
|
-
messages.diagnostics,
|
|
660
|
-
":",
|
|
661
|
-
" ",
|
|
662
|
-
state.snapshot.diagnostics.map((item) => item.code).join(", ")
|
|
663
|
-
] }),
|
|
664
|
-
state.mode === "detail" ? state.scope === "history" ? /* @__PURE__ */ jsx(HistoryDetail, { height: state.height, history: state.history, item: selectedHistory, messages, width: state.width }) : /* @__PURE__ */ jsx(CurrentDetail, { expanded: true, item: selected, messages, snapshot: state.snapshot, width: state.width }) : state.layout === "wide" ? /* @__PURE__ */ jsxs(Box, { flexGrow: 1, children: [
|
|
665
|
-
list,
|
|
666
|
-
state.scope === "history" ? /* @__PURE__ */ jsx(HistorySummary, { item: selectedHistory, messages, width: detailWidth }) : /* @__PURE__ */ jsx(CurrentDetail, { expanded: false, item: selected, messages, snapshot: state.snapshot, width: detailWidth })
|
|
667
|
-
] }) : list,
|
|
668
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: truncateDisplay(messages.help, state.width) })
|
|
669
|
-
] });
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
// src/tui/history-source.ts
|
|
673
|
-
var defaultDependencies = {
|
|
674
|
-
inspect: inspectArchiveHistory,
|
|
675
|
-
readDetail: readArchiveHistoryDetail
|
|
676
|
-
};
|
|
677
|
-
function createTuiHistorySource(dependencies = defaultDependencies) {
|
|
678
|
-
let recordsByPath = /* @__PURE__ */ new Map();
|
|
679
|
-
return {
|
|
680
|
-
async list() {
|
|
681
|
-
const inspection = await dependencies.inspect();
|
|
682
|
-
if (!historyInspectionComplete(inspection)) {
|
|
683
|
-
const first = inspection.diagnostics[0];
|
|
684
|
-
throw new ArchiveHistoryError("archive_inspection_incomplete", `archive_inspection_incomplete${first ? `: ${first.path ?? "archive"} \u2014 ${first.message}` : ""}`);
|
|
685
|
-
}
|
|
686
|
-
const result = queryArchiveHistory(inspection.records);
|
|
687
|
-
const returnedPaths = new Set(result.records.map((record) => record.path));
|
|
688
|
-
const nextRecords = new Map(inspection.records.filter((record) => returnedPaths.has(record.path)).map((record) => [record.path, record]));
|
|
689
|
-
recordsByPath = nextRecords;
|
|
690
|
-
return result;
|
|
691
|
-
},
|
|
692
|
-
detail(path) {
|
|
693
|
-
const record = recordsByPath.get(path);
|
|
694
|
-
if (!record)
|
|
695
|
-
throw new ArchiveHistoryError("archive_not_found", `archived Change is not present in the last successful bounded TUI result: ${path}`);
|
|
696
|
-
return dependencies.readDetail(record);
|
|
697
|
-
}
|
|
698
|
-
};
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
// src/tui/i18n/messages.ts
|
|
702
|
-
var en = {
|
|
703
|
-
title: "RSP dashboard",
|
|
704
|
-
changes: "Changes",
|
|
705
|
-
groups: "Groups",
|
|
706
|
-
history: "History",
|
|
707
|
-
focused: "focused",
|
|
708
|
-
blocked: "blocked",
|
|
709
|
-
ready: "ready",
|
|
710
|
-
waiting: "waiting",
|
|
711
|
-
resolved: "resolved",
|
|
712
|
-
progress: "Progress",
|
|
713
|
-
status: "Status",
|
|
714
|
-
blockers: "Blockers",
|
|
715
|
-
dependencies: "Prerequisites",
|
|
716
|
-
dependencyDirection: "Dependency trees read from work to prerequisites.",
|
|
717
|
-
reason: "Reason",
|
|
718
|
-
shared: "shared",
|
|
719
|
-
legend: "Legend \u25CE focused \u25CF ready \u25CB waiting",
|
|
720
|
-
legendMore: " \u2713 resolved ! blocked",
|
|
721
|
-
canonicalStates: "Canonical states: focused \xB7 ready \xB7 waiting \xB7 resolved \xB7 blocked",
|
|
722
|
-
nextCommand: "Next command",
|
|
723
|
-
nextAction: "Next action",
|
|
724
|
-
awaitingOwnerDecision: "Awaiting owner decision",
|
|
725
|
-
reviewGroupBrief: "Review Group Brief",
|
|
726
|
-
none: "none",
|
|
727
|
-
yes: "yes",
|
|
728
|
-
noWork: "No open work.",
|
|
729
|
-
noMatches: "No items match the filter.",
|
|
730
|
-
compact: "Terminal is too small. Resize to at least 40 columns by 8 rows.",
|
|
731
|
-
filter: "Filter",
|
|
732
|
-
refreshing: "Refreshing\u2026",
|
|
733
|
-
refreshFailed: "Refresh failed; showing the last valid snapshot.",
|
|
734
|
-
help: "Tab: Changes \u2192 Groups \u2192 History \xB7 \u2191/\u2193 or j/k move \xB7 / filter \xB7 Enter detail \xB7 r refresh \xB7 ? help \xB7 q quit",
|
|
735
|
-
helpTitle: "Keyboard help",
|
|
736
|
-
closeHelp: "Press Esc or ? to close help.",
|
|
737
|
-
diagnostics: "Diagnostics",
|
|
738
|
-
detail: "Detail",
|
|
739
|
-
archiveDate: "Archive date",
|
|
740
|
-
kind: "Kind",
|
|
741
|
-
summary: "Summary",
|
|
742
|
-
path: "Path",
|
|
743
|
-
scenarios: "Scenarios",
|
|
744
|
-
tasks: "Tasks",
|
|
745
|
-
verify: "Verify",
|
|
746
|
-
truncated: "(truncated)",
|
|
747
|
-
noHistory: "No archived Changes in the bounded result.",
|
|
748
|
-
historyLoading: "Loading archive history\u2026",
|
|
749
|
-
historyRefreshFailed: "Archive history refresh failed; showing the last valid result.",
|
|
750
|
-
historyDetailLoading: "Loading structured archive detail\u2026",
|
|
751
|
-
historyDetailFailed: "Archive detail failed:",
|
|
752
|
-
archivedChangesShown: "archived Changes shown",
|
|
753
|
-
historyBounded: "bounded result; use rsp history filters for older work",
|
|
754
|
-
enterHistoryDetail: "Press Enter to load structured detail.",
|
|
755
|
-
historyEvidenceHidden: "Resize the terminal to show bounded evidence items.",
|
|
756
|
-
historyDetailCompact: "Resize to at least 12 rows to view History detail."
|
|
757
|
-
};
|
|
758
|
-
var zhCN = {
|
|
759
|
-
title: "RSP \u4EEA\u8868\u76D8",
|
|
760
|
-
changes: "\u53D8\u66F4",
|
|
761
|
-
groups: "\u53D8\u66F4\u7EC4",
|
|
762
|
-
history: "\u5386\u53F2",
|
|
763
|
-
focused: "\u5DF2\u805A\u7126",
|
|
764
|
-
blocked: "\u963B\u585E",
|
|
765
|
-
ready: "\u5C31\u7EEA",
|
|
766
|
-
waiting: "\u7B49\u5F85",
|
|
767
|
-
resolved: "\u5DF2\u89E3\u51B3",
|
|
768
|
-
progress: "\u8FDB\u5EA6",
|
|
769
|
-
status: "\u72B6\u6001",
|
|
770
|
-
blockers: "\u963B\u585E\u9879",
|
|
771
|
-
dependencies: "\u524D\u7F6E\u4F9D\u8D56",
|
|
772
|
-
dependencyDirection: "\u4F9D\u8D56\u6811\u4ECE\u5F53\u524D\u5DE5\u4F5C\u6307\u5411\u6240\u9700\u524D\u7F6E\u9879\u3002",
|
|
773
|
-
reason: "\u539F\u56E0",
|
|
774
|
-
shared: "\u5171\u4EAB",
|
|
775
|
-
legend: "\u56FE\u4F8B \u25CE \u805A\u7126 \u25CF \u5C31\u7EEA \u25CB \u7B49\u5F85",
|
|
776
|
-
legendMore: " \u2713 \u5DF2\u89E3\u51B3 ! \u963B\u585E",
|
|
777
|
-
canonicalStates: "\u89C4\u8303\u72B6\u6001\uFF1Afocused \xB7 ready \xB7 waiting \xB7 resolved \xB7 blocked",
|
|
778
|
-
nextCommand: "\u4E0B\u4E00\u6761\u547D\u4EE4",
|
|
779
|
-
nextAction: "\u4E0B\u4E00\u6B65",
|
|
780
|
-
awaitingOwnerDecision: "\u7B49\u5F85\u7EF4\u62A4\u8005\u51B3\u7B56",
|
|
781
|
-
reviewGroupBrief: "\u67E5\u770B\u53D8\u66F4\u7EC4\u8BF4\u660E",
|
|
782
|
-
none: "\u65E0",
|
|
783
|
-
yes: "\u662F",
|
|
784
|
-
noWork: "\u6CA1\u6709\u5F00\u653E\u5DE5\u4F5C\u3002",
|
|
785
|
-
noMatches: "\u6CA1\u6709\u7B26\u5408\u7B5B\u9009\u6761\u4EF6\u7684\u9879\u76EE\u3002",
|
|
786
|
-
compact: "\u7EC8\u7AEF\u8FC7\u5C0F\uFF0C\u8BF7\u8C03\u6574\u5230\u81F3\u5C11 40 \u5217 \xD7 8 \u884C\u3002",
|
|
787
|
-
filter: "\u7B5B\u9009",
|
|
788
|
-
refreshing: "\u6B63\u5728\u5237\u65B0\u2026",
|
|
789
|
-
refreshFailed: "\u5237\u65B0\u5931\u8D25\uFF0C\u7EE7\u7EED\u663E\u793A\u6700\u8FD1\u4E00\u6B21\u6709\u6548\u72B6\u6001\u3002",
|
|
790
|
-
help: "Tab\uFF1A\u53D8\u66F4 \u2192 \u53D8\u66F4\u7EC4 \u2192 \u5386\u53F2 \xB7 \u2191/\u2193 \u6216 j/k \u79FB\u52A8 \xB7 / \u7B5B\u9009 \xB7 Enter \u8BE6\u60C5 \xB7 r \u5237\u65B0 \xB7 ? \u5E2E\u52A9 \xB7 q \u9000\u51FA",
|
|
791
|
-
helpTitle: "\u5FEB\u6377\u952E\u5E2E\u52A9",
|
|
792
|
-
closeHelp: "\u6309 Esc \u6216 ? \u5173\u95ED\u5E2E\u52A9\u3002",
|
|
793
|
-
diagnostics: "\u8BCA\u65AD",
|
|
794
|
-
detail: "\u8BE6\u60C5",
|
|
795
|
-
archiveDate: "\u5F52\u6863\u65E5\u671F",
|
|
796
|
-
kind: "\u7C7B\u578B",
|
|
797
|
-
summary: "\u6458\u8981",
|
|
798
|
-
path: "\u8DEF\u5F84",
|
|
799
|
-
scenarios: "\u573A\u666F",
|
|
800
|
-
tasks: "\u4EFB\u52A1",
|
|
801
|
-
verify: "\u9A8C\u8BC1",
|
|
802
|
-
truncated: "\uFF08\u5DF2\u622A\u65AD\uFF09",
|
|
803
|
-
noHistory: "\u6709\u754C\u7ED3\u679C\u4E2D\u6CA1\u6709\u5DF2\u5F52\u6863\u53D8\u66F4\u3002",
|
|
804
|
-
historyLoading: "\u6B63\u5728\u52A0\u8F7D\u5F52\u6863\u5386\u53F2\u2026",
|
|
805
|
-
historyRefreshFailed: "\u5F52\u6863\u5386\u53F2\u5237\u65B0\u5931\u8D25\uFF0C\u7EE7\u7EED\u663E\u793A\u6700\u8FD1\u4E00\u6B21\u6709\u6548\u7ED3\u679C\u3002",
|
|
806
|
-
historyDetailLoading: "\u6B63\u5728\u52A0\u8F7D\u7ED3\u6784\u5316\u5F52\u6863\u8BE6\u60C5\u2026",
|
|
807
|
-
historyDetailFailed: "\u5F52\u6863\u8BE6\u60C5\u52A0\u8F7D\u5931\u8D25\uFF1A",
|
|
808
|
-
archivedChangesShown: "\u4E2A\u5DF2\u5F52\u6863\u53D8\u66F4",
|
|
809
|
-
historyBounded: "\u5F53\u524D\u4E3A\u6709\u754C\u7ED3\u679C\uFF1B\u66F4\u65E9\u8BB0\u5F55\u8BF7\u4F7F\u7528 rsp history \u7B5B\u9009",
|
|
810
|
-
enterHistoryDetail: "\u6309 Enter \u52A0\u8F7D\u7ED3\u6784\u5316\u8BE6\u60C5\u3002",
|
|
811
|
-
historyEvidenceHidden: "\u8BF7\u589E\u5927\u7EC8\u7AEF\u4EE5\u663E\u793A\u6709\u754C\u8BC1\u636E\u6761\u76EE\u3002",
|
|
812
|
-
historyDetailCompact: "\u8BF7\u5C06\u7EC8\u7AEF\u9AD8\u5EA6\u8C03\u6574\u5230\u81F3\u5C11 12 \u884C\u4EE5\u67E5\u770B\u5386\u53F2\u8BE6\u60C5\u3002"
|
|
813
|
-
};
|
|
814
|
-
var catalogs = {
|
|
815
|
-
en,
|
|
816
|
-
"zh-CN": zhCN
|
|
817
|
-
};
|
|
818
|
-
|
|
819
|
-
// src/tui/entry.tsx
|
|
820
|
-
var signalExitCodes = {
|
|
821
|
-
SIGHUP: 129,
|
|
822
|
-
SIGINT: 130,
|
|
823
|
-
SIGTERM: 143
|
|
824
|
-
};
|
|
825
|
-
var defaultHistorySource = createTuiHistorySource();
|
|
826
|
-
var defaultRuntime = {
|
|
827
|
-
inspect: inspectProjectStatus,
|
|
828
|
-
inspectHistory: defaultHistorySource.list,
|
|
829
|
-
inspectHistoryDetail: defaultHistorySource.detail,
|
|
830
|
-
render,
|
|
831
|
-
openSession: openTerminalSession,
|
|
832
|
-
host: process,
|
|
833
|
-
reportError: (message) => console.error(message)
|
|
834
|
-
};
|
|
835
|
-
async function runTui(locale, runtime = defaultRuntime) {
|
|
836
|
-
const screenReader = runtime.host.env.INK_SCREEN_READER === "true" || runtime.host.env.INK_SCREEN_READER === "1";
|
|
837
|
-
const session = runtime.openSession(runtime.host.stdout, { screenReader });
|
|
838
|
-
let instance;
|
|
839
|
-
const listeners = /* @__PURE__ */ new Map();
|
|
840
|
-
let cleaned = false;
|
|
841
|
-
const cleanup = () => {
|
|
842
|
-
if (cleaned)
|
|
843
|
-
return;
|
|
844
|
-
cleaned = true;
|
|
845
|
-
for (const [signal, listener] of listeners)
|
|
846
|
-
runtime.host.off(signal, listener);
|
|
847
|
-
listeners.clear();
|
|
848
|
-
instance?.unmount();
|
|
849
|
-
session.cleanup();
|
|
850
|
-
};
|
|
851
|
-
try {
|
|
852
|
-
const snapshot = await runtime.inspect();
|
|
853
|
-
instance = runtime.render(React.createElement(DashboardApp, {
|
|
854
|
-
initialSnapshot: snapshot,
|
|
855
|
-
messages: catalogs[locale],
|
|
856
|
-
inspectStatus: runtime.inspect,
|
|
857
|
-
inspectHistory: runtime.inspectHistory,
|
|
858
|
-
inspectHistoryDetail: runtime.inspectHistoryDetail
|
|
859
|
-
}), {
|
|
860
|
-
exitOnCtrlC: false,
|
|
861
|
-
patchConsole: false
|
|
862
|
-
});
|
|
863
|
-
for (const signal of Object.keys(signalExitCodes)) {
|
|
864
|
-
const listener = () => {
|
|
865
|
-
cleanup();
|
|
866
|
-
runtime.host.exit(signalExitCodes[signal]);
|
|
867
|
-
};
|
|
868
|
-
listeners.set(signal, listener);
|
|
869
|
-
runtime.host.once(signal, listener);
|
|
870
|
-
}
|
|
871
|
-
await instance.waitUntilExit();
|
|
872
|
-
cleanup();
|
|
873
|
-
return 0;
|
|
874
|
-
} catch (error) {
|
|
875
|
-
cleanup();
|
|
876
|
-
runtime.reportError(` Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
877
|
-
return 1;
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
export {
|
|
881
|
-
runTui
|
|
882
|
-
};
|