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
@@ -14,6 +14,8 @@ import {
14
14
  getHealthTrend,
15
15
  getConsecutiveErrorUnits,
16
16
  getHealthHistory,
17
+ getLatestHealthIssues,
18
+ getLatestHealthFixes,
17
19
  type HealthSnapshot,
18
20
  } from "./doctor-proactive.js";
19
21
 
@@ -77,6 +79,27 @@ export function computeProgressScore(): ProgressScore {
77
79
  signals.push({ kind: "neutral", label: "No health data yet" });
78
80
  }
79
81
 
82
+ // Surface actual doctor issue details when degraded
83
+ if (level !== "green") {
84
+ const latestIssues = getLatestHealthIssues();
85
+ // Show up to 5 most relevant issues (errors first, then warnings)
86
+ const sorted = [...latestIssues].sort((a, b) => {
87
+ const rank = { error: 0, warning: 1, info: 2 };
88
+ return rank[a.severity] - rank[b.severity];
89
+ });
90
+ for (const issue of sorted.slice(0, 5)) {
91
+ signals.push({
92
+ kind: issue.severity === "error" ? "negative" : "neutral",
93
+ label: issue.message,
94
+ });
95
+ }
96
+
97
+ const latestFixes = getLatestHealthFixes();
98
+ for (const fix of latestFixes.slice(0, 3)) {
99
+ signals.push({ kind: "positive", label: `Fixed: ${fix}` });
100
+ }
101
+ }
102
+
80
103
  const summary = level === "green"
81
104
  ? "Progressing well"
82
105
  : level === "yellow"
@@ -1,4 +1,4 @@
1
- You are investigating a GSD auto-mode failure. The user has described their problem and a structured forensic report has been gathered automatically.
1
+ You are debugging GSD itself. The user is donating their tokens to help find bugs in GSD's source code. Your job is to trace from symptom to root cause in the actual source and produce a filing-ready GitHub issue with specific file:line references and a concrete fix suggestion.
2
2
 
3
3
  ## User's Problem
4
4
 
@@ -10,62 +10,137 @@ You are investigating a GSD auto-mode failure. The user has described their prob
10
10
 
11
11
  ## GSD Source Location
12
12
 
13
- GSD extension source code is at: {{gsdSourceDir}}
14
- Key files for understanding failures:
15
- - auto.ts — unit dispatch loop, stuck detection, timeout recovery
16
- - session-forensics.ts — trace extraction from activity logs
17
- - auto-recovery.ts — artifact verification, skip logic
18
- - crash-recovery.ts — crash lock lifecycle
19
- - doctor.ts — state integrity checks
13
+ GSD extension source code is at: `{{gsdSourceDir}}`
20
14
 
21
- You may read these files to identify the specific code path that caused the failure.
15
+ ### Source Map by Domain
22
16
 
23
- ## Your Task
17
+ | Domain | Files |
18
+ |--------|-------|
19
+ | **Auto-mode engine** | `auto.ts` `auto-loop.ts` `auto-dispatch.ts` `auto-start.ts` `auto-supervisor.ts` `auto-timers.ts` `auto-timeout-recovery.ts` `auto-unit-closeout.ts` `auto-post-unit.ts` `auto-verification.ts` `auto-recovery.ts` `auto-worktree.ts` `auto-worktree-sync.ts` `auto-model-selection.ts` `auto-budget.ts` `dispatch-guard.ts` |
20
+ | **State & persistence** | `state.ts` `types.ts` `files.ts` `paths.ts` `json-persistence.ts` `atomic-write.ts` |
21
+ | **Forensics & recovery** | `forensics.ts` `session-forensics.ts` `crash-recovery.ts` `session-lock.ts` |
22
+ | **Metrics & telemetry** | `metrics.ts` `skill-telemetry.ts` `token-counter.ts` |
23
+ | **Health & diagnostics** | `doctor.ts` `doctor-types.ts` `doctor-checks.ts` `doctor-format.ts` `doctor-environment.ts` |
24
+ | **Prompts & context** | `prompt-loader.ts` `prompt-cache-optimizer.ts` `context-budget.ts` |
25
+ | **Git & worktrees** | `git-service.ts` `worktree.ts` `worktree-manager.ts` `git-self-heal.ts` |
26
+ | **Commands** | `commands.ts` `commands-inspect.ts` `commands-maintenance.ts` |
24
27
 
