gsd-pi 2.36.0-dev.d612764 → 2.36.0-dev.f887f4e
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/resources/extensions/gsd/auto-dashboard.js +104 -334
- package/dist/resources/extensions/gsd/auto-loop.js +0 -11
- package/dist/resources/extensions/gsd/auto.js +0 -16
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
- package/dist/resources/extensions/gsd/commands.js +1 -51
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -25
- package/dist/resources/extensions/gsd/index.js +0 -5
- package/dist/resources/extensions/gsd/notifications.js +1 -10
- package/dist/resources/extensions/gsd/preferences-types.js +0 -2
- package/dist/resources/extensions/gsd/preferences-validation.js +0 -29
- package/dist/resources/extensions/gsd/preferences.js +0 -3
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +3 -4
- package/dist/resources/extensions/gsd/prompts/research-slice.md +2 -3
- package/dist/resources/extensions/gsd/templates/preferences.md +0 -6
- package/dist/resources/extensions/search-the-web/native-search.js +4 -45
- package/dist/resources/extensions/shared/terminal.js +0 -5
- package/dist/resources/extensions/subagent/index.js +60 -180
- package/package.json +1 -1
- package/packages/pi-tui/dist/terminal-image.d.ts.map +1 -1
- package/packages/pi-tui/dist/terminal-image.js +0 -4
- package/packages/pi-tui/dist/terminal-image.js.map +1 -1
- package/packages/pi-tui/src/terminal-image.ts +0 -5
- package/src/resources/extensions/gsd/auto-dashboard.ts +116 -363
- package/src/resources/extensions/gsd/auto-loop.ts +0 -42
- package/src/resources/extensions/gsd/auto.ts +0 -21
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
- package/src/resources/extensions/gsd/commands.ts +1 -54
- package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -25
- package/src/resources/extensions/gsd/index.ts +0 -8
- package/src/resources/extensions/gsd/notifications.ts +1 -10
- package/src/resources/extensions/gsd/preferences-types.ts +0 -13
- package/src/resources/extensions/gsd/preferences-validation.ts +0 -26
- package/src/resources/extensions/gsd/preferences.ts +0 -4
- package/src/resources/extensions/gsd/prompts/research-milestone.md +3 -4
- package/src/resources/extensions/gsd/prompts/research-slice.md +2 -3
- package/src/resources/extensions/gsd/templates/preferences.md +0 -6
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +0 -2
- package/src/resources/extensions/gsd/tests/preferences.test.ts +0 -23
- package/src/resources/extensions/search-the-web/native-search.ts +4 -50
- package/src/resources/extensions/shared/terminal.ts +0 -5
- package/src/resources/extensions/subagent/index.ts +79 -236
- package/dist/resources/extensions/cmux/index.js +0 -321
- package/dist/resources/extensions/gsd/commands-cmux.js +0 -120
- package/src/resources/extensions/cmux/index.ts +0 -384
- package/src/resources/extensions/gsd/commands-cmux.ts +0 -143
- package/src/resources/extensions/gsd/tests/cmux.test.ts +0 -98
|
@@ -7,16 +7,12 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { getCurrentBranch } from "./worktree.js";
|
|
9
9
|
import { getActiveHook } from "./post-unit-hooks.js";
|
|
10
|
-
import { getLedger, getProjectTotals } from "./metrics.js";
|
|
10
|
+
import { getLedger, getProjectTotals, formatTierSavings } from "./metrics.js";
|
|
11
11
|
import { resolveMilestoneFile, resolveSliceFile, } from "./paths.js";
|
|
12
12
|
import { parseRoadmap, parsePlan } from "./files.js";
|
|
13
|
-
import { readFileSync,
|
|
14
|
-
import { execFileSync } from "node:child_process";
|
|
13
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
15
14
|
import { truncateToWidth, visibleWidth } from "@gsd/pi-tui";
|
|
16
15
|
import { makeUI, GLYPH, INDENT } from "../shared/mod.js";
|
|
17
|
-
import { computeProgressScore } from "./progress-score.js";
|
|
18
|
-
import { getActiveWorktreeName } from "./worktree-command.js";
|
|
19
|
-
import { loadEffectiveGSDPreferences, getGlobalGSDPreferencesPath } from "./preferences.js";
|
|
20
16
|
// ─── Unit Description Helpers ─────────────────────────────────────────────────
|
|
21
17
|
export function unitVerb(unitType) {
|
|
22
18
|
if (unitType.startsWith("hook/"))
|
|
@@ -168,6 +164,7 @@ export function estimateTimeRemaining() {
|
|
|
168
164
|
const rm = m % 60;
|
|
169
165
|
return rm > 0 ? `~${h}h ${rm}m remaining` : `~${h}h remaining`;
|
|
170
166
|
}
|
|
167
|
+
// ─── Slice Progress Cache ─────────────────────────────────────────────────────
|
|
171
168
|
/** Cached slice progress for the widget — avoid async in render */
|
|
172
169
|
let cachedSliceProgress = null;
|
|
173
170
|
export function updateSliceProgressCache(base, mid, activeSid) {
|
|
@@ -178,7 +175,6 @@ export function updateSliceProgressCache(base, mid, activeSid) {
|
|
|
178
175
|
const content = readFileSync(roadmapFile, "utf-8");
|
|
179
176
|
const roadmap = parseRoadmap(content);
|
|
180
177
|
let activeSliceTasks = null;
|
|
181
|
-
let taskDetails = null;
|
|
182
178
|
if (activeSid) {
|
|
183
179
|
try {
|
|
184
180
|
const planFile = resolveSliceFile(base, mid, activeSid, "PLAN");
|
|
@@ -189,7 +185,6 @@ export function updateSliceProgressCache(base, mid, activeSid) {
|
|
|
189
185
|
done: plan.tasks.filter(t => t.done).length,
|
|
190
186
|
total: plan.tasks.length,
|
|
191
187
|
};
|
|
192
|
-
taskDetails = plan.tasks.map(t => ({ id: t.id, title: t.title, done: t.done }));
|
|
193
188
|
}
|
|
194
189
|
}
|
|
195
190
|
catch {
|
|
@@ -201,7 +196,6 @@ export function updateSliceProgressCache(base, mid, activeSid) {
|
|
|
201
196
|
total: roadmap.slices.length,
|
|
202
197
|
milestoneId: mid,
|
|
203
198
|
activeSliceTasks,
|
|
204
|
-
taskDetails,
|
|
205
199
|
};
|
|
206
200
|
}
|
|
207
201
|
catch {
|
|
@@ -214,38 +208,6 @@ export function getRoadmapSlicesSync() {
|
|
|
214
208
|
export function clearSliceProgressCache() {
|
|
215
209
|
cachedSliceProgress = null;
|
|
216
210
|
}
|
|
217
|
-
// ─── Last Commit Cache ────────────────────────────────────────────────────────
|
|
218
|
-
/** Cached last commit info — refreshed on the 15s timer, not every render */
|
|
219
|
-
let cachedLastCommit = null;
|
|
220
|
-
let lastCommitFetchedAt = 0;
|
|
221
|
-
function refreshLastCommit(basePath) {
|
|
222
|
-
try {
|
|
223
|
-
const raw = execFileSync("git", ["log", "-1", "--format=%cr|%s"], {
|
|
224
|
-
cwd: basePath,
|
|
225
|
-
encoding: "utf-8",
|
|
226
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
227
|
-
timeout: 3000,
|
|
228
|
-
}).trim();
|
|
229
|
-
const sep = raw.indexOf("|");
|
|
230
|
-
if (sep > 0) {
|
|
231
|
-
cachedLastCommit = {
|
|
232
|
-
timeAgo: raw.slice(0, sep).replace(/ ago$/, "").replace(/ /g, ""),
|
|
233
|
-
message: raw.slice(sep + 1),
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
lastCommitFetchedAt = Date.now();
|
|
237
|
-
}
|
|
238
|
-
catch {
|
|
239
|
-
// Non-fatal — just skip last commit display
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
function getLastCommit(basePath) {
|
|
243
|
-
// Refresh at most every 15 seconds
|
|
244
|
-
if (Date.now() - lastCommitFetchedAt > 15_000) {
|
|
245
|
-
refreshLastCommit(basePath);
|
|
246
|
-
}
|
|
247
|
-
return cachedLastCommit;
|
|
248
|
-
}
|
|
249
211
|
// ─── Footer Factory ───────────────────────────────────────────────────────────
|
|
250
212
|
/**
|
|
251
213
|
* Footer factory that renders zero lines — hides the built-in footer entirely.
|
|
@@ -257,61 +219,6 @@ export const hideFooter = () => ({
|
|
|
257
219
|
invalidate() { },
|
|
258
220
|
dispose() { },
|
|
259
221
|
});
|
|
260
|
-
const WIDGET_MODES = ["full", "small", "min", "off"];
|
|
261
|
-
let widgetMode = "full";
|
|
262
|
-
let widgetModeInitialized = false;
|
|
263
|
-
/** Load widget mode from preferences (once). */
|
|
264
|
-
function ensureWidgetModeLoaded() {
|
|
265
|
-
if (widgetModeInitialized)
|
|
266
|
-
return;
|
|
267
|
-
widgetModeInitialized = true;
|
|
268
|
-
try {
|
|
269
|
-
const loaded = loadEffectiveGSDPreferences();
|
|
270
|
-
const saved = loaded?.preferences?.widget_mode;
|
|
271
|
-
if (saved && WIDGET_MODES.includes(saved)) {
|
|
272
|
-
widgetMode = saved;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
catch { /* non-fatal — use default */ }
|
|
276
|
-
}
|
|
277
|
-
/** Persist widget mode to global preferences YAML. */
|
|
278
|
-
function persistWidgetMode(mode) {
|
|
279
|
-
try {
|
|
280
|
-
const prefsPath = getGlobalGSDPreferencesPath();
|
|
281
|
-
let content = "";
|
|
282
|
-
if (existsSync(prefsPath)) {
|
|
283
|
-
content = readFileSync(prefsPath, "utf-8");
|
|
284
|
-
}
|
|
285
|
-
const line = `widget_mode: ${mode}`;
|
|
286
|
-
const re = /^widget_mode:\s*\S+/m;
|
|
287
|
-
if (re.test(content)) {
|
|
288
|
-
content = content.replace(re, line);
|
|
289
|
-
}
|
|
290
|
-
else {
|
|
291
|
-
content = content.trimEnd() + "\n" + line + "\n";
|
|
292
|
-
}
|
|
293
|
-
writeFileSync(prefsPath, content, "utf-8");
|
|
294
|
-
}
|
|
295
|
-
catch { /* non-fatal — mode still set in memory */ }
|
|
296
|
-
}
|
|
297
|
-
/** Cycle to the next widget mode. Returns the new mode. */
|
|
298
|
-
export function cycleWidgetMode() {
|
|
299
|
-
ensureWidgetModeLoaded();
|
|
300
|
-
const idx = WIDGET_MODES.indexOf(widgetMode);
|
|
301
|
-
widgetMode = WIDGET_MODES[(idx + 1) % WIDGET_MODES.length];
|
|
302
|
-
persistWidgetMode(widgetMode);
|
|
303
|
-
return widgetMode;
|
|
304
|
-
}
|
|
305
|
-
/** Set widget mode directly. */
|
|
306
|
-
export function setWidgetMode(mode) {
|
|
307
|
-
widgetMode = mode;
|
|
308
|
-
persistWidgetMode(widgetMode);
|
|
309
|
-
}
|
|
310
|
-
/** Get current widget mode. */
|
|
311
|
-
export function getWidgetMode() {
|
|
312
|
-
ensureWidgetModeLoaded();
|
|
313
|
-
return widgetMode;
|
|
314
|
-
}
|
|
315
222
|
export function updateProgressWidget(ctx, unitType, unitId, state, accessors, tierBadge) {
|
|
316
223
|
if (!ctx.hasUI)
|
|
317
224
|
return;
|
|
@@ -320,33 +227,21 @@ export function updateProgressWidget(ctx, unitType, unitId, state, accessors, ti
|
|
|
320
227
|
const mid = state.activeMilestone;
|
|
321
228
|
const slice = state.activeSlice;
|
|
322
229
|
const task = state.activeTask;
|
|
323
|
-
const
|
|
230
|
+
const next = peekNext(unitType, state);
|
|
324
231
|
// Cache git branch at widget creation time (not per render)
|
|
325
232
|
let cachedBranch = null;
|
|
326
233
|
try {
|
|
327
234
|
cachedBranch = getCurrentBranch(accessors.getBasePath());
|
|
328
235
|
}
|
|
329
236
|
catch { /* not in git repo */ }
|
|
330
|
-
// Cache
|
|
331
|
-
let widgetPwd;
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
if (widgetHome && fullPwd.startsWith(widgetHome)) {
|
|
336
|
-
fullPwd = `~${fullPwd.slice(widgetHome.length)}`;
|
|
337
|
-
}
|
|
338
|
-
const parts = fullPwd.split("/");
|
|
339
|
-
widgetPwd = parts.length > 2 ? parts.slice(-2).join("/") : fullPwd;
|
|
237
|
+
// Cache pwd with ~ substitution
|
|
238
|
+
let widgetPwd = process.cwd();
|
|
239
|
+
const widgetHome = process.env.HOME || process.env.USERPROFILE;
|
|
240
|
+
if (widgetHome && widgetPwd.startsWith(widgetHome)) {
|
|
241
|
+
widgetPwd = `~${widgetPwd.slice(widgetHome.length)}`;
|
|
340
242
|
}
|
|
341
|
-
|
|
342
|
-
if (worktreeName && cachedBranch) {
|
|
343
|
-
widgetPwd = `${widgetPwd} (\u2387 ${cachedBranch})`;
|
|
344
|
-
}
|
|
345
|
-
else if (cachedBranch) {
|
|
243
|
+
if (cachedBranch)
|
|
346
244
|
widgetPwd = `${widgetPwd} (${cachedBranch})`;
|
|
347
|
-
}
|
|
348
|
-
// Pre-fetch last commit for display
|
|
349
|
-
refreshLastCommit(accessors.getBasePath());
|
|
350
245
|
ctx.ui.setWidget("gsd-progress", (tui, theme) => {
|
|
351
246
|
let pulseBright = true;
|
|
352
247
|
let cachedLines;
|
|
@@ -381,250 +276,132 @@ export function updateProgressWidget(ctx, unitType, unitId, state, accessors, ti
|
|
|
381
276
|
: theme.fg("dim", GLYPH.statusPending);
|
|
382
277
|
const elapsed = formatAutoElapsed(accessors.getAutoStartTime());
|
|
383
278
|
const modeTag = accessors.isStepMode() ? "NEXT" : "AUTO";
|
|
384
|
-
|
|
385
|
-
const
|
|
386
|
-
const healthColor = score.level === "green" ? "success"
|
|
387
|
-
: score.level === "yellow" ? "warning"
|
|
388
|
-
: "error";
|
|
389
|
-
const healthIcon = score.level === "green" ? GLYPH.statusActive
|
|
390
|
-
: score.level === "yellow" ? "!"
|
|
391
|
-
: "x";
|
|
392
|
-
const healthStr = ` ${theme.fg(healthColor, healthIcon)} ${theme.fg(healthColor, score.summary)}`;
|
|
393
|
-
const headerLeft = `${pad}${dot} ${theme.fg("accent", theme.bold("GSD"))} ${theme.fg("success", modeTag)}${healthStr}`;
|
|
394
|
-
// ETA in header right, after elapsed
|
|
395
|
-
const eta = estimateTimeRemaining();
|
|
396
|
-
const etaShort = eta ? eta.replace(" remaining", " left") : null;
|
|
397
|
-
const headerRight = elapsed
|
|
398
|
-
? (etaShort
|
|
399
|
-
? `${theme.fg("dim", elapsed)} ${theme.fg("dim", "·")} ${theme.fg("dim", etaShort)}`
|
|
400
|
-
: theme.fg("dim", elapsed))
|
|
401
|
-
: "";
|
|
279
|
+
const headerLeft = `${pad}${dot} ${theme.fg("accent", theme.bold("GSD"))} ${theme.fg("success", modeTag)}`;
|
|
280
|
+
const headerRight = elapsed ? theme.fg("dim", elapsed) : "";
|
|
402
281
|
lines.push(rightAlign(headerLeft, headerRight, width));
|
|
403
|
-
// ── Gather stats (needed by multiple modes) ─────────────────────
|
|
404
|
-
const cmdCtx = accessors.getCmdCtx();
|
|
405
|
-
let totalInput = 0;
|
|
406
|
-
let totalCacheRead = 0;
|
|
407
|
-
if (cmdCtx) {
|
|
408
|
-
for (const entry of cmdCtx.sessionManager.getEntries()) {
|
|
409
|
-
if (entry.type === "message") {
|
|
410
|
-
const msgEntry = entry;
|
|
411
|
-
if (msgEntry.message?.role === "assistant") {
|
|
412
|
-
const u = msgEntry.message.usage;
|
|
413
|
-
if (u) {
|
|
414
|
-
totalInput += u.input || 0;
|
|
415
|
-
totalCacheRead += u.cacheRead || 0;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
const mLedger = getLedger();
|
|
422
|
-
const autoTotals = mLedger ? getProjectTotals(mLedger.units) : null;
|
|
423
|
-
const cumulativeCost = autoTotals?.cost ?? 0;
|
|
424
|
-
const cxUsage = cmdCtx?.getContextUsage?.();
|
|
425
|
-
const cxWindow = cxUsage?.contextWindow ?? cmdCtx?.model?.contextWindow ?? 0;
|
|
426
|
-
const cxPctVal = cxUsage?.percent ?? 0;
|
|
427
|
-
const cxPct = cxUsage?.percent !== null ? cxPctVal.toFixed(1) : "?";
|
|
428
|
-
// Model display — shown in context section, not stats
|
|
429
|
-
const modelId = cmdCtx?.model?.id ?? "";
|
|
430
|
-
const modelProvider = cmdCtx?.model?.provider ?? "";
|
|
431
|
-
const modelDisplay = modelProvider && modelId
|
|
432
|
-
? `${modelProvider}/${modelId}`
|
|
433
|
-
: modelId;
|
|
434
|
-
// ── Mode: off — return empty ──────────────────────────────────
|
|
435
|
-
if (widgetMode === "off") {
|
|
436
|
-
cachedLines = [];
|
|
437
|
-
cachedWidth = width;
|
|
438
|
-
return [];
|
|
439
|
-
}
|
|
440
|
-
// ── Mode: min — header line only ──────────────────────────────
|
|
441
|
-
if (widgetMode === "min") {
|
|
442
|
-
lines.push(...ui.bar());
|
|
443
|
-
cachedLines = lines;
|
|
444
|
-
cachedWidth = width;
|
|
445
|
-
return lines;
|
|
446
|
-
}
|
|
447
|
-
// ── Mode: small — header + progress bar + compact stats ───────
|
|
448
|
-
if (widgetMode === "small") {
|
|
449
|
-
lines.push("");
|
|
450
|
-
// Action line
|
|
451
|
-
const target = task ? `${task.id}: ${task.title}` : unitId;
|
|
452
|
-
const actionLeft = `${pad}${theme.fg("accent", "▸")} ${theme.fg("accent", verb)} ${theme.fg("text", target)}`;
|
|
453
|
-
lines.push(rightAlign(actionLeft, theme.fg("dim", phaseLabel), width));
|
|
454
|
-
// Progress bar
|
|
455
|
-
const roadmapSlices = mid ? getRoadmapSlicesSync() : null;
|
|
456
|
-
if (roadmapSlices) {
|
|
457
|
-
const { done, total, activeSliceTasks } = roadmapSlices;
|
|
458
|
-
const barWidth = Math.max(6, Math.min(18, Math.floor(width * 0.25)));
|
|
459
|
-
const pct = total > 0 ? done / total : 0;
|
|
460
|
-
const filled = Math.round(pct * barWidth);
|
|
461
|
-
const bar = theme.fg("success", "━".repeat(filled))
|
|
462
|
-
+ theme.fg("dim", "─".repeat(barWidth - filled));
|
|
463
|
-
let meta = `${theme.fg("text", `${done}`)}${theme.fg("dim", `/${total} slices`)}`;
|
|
464
|
-
if (activeSliceTasks && activeSliceTasks.total > 0) {
|
|
465
|
-
const tn = Math.min(activeSliceTasks.done + 1, activeSliceTasks.total);
|
|
466
|
-
meta += `${theme.fg("dim", " · task ")}${theme.fg("accent", `${tn}`)}${theme.fg("dim", `/${activeSliceTasks.total}`)}`;
|
|
467
|
-
}
|
|
468
|
-
lines.push(`${pad}${bar} ${meta}`);
|
|
469
|
-
}
|
|
470
|
-
// Compact stats: cost + context only
|
|
471
|
-
const smallStats = [];
|
|
472
|
-
if (cumulativeCost)
|
|
473
|
-
smallStats.push(theme.fg("warning", `$${cumulativeCost.toFixed(2)}`));
|
|
474
|
-
const cxDisplay = `${cxPct}%ctx`;
|
|
475
|
-
if (cxPctVal > 90)
|
|
476
|
-
smallStats.push(theme.fg("error", cxDisplay));
|
|
477
|
-
else if (cxPctVal > 70)
|
|
478
|
-
smallStats.push(theme.fg("warning", cxDisplay));
|
|
479
|
-
else
|
|
480
|
-
smallStats.push(theme.fg("dim", cxDisplay));
|
|
481
|
-
if (smallStats.length > 0) {
|
|
482
|
-
lines.push(rightAlign("", smallStats.join(theme.fg("dim", " ")), width));
|
|
483
|
-
}
|
|
484
|
-
lines.push(...ui.bar());
|
|
485
|
-
cachedLines = lines;
|
|
486
|
-
cachedWidth = width;
|
|
487
|
-
return lines;
|
|
488
|
-
}
|
|
489
|
-
// ── Mode: full — complete two-column layout ───────────────────
|
|
490
282
|
lines.push("");
|
|
491
|
-
// Context section: milestone + slice + model
|
|
492
|
-
const hasContext = !!(mid || (slice && unitType !== "research-milestone" && unitType !== "plan-milestone"));
|
|
493
283
|
if (mid) {
|
|
494
|
-
|
|
495
|
-
lines.push(truncateToWidth(`${pad}${theme.fg("dim", mid.title)}${modelTag}`, width));
|
|
284
|
+
lines.push(truncateToWidth(`${pad}${theme.fg("dim", mid.title)}`, width));
|
|
496
285
|
}
|
|
497
286
|
if (slice && unitType !== "research-milestone" && unitType !== "plan-milestone") {
|
|
498
287
|
lines.push(truncateToWidth(`${pad}${theme.fg("text", theme.bold(`${slice.id}: ${slice.title}`))}`, width));
|
|
499
288
|
}
|
|
500
|
-
|
|
501
|
-
lines.push("");
|
|
289
|
+
lines.push("");
|
|
502
290
|
const target = task ? `${task.id}: ${task.title}` : unitId;
|
|
503
291
|
const actionLeft = `${pad}${theme.fg("accent", "▸")} ${theme.fg("accent", verb)} ${theme.fg("text", target)}`;
|
|
504
292
|
const tierTag = tierBadge ? theme.fg("dim", `[${tierBadge}] `) : "";
|
|
505
293
|
const phaseBadge = `${tierTag}${theme.fg("dim", phaseLabel)}`;
|
|
506
294
|
lines.push(rightAlign(actionLeft, phaseBadge, width));
|
|
507
295
|
lines.push("");
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
const filled = Math.round(pct * barWidth);
|
|
522
|
-
const bar = theme.fg("success", "━".repeat(filled))
|
|
523
|
-
+ theme.fg("dim", "─".repeat(barWidth - filled));
|
|
524
|
-
let meta = `${theme.fg("text", `${done}`)}${theme.fg("dim", `/${total} slices`)}`;
|
|
525
|
-
if (activeSliceTasks && activeSliceTasks.total > 0) {
|
|
526
|
-
const taskNum = isHook
|
|
527
|
-
? Math.max(activeSliceTasks.done, 1)
|
|
528
|
-
: Math.min(activeSliceTasks.done + 1, activeSliceTasks.total);
|
|
529
|
-
meta += `${theme.fg("dim", " · task ")}${theme.fg("accent", `${taskNum}`)}${theme.fg("dim", `/${activeSliceTasks.total}`)}`;
|
|
530
|
-
}
|
|
531
|
-
leftLines.push(`${pad}${bar} ${meta}`);
|
|
532
|
-
}
|
|
533
|
-
// Build right column: task checklist
|
|
534
|
-
const rightLines = [];
|
|
535
|
-
const maxVisibleTasks = 8;
|
|
536
|
-
function formatTaskLine(t, isCurrent) {
|
|
537
|
-
const glyph = t.done
|
|
538
|
-
? theme.fg("success", "*")
|
|
539
|
-
: isCurrent
|
|
540
|
-
? theme.fg("accent", ">")
|
|
541
|
-
: theme.fg("dim", ".");
|
|
542
|
-
const id = isCurrent
|
|
543
|
-
? theme.fg("accent", t.id)
|
|
544
|
-
: t.done
|
|
545
|
-
? theme.fg("muted", t.id)
|
|
546
|
-
: theme.fg("dim", t.id);
|
|
547
|
-
const title = isCurrent
|
|
548
|
-
? theme.fg("text", t.title)
|
|
549
|
-
: t.done
|
|
550
|
-
? theme.fg("muted", t.title)
|
|
551
|
-
: theme.fg("text", t.title);
|
|
552
|
-
return `${glyph} ${id}: ${title}`;
|
|
553
|
-
}
|
|
554
|
-
if (useTwoCol && taskDetailsCol) {
|
|
555
|
-
for (const t of taskDetailsCol.slice(0, maxVisibleTasks)) {
|
|
556
|
-
rightLines.push(formatTaskLine(t, !!(task && t.id === task.id)));
|
|
557
|
-
}
|
|
558
|
-
if (taskDetailsCol.length > maxVisibleTasks) {
|
|
559
|
-
rightLines.push(theme.fg("dim", ` +${taskDetailsCol.length - maxVisibleTasks} more`));
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
else if (!useTwoCol && taskDetailsCol && taskDetailsCol.length > 0) {
|
|
563
|
-
for (const t of taskDetailsCol.slice(0, maxVisibleTasks)) {
|
|
564
|
-
leftLines.push(`${pad}${formatTaskLine(t, !!(task && t.id === task.id))}`);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
// Compose columns
|
|
568
|
-
if (useTwoCol) {
|
|
569
|
-
const maxRows = Math.max(leftLines.length, rightLines.length);
|
|
570
|
-
if (maxRows > 0) {
|
|
571
|
-
lines.push("");
|
|
572
|
-
for (let i = 0; i < maxRows; i++) {
|
|
573
|
-
const left = padToWidth(truncateToWidth(leftLines[i] ?? "", leftColWidth), leftColWidth);
|
|
574
|
-
const right = rightLines[i] ?? "";
|
|
575
|
-
lines.push(`${left}${right}`);
|
|
296
|
+
if (mid) {
|
|
297
|
+
const roadmapSlices = getRoadmapSlicesSync();
|
|
298
|
+
if (roadmapSlices) {
|
|
299
|
+
const { done, total, activeSliceTasks } = roadmapSlices;
|
|
300
|
+
const barWidth = Math.max(8, Math.min(24, Math.floor(width * 0.3)));
|
|
301
|
+
const pct = total > 0 ? done / total : 0;
|
|
302
|
+
const filled = Math.round(pct * barWidth);
|
|
303
|
+
const bar = theme.fg("success", "█".repeat(filled))
|
|
304
|
+
+ theme.fg("dim", "░".repeat(barWidth - filled));
|
|
305
|
+
let meta = theme.fg("dim", `${done}/${total} slices`);
|
|
306
|
+
if (activeSliceTasks && activeSliceTasks.total > 0) {
|
|
307
|
+
const taskNum = Math.min(activeSliceTasks.done + 1, activeSliceTasks.total);
|
|
308
|
+
meta += theme.fg("dim", ` · task ${taskNum}/${activeSliceTasks.total}`);
|
|
576
309
|
}
|
|
310
|
+
// ETA estimate
|
|
311
|
+
const eta = estimateTimeRemaining();
|
|
312
|
+
if (eta) {
|
|
313
|
+
meta += theme.fg("dim", ` · ${eta}`);
|
|
314
|
+
}
|
|
315
|
+
lines.push(truncateToWidth(`${pad}${bar} ${meta}`, width));
|
|
577
316
|
}
|
|
578
317
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
for (const l of leftLines)
|
|
583
|
-
lines.push(truncateToWidth(l, width));
|
|
584
|
-
}
|
|
318
|
+
lines.push("");
|
|
319
|
+
if (next) {
|
|
320
|
+
lines.push(truncateToWidth(`${pad}${theme.fg("dim", "→")} ${theme.fg("dim", `then ${next}`)}`, width));
|
|
585
321
|
}
|
|
586
|
-
// ── Footer
|
|
322
|
+
// ── Footer info (pwd, tokens, cost, context, model) ──────────────
|
|
587
323
|
lines.push("");
|
|
324
|
+
lines.push(truncateToWidth(theme.fg("dim", `${pad}${widgetPwd}`), width, theme.fg("dim", "…")));
|
|
325
|
+
// Token stats from current unit session + cumulative cost from metrics
|
|
588
326
|
{
|
|
327
|
+
const cmdCtx = accessors.getCmdCtx();
|
|
328
|
+
let totalInput = 0, totalOutput = 0;
|
|
329
|
+
let totalCacheRead = 0, totalCacheWrite = 0;
|
|
330
|
+
if (cmdCtx) {
|
|
331
|
+
for (const entry of cmdCtx.sessionManager.getEntries()) {
|
|
332
|
+
if (entry.type === "message") {
|
|
333
|
+
const msgEntry = entry;
|
|
334
|
+
if (msgEntry.message?.role === "assistant") {
|
|
335
|
+
const u = msgEntry.message.usage;
|
|
336
|
+
if (u) {
|
|
337
|
+
totalInput += u.input || 0;
|
|
338
|
+
totalOutput += u.output || 0;
|
|
339
|
+
totalCacheRead += u.cacheRead || 0;
|
|
340
|
+
totalCacheWrite += u.cacheWrite || 0;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
const mLedger = getLedger();
|
|
347
|
+
const autoTotals = mLedger ? getProjectTotals(mLedger.units) : null;
|
|
348
|
+
const cumulativeCost = autoTotals?.cost ?? 0;
|
|
349
|
+
const cxUsage = cmdCtx?.getContextUsage?.();
|
|
350
|
+
const cxWindow = cxUsage?.contextWindow ?? cmdCtx?.model?.contextWindow ?? 0;
|
|
351
|
+
const cxPctVal = cxUsage?.percent ?? 0;
|
|
352
|
+
const cxPct = cxUsage?.percent !== null ? cxPctVal.toFixed(1) : "?";
|
|
589
353
|
const sp = [];
|
|
354
|
+
if (totalInput)
|
|
355
|
+
sp.push(`↑${formatWidgetTokens(totalInput)}`);
|
|
356
|
+
if (totalOutput)
|
|
357
|
+
sp.push(`↓${formatWidgetTokens(totalOutput)}`);
|
|
358
|
+
if (totalCacheRead)
|
|
359
|
+
sp.push(`R${formatWidgetTokens(totalCacheRead)}`);
|
|
360
|
+
if (totalCacheWrite)
|
|
361
|
+
sp.push(`W${formatWidgetTokens(totalCacheWrite)}`);
|
|
362
|
+
// Cache hit rate for current unit
|
|
590
363
|
if (totalCacheRead + totalInput > 0) {
|
|
591
364
|
const hitRate = Math.round((totalCacheRead / (totalCacheRead + totalInput)) * 100);
|
|
592
|
-
|
|
593
|
-
sp.push(theme.fg(hitColor, `${hitRate}%hit`));
|
|
365
|
+
sp.push(`\u26A1${hitRate}%`);
|
|
594
366
|
}
|
|
595
367
|
if (cumulativeCost)
|
|
596
|
-
sp.push(
|
|
597
|
-
const cxDisplay =
|
|
598
|
-
|
|
368
|
+
sp.push(`$${cumulativeCost.toFixed(3)}`);
|
|
369
|
+
const cxDisplay = cxPct === "?"
|
|
370
|
+
? `?/${formatWidgetTokens(cxWindow)}`
|
|
371
|
+
: `${cxPct}%/${formatWidgetTokens(cxWindow)}`;
|
|
372
|
+
if (cxPctVal > 90) {
|
|
599
373
|
sp.push(theme.fg("error", cxDisplay));
|
|
600
|
-
|
|
374
|
+
}
|
|
375
|
+
else if (cxPctVal > 70) {
|
|
601
376
|
sp.push(theme.fg("warning", cxDisplay));
|
|
602
|
-
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
603
379
|
sp.push(cxDisplay);
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
380
|
+
}
|
|
381
|
+
const sLeft = sp.map(p => p.includes("\x1b[") ? p : theme.fg("dim", p))
|
|
382
|
+
.join(theme.fg("dim", " "));
|
|
383
|
+
const modelId = cmdCtx?.model?.id ?? "";
|
|
384
|
+
const modelProvider = cmdCtx?.model?.provider ?? "";
|
|
385
|
+
const modelPhase = phaseLabel ? theme.fg("dim", `[${phaseLabel}] `) : "";
|
|
386
|
+
const modelDisplay = modelProvider && modelId
|
|
387
|
+
? `${modelProvider}/${modelId}`
|
|
388
|
+
: modelId;
|
|
389
|
+
const sRight = modelDisplay
|
|
390
|
+
? `${modelPhase}${theme.fg("dim", modelDisplay)}`
|
|
391
|
+
: "";
|
|
392
|
+
lines.push(rightAlign(`${pad}${sLeft}`, sRight, width));
|
|
393
|
+
// Dynamic routing savings summary
|
|
394
|
+
if (mLedger && mLedger.units.some(u => u.tier)) {
|
|
395
|
+
const savings = formatTierSavings(mLedger.units);
|
|
396
|
+
if (savings) {
|
|
397
|
+
lines.push(truncateToWidth(theme.fg("dim", `${pad}${savings}`), width));
|
|
398
|
+
}
|
|
608
399
|
}
|
|
609
400
|
}
|
|
610
|
-
// PWD line with last commit info right-aligned
|
|
611
|
-
const lastCommit = getLastCommit(accessors.getBasePath());
|
|
612
|
-
const commitStr = lastCommit
|
|
613
|
-
? theme.fg("dim", `${lastCommit.timeAgo} ago: ${lastCommit.message}`)
|
|
614
|
-
: "";
|
|
615
|
-
const pwdStr = theme.fg("dim", widgetPwd);
|
|
616
|
-
if (commitStr) {
|
|
617
|
-
lines.push(rightAlign(`${pad}${pwdStr}`, truncateToWidth(commitStr, Math.floor(width * 0.45)), width));
|
|
618
|
-
}
|
|
619
|
-
else {
|
|
620
|
-
lines.push(`${pad}${pwdStr}`);
|
|
621
|
-
}
|
|
622
|
-
// Hints line
|
|
623
401
|
const hintParts = [];
|
|
624
402
|
hintParts.push("esc pause");
|
|
625
403
|
hintParts.push(process.platform === "darwin" ? "⌃⌥G dashboard" : "Ctrl+Alt+G dashboard");
|
|
626
|
-
|
|
627
|
-
lines.push(rightAlign("", hintStr, width));
|
|
404
|
+
lines.push(...ui.hints(hintParts));
|
|
628
405
|
lines.push(...ui.bar());
|
|
629
406
|
cachedLines = lines;
|
|
630
407
|
cachedWidth = width;
|
|
@@ -650,10 +427,3 @@ function rightAlign(left, right, width) {
|
|
|
650
427
|
const gap = Math.max(1, width - leftVis - rightVis);
|
|
651
428
|
return truncateToWidth(left + " ".repeat(gap) + right, width);
|
|
652
429
|
}
|
|
653
|
-
/** Pad a string with trailing spaces to fill exactly `colWidth` (ANSI-aware). */
|
|
654
|
-
function padToWidth(s, colWidth) {
|
|
655
|
-
const vis = visibleWidth(s);
|
|
656
|
-
if (vis >= colWidth)
|
|
657
|
-
return truncateToWidth(s, colWidth);
|
|
658
|
-
return s + " ".repeat(colWidth - vis);
|
|
659
|
-
}
|
|
@@ -258,7 +258,6 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
258
258
|
}
|
|
259
259
|
// Derive state
|
|
260
260
|
let state = await deps.deriveState(s.basePath);
|
|
261
|
-
deps.syncCmuxSidebar(deps.loadEffectiveGSDPreferences()?.preferences, state);
|
|
262
261
|
let mid = state.activeMilestone?.id;
|
|
263
262
|
let midTitle = state.activeMilestone?.title;
|
|
264
263
|
debugLog("autoLoop", {
|
|
@@ -271,7 +270,6 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
271
270
|
if (mid && s.currentMilestoneId && mid !== s.currentMilestoneId) {
|
|
272
271
|
ctx.ui.notify(`Milestone ${s.currentMilestoneId} complete. Advancing to ${mid}: ${midTitle}.`, "info");
|
|
273
272
|
deps.sendDesktopNotification("GSD", `Milestone ${s.currentMilestoneId} complete!`, "success", "milestone");
|
|
274
|
-
deps.logCmuxEvent(deps.loadEffectiveGSDPreferences()?.preferences, `Milestone ${s.currentMilestoneId} complete. Advancing to ${mid}.`, "success");
|
|
275
273
|
const vizPrefs = deps.loadEffectiveGSDPreferences()?.preferences;
|
|
276
274
|
if (vizPrefs?.auto_visualize) {
|
|
277
275
|
ctx.ui.notify("Run /gsd visualize to see progress overview.", "info");
|
|
@@ -365,7 +363,6 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
365
363
|
deps.resolver.mergeAndExit(s.currentMilestoneId, ctx.ui);
|
|
366
364
|
}
|
|
367
365
|
deps.sendDesktopNotification("GSD", "All milestones complete!", "success", "milestone");
|
|
368
|
-
deps.logCmuxEvent(deps.loadEffectiveGSDPreferences()?.preferences, "All milestones complete.", "success");
|
|
369
366
|
await deps.stopAuto(ctx, pi, "All milestones complete");
|
|
370
367
|
}
|
|
371
368
|
else if (state.phase === "blocked") {
|
|
@@ -373,7 +370,6 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
373
370
|
await deps.stopAuto(ctx, pi, blockerMsg);
|
|
374
371
|
ctx.ui.notify(`${blockerMsg}. Fix and run /gsd auto.`, "warning");
|
|
375
372
|
deps.sendDesktopNotification("GSD", blockerMsg, "error", "attention");
|
|
376
|
-
deps.logCmuxEvent(deps.loadEffectiveGSDPreferences()?.preferences, blockerMsg, "error");
|
|
377
373
|
}
|
|
378
374
|
else {
|
|
379
375
|
const ids = incomplete.map((m) => m.id).join(", ");
|
|
@@ -419,7 +415,6 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
419
415
|
deps.resolver.mergeAndExit(s.currentMilestoneId, ctx.ui);
|
|
420
416
|
}
|
|
421
417
|
deps.sendDesktopNotification("GSD", `Milestone ${mid} complete!`, "success", "milestone");
|
|
422
|
-
deps.logCmuxEvent(deps.loadEffectiveGSDPreferences()?.preferences, `Milestone ${mid} complete.`, "success");
|
|
423
418
|
await deps.stopAuto(ctx, pi, `Milestone ${mid} complete`);
|
|
424
419
|
debugLog("autoLoop", { phase: "exit", reason: "milestone-complete" });
|
|
425
420
|
break;
|
|
@@ -433,7 +428,6 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
433
428
|
await deps.stopAuto(ctx, pi, blockerMsg);
|
|
434
429
|
ctx.ui.notify(`${blockerMsg}. Fix and run /gsd auto.`, "warning");
|
|
435
430
|
deps.sendDesktopNotification("GSD", blockerMsg, "error", "attention");
|
|
436
|
-
deps.logCmuxEvent(deps.loadEffectiveGSDPreferences()?.preferences, blockerMsg, "error");
|
|
437
431
|
debugLog("autoLoop", { phase: "exit", reason: "blocked" });
|
|
438
432
|
break;
|
|
439
433
|
}
|
|
@@ -464,35 +458,30 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
464
458
|
if (budgetEnforcementAction === "pause") {
|
|
465
459
|
ctx.ui.notify(`${msg} Pausing auto-mode — /gsd auto to override and continue.`, "warning");
|
|
466
460
|
deps.sendDesktopNotification("GSD", msg, "warning", "budget");
|
|
467
|
-
deps.logCmuxEvent(prefs, msg, "warning");
|
|
468
461
|
await deps.pauseAuto(ctx, pi);
|
|
469
462
|
debugLog("autoLoop", { phase: "exit", reason: "budget-pause" });
|
|
470
463
|
break;
|
|
471
464
|
}
|
|
472
465
|
ctx.ui.notify(`${msg} Continuing (enforcement: warn).`, "warning");
|
|
473
466
|
deps.sendDesktopNotification("GSD", msg, "warning", "budget");
|
|
474
|
-
deps.logCmuxEvent(prefs, msg, "warning");
|
|
475
467
|
}
|
|
476
468
|
else if (newBudgetAlertLevel === 90) {
|
|
477
469
|
s.lastBudgetAlertLevel =
|
|
478
470
|
newBudgetAlertLevel;
|
|
479
471
|
ctx.ui.notify(`Budget 90%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning");
|
|
480
472
|
deps.sendDesktopNotification("GSD", `Budget 90%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning", "budget");
|
|
481
|
-
deps.logCmuxEvent(prefs, `Budget 90%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning");
|
|
482
473
|
}
|
|
483
474
|
else if (newBudgetAlertLevel === 80) {
|
|
484
475
|
s.lastBudgetAlertLevel =
|
|
485
476
|
newBudgetAlertLevel;
|
|
486
477
|
ctx.ui.notify(`Approaching budget ceiling — 80%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning");
|
|
487
478
|
deps.sendDesktopNotification("GSD", `Approaching budget ceiling — 80%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning", "budget");
|
|
488
|
-
deps.logCmuxEvent(prefs, `Budget 80%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning");
|
|
489
479
|
}
|
|
490
480
|
else if (newBudgetAlertLevel === 75) {
|
|
491
481
|
s.lastBudgetAlertLevel =
|
|
492
482
|
newBudgetAlertLevel;
|
|
493
483
|
ctx.ui.notify(`Budget 75%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "info");
|
|
494
484
|
deps.sendDesktopNotification("GSD", `Budget 75%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "info", "budget");
|
|
495
|
-
deps.logCmuxEvent(prefs, `Budget 75%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "progress");
|
|
496
485
|
}
|
|
497
486
|
else if (budgetAlertLevel === 0) {
|
|
498
487
|
s.lastBudgetAlertLevel = 0;
|