gsd-pi 2.39.0 → 2.40.0-dev.4a93031

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.
Files changed (133) hide show
  1. package/dist/resource-loader.js +66 -2
  2. package/dist/resources/extensions/async-jobs/index.js +10 -0
  3. package/dist/resources/extensions/get-secrets-from-user.js +1 -1
  4. package/dist/resources/extensions/gsd/auto-dashboard.js +7 -0
  5. package/dist/resources/extensions/gsd/auto-loop.js +761 -673
  6. package/dist/resources/extensions/gsd/auto-post-unit.js +10 -2
  7. package/dist/resources/extensions/gsd/auto-prompts.js +3 -3
  8. package/dist/resources/extensions/gsd/auto-start.js +6 -1
  9. package/dist/resources/extensions/gsd/auto.js +6 -4
  10. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +126 -0
  11. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +233 -0
  12. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +59 -0
  13. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +38 -0
  14. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +156 -0
  15. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +46 -0
  16. package/dist/resources/extensions/gsd/bootstrap/system-context.js +300 -0
  17. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +38 -0
  18. package/dist/resources/extensions/gsd/commands/catalog.js +278 -0
  19. package/dist/resources/extensions/gsd/commands/context.js +84 -0
  20. package/dist/resources/extensions/gsd/commands/dispatcher.js +21 -0
  21. package/dist/resources/extensions/gsd/commands/handlers/auto.js +72 -0
  22. package/dist/resources/extensions/gsd/commands/handlers/core.js +246 -0
  23. package/dist/resources/extensions/gsd/commands/handlers/ops.js +166 -0
  24. package/dist/resources/extensions/gsd/commands/handlers/parallel.js +94 -0
  25. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +102 -0
  26. package/dist/resources/extensions/gsd/commands/index.js +11 -0
  27. package/dist/resources/extensions/gsd/commands-handlers.js +1 -1
  28. package/dist/resources/extensions/gsd/commands.js +8 -1190
  29. package/dist/resources/extensions/gsd/dashboard-overlay.js +9 -0
  30. package/dist/resources/extensions/gsd/doctor-proactive.js +80 -10
  31. package/dist/resources/extensions/gsd/doctor.js +32 -2
  32. package/dist/resources/extensions/gsd/export-html.js +46 -0
  33. package/dist/resources/extensions/gsd/files.js +1 -1
  34. package/dist/resources/extensions/gsd/health-widget.js +1 -1
  35. package/dist/resources/extensions/gsd/index.js +4 -1115
  36. package/dist/resources/extensions/gsd/progress-score.js +20 -1
  37. package/dist/resources/extensions/gsd/prompts/forensics.md +121 -46
  38. package/dist/resources/extensions/gsd/visualizer-data.js +26 -1
  39. package/dist/resources/extensions/gsd/visualizer-views.js +52 -0
  40. package/dist/welcome-screen.d.ts +3 -2
  41. package/dist/welcome-screen.js +66 -22
  42. package/package.json +1 -1
  43. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +12 -0
  44. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  45. package/packages/pi-coding-agent/dist/core/agent-session.js +107 -24
  46. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  47. package/packages/pi-coding-agent/dist/core/skill-tool.test.d.ts +2 -0
  48. package/packages/pi-coding-agent/dist/core/skill-tool.test.d.ts.map +1 -0
  49. package/packages/pi-coding-agent/dist/core/skill-tool.test.js +70 -0
  50. package/packages/pi-coding-agent/dist/core/skill-tool.test.js.map +1 -0
  51. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  52. package/packages/pi-coding-agent/dist/core/skills.js +2 -1
  53. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  54. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts +17 -0
  55. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -0
  56. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +244 -0
  57. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -0
  58. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.d.ts +3 -0
  59. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.d.ts.map +1 -0
  60. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js +58 -0
  61. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js.map +1 -0
  62. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts +12 -0
  63. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -0
  64. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +54 -0
  65. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -0
  66. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts +6 -0
  67. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts.map +1 -0
  68. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js +63 -0
  69. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js.map +1 -0
  70. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +38 -0
  71. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -0
  72. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js +2 -0
  73. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -0
  74. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +1 -1
  75. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  76. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +15 -457
  77. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  78. package/packages/pi-coding-agent/package.json +1 -1
  79. package/packages/pi-coding-agent/src/core/agent-session.ts +122 -23
  80. package/packages/pi-coding-agent/src/core/skill-tool.test.ts +89 -0
  81. package/packages/pi-coding-agent/src/core/skills.ts +2 -1
  82. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +302 -0
  83. package/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +59 -0
  84. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +68 -0
  85. package/packages/pi-coding-agent/src/modes/interactive/controllers/model-controller.ts +71 -0
  86. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +37 -0
  87. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +18 -510
  88. package/pkg/package.json +1 -1
  89. package/src/resources/extensions/async-jobs/index.ts +11 -0
  90. package/src/resources/extensions/get-secrets-from-user.ts +1 -1
  91. package/src/resources/extensions/gsd/auto-dashboard.ts +10 -0
  92. package/src/resources/extensions/gsd/auto-loop.ts +1075 -921
  93. package/src/resources/extensions/gsd/auto-post-unit.ts +10 -2
  94. package/src/resources/extensions/gsd/auto-prompts.ts +3 -3
  95. package/src/resources/extensions/gsd/auto-start.ts +6 -1
  96. package/src/resources/extensions/gsd/auto.ts +13 -10
  97. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +142 -0
  98. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +238 -0
  99. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +90 -0
  100. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +46 -0
  101. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +167 -0
  102. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +55 -0
  103. package/src/resources/extensions/gsd/bootstrap/system-context.ts +340 -0
  104. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +51 -0
  105. package/src/resources/extensions/gsd/commands/catalog.ts +301 -0
  106. package/src/resources/extensions/gsd/commands/context.ts +101 -0
  107. package/src/resources/extensions/gsd/commands/dispatcher.ts +32 -0
  108. package/src/resources/extensions/gsd/commands/handlers/auto.ts +74 -0
  109. package/src/resources/extensions/gsd/commands/handlers/core.ts +274 -0
  110. package/src/resources/extensions/gsd/commands/handlers/ops.ts +169 -0
  111. package/src/resources/extensions/gsd/commands/handlers/parallel.ts +118 -0
  112. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +109 -0
  113. package/src/resources/extensions/gsd/commands/index.ts +14 -0
  114. package/src/resources/extensions/gsd/commands-handlers.ts +1 -1
  115. package/src/resources/extensions/gsd/commands.ts +10 -1329
  116. package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -0
  117. package/src/resources/extensions/gsd/doctor-proactive.ts +106 -10
  118. package/src/resources/extensions/gsd/doctor.ts +47 -3
  119. package/src/resources/extensions/gsd/export-html.ts +51 -0
  120. package/src/resources/extensions/gsd/files.ts +1 -1
  121. package/src/resources/extensions/gsd/health-widget.ts +2 -1
  122. package/src/resources/extensions/gsd/index.ts +12 -1314
  123. package/src/resources/extensions/gsd/progress-score.ts +23 -0
  124. package/src/resources/extensions/gsd/prompts/forensics.md +121 -46
  125. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +13 -9
  126. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +3 -3
  127. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +16 -16
  128. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +4 -4
  129. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +10 -10
  130. package/src/resources/extensions/gsd/visualizer-data.ts +51 -1
  131. package/src/resources/extensions/gsd/visualizer-views.ts +58 -0
  132. /package/dist/resources/extensions/{env-utils.js → gsd/env-utils.js} +0 -0
  133. /package/src/resources/extensions/{env-utils.ts → gsd/env-utils.ts} +0 -0
