gsd-pi 2.29.0-dev.77f06e2 → 2.29.0-dev.953d788
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 +17 -24
- package/dist/resources/extensions/bg-shell/process-manager.ts +0 -13
- package/dist/resources/extensions/gsd/auto-dashboard.ts +65 -186
- package/dist/resources/extensions/gsd/auto-post-unit.ts +3 -6
- package/dist/resources/extensions/gsd/auto-recovery.ts +22 -16
- package/dist/resources/extensions/gsd/auto-worktree-sync.ts +6 -7
- package/dist/resources/extensions/gsd/auto.ts +15 -0
- package/dist/resources/extensions/gsd/commands-handlers.ts +1 -20
- package/dist/resources/extensions/gsd/commands-logs.ts +14 -13
- package/dist/resources/extensions/gsd/commands-prefs-wizard.ts +14 -44
- package/dist/resources/extensions/gsd/commands.ts +22 -55
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +1 -2
- package/dist/resources/extensions/gsd/json-persistence.ts +1 -16
- package/dist/resources/extensions/gsd/queue-order.ts +11 -10
- package/dist/resources/extensions/gsd/session-status-io.ts +41 -23
- package/dist/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/auto-skip-loop.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/extension-selector-separator.test.ts +38 -60
- package/dist/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +1 -1
- package/dist/resources/extensions/mcporter/index.ts +525 -0
- package/dist/resources/extensions/remote-questions/discord-adapter.ts +19 -8
- package/dist/resources/extensions/remote-questions/slack-adapter.ts +17 -11
- package/dist/resources/extensions/remote-questions/telegram-adapter.ts +19 -8
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +0 -13
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +0 -13
- package/src/resources/extensions/bg-shell/process-manager.ts +0 -13
- package/src/resources/extensions/gsd/auto-dashboard.ts +65 -186
- package/src/resources/extensions/gsd/auto-post-unit.ts +3 -6
- package/src/resources/extensions/gsd/auto-recovery.ts +22 -16
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +6 -7
- package/src/resources/extensions/gsd/auto.ts +15 -0
- package/src/resources/extensions/gsd/commands-handlers.ts +1 -20
- package/src/resources/extensions/gsd/commands-logs.ts +14 -13
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +14 -44
- package/src/resources/extensions/gsd/commands.ts +22 -55
- package/src/resources/extensions/gsd/dashboard-overlay.ts +1 -2
- package/src/resources/extensions/gsd/json-persistence.ts +1 -16
- package/src/resources/extensions/gsd/queue-order.ts +11 -10
- package/src/resources/extensions/gsd/session-status-io.ts +41 -23
- package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/extension-selector-separator.test.ts +38 -60
- package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +1 -1
- package/src/resources/extensions/mcporter/index.ts +525 -0
- package/src/resources/extensions/remote-questions/discord-adapter.ts +19 -8
- package/src/resources/extensions/remote-questions/slack-adapter.ts +17 -11
- package/src/resources/extensions/remote-questions/telegram-adapter.ts +19 -8
- package/dist/resources/extensions/gsd/commands-workflow-templates.ts +0 -544
- package/dist/resources/extensions/gsd/prompts/workflow-start.md +0 -28
- package/dist/resources/extensions/gsd/tests/workflow-templates.test.ts +0 -173
- package/dist/resources/extensions/gsd/workflow-templates/bugfix.md +0 -87
- package/dist/resources/extensions/gsd/workflow-templates/dep-upgrade.md +0 -74
- package/dist/resources/extensions/gsd/workflow-templates/full-project.md +0 -41
- package/dist/resources/extensions/gsd/workflow-templates/hotfix.md +0 -45
- package/dist/resources/extensions/gsd/workflow-templates/refactor.md +0 -83
- package/dist/resources/extensions/gsd/workflow-templates/registry.json +0 -85
- package/dist/resources/extensions/gsd/workflow-templates/security-audit.md +0 -73
- package/dist/resources/extensions/gsd/workflow-templates/small-feature.md +0 -81
- package/dist/resources/extensions/gsd/workflow-templates/spike.md +0 -69
- package/dist/resources/extensions/gsd/workflow-templates.ts +0 -241
- package/dist/resources/extensions/mcp-client/index.ts +0 -459
- package/dist/resources/extensions/remote-questions/http-client.ts +0 -76
- package/src/resources/extensions/gsd/commands-workflow-templates.ts +0 -544
- package/src/resources/extensions/gsd/prompts/workflow-start.md +0 -28
- package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +0 -173
- package/src/resources/extensions/gsd/workflow-templates/bugfix.md +0 -87
- package/src/resources/extensions/gsd/workflow-templates/dep-upgrade.md +0 -74
- package/src/resources/extensions/gsd/workflow-templates/full-project.md +0 -41
- package/src/resources/extensions/gsd/workflow-templates/hotfix.md +0 -45
- package/src/resources/extensions/gsd/workflow-templates/refactor.md +0 -83
- package/src/resources/extensions/gsd/workflow-templates/registry.json +0 -85
- package/src/resources/extensions/gsd/workflow-templates/security-audit.md +0 -73
- package/src/resources/extensions/gsd/workflow-templates/small-feature.md +0 -81
- package/src/resources/extensions/gsd/workflow-templates/spike.md +0 -69
- package/src/resources/extensions/gsd/workflow-templates.ts +0 -241
- package/src/resources/extensions/mcp-client/index.ts +0 -459
- package/src/resources/extensions/remote-questions/http-client.ts +0 -76
|
@@ -204,13 +204,6 @@ export function estimateTimeRemaining(): string | null {
|
|
|
204
204
|
|
|
205
205
|
// ─── Slice Progress Cache ─────────────────────────────────────────────────────
|
|
206
206
|
|
|
207
|
-
/** Cached task detail for the widget task checklist */
|
|
208
|
-
interface CachedTaskDetail {
|
|
209
|
-
id: string;
|
|
210
|
-
title: string;
|
|
211
|
-
done: boolean;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
207
|
/** Cached slice progress for the widget — avoid async in render */
|
|
215
208
|
let cachedSliceProgress: {
|
|
216
209
|
done: number;
|
|
@@ -218,8 +211,6 @@ let cachedSliceProgress: {
|
|
|
218
211
|
milestoneId: string;
|
|
219
212
|
/** Real task progress for the active slice, if its plan file exists */
|
|
220
213
|
activeSliceTasks: { done: number; total: number } | null;
|
|
221
|
-
/** Full task list for the active slice checklist */
|
|
222
|
-
taskDetails: CachedTaskDetail[] | null;
|
|
223
214
|
} | null = null;
|
|
224
215
|
|
|
225
216
|
export function updateSliceProgressCache(base: string, mid: string, activeSid?: string): void {
|
|
@@ -230,7 +221,6 @@ export function updateSliceProgressCache(base: string, mid: string, activeSid?:
|
|
|
230
221
|
const roadmap = parseRoadmap(content);
|
|
231
222
|
|
|
232
223
|
let activeSliceTasks: { done: number; total: number } | null = null;
|
|
233
|
-
let taskDetails: CachedTaskDetail[] | null = null;
|
|
234
224
|
if (activeSid) {
|
|
235
225
|
try {
|
|
236
226
|
const planFile = resolveSliceFile(base, mid, activeSid, "PLAN");
|
|
@@ -241,7 +231,6 @@ export function updateSliceProgressCache(base: string, mid: string, activeSid?:
|
|
|
241
231
|
done: plan.tasks.filter(t => t.done).length,
|
|
242
232
|
total: plan.tasks.length,
|
|
243
233
|
};
|
|
244
|
-
taskDetails = plan.tasks.map(t => ({ id: t.id, title: t.title, done: t.done }));
|
|
245
234
|
}
|
|
246
235
|
} catch {
|
|
247
236
|
// Non-fatal — just omit task count
|
|
@@ -253,19 +242,13 @@ export function updateSliceProgressCache(base: string, mid: string, activeSid?:
|
|
|
253
242
|
total: roadmap.slices.length,
|
|
254
243
|
milestoneId: mid,
|
|
255
244
|
activeSliceTasks,
|
|
256
|
-
taskDetails,
|
|
257
245
|
};
|
|
258
246
|
} catch {
|
|
259
247
|
// Non-fatal — widget just won't show progress bar
|
|
260
248
|
}
|
|
261
249
|
}
|
|
262
250
|
|
|
263
|
-
export function getRoadmapSlicesSync(): {
|
|
264
|
-
done: number;
|
|
265
|
-
total: number;
|
|
266
|
-
activeSliceTasks: { done: number; total: number } | null;
|
|
267
|
-
taskDetails: CachedTaskDetail[] | null;
|
|
268
|
-
} | null {
|
|
251
|
+
export function getRoadmapSlicesSync(): { done: number; total: number; activeSliceTasks: { done: number; total: number } | null } | null {
|
|
269
252
|
return cachedSliceProgress;
|
|
270
253
|
}
|
|
271
254
|
|
|
@@ -366,84 +349,87 @@ export function updateProgressWidget(
|
|
|
366
349
|
const lines: string[] = [];
|
|
367
350
|
const pad = INDENT.base;
|
|
368
351
|
|
|
369
|
-
// ── Top bar
|
|
352
|
+
// ── Line 1: Top bar ───────────────────────────────────────────────
|
|
370
353
|
lines.push(...ui.bar());
|
|
371
354
|
|
|
372
|
-
// ── Header: GSD AUTO ... elapsed ────────────────────────────────
|
|
373
355
|
const dot = pulseBright
|
|
374
356
|
? theme.fg("accent", GLYPH.statusActive)
|
|
375
357
|
: theme.fg("dim", GLYPH.statusPending);
|
|
376
358
|
const elapsed = formatAutoElapsed(accessors.getAutoStartTime());
|
|
377
359
|
const modeTag = accessors.isStepMode() ? "NEXT" : "AUTO";
|
|
378
|
-
const headerLeft = `${pad}${dot} ${theme.fg("accent", theme.bold("GSD"))}
|
|
360
|
+
const headerLeft = `${pad}${dot} ${theme.fg("accent", theme.bold("GSD"))} ${theme.fg("success", modeTag)}`;
|
|
379
361
|
const headerRight = elapsed ? theme.fg("dim", elapsed) : "";
|
|
380
362
|
lines.push(rightAlign(headerLeft, headerRight, width));
|
|
381
363
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
if (mid)
|
|
364
|
+
lines.push("");
|
|
365
|
+
|
|
366
|
+
if (mid) {
|
|
367
|
+
lines.push(truncateToWidth(`${pad}${theme.fg("dim", mid.title)}`, width));
|
|
368
|
+
}
|
|
369
|
+
|
|
385
370
|
if (slice && unitType !== "research-milestone" && unitType !== "plan-milestone") {
|
|
386
|
-
|
|
371
|
+
lines.push(truncateToWidth(
|
|
372
|
+
`${pad}${theme.fg("text", theme.bold(`${slice.id}: ${slice.title}`))}`,
|
|
373
|
+
width,
|
|
374
|
+
));
|
|
387
375
|
}
|
|
376
|
+
|
|
377
|
+
lines.push("");
|
|
378
|
+
|
|
388
379
|
const isHook = unitType.startsWith("hook/");
|
|
389
380
|
const target = isHook
|
|
390
381
|
? (unitId.split("/").pop() ?? unitId)
|
|
391
382
|
: (task ? `${task.id}: ${task.title}` : unitId);
|
|
392
|
-
|
|
393
|
-
|
|
383
|
+
const actionLeft = `${pad}${theme.fg("accent", "▸")} ${theme.fg("accent", verb)} ${theme.fg("text", target)}`;
|
|
394
384
|
const tierTag = tierBadge ? theme.fg("dim", `[${tierBadge}] `) : "";
|
|
395
385
|
const phaseBadge = `${tierTag}${theme.fg("dim", phaseLabel)}`;
|
|
396
|
-
|
|
397
|
-
lines.push(
|
|
398
|
-
|
|
399
|
-
// ── Two-column body ─────────────────────────────────────────────
|
|
400
|
-
// Left: progress, ETA, next, stats (fixed) | Right: task checklist (fixed, adjacent)
|
|
401
|
-
// Both columns sit left-to-center; empty space is on the right.
|
|
402
|
-
const divider = theme.fg("dim", "│");
|
|
403
|
-
const minTwoColWidth = 100;
|
|
404
|
-
const rightColFixed = 44;
|
|
405
|
-
const colGap = 5; // breathing room between columns
|
|
406
|
-
// Left column takes remaining space — no truncation on wide terminals
|
|
407
|
-
const useTwoCol = width >= minTwoColWidth;
|
|
408
|
-
const rightColWidth = useTwoCol ? rightColFixed : 0;
|
|
409
|
-
const leftColWidth = useTwoCol ? width - rightColWidth - colGap : width;
|
|
410
|
-
|
|
411
|
-
const roadmapSlices = mid ? getRoadmapSlicesSync() : null;
|
|
412
|
-
|
|
413
|
-
// Build left column: progress bar, ETA, next step, token stats
|
|
414
|
-
const leftLines: string[] = [];
|
|
415
|
-
|
|
416
|
-
if (roadmapSlices) {
|
|
417
|
-
const { done, total, activeSliceTasks } = roadmapSlices;
|
|
418
|
-
const barWidth = Math.max(6, Math.min(18, Math.floor(leftColWidth * 0.4)));
|
|
419
|
-
const pct = total > 0 ? done / total : 0;
|
|
420
|
-
const filled = Math.round(pct * barWidth);
|
|
421
|
-
const bar = theme.fg("success", "█".repeat(filled))
|
|
422
|
-
+ theme.fg("dim", "░".repeat(barWidth - filled));
|
|
423
|
-
|
|
424
|
-
let meta = theme.fg("dim", `${done}/${total} slices`);
|
|
425
|
-
if (activeSliceTasks && activeSliceTasks.total > 0) {
|
|
426
|
-
const taskNum = isHook
|
|
427
|
-
? Math.max(activeSliceTasks.done, 1)
|
|
428
|
-
: Math.min(activeSliceTasks.done + 1, activeSliceTasks.total);
|
|
429
|
-
meta += theme.fg("dim", ` · task ${taskNum}/${activeSliceTasks.total}`);
|
|
430
|
-
}
|
|
431
|
-
leftLines.push(truncateToWidth(`${pad}${bar} ${meta}`, leftColWidth));
|
|
386
|
+
lines.push(rightAlign(actionLeft, phaseBadge, width));
|
|
387
|
+
lines.push("");
|
|
432
388
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
389
|
+
if (mid) {
|
|
390
|
+
const roadmapSlices = getRoadmapSlicesSync();
|
|
391
|
+
if (roadmapSlices) {
|
|
392
|
+
const { done, total, activeSliceTasks } = roadmapSlices;
|
|
393
|
+
const barWidth = Math.max(8, Math.min(24, Math.floor(width * 0.3)));
|
|
394
|
+
const pct = total > 0 ? done / total : 0;
|
|
395
|
+
const filled = Math.round(pct * barWidth);
|
|
396
|
+
const bar = theme.fg("success", "█".repeat(filled))
|
|
397
|
+
+ theme.fg("dim", "░".repeat(barWidth - filled));
|
|
398
|
+
|
|
399
|
+
let meta = theme.fg("dim", `${done}/${total} slices`);
|
|
400
|
+
|
|
401
|
+
if (activeSliceTasks && activeSliceTasks.total > 0) {
|
|
402
|
+
// For hooks, show the trigger task number (done), not the next task (done + 1)
|
|
403
|
+
const taskNum = isHook
|
|
404
|
+
? Math.max(activeSliceTasks.done, 1)
|
|
405
|
+
: Math.min(activeSliceTasks.done + 1, activeSliceTasks.total);
|
|
406
|
+
meta += theme.fg("dim", ` · task ${taskNum}/${activeSliceTasks.total}`);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// ETA estimate
|
|
410
|
+
const eta = estimateTimeRemaining();
|
|
411
|
+
if (eta) {
|
|
412
|
+
meta += theme.fg("dim", ` · ${eta}`);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
lines.push(truncateToWidth(`${pad}${bar} ${meta}`, width));
|
|
436
416
|
}
|
|
437
417
|
}
|
|
438
418
|
|
|
419
|
+
lines.push("");
|
|
420
|
+
|
|
439
421
|
if (next) {
|
|
440
|
-
|
|
422
|
+
lines.push(truncateToWidth(
|
|
441
423
|
`${pad}${theme.fg("dim", "→")} ${theme.fg("dim", `then ${next}`)}`,
|
|
442
|
-
|
|
424
|
+
width,
|
|
443
425
|
));
|
|
444
426
|
}
|
|
445
427
|
|
|
446
|
-
//
|
|
428
|
+
// ── Footer info (pwd, tokens, cost, context, model) ──────────────
|
|
429
|
+
lines.push("");
|
|
430
|
+
lines.push(truncateToWidth(theme.fg("dim", `${pad}${widgetPwd}`), width, theme.fg("dim", "…")));
|
|
431
|
+
|
|
432
|
+
// Token stats from current unit session + cumulative cost from metrics
|
|
447
433
|
{
|
|
448
434
|
const cmdCtx = accessors.getCmdCtx();
|
|
449
435
|
let totalInput = 0, totalOutput = 0;
|
|
@@ -478,6 +464,7 @@ export function updateProgressWidget(
|
|
|
478
464
|
if (totalOutput) sp.push(`↓${formatWidgetTokens(totalOutput)}`);
|
|
479
465
|
if (totalCacheRead) sp.push(`R${formatWidgetTokens(totalCacheRead)}`);
|
|
480
466
|
if (totalCacheWrite) sp.push(`W${formatWidgetTokens(totalCacheWrite)}`);
|
|
467
|
+
// Cache hit rate for current unit
|
|
481
468
|
if (totalCacheRead + totalInput > 0) {
|
|
482
469
|
const hitRate = Math.round((totalCacheRead / (totalCacheRead + totalInput)) * 100);
|
|
483
470
|
sp.push(`\u26A1${hitRate}%`);
|
|
@@ -496,134 +483,33 @@ export function updateProgressWidget(
|
|
|
496
483
|
sp.push(cxDisplay);
|
|
497
484
|
}
|
|
498
485
|
|
|
499
|
-
const
|
|
486
|
+
const sLeft = sp.map(p => p.includes("\x1b[") ? p : theme.fg("dim", p))
|
|
500
487
|
.join(theme.fg("dim", " "));
|
|
501
|
-
leftLines.push(truncateToWidth(`${pad}${tokenLine}`, leftColWidth));
|
|
502
488
|
|
|
503
489
|
const modelId = cmdCtx?.model?.id ?? "";
|
|
504
490
|
const modelProvider = cmdCtx?.model?.provider ?? "";
|
|
491
|
+
const modelPhase = phaseLabel ? theme.fg("dim", `[${phaseLabel}] `) : "";
|
|
505
492
|
const modelDisplay = modelProvider && modelId
|
|
506
493
|
? `${modelProvider}/${modelId}`
|
|
507
494
|
: modelId;
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
495
|
+
const sRight = modelDisplay
|
|
496
|
+
? `${modelPhase}${theme.fg("dim", modelDisplay)}`
|
|
497
|
+
: "";
|
|
498
|
+
lines.push(rightAlign(`${pad}${sLeft}`, sRight, width));
|
|
511
499
|
|
|
512
|
-
// Dynamic routing savings
|
|
500
|
+
// Dynamic routing savings summary
|
|
513
501
|
if (mLedger && mLedger.units.some(u => u.tier)) {
|
|
514
502
|
const savings = formatTierSavings(mLedger.units);
|
|
515
503
|
if (savings) {
|
|
516
|
-
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
// Build right column: task checklist (pegged to right edge)
|
|
522
|
-
const rightLines: string[] = [];
|
|
523
|
-
const taskDetails = roadmapSlices?.taskDetails ?? null;
|
|
524
|
-
const maxVisibleTasks = 8;
|
|
525
|
-
const rpad = " ";
|
|
526
|
-
|
|
527
|
-
if (useTwoCol) {
|
|
528
|
-
if (taskDetails && taskDetails.length > 0) {
|
|
529
|
-
const visibleTasks = taskDetails.slice(0, maxVisibleTasks);
|
|
530
|
-
for (const t of visibleTasks) {
|
|
531
|
-
const isCurrent = task && t.id === task.id;
|
|
532
|
-
const glyph = t.done
|
|
533
|
-
? theme.fg("success", GLYPH.statusDone)
|
|
534
|
-
: isCurrent
|
|
535
|
-
? theme.fg("accent", "▸")
|
|
536
|
-
: theme.fg("dim", " ");
|
|
537
|
-
const label = isCurrent
|
|
538
|
-
? theme.fg("text", `${t.id}: ${t.title}`)
|
|
539
|
-
: t.done
|
|
540
|
-
? theme.fg("dim", `${t.id}: ${t.title}`)
|
|
541
|
-
: theme.fg("text", `${t.id}: ${t.title}`);
|
|
542
|
-
rightLines.push(truncateToWidth(`${rpad}${glyph} ${label}`, rightColWidth));
|
|
543
|
-
}
|
|
544
|
-
if (taskDetails.length > maxVisibleTasks) {
|
|
545
|
-
rightLines.push(truncateToWidth(
|
|
546
|
-
`${rpad}${theme.fg("dim", ` …+${taskDetails.length - maxVisibleTasks} more`)}`,
|
|
547
|
-
rightColWidth,
|
|
548
|
-
));
|
|
549
|
-
}
|
|
550
|
-
} else if (roadmapSlices?.activeSliceTasks) {
|
|
551
|
-
const { done: tDone, total: tTotal } = roadmapSlices.activeSliceTasks;
|
|
552
|
-
rightLines.push(`${rpad}${theme.fg("dim", `${tDone}/${tTotal} tasks`)}`);
|
|
553
|
-
}
|
|
554
|
-
} else {
|
|
555
|
-
// Narrow single-column: task list goes into left column
|
|
556
|
-
if (taskDetails && taskDetails.length > 0) {
|
|
557
|
-
for (const t of taskDetails.slice(0, maxVisibleTasks)) {
|
|
558
|
-
const isCurrent = task && t.id === task.id;
|
|
559
|
-
const glyph = t.done
|
|
560
|
-
? theme.fg("success", GLYPH.statusDone)
|
|
561
|
-
: isCurrent
|
|
562
|
-
? theme.fg("accent", "▸")
|
|
563
|
-
: theme.fg("dim", " ");
|
|
564
|
-
const label = isCurrent
|
|
565
|
-
? theme.fg("text", `${t.id}: ${t.title}`)
|
|
566
|
-
: t.done
|
|
567
|
-
? theme.fg("dim", `${t.id}: ${t.title}`)
|
|
568
|
-
: theme.fg("text", `${t.id}: ${t.title}`);
|
|
569
|
-
leftLines.push(truncateToWidth(`${pad}${glyph} ${label}`, leftColWidth));
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
// Add progress bar inline
|
|
573
|
-
if (roadmapSlices) {
|
|
574
|
-
const { done, total, activeSliceTasks } = roadmapSlices;
|
|
575
|
-
const barWidth = Math.max(6, Math.min(18, Math.floor(leftColWidth * 0.4)));
|
|
576
|
-
const pct = total > 0 ? done / total : 0;
|
|
577
|
-
const filled = Math.round(pct * barWidth);
|
|
578
|
-
const bar = theme.fg("success", "█".repeat(filled))
|
|
579
|
-
+ theme.fg("dim", "░".repeat(barWidth - filled));
|
|
580
|
-
let meta = theme.fg("dim", `${done}/${total} slices`);
|
|
581
|
-
if (activeSliceTasks && activeSliceTasks.total > 0) {
|
|
582
|
-
const taskNum = isHook
|
|
583
|
-
? Math.max(activeSliceTasks.done, 1)
|
|
584
|
-
: Math.min(activeSliceTasks.done + 1, activeSliceTasks.total);
|
|
585
|
-
meta += theme.fg("dim", ` · task ${taskNum}/${activeSliceTasks.total}`);
|
|
504
|
+
lines.push(truncateToWidth(theme.fg("dim", `${pad}${savings}`), width));
|
|
586
505
|
}
|
|
587
|
-
const eta = estimateTimeRemaining();
|
|
588
|
-
if (eta) meta += theme.fg("dim", ` · ${eta}`);
|
|
589
|
-
leftLines.push(truncateToWidth(`${pad}${bar} ${meta}`, leftColWidth));
|
|
590
|
-
}
|
|
591
|
-
if (next) {
|
|
592
|
-
leftLines.push(truncateToWidth(
|
|
593
|
-
`${pad}${theme.fg("dim", "→")} ${theme.fg("dim", `then ${next}`)}`,
|
|
594
|
-
leftColWidth,
|
|
595
|
-
));
|
|
596
506
|
}
|
|
597
507
|
}
|
|
598
508
|
|
|
599
|
-
// Compose columns
|
|
600
|
-
if (useTwoCol) {
|
|
601
|
-
const maxRows = Math.max(leftLines.length, rightLines.length);
|
|
602
|
-
if (maxRows > 0) {
|
|
603
|
-
lines.push(""); // spacer before columns
|
|
604
|
-
for (let i = 0; i < maxRows; i++) {
|
|
605
|
-
const left = padToWidth(leftLines[i] ?? "", leftColWidth);
|
|
606
|
-
const gap = " ".repeat(colGap - 2); // colGap minus divider and its trailing space
|
|
607
|
-
const right = rightLines[i] ?? "";
|
|
608
|
-
lines.push(truncateToWidth(`${left}${gap}${divider} ${right}`, width));
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
} else {
|
|
612
|
-
// Narrow single-column: just stack
|
|
613
|
-
if (leftLines.length > 0) {
|
|
614
|
-
lines.push("");
|
|
615
|
-
for (const l of leftLines) lines.push(l);
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
// ── Footer: pwd + hints ─────────────────────────────────────────
|
|
620
|
-
lines.push("");
|
|
621
509
|
const hintParts: string[] = [];
|
|
622
510
|
hintParts.push("esc pause");
|
|
623
511
|
hintParts.push(process.platform === "darwin" ? "⌃⌥G dashboard" : "Ctrl+Alt+G dashboard");
|
|
624
|
-
|
|
625
|
-
const pwdStr = theme.fg("dim", widgetPwd);
|
|
626
|
-
lines.push(rightAlign(`${pad}${pwdStr}`, hintStr, width));
|
|
512
|
+
lines.push(...ui.hints(hintParts));
|
|
627
513
|
|
|
628
514
|
lines.push(...ui.bar());
|
|
629
515
|
|
|
@@ -711,10 +597,3 @@ function rightAlign(left: string, right: string, width: number): string {
|
|
|
711
597
|
const gap = Math.max(1, width - leftVis - rightVis);
|
|
712
598
|
return truncateToWidth(left + " ".repeat(gap) + right, width);
|
|
713
599
|
}
|
|
714
|
-
|
|
715
|
-
/** Pad a string with trailing spaces to fill exactly `colWidth` (ANSI-aware). */
|
|
716
|
-
function padToWidth(s: string, colWidth: number): string {
|
|
717
|
-
const vis = visibleWidth(s);
|
|
718
|
-
if (vis >= colWidth) return truncateToWidth(s, colWidth);
|
|
719
|
-
return s + " ".repeat(colWidth - vis);
|
|
720
|
-
}
|
|
@@ -176,7 +176,7 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
|
|
|
176
176
|
);
|
|
177
177
|
try {
|
|
178
178
|
const { formatDoctorIssuesForPrompt, formatDoctorReport } = await import("./doctor.js");
|
|
179
|
-
const { dispatchDoctorHeal } = await import("./commands
|
|
179
|
+
const { dispatchDoctorHeal } = await import("./commands.js");
|
|
180
180
|
const actionable = report.issues.filter(i => i.severity === "error");
|
|
181
181
|
const reportText = formatDoctorReport(report, { scope: doctorScope, includeWarnings: true });
|
|
182
182
|
const structuredIssues = formatDoctorIssuesForPrompt(actionable);
|
|
@@ -202,13 +202,10 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
|
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
// Prune dead bg-shell processes
|
|
206
|
-
// Without killing live processes between units, dev servers spawned during
|
|
207
|
-
// one task keep ports bound, causing conflicts in subsequent tasks (#1209).
|
|
205
|
+
// Prune dead bg-shell processes
|
|
208
206
|
try {
|
|
209
|
-
const { pruneDeadProcesses
|
|
207
|
+
const { pruneDeadProcesses } = await import("../bg-shell/process-manager.js");
|
|
210
208
|
pruneDeadProcesses();
|
|
211
|
-
killSessionProcesses();
|
|
212
209
|
} catch {
|
|
213
210
|
// Non-fatal
|
|
214
211
|
}
|
|
@@ -39,7 +39,6 @@ import {
|
|
|
39
39
|
import { isValidationTerminal } from "./state.js";
|
|
40
40
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
|
|
41
41
|
import { atomicWriteSync } from "./atomic-write.js";
|
|
42
|
-
import { loadJsonFileOrNull } from "./json-persistence.js";
|
|
43
42
|
import { dirname, join } from "node:path";
|
|
44
43
|
|
|
45
44
|
// ─── Artifact Resolution & Verification ───────────────────────────────────────
|
|
@@ -355,10 +354,6 @@ export function skipExecuteTask(
|
|
|
355
354
|
|
|
356
355
|
// ─── Disk-backed completed-unit helpers ───────────────────────────────────────
|
|
357
356
|
|
|
358
|
-
function isStringArray(data: unknown): data is string[] {
|
|
359
|
-
return Array.isArray(data) && data.every(item => typeof item === "string");
|
|
360
|
-
}
|
|
361
|
-
|
|
362
357
|
/** Path to the persisted completed-unit keys file. */
|
|
363
358
|
export function completedKeysPath(base: string): string {
|
|
364
359
|
return join(base, ".gsd", "completed-units.json");
|
|
@@ -367,7 +362,12 @@ export function completedKeysPath(base: string): string {
|
|
|
367
362
|
/** Write a completed unit key to disk (read-modify-write append to set). */
|
|
368
363
|
export function persistCompletedKey(base: string, key: string): void {
|
|
369
364
|
const file = completedKeysPath(base);
|
|
370
|
-
|
|
365
|
+
let keys: string[] = [];
|
|
366
|
+
try {
|
|
367
|
+
if (existsSync(file)) {
|
|
368
|
+
keys = JSON.parse(readFileSync(file, "utf-8"));
|
|
369
|
+
}
|
|
370
|
+
} catch (e) { /* corrupt file — start fresh */ void e; }
|
|
371
371
|
const keySet = new Set(keys);
|
|
372
372
|
if (!keySet.has(key)) {
|
|
373
373
|
keys.push(key);
|
|
@@ -378,21 +378,27 @@ export function persistCompletedKey(base: string, key: string): void {
|
|
|
378
378
|
/** Remove a stale completed unit key from disk. */
|
|
379
379
|
export function removePersistedKey(base: string, key: string): void {
|
|
380
380
|
const file = completedKeysPath(base);
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
381
|
+
try {
|
|
382
|
+
if (existsSync(file)) {
|
|
383
|
+
const keys: string[] = JSON.parse(readFileSync(file, "utf-8"));
|
|
384
|
+
const filtered = keys.filter(k => k !== key);
|
|
385
|
+
// Only write if the key was actually present
|
|
386
|
+
if (filtered.length !== keys.length) {
|
|
387
|
+
atomicWriteSync(file, JSON.stringify(filtered));
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
} catch (e) { /* non-fatal: removePersistedKey failure */ void e; }
|
|
387
391
|
}
|
|
388
392
|
|
|
389
393
|
/** Load all completed unit keys from disk into the in-memory set. */
|
|
390
394
|
export function loadPersistedKeys(base: string, target: Set<string>): void {
|
|
391
395
|
const file = completedKeysPath(base);
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
+
try {
|
|
397
|
+
if (existsSync(file)) {
|
|
398
|
+
const keys: string[] = JSON.parse(readFileSync(file, "utf-8"));
|
|
399
|
+
for (const k of keys) target.add(k);
|
|
400
|
+
}
|
|
401
|
+
} catch (e) { /* non-fatal: loadPersistedKeys failure */ void e; }
|
|
396
402
|
}
|
|
397
403
|
|
|
398
404
|
// ─── Merge State Reconciliation ───────────────────────────────────────────────
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { existsSync, mkdirSync, readFileSync, cpSync, unlinkSync, readdirSync } from "node:fs";
|
|
14
|
-
import { loadJsonFileOrNull } from "./json-persistence.js";
|
|
15
14
|
import { join, sep as pathSep } from "node:path";
|
|
16
15
|
import { homedir } from "node:os";
|
|
17
16
|
import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
|
|
@@ -113,15 +112,15 @@ export function syncStateToProjectRoot(worktreePath: string, projectRoot: string
|
|
|
113
112
|
* Uses gsdVersion instead of syncedAt so that launching a second session
|
|
114
113
|
* doesn't falsely trigger staleness (#804).
|
|
115
114
|
*/
|
|
116
|
-
function isManifestWithVersion(data: unknown): data is { gsdVersion: string } {
|
|
117
|
-
return data !== null && typeof data === "object" && "gsdVersion" in data! && typeof (data as Record<string, unknown>).gsdVersion === "string";
|
|
118
|
-
}
|
|
119
|
-
|
|
120
115
|
export function readResourceVersion(): string | null {
|
|
121
116
|
const agentDir = process.env.GSD_CODING_AGENT_DIR || join(homedir(), ".gsd", "agent");
|
|
122
117
|
const manifestPath = join(agentDir, "managed-resources.json");
|
|
123
|
-
|
|
124
|
-
|
|
118
|
+
try {
|
|
119
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
120
|
+
return typeof manifest?.gsdVersion === "string" ? manifest.gsdVersion : null;
|
|
121
|
+
} catch {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
125
124
|
}
|
|
126
125
|
|
|
127
126
|
/**
|
|
@@ -22,6 +22,7 @@ import { loadFile, getManifestStatus, resolveAllOverrides, parsePlan, parseSumma
|
|
|
22
22
|
import { loadPrompt } from "./prompt-loader.js";
|
|
23
23
|
import { runVerificationGate, formatFailureContext, captureRuntimeErrors, runDependencyAudit } from "./verification-gate.js";
|
|
24
24
|
import { writeVerificationJSON } from "./verification-evidence.js";
|
|
25
|
+
export { inlinePriorMilestoneSummary } from "./files.js";
|
|
25
26
|
import { collectSecretsFromManifest } from "../get-secrets-from-user.js";
|
|
26
27
|
import {
|
|
27
28
|
gsdRoot, resolveMilestoneFile, resolveSliceFile, resolveSlicePath,
|
|
@@ -191,6 +192,12 @@ import {
|
|
|
191
192
|
NEW_SESSION_TIMEOUT_MS, DISPATCH_HANG_TIMEOUT_MS,
|
|
192
193
|
} from "./auto/session.js";
|
|
193
194
|
import type { CompletedUnit, CurrentUnit, UnitRouting, StartModel, PendingVerificationRetry } from "./auto/session.js";
|
|
195
|
+
export {
|
|
196
|
+
MAX_UNIT_DISPATCHES, STUB_RECOVERY_THRESHOLD, MAX_LIFETIME_DISPATCHES,
|
|
197
|
+
MAX_CONSECUTIVE_SKIPS, DISPATCH_GAP_TIMEOUT_MS, MAX_SKIP_DEPTH,
|
|
198
|
+
NEW_SESSION_TIMEOUT_MS, DISPATCH_HANG_TIMEOUT_MS,
|
|
199
|
+
} from "./auto/session.js";
|
|
200
|
+
export type { CompletedUnit, CurrentUnit, UnitRouting, StartModel } from "./auto/session.js";
|
|
194
201
|
|
|
195
202
|
// ── ENCAPSULATION INVARIANT ─────────────────────────────────────────────────
|
|
196
203
|
// ALL mutable auto-mode state lives in the AutoSession class (auto/session.ts).
|
|
@@ -261,6 +268,8 @@ export function shouldUseWorktreeIsolation(): boolean {
|
|
|
261
268
|
* Maps toolCallId → start timestamp (ms) so the idle watchdog can detect tools that have been
|
|
262
269
|
* running suspiciously long (e.g., a Bash command hung because `&` kept stdout open).
|
|
263
270
|
*/
|
|
271
|
+
// Re-export budget utilities for external consumers
|
|
272
|
+
export { getBudgetAlertLevel, getNewBudgetAlertLevel, getBudgetEnforcementAction } from "./auto-budget.js";
|
|
264
273
|
|
|
265
274
|
/** Wrapper: register SIGTERM handler and store reference. */
|
|
266
275
|
function registerSigtermHandler(currentBasePath: string): void {
|
|
@@ -273,6 +282,8 @@ function deregisterSigtermHandler(): void {
|
|
|
273
282
|
s.sigtermHandler = null;
|
|
274
283
|
}
|
|
275
284
|
|
|
285
|
+
export { type AutoDashboardData } from "./auto-dashboard.js";
|
|
286
|
+
|
|
276
287
|
export function getAutoDashboardData(): AutoDashboardData {
|
|
277
288
|
const ledger = getLedger();
|
|
278
289
|
const totals = ledger ? getProjectTotals(ledger.units) : null;
|
|
@@ -923,6 +934,8 @@ async function showStepWizard(
|
|
|
923
934
|
}
|
|
924
935
|
}
|
|
925
936
|
|
|
937
|
+
// describeNextUnit is imported from auto-dashboard.ts and re-exported
|
|
938
|
+
export { describeNextUnit } from "./auto-dashboard.js";
|
|
926
939
|
|
|
927
940
|
/** Thin wrapper: delegates to auto-dashboard.ts, passing state accessors. */
|
|
928
941
|
function updateProgressWidget(
|
|
@@ -1892,3 +1905,5 @@ export async function dispatchHookUnit(
|
|
|
1892
1905
|
}
|
|
1893
1906
|
|
|
1894
1907
|
|
|
1908
|
+
// Direct phase dispatch → auto-direct-dispatch.ts
|
|
1909
|
+
export { dispatchDirectPhase } from "./auto-direct-dispatch.js";
|
|
@@ -19,26 +19,7 @@ import {
|
|
|
19
19
|
filterDoctorIssues,
|
|
20
20
|
} from "./doctor.js";
|
|
21
21
|
import { isAutoActive } from "./auto.js";
|
|
22
|
-
import { projectRoot } from "./commands.js";
|
|
23
|
-
import { loadPrompt } from "./prompt-loader.js";
|
|
24
|
-
|
|
25
|
-
export function dispatchDoctorHeal(pi: ExtensionAPI, scope: string | undefined, reportText: string, structuredIssues: string): void {
|
|
26
|
-
const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".pi", "GSD-WORKFLOW.md");
|
|
27
|
-
const workflow = readFileSync(workflowPath, "utf-8");
|
|
28
|
-
const prompt = loadPrompt("doctor-heal", {
|
|
29
|
-
doctorSummary: reportText,
|
|
30
|
-
structuredIssues,
|
|
31
|
-
scopeLabel: scope ?? "active milestone / blocking scope",
|
|
32
|
-
doctorCommandSuffix: scope ? ` ${scope}` : "",
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
const content = `Read the following GSD workflow protocol and execute exactly.\n\n${workflow}\n\n## Your Task\n\n${prompt}`;
|
|
36
|
-
|
|
37
|
-
pi.sendMessage(
|
|
38
|
-
{ customType: "gsd-doctor-heal", content, display: false },
|
|
39
|
-
{ triggerTurn: true },
|
|
40
|
-
);
|
|
41
|
-
}
|
|
22
|
+
import { projectRoot, dispatchDoctorHeal } from "./commands.js";
|
|
42
23
|
|
|
43
24
|
export async function handleDoctor(args: string, ctx: ExtensionCommandContext, pi: ExtensionAPI): Promise<void> {
|
|
44
25
|
const trimmed = args.trim();
|
|
@@ -14,7 +14,6 @@ import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
|
14
14
|
import { existsSync, readdirSync, readFileSync, statSync, unlinkSync } from "node:fs";
|
|
15
15
|
import { join } from "node:path";
|
|
16
16
|
import { gsdRoot } from "./paths.js";
|
|
17
|
-
import { loadJsonFileOrNull } from "./json-persistence.js";
|
|
18
17
|
|
|
19
18
|
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
20
19
|
|
|
@@ -332,18 +331,20 @@ async function handleLogsList(basePath: string, ctx: ExtensionCommandContext): P
|
|
|
332
331
|
|
|
333
332
|
// Metrics summary
|
|
334
333
|
const metricsPath = join(gsdRoot(basePath), "metrics.json");
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
334
|
+
if (existsSync(metricsPath)) {
|
|
335
|
+
try {
|
|
336
|
+
const metrics = JSON.parse(readFileSync(metricsPath, "utf-8"));
|
|
337
|
+
const units = metrics?.units;
|
|
338
|
+
if (Array.isArray(units) && units.length > 0) {
|
|
339
|
+
const totalCost = units.reduce((sum: number, u: Record<string, unknown>) => sum + ((u.cost as number) ?? 0), 0);
|
|
340
|
+
const totalTokens = units.reduce((sum: number, u: Record<string, unknown>) => {
|
|
341
|
+
const t = u.tokens as Record<string, number> | undefined;
|
|
342
|
+
return sum + (t?.total ?? 0);
|
|
343
|
+
}, 0);
|
|
344
|
+
lines.push("");
|
|
345
|
+
lines.push(`Metrics: ${units.length} units tracked · $${totalCost.toFixed(2)} · ${(totalTokens / 1000).toFixed(0)}K tokens`);
|
|
346
|
+
}
|
|
347
|
+
} catch { /* ignore */ }
|
|
347
348
|
}
|
|
348
349
|
|
|
349
350
|
lines.push("");
|