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
@@ -320,6 +320,16 @@ export class GSDDashboardOverlay {
320
320
  : progressScore.level === "yellow" ? th.fg("warning", "●")
321
321
  : th.fg("error", "●");
322
322
  lines.push(row(`${progressIcon} ${th.fg("text", progressScore.summary)}`));
323
+
324
+ // Show signal details when degraded — real-time visibility into what doctor found
325
+ if (progressScore.level !== "green" && progressScore.signals.length > 0) {
326
+ for (const signal of progressScore.signals) {
327
+ const prefix = signal.kind === "positive" ? th.fg("success", " ✓")
328
+ : signal.kind === "negative" ? th.fg("error", " ✗")
329
+ : th.fg("dim", " ·");
330
+ lines.push(row(`${prefix} ${th.fg("dim", signal.label)}`));
331
+ }
332
+ }
323
333
  }
324
334
  lines.push(blank());
325
335
 
@@ -26,12 +26,26 @@ import { nativeBranchExists, nativeIsRepo } from "./native-git-bridge.js";
26
26
 
27
27
  // ── Health Score Tracking ──────────────────────────────────────────────────
28
28
 
29
+ /** Compact issue detail stored per snapshot for real-time visibility. */
30
+ export interface HealthIssueDetail {
31
+ code: string;
32
+ message: string;
33
+ severity: "error" | "warning" | "info";
34
+ unitId: string;
35
+ }
36
+
29
37
  export interface HealthSnapshot {
30
38
  timestamp: number;
31
39
  errors: number;
32
40
  warnings: number;
33
41
  fixesApplied: number;
34
42
  unitIndex: number; // which unit dispatch triggered this snapshot
43
+ /** Top issues from the doctor run that produced this snapshot. */
44
+ issues: HealthIssueDetail[];
45
+ /** Fixes that were auto-applied during this snapshot's doctor run. */
46
+ fixes: string[];
47
+ /** Milestone/slice scope this snapshot belongs to (e.g. "M001" or "M001/S02"). */
48
+ scope?: string;
35
49
  }
36
50
 
37
51
  /** In-memory health history for the current auto-mode session. */
@@ -43,11 +57,33 @@ let consecutiveErrorUnits = 0;
43
57
  /** Unit index counter for health tracking. */
44
58
  let healthUnitIndex = 0;
45
59
 
60
+ /** Previous progress level for state transition detection. */
61
+ let previousProgressLevel: "green" | "yellow" | "red" = "green";
62
+
63
+ /** Callback for state transition notifications. Set by auto-mode. */
64
+ let onLevelChange: ((from: string, to: string, summary: string) => void) | null = null;
65
+
66
+ /**
67
+ * Register a callback for progress level transitions (green→yellow, yellow→red, etc.).
68
+ * Called once when auto-mode starts. Pass null to unregister.
69
+ */
70
+ export function setLevelChangeCallback(cb: ((from: string, to: string, summary: string) => void) | null): void {
71
+ onLevelChange = cb;
72
+ previousProgressLevel = "green";
73
+ }
74
+
46
75
  /**
47
76
  * Record a health snapshot after a doctor run.
48
- * Called from the post-unit hook in auto.ts.
77
+ * Called from the post-unit hook in auto-post-unit.ts.
49
78
  */