25
- 1. **Analyze** the forensic report. Identify the root cause of the user's problem.
28
+ ### Runtime Path Reference
26
29
 
27
- 2. **Clarify** if needed. Use ask_user_questions (max 2 questions) to narrow down ambiguity. Only ask if the report is genuinely insufficient — do not ask questions you can answer from the data.
30
+ ```
31
+ .gsd/
32
+ ├── PROJECT.md, DECISIONS.md, QUEUE.md, STATE.md, REQUIREMENTS.md, OVERRIDES.md, KNOWLEDGE.md, RUNTIME.md
33
+ ├── auto.lock — crash lock (JSON: pid, unitType, unitId, sessionFile)
34
+ ├── metrics.json — token/cost ledger (units array with cost, tokens, duration)
35
+ ├── completed-units.json — array of "type/id" strings
36
+ ├── doctor-history.jsonl — doctor check history
37
+ ├── activity/ — session activity logs (JSONL per unit)
38
+ │ └── {seq}-{unitType}-{unitId}.jsonl
39
+ ├── runtime/
40
+ │ ├── paused-session.json — serialized session when auto pauses
41
+ │ └── headless-context.md — headless resume context
42
+ ├── debug/ — debug logs
43
+ ├── forensics/ — saved forensic reports
44
+ ├── milestones/{ID}/ — milestone artifacts
45
+ │ ├── {ID}-ROADMAP.md, {ID}-RESEARCH.md, {ID}-CONTEXT.md, {ID}-SUMMARY.md
46
+ │ └── slices/{SID}/ — slice artifacts
47
+ │ ├── {SID}-PLAN.md, {SID}-RESEARCH.md, {SID}-UAT-RESULT.md, {SID}-SUMMARY.md
48
+ │ └── tasks/{TID}-PLAN.md, {TID}-SUMMARY.md
49
+ └── worktrees/{milestoneId}/ — per-milestone worktree with replicated .gsd/
50
+ ```
28
51
 