@@ -282,6 +282,15 @@ export class GSDDashboardOverlay {
282
282
  : progressScore.level === "yellow" ? th.fg("warning", "●")
283
283
  : th.fg("error", "●");
284
284
  lines.push(row(`${progressIcon} ${th.fg("text", progressScore.summary)}`));
285
+ // Show signal details when degraded — real-time visibility into what doctor found
286
+ if (progressScore.level !== "green" && progressScore.signals.length > 0) {
287
+ for (const signal of progressScore.signals) {
288
+ const prefix = signal.kind === "positive" ? th.fg("success", " ✓")
289
+ : signal.kind === "negative" ? th.fg("error", " ✗")
290
+ : th.fg("dim", " ·");
291
+ lines.push(row(`${prefix} ${th.fg("dim", signal.label)}`));
292
+ }
293
+ }
285
294
  }
286
295
  lines.push(blank());
287
296
  if (this.dashData.currentUnit) {
@@ -28,11 +28,23 @@ let healthHistory = [];
28
28
  let consecutiveErrorUnits = 0;
29
29
  /** Unit index counter for health tracking. */
30
30
  let healthUnitIndex = 0;
31
+ /** Previous progress level for state transition detection. */
32
+ let previousProgressLevel = "green";
33
+ /** Callback for state transition notifications. Set by auto-mode. */
34
+ let onLevelChange = null;
35
+ /**
36
+ * Register a callback for progress level transitions (green→yellow, yellow→red, etc.).
37
+ * Called once when auto-mode starts. Pass null to unregister.
38
+ */
39
+ export function setLevelChangeCallback(cb) {
40
+ onLevelChange = cb;
41
+ previousProgressLevel = "green";
42
+ }
31
43
  /**
32
44
  * Record a health snapshot after a doctor run.
33
- * Called from the post-unit hook in auto.ts.
45
+ * Called from the post-unit hook in auto-post-unit.ts.
34
46
  */
35
- export function recordHealthSnapshot(errors, warnings, fixesApplied) {
47
+ export function recordHealthSnapshot(errors, warnings, fixesApplied, issues, fixes, scope) {
36
48
  healthUnitIndex++;
37
49
  healthHistory.push({
38
50
  timestamp: Date.now(),
@@ -40,6 +52,9 @@ export function recordHealthSnapshot(errors, warnings, fixesApplied) {
40
52
  warnings,
41
53
  fixesApplied,
42
54
  unitIndex: healthUnitIndex,
55
+ issues: issues ?? [],
56
+ fixes: fixes ?? [],
57
+ scope,
43
58
  });
44
59
  // Keep only the last 50 snapshots to bound memory
45
60
  if (healthHistory.length > 50) {
@@ -51,6 +66,18 @@ export function recordHealthSnapshot(errors, warnings, fixesApplied) {
51
66
  else {
52
67
  consecutiveErrorUnits = 0;
53
68
  }
69
+ // Detect progress level transitions and notify
70
+ if (onLevelChange) {
71
+ const newLevel = consecutiveErrorUnits >= 3 ? "red"
72
+ : consecutiveErrorUnits >= 1 || getHealthTrend() === "degrading" ? "yellow"
73
+ : "green";
74
+ if (newLevel !== previousProgressLevel) {
75
+ const topIssue = (issues ?? []).find(i => i.severity === "error") ?? (issues ?? [])[0];
76
+ const detail = topIssue ? `: ${topIssue.message}` : "";
77
+ onLevelChange(previousProgressLevel, newLevel, `Health ${previousProgressLevel} → ${newLevel}${detail}`);
78
+ previousProgressLevel = newLevel;
79
+ }
80
+ }
54
81
  }
55
82
  /**
56
83
  * Get the current health trend.
@@ -84,6 +111,27 @@ export function getConsecutiveErrorUnits() {
84
111
  export function getHealthHistory() {
85
112
  return healthHistory;
86
113
  }
114
+ /**
115
+ * Get the latest health issues from the most recent snapshot.
116
+ * Returns issues from the last snapshot that had any, for real-time visibility.
117
+ */
118
+ export function getLatestHealthIssues() {
119
+ for (let i = healthHistory.length - 1; i >= 0; i--) {
120
+ if (healthHistory[i].issues.length > 0)
121
+ return healthHistory[i].issues;
122
+ }
123
+ return [];
124
+ }
125
+ /**
126
+ * Get the latest fixes applied from the most recent snapshot.
127
+ */
128
+ export function getLatestHealthFixes() {
129
+ for (let i = healthHistory.length - 1; i >= 0; i--) {
130
+ if (healthHistory[i].fixes.length > 0)
131
+ return healthHistory[i].fixes;
132
+ }
133
+ return [];
134
+ }
87
135
  /**
88
136
  * Reset health tracking state. Called on auto-mode start/stop.
89
137
  */
@@ -91,6 +139,7 @@ export function resetHealthTracking() {
91
139
  healthHistory = [];
92
140
  consecutiveErrorUnits = 0;
93
141
  healthUnitIndex = 0;
142
+ previousProgressLevel = "green";
94
143
  }
95
144
  /**
96
145
  * Lightweight pre-dispatch health check. Runs fast checks that should
@@ -234,23 +283,44 @@ export function resetEscalation() {
234
283
  }
235
284
  /**
236
285
  * Format a health summary for display in the auto-mode dashboard.
286
+ * Human-readable with full words, not abbreviations.
237
287
  */
238
288
  export function formatHealthSummary() {
239
289
  if (healthHistory.length === 0)
240
290
  return "No health data yet.";
241
291
  const latest = healthHistory[healthHistory.length - 1];
242
292
  const trend = getHealthTrend();
243
- const trendIcon = trend === "improving" ? "+" : trend === "degrading" ? "-" : "=";
293
+ const trendLabel = trend === "improving" ? "improving"
294
+ : trend === "degrading" ? "degrading"
295
+ : trend === "stable" ? "stable"
296
+ : "unknown";
244
297
  const totalFixes = healthHistory.reduce((sum, s) => sum + s.fixesApplied, 0);
245
- const parts = [
246
- `Health: ${latest.errors}E/${latest.warnings}W`,
247
- `trend:${trendIcon}`,
248
- `fixes:${totalFixes}`,
249
- ];
298
+ const parts = [];
299
+ // Error/warning summary
300
+ if (latest.errors === 0 && latest.warnings === 0) {
301
+ parts.push("No issues");
302
+ }
303
+ else {
304
+ const counts = [];
305
+ if (latest.errors > 0)
306
+ counts.push(`${latest.errors} error${latest.errors > 1 ? "s" : ""}`);
307
+ if (latest.warnings > 0)
308
+ counts.push(`${latest.warnings} warning${latest.warnings > 1 ? "s" : ""}`);
309
+ parts.push(counts.join(", "));
310
+ }
311
+ parts.push(`trend ${trendLabel}`);
312
+ if (totalFixes > 0) {
313
+ parts.push(`${totalFixes} fix${totalFixes > 1 ? "es" : ""} applied`);
314
+ }
250
315
  if (consecutiveErrorUnits > 0) {
251
- parts.push(`streak:${consecutiveErrorUnits}/${ESCALATION_THRESHOLD}`);
316
+ parts.push(`${consecutiveErrorUnits} of ${ESCALATION_THRESHOLD} consecutive errors before escalation`);
317
+ }
318
+ // Include top issue from latest snapshot
319
+ if (latest.issues.length > 0) {
320
+ const topIssue = latest.issues.find(i => i.severity === "error") ?? latest.issues[0];
321
+ parts.push(`latest: ${topIssue.message}`);
252
322
  }
253
- return parts.join(" | ");
323
+ return parts.join(" · ");
254
324
  }
255
325
  /**
256
326
  * Reset all proactive healing state. Called on auto-mode start/stop.
@@ -368,13 +368,43 @@ function detectCircularDependencies(slices) {
368
368
  async function appendDoctorHistory(basePath, report) {
369
369
  try {
370
370
  const historyPath = join(gsdRoot(basePath), "doctor-history.jsonl");
371
+ const errorCount = report.issues.filter(i => i.severity === "error").length;
372
+ const warningCount = report.issues.filter(i => i.severity === "warning").length;
373
+ const issueDetails = report.issues
374
+ .filter(i => i.severity === "error" || i.severity === "warning")
375
+ .slice(0, 10) // cap to keep JSONL lines bounded
376
+ .map(i => ({ severity: i.severity, code: i.code, message: i.message, unitId: i.unitId }));
377
+ // Human-readable one-line summary
378
+ const summaryParts = [];
379
+ if (report.ok) {
380
+ summaryParts.push("Clean");
381
+ }
382
+ else {
383
+ const counts = [];
384
+ if (errorCount > 0)
385
+ counts.push(`${errorCount} error${errorCount > 1 ? "s" : ""}`);
386
+ if (warningCount > 0)
387
+ counts.push(`${warningCount} warning${warningCount > 1 ? "s" : ""}`);
388
+ summaryParts.push(counts.join(", "));
389
+ }
390
+ if (report.fixesApplied.length > 0) {
391
+ summaryParts.push(`${report.fixesApplied.length} fixed`);
392
+ }
393
+ if (issueDetails.length > 0) {
394
+ const topIssue = issueDetails.find(i => i.severity === "error") ?? issueDetails[0];
395
+ summaryParts.push(topIssue.message);
396
+ }
371
397
  const entry = JSON.stringify({
372
398
  ts: new Date().toISOString(),
373
399
  ok: report.ok,
374
- errors: report.issues.filter(i => i.severity === "error").length,
375
- warnings: report.issues.filter(i => i.severity === "warning").length,
400
+ errors: errorCount,
401
+ warnings: warningCount,
376
402
  fixes: report.fixesApplied.length,
377
403
  codes: [...new Set(report.issues.map(i => i.code))],
404
+ issues: issueDetails.length > 0 ? issueDetails : undefined,
405
+ fixDescriptions: report.fixesApplied.length > 0 ? report.fixesApplied : undefined,
406
+ scope: report.scope,
407
+ summary: summaryParts.join(" · "),
378
408
  });
379
409
  const existing = existsSync(historyPath) ? readFileSync(historyPath, "utf-8") : "";
380
410
  await saveFile(historyPath, existing + entry + "\n");
@@ -236,9 +236,55 @@ function buildHealthSection(data) {
236
236
  <td>${formatTokenCount(tb.tokens.total)}</td></tr>`).join('')}
237
237
  </tbody>
238
238
  </table>` : '';
239
+ // Progress score section
240
+ let progressHtml = '';
241
+ if (h.progressScore) {
242
+ const ps = h.progressScore;
243
+ const scoreColor = ps.level === 'green' ? '#22c55e' : ps.level === 'yellow' ? '#eab308' : '#ef4444';
244
+ const signalRows = ps.signals.map(s => {
245
+ const icon = s.kind === 'positive' ? '✓' : s.kind === 'negative' ? '✗' : '·';
246
+ const color = s.kind === 'positive' ? '#22c55e' : s.kind === 'negative' ? '#ef4444' : '#888';
247
+ return `<div style="margin-left:1em;color:${color}">${icon} ${esc(s.label)}</div>`;
248
+ }).join('');
249
+ progressHtml = `
250
+ <h3>Progress Score</h3>
251
+ <div style="font-size:1.1em;font-weight:bold;color:${scoreColor}">● ${esc(ps.summary)}</div>
252
+ ${signalRows}`;
253
+ }
254
+ // Doctor history section
255
+ let historyHtml = '';
256
+ const doctorHistory = h.doctorHistory ?? [];
257
+ if (doctorHistory.length > 0) {
258
+ const historyRows = doctorHistory.slice(0, 20).map(entry => {
259
+ const statusIcon = entry.ok ? '✓' : '✗';
260
+ const statusColor = entry.ok ? '#22c55e' : '#ef4444';
261
+ const ts = entry.ts.replace('T', ' ').slice(0, 19);
262
+ const scopeTag = entry.scope ? `<span class="mono" style="color:#888"> [${esc(entry.scope)}]</span>` : '';
263
+ const summaryText = entry.summary ? esc(entry.summary) : `${entry.errors} errors, ${entry.warnings} warnings, ${entry.fixes} fixes`;
264
+ const issueDetails = (entry.issues ?? []).slice(0, 3).map(i => {
265
+ const iColor = i.severity === 'error' ? '#ef4444' : '#eab308';
266
+ return `<div style="margin-left:2em;color:${iColor};font-size:0.85em">${i.severity === 'error' ? '✗' : '⚠'} ${esc(i.message)} <span class="mono" style="color:#888">${esc(i.unitId)}</span></div>`;
267
+ }).join('');
268
+ const fixDetails = (entry.fixDescriptions ?? []).slice(0, 2).map(f => `<div style="margin-left:2em;color:#22c55e;font-size:0.85em">↳ ${esc(f)}</div>`).join('');
269
+ return `<tr style="color:${statusColor}">
270
+ <td class="mono">${statusIcon}</td>
271
+ <td class="mono">${esc(ts)}${scopeTag}</td>
272
+ <td>${summaryText}</td>
273
+ </tr>
274
+ ${issueDetails || fixDetails ? `<tr><td colspan="3">${issueDetails}${fixDetails}</td></tr>` : ''}`;
275
+ }).join('');
276
+ historyHtml = `
277
+ <h3>Doctor Run History</h3>
278
+ <table class="tbl">
279
+ <thead><tr><th></th><th>Time</th><th>Summary</th></tr></thead>
280
+ <tbody>${historyRows}</tbody>
281
+ </table>`;
282
+ }
239
283
  return section('health', 'Health', `
240
284
  <table class="tbl tbl-kv"><tbody>${rows.join('')}</tbody></table>
241
285
  ${tierRows}
286
+ ${progressHtml}
287
+ ${historyHtml}
242
288
  `);
243
289
  }
244
290
  // ─── Section: Progress ────────────────────────────────────────────────────────
@@ -7,7 +7,7 @@ import { resolve } from 'node:path';
7
7
  import { atomicWriteAsync } from './atomic-write.js';
8
8
  import { resolveMilestoneFile, relMilestoneFile, resolveGsdRootFile } from './paths.js';
9
9
  import { findMilestoneIds } from './milestone-ids.js';
10
- import { checkExistingEnvKeys } from '../env-utils.js';
10
+ import { checkExistingEnvKeys } from './env-utils.js';
11
11
  import { parseRoadmapSlices } from './roadmap-slices.js';
12
12
  import { nativeParseRoadmap, nativeExtractSection, nativeParsePlanFile, nativeParseSummaryFile, NATIVE_UNAVAILABLE } from './native-parser-bridge.js';
13
13
  import { debugTime, debugCount } from './debug-logger.js';
@@ -11,7 +11,7 @@ import { runProviderChecks, summariseProviderIssues } from "./doctor-providers.j
11
11
  import { runEnvironmentChecks } from "./doctor-environment.js";
12
12
  import { loadEffectiveGSDPreferences } from "./preferences.js";
13
13
  import { loadLedgerFromDisk, getProjectTotals } from "./metrics.js";
14
- import { projectRoot } from "./commands.js";
14
+ import { projectRoot } from "./commands/context.js";
15
15
  import { buildHealthLines, detectHealthWidgetProjectState, } from "./health-widget-core.js";
16
16
  // ── Data loader ────────────────────────────────────────────────────────────────
17
17
  function loadHealthWidgetData(basePath) {