50
- export function recordHealthSnapshot(errors: number, warnings: number, fixesApplied: number): void {
79
+ export function recordHealthSnapshot(
80
+ errors: number,
81
+ warnings: number,
82
+ fixesApplied: number,
83
+ issues?: HealthIssueDetail[],
84
+ fixes?: string[],
85
+ scope?: string,
86
+ ): void {
51
87
  healthUnitIndex++;
52
88
  healthHistory.push({
53
89
  timestamp: Date.now(),
@@ -55,6 +91,9 @@ export function recordHealthSnapshot(errors: number, warnings: number, fixesAppl
55
91
  warnings,
56
92
  fixesApplied,
57
93
  unitIndex: healthUnitIndex,
94
+ issues: issues ?? [],
95
+ fixes: fixes ?? [],
96
+ scope,
58
97
  });
59
98
 
60
99
  // Keep only the last 50 snapshots to bound memory
@@ -67,6 +106,19 @@ export function recordHealthSnapshot(errors: number, warnings: number, fixesAppl
67
106
  } else {
68
107
  consecutiveErrorUnits = 0;
69
108
  }
109
+
110
+ // Detect progress level transitions and notify
111
+ if (onLevelChange) {
112
+ const newLevel = consecutiveErrorUnits >= 3 ? "red"
113
+ : consecutiveErrorUnits >= 1 || getHealthTrend() === "degrading" ? "yellow"
114
+ : "green";
115
+ if (newLevel !== previousProgressLevel) {
116
+ const topIssue = (issues ?? []).find(i => i.severity === "error") ?? (issues ?? [])[0];
117
+ const detail = topIssue ? `: ${topIssue.message}` : "";
118
+ onLevelChange(previousProgressLevel, newLevel, `Health ${previousProgressLevel} → ${newLevel}${detail}`);
119
+ previousProgressLevel = newLevel;
120
+ }
121
+ }
70
122
  }
71
123
 
72
124
  /**
@@ -104,6 +156,27 @@ export function getHealthHistory(): readonly HealthSnapshot[] {
104
156
  return healthHistory;
105
157
  }
106
158
 
159
+ /**
160
+ * Get the latest health issues from the most recent snapshot.
161
+ * Returns issues from the last snapshot that had any, for real-time visibility.
162
+ */
163
+ export function getLatestHealthIssues(): HealthIssueDetail[] {
164
+ for (let i = healthHistory.length - 1; i >= 0; i--) {
165
+ if (healthHistory[i]!.issues.length > 0) return healthHistory[i]!.issues;
166
+ }
167
+ return [];
168
+ }
169
+
170
+ /**
171
+ * Get the latest fixes applied from the most recent snapshot.
172
+ */
173
+ export function getLatestHealthFixes(): string[] {
174
+ for (let i = healthHistory.length - 1; i >= 0; i--) {
175
+ if (healthHistory[i]!.fixes.length > 0) return healthHistory[i]!.fixes;
176
+ }
177
+ return [];
178
+ }
179
+
107
180
  /**
108
181
  * Reset health tracking state. Called on auto-mode start/stop.
109
182
  */
@@ -111,6 +184,7 @@ export function resetHealthTracking(): void {
111
184
  healthHistory = [];
112
185
  consecutiveErrorUnits = 0;
113
186
  healthUnitIndex = 0;
187
+ previousProgressLevel = "green";
114
188
  }
115
189
 
116
190
  // ── Pre-Dispatch Health Gate ───────────────────────────────────────────────
@@ -285,26 +359,48 @@ export function resetEscalation(): void {
285
359
 
286
360
  /**
287
361
  * Format a health summary for display in the auto-mode dashboard.
362
+ * Human-readable with full words, not abbreviations.
288
363
  */
289
364
  export function formatHealthSummary(): string {
290
365
  if (healthHistory.length === 0) return "No health data yet.";
291
366
 
292
367
  const latest = healthHistory[healthHistory.length - 1]!;
293
368
  const trend = getHealthTrend();
294
- const trendIcon = trend === "improving" ? "+" : trend === "degrading" ? "-" : "=";
369
+ const trendLabel = trend === "improving" ? "improving"
370
+ : trend === "degrading" ? "degrading"
371
+ : trend === "stable" ? "stable"
372
+ : "unknown";
295
373
  const totalFixes = healthHistory.reduce((sum, s) => sum + s.fixesApplied, 0);
296
374
 
297
- const parts = [
298
- `Health: ${latest.errors}E/${latest.warnings}W`,
299
- `trend:${trendIcon}`,
300
- `fixes:${totalFixes}`,
301
- ];
375
+ const parts: string[] = [];
376
+
377
+ // Error/warning summary
378
+ if (latest.errors === 0 && latest.warnings === 0) {
379
+ parts.push("No issues");
380
+ } else {
381
+ const counts: string[] = [];
382
+ if (latest.errors > 0) counts.push(`${latest.errors} error${latest.errors > 1 ? "s" : ""}`);
383
+ if (latest.warnings > 0) counts.push(`${latest.warnings} warning${latest.warnings > 1 ? "s" : ""}`);
384
+ parts.push(counts.join(", "));
385
+ }
386
+
387
+ parts.push(`trend ${trendLabel}`);
388
+
389
+ if (totalFixes > 0) {
390
+ parts.push(`${totalFixes} fix${totalFixes > 1 ? "es" : ""} applied`);
391
+ }
302
392
 
303
393
  if (consecutiveErrorUnits > 0) {
304
- parts.push(`streak:${consecutiveErrorUnits}/${ESCALATION_THRESHOLD}`);
394
+ parts.push(`${consecutiveErrorUnits} of ${ESCALATION_THRESHOLD} consecutive errors before escalation`);
395
+ }
396
+
397
+ // Include top issue from latest snapshot
398
+ if (latest.issues.length > 0) {
399
+ const topIssue = latest.issues.find(i => i.severity === "error") ?? latest.issues[0]!;
400
+ parts.push(`latest: ${topIssue.message}`);
305
401
  }
306
402
 
307
- return parts.join(" | ");
403
+ return parts.join(" · ");
308
404
  }
309
405
 
310
406
  /**
@@ -387,18 +387,62 @@ function detectCircularDependencies(slices: RoadmapSliceEntry[]): string[][] {
387
387
  }
388
388
 
389
389
  // ── Helper: doctor run history ──────────────────────────────────────────────
390
- interface DoctorHistoryEntry { ts: string; ok: boolean; errors: number; warnings: number; fixes: number; codes: string[] }
390
+ export interface DoctorHistoryEntry {
391
+ ts: string;
392
+ ok: boolean;
393
+ errors: number;
394
+ warnings: number;
395
+ fixes: number;
396
+ codes: string[];
397
+ /** Issue messages with severity and scope (added in Phase 2). */
398
+ issues?: Array<{ severity: string; code: string; message: string; unitId: string }>;
399
+ /** Fix descriptions applied during this run (added in Phase 2). */
400
+ fixDescriptions?: string[];
401
+ /** Milestone/slice scope this doctor run was scoped to (e.g. "M001/S02"). */
402
+ scope?: string;
403
+ /** Human-readable one-line summary of this doctor run. */
404
+ summary?: string;
405
+ }
391
406
 
392
407
  async function appendDoctorHistory(basePath: string, report: DoctorReport): Promise<void> {
393
408
  try {
394
409
  const historyPath = join(gsdRoot(basePath), "doctor-history.jsonl");
410
+ const errorCount = report.issues.filter(i => i.severity === "error").length;
411
+ const warningCount = report.issues.filter(i => i.severity === "warning").length;
412
+ const issueDetails = report.issues
413
+ .filter(i => i.severity === "error" || i.severity === "warning")
414
+ .slice(0, 10) // cap to keep JSONL lines bounded
415
+ .map(i => ({ severity: i.severity, code: i.code, message: i.message, unitId: i.unitId }));
416
+
417
+ // Human-readable one-line summary
418
+ const summaryParts: string[] = [];
419
+ if (report.ok) {
420
+ summaryParts.push("Clean");
421
+ } else {
422
+ const counts: string[] = [];
423
+ if (errorCount > 0) counts.push(`${errorCount} error${errorCount > 1 ? "s" : ""}`);
424
+ if (warningCount > 0) counts.push(`${warningCount} warning${warningCount > 1 ? "s" : ""}`);
425
+ summaryParts.push(counts.join(", "));
426
+ }
427
+ if (report.fixesApplied.length > 0) {
428
+ summaryParts.push(`${report.fixesApplied.length} fixed`);
429
+ }
430
+ if (issueDetails.length > 0) {
431
+ const topIssue = issueDetails.find(i => i.severity === "error") ?? issueDetails[0]!;
432
+ summaryParts.push(topIssue.message);
433
+ }
434
+
395
435
  const entry = JSON.stringify({
396
436
  ts: new Date().toISOString(),
397
437
  ok: report.ok,
398
- errors: report.issues.filter(i => i.severity === "error").length,
399
- warnings: report.issues.filter(i => i.severity === "warning").length,
438
+ errors: errorCount,
439
+ warnings: warningCount,
400
440
  fixes: report.fixesApplied.length,
401
441
  codes: [...new Set(report.issues.map(i => i.code))],
442
+ issues: issueDetails.length > 0 ? issueDetails : undefined,
443
+ fixDescriptions: report.fixesApplied.length > 0 ? report.fixesApplied : undefined,
444
+ scope: (report as any).scope as string | undefined,
445
+ summary: summaryParts.join(" · "),
402
446
  } satisfies DoctorHistoryEntry);
403
447
  const existing = existsSync(historyPath) ? readFileSync(historyPath, "utf-8") : "";
404
448
  await saveFile(historyPath, existing + entry + "\n");
@@ -296,9 +296,60 @@ function buildHealthSection(data: VisualizerData): string {
296
296
  </tbody>
297
297
  </table>` : '';
298
298
 
299
+ // Progress score section
300
+ let progressHtml = '';
301
+ if (h.progressScore) {
302
+ const ps = h.progressScore;
303
+ const scoreColor = ps.level === 'green' ? '#22c55e' : ps.level === 'yellow' ? '#eab308' : '#ef4444';
304
+ const signalRows = ps.signals.map(s => {
305
+ const icon = s.kind === 'positive' ? '✓' : s.kind === 'negative' ? '✗' : '·';
306
+ const color = s.kind === 'positive' ? '#22c55e' : s.kind === 'negative' ? '#ef4444' : '#888';
307
+ return `<div style="margin-left:1em;color:${color}">${icon} ${esc(s.label)}</div>`;
308
+ }).join('');
309
+ progressHtml = `
310
+ <h3>Progress Score</h3>
311
+ <div style="font-size:1.1em;font-weight:bold;color:${scoreColor}">● ${esc(ps.summary)}</div>
312
+ ${signalRows}`;
313
+ }
314
+
315
+ // Doctor history section
316
+ let historyHtml = '';
317
+ const doctorHistory = h.doctorHistory ?? [];
318
+ if (doctorHistory.length > 0) {
319
+ const historyRows = doctorHistory.slice(0, 20).map(entry => {
320
+ const statusIcon = entry.ok ? '✓' : '✗';
321
+ const statusColor = entry.ok ? '#22c55e' : '#ef4444';
322
+ const ts = entry.ts.replace('T', ' ').slice(0, 19);
323
+ const scopeTag = entry.scope ? `<span class="mono" style="color:#888"> [${esc(entry.scope)}]</span>` : '';
324
+ const summaryText = entry.summary ? esc(entry.summary) : `${entry.errors} errors, ${entry.warnings} warnings, ${entry.fixes} fixes`;
325
+ const issueDetails = (entry.issues ?? []).slice(0, 3).map(i => {
326
+ const iColor = i.severity === 'error' ? '#ef4444' : '#eab308';
327
+ 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>`;
328
+ }).join('');
329
+ const fixDetails = (entry.fixDescriptions ?? []).slice(0, 2).map(f =>
330
+ `<div style="margin-left:2em;color:#22c55e;font-size:0.85em">↳ ${esc(f)}</div>`
331
+ ).join('');
332
+ return `<tr style="color:${statusColor}">
333
+ <td class="mono">${statusIcon}</td>
334
+ <td class="mono">${esc(ts)}${scopeTag}</td>
335
+ <td>${summaryText}</td>
336
+ </tr>
337
+ ${issueDetails || fixDetails ? `<tr><td colspan="3">${issueDetails}${fixDetails}</td></tr>` : ''}`;
338
+ }).join('');
339
+
340
+ historyHtml = `
341
+ <h3>Doctor Run History</h3>
342
+ <table class="tbl">
343
+ <thead><tr><th></th><th>Time</th><th>Summary</th></tr></thead>
344
+ <tbody>${historyRows}</tbody>
345
+ </table>`;
346
+ }
347
+
299
348
  return section('health', 'Health', `
300
349
  <table class="tbl tbl-kv"><tbody>${rows.join('')}</tbody></table>
301
350
  ${tierRows}
351
+ ${progressHtml}
352
+ ${historyHtml}
302
353
  `);