29
- 3. **Explain** your findings clearly:
30
- - What happened (the failure sequence)
31
- - Why it happened (root cause in GSD's logic)
32
- - What the user can do to recover (immediate fix)
52
+ ### Activity Log Format
33
53
 
34
- 4. **Offer GitHub issue creation.** Ask the user:
35
- "Would you like me to create a GitHub issue for this on gsd-build/gsd-2?"
54
+ - **Filename**: `{3-digit-seq}-{unitType}-{unitId}.jsonl`
55
+ - Each line is a JSON object with `type: "message"` and a `message` field
56
+ - `message.role: "assistant"` — contains `content[]` array:
57
+ - `type: "text"` entries hold the agent's reasoning
58
+ - `type: "toolCall"` entries hold tool invocations (`name`, `id`, `arguments`)
59
+ - `message.role: "toolResult"` — contains `toolCallId`, `toolName`, `isError`, `content`
60
+ - `usage` field on assistant messages: `input`, `output`, `cacheRead`, `cacheWrite`, `totalTokens`, `cost`
61
+ - **To trace a failure**: find the last activity log, search for `isError: true` tool results, then read the agent's reasoning text preceding that error
36
62
 
37
- If yes, create the issue using bash with `gh issue create`:
38
- - Repository: gsd-build/gsd-2
39
- - Labels: bug, auto-generated
40
- - Title: concise description of the failure
41
- - Body format:
42
- ```
43
- ## Problem
44
- [1-2 sentence summary]
63
+ ### Crash Lock Format (`auto.lock`)
45
64
 
46
- ## Environment
47
- - GSD version: [from report]
48
- - Model: [from report]
49
- - Unit: [type/id that failed]
65
+ JSON with fields: `pid`, `startedAt`, `unitType`, `unitId`, `unitStartedAt`, `completedUnits`, `sessionFile`
50
66
 
51
- ## Reproduction Context
52
- [What was happening when it failed — phase, milestone, slice]
67
+ A stale lock (PID is dead) means the previous auto-mode session crashed mid-unit.
53
68
 
54
- ## Forensic Findings
55
- [Key anomalies detected, error traces, relevant tool call sequences]
69
+ ### Metrics Ledger Format (`metrics.json`)
56
70
 
57
- ## Suggested Fix Area
58
- [File:line references in GSD source if identified]
71
+ ```
72
+ { version: 1, projectStartedAt: <ms>, units: [{ type, id, model, startedAt, finishedAt, tokens: { input, output, cacheRead, cacheWrite, total }, cost, toolCalls, assistantMessages, ... }] }
73
+ ```
59
74
 
60
- ---
61
- *Auto-generated by `/gsd forensics`*
62
- ```
75
+ A unit dispatched more than once (`type/id` appears multiple times) indicates a stuck loop — the unit completed but artifact verification failed.
63
76
 
64
- **CRITICAL REDACTION RULES** before creating the issue:
65
- - Replace all absolute paths with relative paths
66
- - Remove any API keys, tokens, or credentials
67
- - Remove any environment variable values
68
- - Do not include file content (code written by the user)
69
- - Only include GSD structural information (tool names, file names, error messages)
77
+ ## Investigation Protocol
70
78
 
71
- 5. **Report saved.** Remind the user that the full forensic report was saved locally (the path will be in the notification).
79
+ 1. **Start with the pre-parsed forensic report** above. The anomaly section contains automated findings treat these as leads, not conclusions.
80
+
81
+ 2. **Form hypotheses** about which module and code path is responsible. Use the source map to identify candidate files.
82
+
83
+ 3. **Read the actual GSD source code** at `{{gsdSourceDir}}` to confirm or deny each hypothesis. Do not guess what code does — read it.
84
+
85
+ 4. **Trace the code path** from the entry point (usually `auto-loop.ts` dispatch or `auto-dispatch.ts`) through to the failure point. Follow function calls across files.
86
+
87
+ 5. **Identify the specific file and line** where the bug lives. Determine what kind of defect it is:
88
+ - Missing edge case / unhandled condition
89
+ - Wrong boolean logic or comparison
90
+ - Race condition or ordering issue
91
+ - State corruption (e.g. completed-units.json out of sync with artifacts)
92
+ - Timeout / recovery logic not triggering correctly
93
+
94
+ 6. **Clarify if needed.** Use ask_user_questions (max 2 questions) only if the report is genuinely insufficient. Do not ask questions you can answer from the data or source code.
95
+
96
+ ## Output
97
+
98
+ Explain your findings:
99
+ - **What happened** — the failure sequence reconstructed from activity logs and anomalies
100
+ - **Why it happened** — root cause traced to specific code in GSD source, with `file:line` references
101
+ - **Code snippet** — the problematic code and what it should do instead
102
+ - **Recovery** — what the user can do right now to get unstuck
103
+
104
+ Then **offer GitHub issue creation**: "Would you like me to create a GitHub issue for this on gsd-build/gsd-2?"
105
+
106
+ If yes, create using `gh issue create` with this format:
107
+
108
+ ```
109
+ ## Problem
110
+ [1-2 sentence summary]
111
+
112
+ ## Root Cause
113
+ [Specific file:line in GSD source, with code snippet showing the bug]
114
+
115
+ ## Expected Behavior
116
+ [What the code should do instead — concrete fix suggestion]
117
+
118
+ ## Environment
119
+ - GSD version: [from report]
120
+ - Model: [from report]
121
+ - Unit: [type/id that failed]
122
+
123
+ ## Reproduction Context
124
+ [Phase, milestone, slice, what was happening when it failed]
125
+
126
+ ## Forensic Evidence
127
+ [Key anomalies, error traces, relevant tool call sequences from the report]
128
+
129
+ ---
130
+ *Auto-generated by `/gsd forensics`*
131
+ ```
132
+
133
+ **Repository:** gsd-build/gsd-2
134
+ **Labels:** bug, auto-generated
135
+
136
+ ### Redaction Rules (CRITICAL)
137
+
138
+ Before creating the issue, you MUST:
139
+ - Replace all absolute paths with relative paths
140
+ - Remove any API keys, tokens, or credentials
141
+ - Remove any environment variable values
142
+ - Do not include user's project code — only GSD structural information (tool names, file names, error messages)
143
+
144
+ ## Report Saved
145
+
146
+ Remind the user that the full forensic report was saved locally (the path will be in the notification).
@@ -962,21 +962,25 @@ test("auto.ts startAuto calls autoLoop (not dispatchNextUnit as first dispatch)"
962
962
  );
963
963
  });
964
964
 
965
- test("index.ts agent_end handler calls resolveAgentEnd (not handleAgentEnd)", () => {
966
- const src = readFileSync(
967
- resolve(import.meta.dirname, "..", "index.ts"),
965
+ test("agent_end handler calls resolveAgentEnd (not handleAgentEnd)", () => {
966
+ const hooksSrc = readFileSync(
967
+ resolve(import.meta.dirname, "..", "bootstrap", "register-hooks.ts"),
968
+ "utf-8",
969
+ );
970
+ // Verify the agent_end hook is registered
971
+ const handlerIdx = hooksSrc.indexOf('pi.on("agent_end"');
972
+ assert.ok(handlerIdx > -1, "register-hooks.ts must have an agent_end handler");
973
+
974
+ const recoverySrc = readFileSync(
975
+ resolve(import.meta.dirname, "..", "bootstrap", "agent-end-recovery.ts"),
968
976
  "utf-8",
969
977
  );
970
- // Find the agent_end handler success path
971
- const handlerIdx = src.indexOf('pi.on("agent_end"');
972
- assert.ok(handlerIdx > -1, "index.ts must have an agent_end handler");
973
- const handlerBlock = src.slice(handlerIdx, handlerIdx + 10000);
974
978
  assert.ok(
975
- handlerBlock.includes("resolveAgentEnd(event)"),
979
+ recoverySrc.includes("resolveAgentEnd(event)"),
976
980
  "agent_end success path must call resolveAgentEnd(event) instead of handleAgentEnd(ctx, pi)",
977
981
  );
978
982
  assert.ok(
979
- handlerBlock.includes("isSessionSwitchInFlight()"),
983
+ recoverySrc.includes("isSessionSwitchInFlight()"),
980
984
  "agent_end handler must ignore session-switch agent_end events from cmdCtx.newSession()",
981
985
  );
982
986
  });
@@ -176,9 +176,9 @@ async function main(): Promise<void> {
176
176
 
177
177
  recordHealthSnapshot(2, 3, 1);
178
178
  const summary = formatHealthSummary();
179
- assertTrue(summary.includes("2E/3W"), "summary includes error/warning counts");
180
- assertTrue(summary.includes("fixes:1"), "summary includes fix count");
181
- assertTrue(summary.includes("streak:1/5"), "summary includes error streak");
179
+ assertTrue(summary.includes("2 errors") && summary.includes("3 warnings"), "summary includes error/warning counts");
180
+ assertTrue(summary.includes("1 fix applied"), "summary includes fix count");
181
+ assertTrue(summary.includes("1 of 5 consecutive errors"), "summary includes error streak");
182
182
  }
183
183
 
184
184
  // ─── Pre-Dispatch Health Gate ─────────────────────────────────────
@@ -261,38 +261,38 @@ test("pauseAutoForProviderError falls back to indefinite pause when not rate lim
261
261
 
262
262
  // ── Escalating backoff for transient errors (#1166) ─────────────────────────
263
263
 
264
- test("index.ts tracks consecutive transient errors for escalating backoff", () => {
265
- const indexSource = readFileSync(join(__dirname, "..", "index.ts"), "utf-8");
264
+ test("agent-end-recovery.ts tracks consecutive transient errors for escalating backoff", () => {
265
+ const src = readFileSync(join(__dirname, "..", "bootstrap", "agent-end-recovery.ts"), "utf-8");
266
266
 
267
267
  assert.ok(
268
- indexSource.includes("consecutiveTransientErrors"),
269
- "index.ts must track consecutiveTransientErrors for escalating backoff (#1166)",
268
+ src.includes("consecutiveTransientErrors"),
269
+ "agent-end-recovery.ts must track consecutiveTransientErrors for escalating backoff (#1166)",
270
270
  );
271
271
  assert.ok(
272
- indexSource.includes("MAX_TRANSIENT_AUTO_RESUMES"),
273
- "index.ts must define MAX_TRANSIENT_AUTO_RESUMES to cap infinite retries (#1166)",
272
+ src.includes("MAX_TRANSIENT_AUTO_RESUMES"),
273
+ "agent-end-recovery.ts must define MAX_TRANSIENT_AUTO_RESUMES to cap infinite retries (#1166)",
274
274
  );
275
275
  });
276
276
 
277
- test("index.ts resets consecutive transient error counter on success", () => {
278
- const indexSource = readFileSync(join(__dirname, "..", "index.ts"), "utf-8");
277
+ test("agent-end-recovery.ts resets consecutive transient error counter on success", () => {
278
+ const src = readFileSync(join(__dirname, "..", "bootstrap", "agent-end-recovery.ts"), "utf-8");
279
279
 
280
- // After successful unit completion, the counter must be reset.
280
+ // After successful agent_end (before resolveAgentEnd), the counter must be reset.
281
281
  // Use a regex across the success block so CRLF checkouts on Windows do not
282
282
  // push the reset line outside a fixed substring window.
283
283
  assert.ok(
284
- /consecutiveTransientErrors\s*=\s*0\s*;[\s\S]{0,250}successful unit completion/.test(indexSource),
285
- "consecutive transient error counter must be reset on successful unit completion (#1166)",
284
+ /consecutiveTransientErrors\s*=\s*0\s*;[\s\S]{0,250}resolveAgentEnd/.test(src),
285
+ "consecutive transient error counter must be reset before resolveAgentEnd on the success path (#1166)",
286
286
  );
287
287
  });
288
288
 
289
- test("index.ts applies escalating delay for repeated transient errors", () => {
290
- const indexSource = readFileSync(join(__dirname, "..", "index.ts"), "utf-8");
289
+ test("agent-end-recovery.ts applies escalating delay for repeated transient errors", () => {
290
+ const src = readFileSync(join(__dirname, "..", "bootstrap", "agent-end-recovery.ts"), "utf-8");
291
291
 
292
- // Must contain the exponential backoff formula
292
+ // Must contain the exponential backoff formula (may span multiple lines)
293
293
  assert.ok(
294
- /retryAfterMs\s*[=*].*2\s*\*\*/.test(indexSource),
295
- "index.ts must escalate retryAfterMs exponentially for consecutive transient errors (#1166)",
294
+ src.includes("2 ** Math.max(0, consecutiveTransientErrors"),
295
+ "agent-end-recovery.ts must escalate retryAfterMs exponentially for consecutive transient errors (#1166)",
296
296
  );
297
297
  });
298
298
 
@@ -60,17 +60,17 @@ test("buildSkillActivationBlock matches installed skills from task context", ()
60
60
  }
61
61
  });
62
62
 
63
- test("buildSkillActivationBlock includes always_use_skills from preferences", () => {
63
+ test("buildSkillActivationBlock includes always_use_skills from preferences using exact Skill tool format", () => {
64
64
  const base = makeTempBase();
65
65
  try {
66
- writeSkill(base, "testing", "Use for test setup, assertions, and verification patterns.");
66
+ writeSkill(base, "swift-testing", "Use for Swift Testing assertions and verification patterns.");
67
67
  loadOnlyTestSkills(base);
68
68
 
69
69
  const result = buildBlock(base, { taskTitle: "Unrelated task title" }, {
70
- always_use_skills: ["testing"],
70
+ always_use_skills: ["swift-testing"],
71
71
  });
72
72
 
73
- assert.match(result, /Call Skill\('testing'\)/);
73
+ assert.equal(result, "<skill_activation>Call Skill('swift-testing').</skill_activation>");
74
74
  } finally {
75
75
  cleanup(base);
76
76
  }
@@ -422,25 +422,25 @@ assertTrue(
422
422
  "overlay has 10 tab labels",
423
423
  );
424
424
 
425
- // Verify commands.ts integration
426
- const commandsPath = join(__dirname, "..", "commands.ts");
427
- const commandsSrc = readFileSync(commandsPath, "utf-8");
425
+ // Verify commands/handlers/core.ts integration
426
+ const coreHandlerPath = join(__dirname, "..", "commands", "handlers", "core.ts");
427
+ const coreHandlerSrc = readFileSync(coreHandlerPath, "utf-8");
428
428
 
429
- console.log("\n=== commands.ts integration ===");
429
+ console.log("\n=== commands/handlers/core.ts integration ===");
430
430
 
431
431
  assertTrue(
432
- commandsSrc.includes('"visualize"'),
433
- "commands.ts has visualize in subcommands array",
432
+ coreHandlerSrc.includes('"visualize"'),
433
+ "core.ts has visualize in subcommands array",
434
434
  );
435
435
 
436
436
  assertTrue(
437
- commandsSrc.includes("GSDVisualizerOverlay"),
438
- "commands.ts imports GSDVisualizerOverlay",
437
+ coreHandlerSrc.includes("GSDVisualizerOverlay"),
438
+ "core.ts imports GSDVisualizerOverlay",
439
439
  );
440
440
 
441
441
  assertTrue(
442
- commandsSrc.includes("handleVisualize"),
443
- "commands.ts has handleVisualize handler",
442
+ coreHandlerSrc.includes("handleVisualize"),
443
+ "core.ts has handleVisualize handler",
444
444
  );
445
445
 
446
446
  report();
@@ -1,10 +1,11 @@
1
1
  // Data loader for workflow visualizer overlay — aggregates state + metrics.
2
2
 
3
3
  import { existsSync, readFileSync, statSync } from 'node:fs';
4
+ import { join } from 'node:path';
4
5
  import { deriveState } from './state.js';
5
6
  import { parseRoadmap, parsePlan, parseSummary, loadFile } from './files.js';
6
7
  import { findMilestoneIds } from './milestone-ids.js';
7
- import { resolveMilestoneFile, resolveSliceFile, resolveGsdRootFile } from './paths.js';
8
+ import { resolveMilestoneFile, resolveSliceFile, resolveGsdRootFile, gsdRoot } from './paths.js';
8
9
  import {
9
10
  getLedger,
10
11
  getProjectTotals,
@@ -21,6 +22,8 @@ import { loadEffectiveGSDPreferences } from './preferences.js';
21
22
  import { runProviderChecks, type ProviderCheckResult } from './doctor-providers.js';
22
23
  import { generateSkillHealthReport } from './skill-health.js';
23
24
  import { runEnvironmentChecks, type EnvironmentCheckResult } from './doctor-environment.js';
25
+ import { computeProgressScore } from './progress-score.js';
26
+ import { getHealthHistory } from './doctor-proactive.js';
24
27
 
25
28
  import type { Phase } from './types.js';
26
29
  import type { CaptureEntry } from './captures.js';
@@ -161,6 +164,27 @@ export interface SkillSummaryInfo {
161
164
  topIssue: string | null;
162
165
  }
163
166
 
167
+ /** A single doctor history entry for visualizer display. */
168
+ export interface VisualizerDoctorEntry {
169
+ ts: string;
170
+ ok: boolean;
171
+ errors: number;
172
+ warnings: number;
173
+ fixes: number;
174
+ codes: string[];
175
+ issues?: Array<{ severity: string; code: string; message: string; unitId: string }>;
176
+ fixDescriptions?: string[];
177
+ scope?: string;
178
+ summary?: string;
179
+ }
180
+
181
+ /** Current progress score snapshot for health display. */
182
+ export interface VisualizerProgressScore {
183
+ level: "green" | "yellow" | "red";
184
+ summary: string;
185
+ signals: Array<{ kind: "positive" | "negative" | "neutral"; label: string }>;
186
+ }
187
+
164
188
  export interface HealthInfo {
165
189
  budgetCeiling: number | undefined;
166
190
  tokenProfile: string;
@@ -174,6 +198,10 @@ export interface HealthInfo {
174
198
  providers: ProviderStatusSummary[];
175
199
  skillSummary: SkillSummaryInfo;
176
200
  environmentIssues: import("./doctor-environment.js").EnvironmentCheckResult[];
201
+ /** Persisted doctor run history (most recent first, up to 20 entries). */
202
+ doctorHistory?: VisualizerDoctorEntry[];
203
+ /** Current in-memory progress score (null if auto-mode not active). */
204
+ progressScore?: VisualizerProgressScore | null;
177
205
  }
178
206
 
179
207
  export interface VisualizerData {
@@ -608,6 +636,26 @@ function loadHealth(units: UnitMetrics[], totals: ProjectTotals | null, basePath
608
636
  environmentIssues = runEnvironmentChecks(basePath).filter(r => r.status !== "ok");
609
637
  } catch { /* non-fatal */ }
610
638
 
639
+ // Doctor run history — persisted across sessions (sync read to keep loadHealth sync)
640
+ let doctorHistory: VisualizerDoctorEntry[] = [];
641
+ try {
642
+ const historyPath = join(gsdRoot(basePath), "doctor-history.jsonl");
643
+ if (existsSync(historyPath)) {
644
+ const lines = readFileSync(historyPath, "utf-8").split("\n").filter(l => l.trim());
645
+ doctorHistory = lines.slice(-20).reverse().map(l => JSON.parse(l) as VisualizerDoctorEntry);
646
+ }
647
+ } catch { /* non-fatal */ }
648
+
649
+ // Current progress score — only meaningful when auto-mode has health data
650
+ let progressScore: VisualizerProgressScore | null = null;
651
+ try {
652
+ const history = getHealthHistory();
653
+ if (history.length > 0) {
654
+ const score = computeProgressScore();
655
+ progressScore = { level: score.level, summary: score.summary, signals: score.signals };
656
+ }
657
+ } catch { /* non-fatal */ }
658
+
611
659
  return {
612
660
  budgetCeiling,
613
661
  tokenProfile,
@@ -621,6 +669,8 @@ function loadHealth(units: UnitMetrics[], totals: ProjectTotals | null, basePath
621
669
  providers,
622
670
  skillSummary,
623
671
  environmentIssues,
672
+ doctorHistory,
673
+ progressScore,
624
674
  };
625
675
  }
626
676
 
@@ -1150,6 +1150,64 @@ export function renderHealthView(
1150
1150
  }
1151
1151
  }
1152
1152
 
1153
+ // Progress score section — current traffic light status
1154
+ if (health.progressScore) {
1155
+ lines.push("");
1156
+ lines.push(th.fg("accent", th.bold("Progress Score")));
1157
+ lines.push("");
1158
+ const ps = health.progressScore;
1159
+ const scoreColor = ps.level === "green" ? "success" : ps.level === "yellow" ? "warning" : "error";
1160
+ const scoreIcon = ps.level === "green" ? "●" : ps.level === "yellow" ? "◐" : "○";
1161
+ lines.push(` ${th.fg(scoreColor, scoreIcon)} ${th.fg(scoreColor, ps.summary)}`);
1162
+ for (const signal of ps.signals) {
1163
+ const prefix = signal.kind === "positive" ? th.fg("success", " ✓")
1164
+ : signal.kind === "negative" ? th.fg("error", " ✗")
1165
+ : th.fg("dim", " ·");
1166
+ lines.push(` ${prefix} ${th.fg("dim", signal.label)}`);
1167
+ }
1168
+ }
1169
+
1170
+ // Doctor history section — persisted across sessions
1171
+ const doctorHistory = health.doctorHistory ?? [];
1172
+ if (doctorHistory.length > 0) {
1173
+ lines.push("");
1174
+ lines.push(th.fg("accent", th.bold("Doctor History")));
1175
+ lines.push("");
1176
+
1177
+ for (const entry of doctorHistory.slice(0, 10)) {
1178
+ const icon = entry.ok ? th.fg("success", "✓") : th.fg("error", "✗");
1179
+ const ts = entry.ts.replace("T", " ").slice(0, 19);
1180
+ const scopeTag = entry.scope ? th.fg("accent", ` [${entry.scope}]`) : "";
1181
+ // Prefer human-readable summary, fall back to counts
1182
+ const detail = entry.summary
1183
+ ? th.fg("text", entry.summary)
1184
+ : th.fg("text", `${entry.errors} errors, ${entry.warnings} warnings, ${entry.fixes} fixes`);
1185
+ lines.push(` ${icon} ${th.fg("dim", ts)}${scopeTag} ${detail}`);
1186
+
1187
+ // Show issue details if available
1188
+ if (entry.issues && entry.issues.length > 0) {
1189
+ for (const issue of entry.issues.slice(0, 3)) {
1190
+ const issuePfx = issue.severity === "error" ? th.fg("error", " ✗") : th.fg("warning", " ⚠");
1191
+ lines.push(` ${issuePfx} ${th.fg("dim", truncateToWidth(issue.message, width - 12))}`);
1192
+ }
1193
+ if (entry.issues.length > 3) {
1194
+ lines.push(` ${th.fg("dim", `+${entry.issues.length - 3} more`)}`);
1195
+ }
1196
+ }
1197
+
1198
+ // Show fixes if available
1199
+ if (entry.fixDescriptions && entry.fixDescriptions.length > 0) {
1200
+ for (const fix of entry.fixDescriptions.slice(0, 2)) {
1201
+ lines.push(` ${th.fg("success", "↳")} ${th.fg("dim", truncateToWidth(fix, width - 12))}`);
1202
+ }
1203
+ }
1204
+ }
1205
+
1206
+ if (doctorHistory.length > 10) {
1207
+ lines.push(` ${th.fg("dim", `...${doctorHistory.length - 10} older entries`)}`);
1208
+ }
1209
+ }
1210
+
1153
1211
  // Skills section
1154
1212
  if (health.skillSummary?.total > 0) {
1155
1213
  lines.push("");