303
354
  }
304
355
 
@@ -20,7 +20,7 @@ import type {
20
20
  ManifestStatus,
21
21
  } from './types.js';
22
22
 
23
- import { checkExistingEnvKeys } from '../env-utils.js';
23
+ import { checkExistingEnvKeys } from './env-utils.js';
24
24
  import { parseRoadmapSlices } from './roadmap-slices.js';
25
25
  import { nativeParseRoadmap, nativeExtractSection, nativeParsePlanFile, nativeParseSummaryFile, NATIVE_UNAVAILABLE } from './native-parser-bridge.js';
26
26
  import { debugTime, debugCount } from './debug-logger.js';
@@ -15,7 +15,8 @@ import { runEnvironmentChecks } from "./doctor-environment.js";
15
15
  import { loadEffectiveGSDPreferences } from "./preferences.js";
16
16
  import { loadLedgerFromDisk, getProjectTotals } from "./metrics.js";
17
17
  import { describeNextUnit, estimateTimeRemaining, updateSliceProgressCache } from "./auto-dashboard.js";
18
- import { projectRoot } from "./commands.js";
18
+ import { projectRoot } from "./commands/context.js";
19
+ import { deriveState, invalidateStateCache } from "./state.js";
19
20
  import {
20
21
  buildHealthLines,
21
22
  detectHealthWidgetProjectState,