gsd-pi 2.49.0-dev.de3d9f6 → 2.50.0-dev.9476db8

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 (249) hide show
  1. package/dist/headless-ui.js +12 -2
  2. package/dist/headless.js +29 -13
  3. package/dist/resources/extensions/gsd/auto/infra-errors.js +1 -0
  4. package/dist/resources/extensions/gsd/auto/phases.js +11 -11
  5. package/dist/resources/extensions/gsd/auto/resolve.js +2 -2
  6. package/dist/resources/extensions/gsd/auto/run-unit.js +2 -2
  7. package/dist/resources/extensions/gsd/auto/session.js +4 -0
  8. package/dist/resources/extensions/gsd/auto-artifact-paths.js +8 -10
  9. package/dist/resources/extensions/gsd/auto-dashboard.js +6 -3
  10. package/dist/resources/extensions/gsd/auto-dispatch.js +33 -21
  11. package/dist/resources/extensions/gsd/auto-post-unit.js +17 -24
  12. package/dist/resources/extensions/gsd/auto-prompts.js +102 -21
  13. package/dist/resources/extensions/gsd/auto-recovery.js +62 -184
  14. package/dist/resources/extensions/gsd/auto-start.js +4 -31
  15. package/dist/resources/extensions/gsd/auto-timers.js +2 -2
  16. package/dist/resources/extensions/gsd/auto-verification.js +4 -7
  17. package/dist/resources/extensions/gsd/auto-worktree.js +257 -113
  18. package/dist/resources/extensions/gsd/auto.js +7 -5
  19. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +89 -0
  20. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +8 -1
  21. package/dist/resources/extensions/gsd/branch-patterns.js +13 -0
  22. package/dist/resources/extensions/gsd/doctor-checks.js +5 -1234
  23. package/dist/resources/extensions/gsd/doctor-engine-checks.js +168 -0
  24. package/dist/resources/extensions/gsd/doctor-environment.js +28 -7
  25. package/dist/resources/extensions/gsd/doctor-git-checks.js +405 -0
  26. package/dist/resources/extensions/gsd/doctor-global-checks.js +74 -0
  27. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +600 -0
  28. package/dist/resources/extensions/gsd/doctor.js +9 -1
  29. package/dist/resources/extensions/gsd/extension-manifest.json +1 -1
  30. package/dist/resources/extensions/gsd/git-service.js +9 -10
  31. package/dist/resources/extensions/gsd/gsd-db.js +124 -1
  32. package/dist/resources/extensions/gsd/guided-flow-queue.js +10 -11
  33. package/dist/resources/extensions/gsd/markdown-renderer.js +33 -5
  34. package/dist/resources/extensions/gsd/preferences-types.js +2 -1
  35. package/dist/resources/extensions/gsd/preferences-validation.js +39 -0
  36. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +27 -8
  37. package/dist/resources/extensions/gsd/prompts/complete-slice.md +9 -8
  38. package/dist/resources/extensions/gsd/prompts/execute-task.md +16 -13
  39. package/dist/resources/extensions/gsd/prompts/forensics.md +12 -5
  40. package/dist/resources/extensions/gsd/prompts/gate-evaluate.md +32 -0
  41. package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  42. package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  43. package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  44. package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  45. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  46. package/dist/resources/extensions/gsd/prompts/plan-slice.md +8 -3
  47. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +3 -0
  48. package/dist/resources/extensions/gsd/prompts/replan-slice.md +1 -1
  49. package/dist/resources/extensions/gsd/repo-identity.js +29 -0
  50. package/dist/resources/extensions/gsd/roadmap-slices.js +2 -2
  51. package/dist/resources/extensions/gsd/session-forensics.js +6 -11
  52. package/dist/resources/extensions/gsd/session-lock.js +67 -56
  53. package/dist/resources/extensions/gsd/state.js +34 -7
  54. package/dist/resources/extensions/gsd/templates/milestone-summary.md +8 -0
  55. package/dist/resources/extensions/gsd/templates/plan.md +16 -0
  56. package/dist/resources/extensions/gsd/templates/roadmap.md +13 -0
  57. package/dist/resources/extensions/gsd/templates/slice-summary.md +9 -0
  58. package/dist/resources/extensions/gsd/templates/task-plan.md +24 -0
  59. package/dist/resources/extensions/gsd/tools/plan-slice.js +14 -1
  60. package/dist/resources/extensions/gsd/tools/validate-milestone.js +3 -3
  61. package/dist/resources/extensions/gsd/verdict-parser.js +84 -0
  62. package/dist/resources/extensions/gsd/worktree-resolver.js +24 -0
  63. package/dist/resources/extensions/gsd/worktree.js +3 -2
  64. package/dist/resources/extensions/remote-questions/config.js +3 -5
  65. package/dist/resources/extensions/search-the-web/native-search.js +8 -3
  66. package/dist/resources/extensions/search-the-web/tool-search.js +19 -2
  67. package/dist/resources/skills/github-workflows/references/gh/SKILL.md +22 -1
  68. package/dist/web/standalone/.next/BUILD_ID +1 -1
  69. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  70. package/dist/web/standalone/.next/build-manifest.json +3 -3
  71. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  72. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  73. package/dist/web/standalone/.next/required-server-files.json +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  75. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/index.html +1 -1
  91. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
  98. package/dist/web/standalone/.next/server/chunks/229.js +2 -2
  99. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  100. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  102. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  103. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  104. package/dist/web/standalone/.next/static/chunks/4024.7c75ac378de0f2b5.js +9 -0
  105. package/dist/web/standalone/.next/static/chunks/{webpack-0a4cd455ec4197d2.js → webpack-2473ce2c3879fff4.js} +1 -1
  106. package/dist/web/standalone/server.js +1 -1
  107. package/package.json +1 -1
  108. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  109. package/packages/pi-agent-core/dist/agent-loop.js +4 -1
  110. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  111. package/packages/pi-agent-core/src/agent-loop.ts +4 -1
  112. package/packages/pi-ai/dist/providers/openai-codex-responses.js +39 -10
  113. package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
  114. package/packages/pi-ai/src/providers/openai-codex-responses.ts +39 -8
  115. package/packages/pi-coding-agent/dist/core/blob-store.d.ts.map +1 -1
  116. package/packages/pi-coding-agent/dist/core/blob-store.js +8 -3
  117. package/packages/pi-coding-agent/dist/core/blob-store.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/core/discovery-cache.d.ts.map +1 -1
  119. package/packages/pi-coding-agent/dist/core/discovery-cache.js +9 -2
  120. package/packages/pi-coding-agent/dist/core/discovery-cache.js.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/retry-handler.js +1 -1
  122. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  123. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  124. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +7 -32
  125. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  126. package/packages/pi-coding-agent/dist/modes/rpc/jsonl.d.ts.map +1 -1
  127. package/packages/pi-coding-agent/dist/modes/rpc/jsonl.js +5 -0
  128. package/packages/pi-coding-agent/dist/modes/rpc/jsonl.js.map +1 -1
  129. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  130. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js +0 -1
  131. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js.map +1 -1
  132. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +1 -1
  133. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  134. package/packages/pi-coding-agent/package.json +1 -1
  135. package/packages/pi-coding-agent/src/core/blob-store.ts +6 -3
  136. package/packages/pi-coding-agent/src/core/discovery-cache.ts +9 -2
  137. package/packages/pi-coding-agent/src/core/retry-handler.ts +1 -1
  138. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +7 -32
  139. package/packages/pi-coding-agent/src/modes/rpc/jsonl.ts +6 -0
  140. package/packages/pi-coding-agent/src/modes/rpc/rpc-client.ts +0 -2
  141. package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +2 -2
  142. package/pkg/package.json +1 -1
  143. package/src/resources/extensions/gsd/auto/infra-errors.ts +1 -0
  144. package/src/resources/extensions/gsd/auto/phases.ts +10 -11
  145. package/src/resources/extensions/gsd/auto/resolve.ts +3 -3
  146. package/src/resources/extensions/gsd/auto/run-unit.ts +2 -2
  147. package/src/resources/extensions/gsd/auto/session.ts +5 -0
  148. package/src/resources/extensions/gsd/auto/types.ts +13 -0
  149. package/src/resources/extensions/gsd/auto-artifact-paths.ts +19 -21
  150. package/src/resources/extensions/gsd/auto-dashboard.ts +5 -2
  151. package/src/resources/extensions/gsd/auto-dispatch.ts +39 -21
  152. package/src/resources/extensions/gsd/auto-loop.ts +1 -1
  153. package/src/resources/extensions/gsd/auto-post-unit.ts +18 -28
  154. package/src/resources/extensions/gsd/auto-prompts.ts +113 -19
  155. package/src/resources/extensions/gsd/auto-recovery.ts +65 -199
  156. package/src/resources/extensions/gsd/auto-start.ts +7 -27
  157. package/src/resources/extensions/gsd/auto-timers.ts +2 -2
  158. package/src/resources/extensions/gsd/auto-verification.ts +4 -7
  159. package/src/resources/extensions/gsd/auto-worktree.ts +305 -108
  160. package/src/resources/extensions/gsd/auto.ts +11 -10
  161. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +93 -0
  162. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +8 -0
  163. package/src/resources/extensions/gsd/branch-patterns.ts +16 -0
  164. package/src/resources/extensions/gsd/doctor-checks.ts +5 -1291
  165. package/src/resources/extensions/gsd/doctor-engine-checks.ts +182 -0
  166. package/src/resources/extensions/gsd/doctor-environment.ts +30 -7
  167. package/src/resources/extensions/gsd/doctor-git-checks.ts +415 -0
  168. package/src/resources/extensions/gsd/doctor-global-checks.ts +84 -0
  169. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +626 -0
  170. package/src/resources/extensions/gsd/doctor.ts +9 -1
  171. package/src/resources/extensions/gsd/extension-manifest.json +1 -1
  172. package/src/resources/extensions/gsd/git-service.ts +7 -15
  173. package/src/resources/extensions/gsd/gsd-db.ts +150 -2
  174. package/src/resources/extensions/gsd/guided-flow-queue.ts +11 -12
  175. package/src/resources/extensions/gsd/markdown-renderer.ts +37 -4
  176. package/src/resources/extensions/gsd/preferences-types.ts +5 -1
  177. package/src/resources/extensions/gsd/preferences-validation.ts +37 -0
  178. package/src/resources/extensions/gsd/prompts/complete-milestone.md +27 -8
  179. package/src/resources/extensions/gsd/prompts/complete-slice.md +9 -8
  180. package/src/resources/extensions/gsd/prompts/execute-task.md +16 -13
  181. package/src/resources/extensions/gsd/prompts/forensics.md +12 -5
  182. package/src/resources/extensions/gsd/prompts/gate-evaluate.md +32 -0
  183. package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  184. package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  185. package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  186. package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  187. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  188. package/src/resources/extensions/gsd/prompts/plan-slice.md +8 -3
  189. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +3 -0
  190. package/src/resources/extensions/gsd/prompts/replan-slice.md +1 -1
  191. package/src/resources/extensions/gsd/repo-identity.ts +28 -0
  192. package/src/resources/extensions/gsd/roadmap-slices.ts +2 -2
  193. package/src/resources/extensions/gsd/session-forensics.ts +6 -11
  194. package/src/resources/extensions/gsd/session-lock.ts +92 -64
  195. package/src/resources/extensions/gsd/state.ts +38 -5
  196. package/src/resources/extensions/gsd/templates/milestone-summary.md +8 -0
  197. package/src/resources/extensions/gsd/templates/plan.md +16 -0
  198. package/src/resources/extensions/gsd/templates/roadmap.md +13 -0
  199. package/src/resources/extensions/gsd/templates/slice-summary.md +9 -0
  200. package/src/resources/extensions/gsd/templates/task-plan.md +24 -0
  201. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +2 -2
  202. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +35 -0
  203. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +1 -81
  204. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
  205. package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
  206. package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +9 -12
  207. package/src/resources/extensions/gsd/tests/doctor-environment.test.ts +115 -1
  208. package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +65 -1
  209. package/src/resources/extensions/gsd/tests/doctor-git.test.ts +50 -0
  210. package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +189 -0
  211. package/src/resources/extensions/gsd/tests/gate-storage.test.ts +156 -0
  212. package/src/resources/extensions/gsd/tests/git-service.test.ts +49 -0
  213. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
  214. package/src/resources/extensions/gsd/tests/infra-error.test.ts +12 -2
  215. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +39 -0
  216. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
  217. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  218. package/src/resources/extensions/gsd/tests/quality-gates.test.ts +347 -0
  219. package/src/resources/extensions/gsd/tests/queue-completed-milestone-perf.test.ts +155 -0
  220. package/src/resources/extensions/gsd/tests/replan-slice.test.ts +2 -1
  221. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +32 -0
  222. package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +26 -0
  223. package/src/resources/extensions/gsd/tests/run-uat.test.ts +20 -16
  224. package/src/resources/extensions/gsd/tests/session-lock-transient-read.test.ts +223 -0
  225. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +44 -4
  226. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +1 -1
  227. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +2 -1
  228. package/src/resources/extensions/gsd/tests/verification-gate.test.ts +0 -16
  229. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +67 -0
  230. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +1 -1
  231. package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +204 -0
  232. package/src/resources/extensions/gsd/tools/plan-slice.ts +16 -0
  233. package/src/resources/extensions/gsd/tools/validate-milestone.ts +3 -3
  234. package/src/resources/extensions/gsd/types.ts +30 -0
  235. package/src/resources/extensions/gsd/verdict-parser.ts +95 -0
  236. package/src/resources/extensions/gsd/verification-gate.ts +0 -2
  237. package/src/resources/extensions/gsd/worktree-resolver.ts +31 -0
  238. package/src/resources/extensions/gsd/worktree.ts +3 -2
  239. package/src/resources/extensions/remote-questions/config.ts +3 -5
  240. package/src/resources/extensions/search-the-web/native-search.ts +8 -3
  241. package/src/resources/extensions/search-the-web/tool-search.ts +22 -2
  242. package/src/resources/skills/github-workflows/references/gh/SKILL.md +22 -1
  243. package/dist/resources/extensions/gsd/auto-worktree-sync.js +0 -191
  244. package/dist/resources/extensions/gsd/resource-version.js +0 -97
  245. package/dist/web/standalone/.next/static/chunks/4024.11ca5c01938e5948.js +0 -9
  246. package/src/resources/extensions/gsd/auto-worktree-sync.ts +0 -234
  247. package/src/resources/extensions/gsd/resource-version.ts +0 -101
  248. /package/dist/web/standalone/.next/static/{ceckLbAMjhzHaQ3RPtJnT → MkE9kzqUGny3-cSE0GNnm}/_buildManifest.js +0 -0
  249. /package/dist/web/standalone/.next/static/{ceckLbAMjhzHaQ3RPtJnT → MkE9kzqUGny3-cSE0GNnm}/_ssgManifest.js +0 -0
@@ -0,0 +1,626 @@
1
+ import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync, rmSync, statSync } from "node:fs";
2
+ import { basename, dirname, join } from "node:path";
3
+
4
+ import type { DoctorIssue, DoctorIssueCode } from "./doctor-types.js";
5
+ import { cleanNumberedGsdVariants } from "./repo-identity.js";
6
+ import { milestonesDir, gsdRoot, resolveGsdRootFile } from "./paths.js";
7
+ import { deriveState } from "./state.js";
8
+ import { saveFile } from "./files.js";
9
+ import { nativeIsRepo, nativeForEachRef, nativeUpdateRef } from "./native-git-bridge.js";
10
+ import { readCrashLock, isLockProcessAlive, clearLock } from "./crash-recovery.js";
11
+ import { ensureGitignore } from "./gitignore.js";
12
+ import { readAllSessionStatuses, isSessionStale, removeSessionStatus } from "./session-status-io.js";
13
+ import { recoverFailedMigration } from "./migrate-external.js";
14
+
15
+ export async function checkRuntimeHealth(
16
+ basePath: string,
17
+ issues: DoctorIssue[],
18
+ fixesApplied: string[],
19
+ shouldFix: (code: DoctorIssueCode) => boolean,
20
+ ): Promise<void> {
21
+ const root = gsdRoot(basePath);
22
+
23
+ // ── Stale crash lock ──────────────────────────────────────────────────
24
+ try {
25
+ const lock = readCrashLock(basePath);
26
+ if (lock) {
27
+ const alive = isLockProcessAlive(lock);
28
+ if (!alive) {
29
+ issues.push({
30
+ severity: "error",
31
+ code: "stale_crash_lock",
32
+ scope: "project",
33
+ unitId: "project",
34
+ message: `Stale auto.lock from PID ${lock.pid} (started ${lock.startedAt}, was executing ${lock.unitType} ${lock.unitId}) — process is no longer running`,
35
+ file: ".gsd/auto.lock",
36
+ fixable: true,
37
+ });
38
+
39
+ if (shouldFix("stale_crash_lock")) {
40
+ clearLock(basePath);
41
+ fixesApplied.push("cleared stale auto.lock");
42
+ }
43
+ }
44
+ }
45
+ } catch {
46
+ // Non-fatal — crash lock check failed
47
+ }
48
+
49
+ // ── Stranded lock directory ────────────────────────────────────────────
50
+ // proper-lockfile creates a `.gsd.lock/` directory as the OS-level lock
51
+ // mechanism. If the process was SIGKILLed or crashed hard, this directory
52
+ // can remain on disk without any live process holding it. The next session
53
+ // fails to acquire the lock until the directory is removed (#1245).
54
+ try {
55
+ const lockDir = join(dirname(root), `${basename(root)}.lock`);
56
+ if (existsSync(lockDir)) {
57
+ const statRes = statSync(lockDir);
58
+ if (statRes.isDirectory()) {
59
+ // Check if any live process actually holds this lock
60
+ const lock = readCrashLock(basePath);
61
+ const lockHolderAlive = lock ? isLockProcessAlive(lock) : false;
62
+ if (!lockHolderAlive) {
63
+ issues.push({
64
+ severity: "error",
65
+ code: "stranded_lock_directory",
66
+ scope: "project",
67
+ unitId: "project",
68
+ message: `Stranded lock directory "${lockDir}" exists but no live process holds the session lock. This blocks new auto-mode sessions from starting.`,
69
+ file: lockDir,
70
+ fixable: true,
71
+ });
72
+ if (shouldFix("stranded_lock_directory")) {
73
+ try {
74
+ rmSync(lockDir, { recursive: true, force: true });
75
+ fixesApplied.push(`removed stranded lock directory ${lockDir}`);
76
+ } catch {
77
+ fixesApplied.push(`failed to remove stranded lock directory ${lockDir}`);
78
+ }
79
+ }
80
+ }
81
+ }
82
+ }
83
+ } catch {
84
+ // Non-fatal — stranded lock directory check failed
85
+ }
86
+
87
+ // ── Stale parallel sessions ────────────────────────────────────────────
88
+ try {
89
+ const parallelStatuses = readAllSessionStatuses(basePath);
90
+ for (const status of parallelStatuses) {
91
+ if (isSessionStale(status)) {
92
+ issues.push({
93
+ severity: "warning",
94
+ code: "stale_parallel_session",
95
+ scope: "project",
96
+ unitId: status.milestoneId,
97
+ message: `Stale parallel session for ${status.milestoneId} (PID ${status.pid}, started ${new Date(status.startedAt).toISOString()}, last heartbeat ${new Date(status.lastHeartbeat).toISOString()}) — process is no longer running`,
98
+ file: `.gsd/parallel/${status.milestoneId}.status.json`,
99
+ fixable: true,
100
+ });
101
+
102
+ if (shouldFix("stale_parallel_session")) {
103
+ removeSessionStatus(basePath, status.milestoneId);
104
+ fixesApplied.push(`cleaned up stale parallel session for ${status.milestoneId}`);
105
+ }
106
+ }
107
+ }
108
+ } catch {
109
+ // Non-fatal — parallel session check failed
110
+ }
111
+
112
+ // ── Orphaned completed-units keys ─────────────────────────────────────
113
+ try {
114
+ const completedKeysFile = join(root, "completed-units.json");
115
+ if (existsSync(completedKeysFile)) {
116
+ const raw = readFileSync(completedKeysFile, "utf-8");
117
+ const keys: string[] = JSON.parse(raw);
118
+ const orphaned: string[] = [];
119
+
120
+ for (const key of keys) {
121
+ // Key format: "unitType/unitId" e.g. "execute-task/M001/S01/T01"
122
+ const slashIdx = key.indexOf("/");
123
+ if (slashIdx === -1) continue;
124
+ const unitType = key.slice(0, slashIdx);
125
+ const unitId = key.slice(slashIdx + 1);
126
+
127
+ // Only validate artifact-producing unit types
128
+ const { verifyExpectedArtifact } = await import("./auto-recovery.js");
129
+ if (!verifyExpectedArtifact(unitType, unitId, basePath)) {
130
+ orphaned.push(key);
131
+ }
132
+ }
133
+
134
+ if (orphaned.length > 0) {
135
+ issues.push({
136
+ severity: "warning",
137
+ code: "orphaned_completed_units",
138
+ scope: "project",
139
+ unitId: "project",
140
+ message: `${orphaned.length} completed-unit key(s) reference missing artifacts: ${orphaned.slice(0, 3).join(", ")}${orphaned.length > 3 ? "..." : ""}`,
141
+ file: ".gsd/completed-units.json",
142
+ fixable: true,
143
+ });
144
+
145
+ if (shouldFix("orphaned_completed_units")) {
146
+ const orphanedSet = new Set(orphaned);
147
+ const remaining = keys.filter((key) => !orphanedSet.has(key));
148
+ await saveFile(completedKeysFile, JSON.stringify(remaining));
149
+ fixesApplied.push(`removed ${orphaned.length} orphaned completed-unit key(s)`);
150
+ }
151
+ }
152
+ }
153
+ } catch {
154
+ // Non-fatal — completed-units check failed
155
+ }
156
+
157
+ // ── Stale hook state ──────────────────────────────────────────────────
158
+ try {
159
+ const hookStateFile = join(root, "hook-state.json");
160
+ if (existsSync(hookStateFile)) {
161
+ const raw = readFileSync(hookStateFile, "utf-8");
162
+ const state = JSON.parse(raw);
163
+ const hasCycleCounts = state.cycleCounts && typeof state.cycleCounts === "object"
164
+ && Object.keys(state.cycleCounts).length > 0;
165
+
166
+ // Only flag if there are actual cycle counts AND no auto-mode is running
167
+ if (hasCycleCounts) {
168
+ const lock = readCrashLock(basePath);
169
+ const autoRunning = lock ? isLockProcessAlive(lock) : false;
170
+
171
+ if (!autoRunning) {
172
+ issues.push({
173
+ severity: "info",
174
+ code: "stale_hook_state",
175
+ scope: "project",
176
+ unitId: "project",
177
+ message: `hook-state.json has ${Object.keys(state.cycleCounts).length} residual cycle count(s) from a previous session`,
178
+ file: ".gsd/hook-state.json",
179
+ fixable: true,
180
+ });
181
+
182
+ if (shouldFix("stale_hook_state")) {
183
+ const { clearPersistedHookState } = await import("./post-unit-hooks.js");
184
+ clearPersistedHookState(basePath);
185
+ fixesApplied.push("cleared stale hook-state.json");
186
+ }
187
+ }
188
+ }
189
+ }
190
+ } catch {
191
+ // Non-fatal — hook state check failed
192
+ }
193
+
194
+ // ── Activity log bloat ────────────────────────────────────────────────
195
+ try {
196
+ const activityDir = join(root, "activity");
197
+ if (existsSync(activityDir)) {
198
+ const files = readdirSync(activityDir);
199
+ let totalSize = 0;
200
+ for (const f of files) {
201
+ try {
202
+ totalSize += statSync(join(activityDir, f)).size;
203
+ } catch {
204
+ // stat failed — skip
205
+ }
206
+ }
207
+
208
+ const totalMB = totalSize / (1024 * 1024);
209
+ const BLOAT_FILE_THRESHOLD = 500;
210
+ const BLOAT_SIZE_MB = 100;
211
+
212
+ if (files.length > BLOAT_FILE_THRESHOLD || totalMB > BLOAT_SIZE_MB) {
213
+ issues.push({
214
+ severity: "warning",
215
+ code: "activity_log_bloat",
216
+ scope: "project",
217
+ unitId: "project",
218
+ message: `Activity logs: ${files.length} files, ${totalMB.toFixed(1)}MB (thresholds: ${BLOAT_FILE_THRESHOLD} files / ${BLOAT_SIZE_MB}MB)`,
219
+ file: ".gsd/activity/",
220
+ fixable: true,
221
+ });
222
+
223
+ if (shouldFix("activity_log_bloat")) {
224
+ const { pruneActivityLogs } = await import("./activity-log.js");
225
+ pruneActivityLogs(activityDir, 7); // 7-day retention
226
+ fixesApplied.push("pruned activity logs (7-day retention)");
227
+ }
228
+ }
229
+ }
230
+ } catch {
231
+ // Non-fatal — activity log check failed
232
+ }
233
+
234
+ // ── STATE.md health ───────────────────────────────────────────────────
235
+ try {
236
+ const stateFilePath = resolveGsdRootFile(basePath, "STATE");
237
+ const milestonesPath = milestonesDir(basePath);
238
+
239
+ if (existsSync(milestonesPath)) {
240
+ if (!existsSync(stateFilePath)) {
241
+ issues.push({
242
+ severity: "warning",
243
+ code: "state_file_missing",
244
+ scope: "project",
245
+ unitId: "project",
246
+ message: "STATE.md is missing — state display will not work",
247
+ file: ".gsd/STATE.md",
248
+ fixable: true,
249
+ });
250
+
251
+ if (shouldFix("state_file_missing")) {
252
+ const state = await deriveState(basePath);
253
+ await saveFile(stateFilePath, buildStateMarkdownForCheck(state));
254
+ fixesApplied.push("created STATE.md from derived state");
255
+ }
256
+ } else {
257
+ // Check if STATE.md is stale by comparing active milestone/slice/phase
258
+ const currentContent = readFileSync(stateFilePath, "utf-8");
259
+ const state = await deriveState(basePath);
260
+ const freshContent = buildStateMarkdownForCheck(state);
261
+
262
+ // Extract key fields for comparison — don't compare full content
263
+ // since timestamp/formatting differences are normal
264
+ const extractFields = (content: string) => {
265
+ const milestone = content.match(/\*\*Active Milestone:\*\*\s*(.+)/)?.[1]?.trim() ?? "";
266
+ const slice = content.match(/\*\*Active Slice:\*\*\s*(.+)/)?.[1]?.trim() ?? "";
267
+ const phase = content.match(/\*\*Phase:\*\*\s*(.+)/)?.[1]?.trim() ?? "";
268
+ return { milestone, slice, phase };
269
+ };
270
+
271
+ const current = extractFields(currentContent);
272
+ const fresh = extractFields(freshContent);
273
+
274
+ if (current.milestone !== fresh.milestone || current.slice !== fresh.slice || current.phase !== fresh.phase) {
275
+ issues.push({
276
+ severity: "warning",
277
+ code: "state_file_stale",
278
+ scope: "project",
279
+ unitId: "project",
280
+ message: `STATE.md is stale — shows "${current.phase}" but derived state is "${fresh.phase}"`,
281
+ file: ".gsd/STATE.md",
282
+ fixable: true,
283
+ });
284
+
285
+ if (shouldFix("state_file_stale")) {
286
+ await saveFile(stateFilePath, freshContent);
287
+ fixesApplied.push("rebuilt STATE.md from derived state");
288
+ }
289
+ }
290
+ }
291
+ }
292
+ } catch {
293
+ // Non-fatal — STATE.md check failed
294
+ }
295
+
296
+ // ── Gitignore drift ───────────────────────────────────────────────────
297
+ try {
298
+ const gitignorePath = join(basePath, ".gitignore");
299
+ if (existsSync(gitignorePath) && nativeIsRepo(basePath)) {
300
+ const content = readFileSync(gitignorePath, "utf-8");
301
+ const existingLines = new Set(
302
+ content.split("\n").map(l => l.trim()).filter(l => l && !l.startsWith("#")),
303
+ );
304
+
305
+ // Check for critical runtime patterns that must be present
306
+ const criticalPatterns = [
307
+ ".gsd/activity/",
308
+ ".gsd/runtime/",
309
+ ".gsd/auto.lock",
310
+ ".gsd/gsd.db",
311
+ ".gsd/completed-units.json",
312
+ ];
313
+
314
+ // If blanket .gsd/ or .gsd is present, all patterns are covered
315
+ const hasBlanketIgnore = existingLines.has(".gsd/") || existingLines.has(".gsd");
316
+
317
+ if (!hasBlanketIgnore) {
318
+ const missing = criticalPatterns.filter(p => !existingLines.has(p));
319
+ if (missing.length > 0) {
320
+ issues.push({
321
+ severity: "warning",
322
+ code: "gitignore_missing_patterns",
323
+ scope: "project",
324
+ unitId: "project",
325
+ message: `${missing.length} critical GSD runtime pattern(s) missing from .gitignore: ${missing.join(", ")}`,
326
+ file: ".gitignore",
327
+ fixable: true,
328
+ });
329
+
330
+ if (shouldFix("gitignore_missing_patterns")) {
331
+ ensureGitignore(basePath);
332
+ fixesApplied.push("added missing GSD runtime patterns to .gitignore");
333
+ }
334
+ }
335
+ }
336
+ }
337
+ } catch {
338
+ // Non-fatal — gitignore check failed
339
+ }
340
+
341
+ // ── External state symlink health ──────────────────────────────────────
342
+ try {
343
+ const localGsd = join(basePath, ".gsd");
344
+ if (existsSync(localGsd)) {
345
+ const stat = lstatSync(localGsd);
346
+
347
+ // Check for .gsd.migrating (failed migration)
348
+ const migratingPath = join(basePath, ".gsd.migrating");
349
+ if (existsSync(migratingPath)) {
350
+ issues.push({
351
+ severity: "error",
352
+ code: "failed_migration",
353
+ scope: "project",
354
+ unitId: "project",
355
+ message: "Found .gsd.migrating — a previous external state migration failed. State may be incomplete.",
356
+ file: ".gsd.migrating",
357
+ fixable: true,
358
+ });
359
+
360
+ if (shouldFix("failed_migration")) {
361
+ if (recoverFailedMigration(basePath)) {
362
+ fixesApplied.push("recovered failed migration (.gsd.migrating → .gsd)");
363
+ }
364
+ }
365
+ }
366
+
367
+ // Check symlink target exists
368
+ if (stat.isSymbolicLink()) {
369
+ try {
370
+ realpathSync(localGsd);
371
+ } catch {
372
+ issues.push({
373
+ severity: "error",
374
+ code: "broken_symlink",
375
+ scope: "project",
376
+ unitId: "project",
377
+ message: ".gsd symlink target does not exist. External state directory may have been deleted.",
378
+ file: ".gsd",
379
+ fixable: false,
380
+ });
381
+ }
382
+ }
383
+ }
384
+ } catch {
385
+ // Non-fatal — external state check failed
386
+ }
387
+
388
+ // ── Numbered .gsd collision variants (#2205) ───────────────────────────
389
+ // macOS APFS can create ".gsd 2", ".gsd 3" etc. when a directory blocks
390
+ // symlink creation. These must be removed so the canonical .gsd is used.
391
+ try {
392
+ const variantPattern = /^\.gsd \d+$/;
393
+ const entries = readdirSync(basePath);
394
+ const variants = entries.filter(e => variantPattern.test(e));
395
+ if (variants.length > 0) {
396
+ for (const v of variants) {
397
+ issues.push({
398
+ severity: "warning",
399
+ code: "numbered_gsd_variant",
400
+ scope: "project",
401
+ unitId: "project",
402
+ message: `Found macOS collision variant "${v}" — this can cause GSD state to appear deleted.`,
403
+ file: v,
404
+ fixable: true,
405
+ });
406
+ }
407
+
408
+ if (shouldFix("numbered_gsd_variant")) {
409
+ const removed = cleanNumberedGsdVariants(basePath);
410
+ for (const name of removed) {
411
+ fixesApplied.push(`removed numbered .gsd variant: ${name}`);
412
+ }
413
+ }
414
+ }
415
+ } catch {
416
+ // Non-fatal — variant check failed
417
+ }
418
+
419
+ // ── Metrics ledger integrity ───────────────────────────────────────────
420
+ try {
421
+ const metricsPath = join(root, "metrics.json");
422
+ if (existsSync(metricsPath)) {
423
+ try {
424
+ const raw = readFileSync(metricsPath, "utf-8");
425
+ const ledger = JSON.parse(raw);
426
+ if (ledger.version !== 1 || !Array.isArray(ledger.units)) {
427
+ issues.push({
428
+ severity: "warning",
429
+ code: "metrics_ledger_corrupt",
430
+ scope: "project",
431
+ unitId: "project",
432
+ message: "metrics.json has an unexpected structure (version !== 1 or units is not an array) — metrics data may be unreliable",
433
+ file: ".gsd/metrics.json",
434
+ fixable: false,
435
+ });
436
+ }
437
+ } catch {
438
+ issues.push({
439
+ severity: "warning",
440
+ code: "metrics_ledger_corrupt",
441
+ scope: "project",
442
+ unitId: "project",
443
+ message: "metrics.json is not valid JSON — metrics data may be corrupt",
444
+ file: ".gsd/metrics.json",
445
+ fixable: false,
446
+ });
447
+ }
448
+ }
449
+ } catch {
450
+ // Non-fatal — metrics check failed
451
+ }
452
+
453
+ // ── Metrics ledger bloat ──────────────────────────────────────────────
454
+ // The metrics ledger has no TTL and grows by one entry per completed unit.
455
+ // At 50 units/day a project can accumulate tens of thousands of entries over
456
+ // months of use. Prune to the newest 1500 when the threshold is exceeded.
457
+ try {
458
+ const metricsFilePath = join(root, "metrics.json");
459
+ if (existsSync(metricsFilePath)) {
460
+ try {
461
+ const raw = readFileSync(metricsFilePath, "utf-8");
462
+ const parsed = JSON.parse(raw);
463
+ const BLOAT_UNITS_THRESHOLD = 2000;
464
+ if (parsed.version === 1 && Array.isArray(parsed.units) && parsed.units.length > BLOAT_UNITS_THRESHOLD) {
465
+ const fileSizeMB = (statSync(metricsFilePath).size / (1024 * 1024)).toFixed(1);
466
+ issues.push({
467
+ severity: "warning",
468
+ code: "metrics_ledger_bloat",
469
+ scope: "project",
470
+ unitId: "project",
471
+ message: `metrics.json has ${parsed.units.length} unit entries (${fileSizeMB}MB) — threshold is ${BLOAT_UNITS_THRESHOLD}. Run /gsd doctor --fix to prune to the newest 1500 entries.`,
472
+ file: ".gsd/metrics.json",
473
+ fixable: true,
474
+ });
475
+ if (shouldFix("metrics_ledger_bloat")) {
476
+ const { pruneMetricsLedger } = await import("./metrics.js");
477
+ const removed = pruneMetricsLedger(basePath, 1500);
478
+ fixesApplied.push(`pruned metrics ledger: removed ${removed} oldest entries (${parsed.units.length - removed} remain)`);
479
+ }
480
+ }
481
+ } catch {
482
+ // JSON parse failed — already handled by the integrity check above
483
+ }
484
+ }
485
+ } catch {
486
+ // Non-fatal — metrics bloat check failed
487
+ }
488
+
489
+ // ── Large planning file detection ──────────────────────────────────────
490
+ // Files over 100KB can cause LLM context pressure. Report the worst offenders.
491
+ try {
492
+ const MAX_FILE_BYTES = 100 * 1024; // 100KB
493
+ const milestonesPath = milestonesDir(basePath);
494
+ if (existsSync(milestonesPath)) {
495
+ const largeFiles: Array<{ path: string; sizeKB: number }> = [];
496
+ function scanForLargeFiles(dir: string, depth = 0): void {
497
+ if (depth > 6) return;
498
+ try {
499
+ for (const entry of readdirSync(dir)) {
500
+ const full = join(dir, entry);
501
+ try {
502
+ const s = statSync(full);
503
+ if (s.isDirectory()) { scanForLargeFiles(full, depth + 1); continue; }
504
+ if (entry.endsWith(".md") && s.size > MAX_FILE_BYTES) {
505
+ largeFiles.push({ path: full.replace(basePath + "/", ""), sizeKB: Math.round(s.size / 1024) });
506
+ }
507
+ } catch { /* skip entry */ }
508
+ }
509
+ } catch { /* skip dir */ }
510
+ }
511
+ scanForLargeFiles(milestonesPath);
512
+ if (largeFiles.length > 0) {
513
+ largeFiles.sort((a, b) => b.sizeKB - a.sizeKB);
514
+ const worst = largeFiles[0]!;
515
+ issues.push({
516
+ severity: "warning",
517
+ code: "large_planning_file",
518
+ scope: "project",
519
+ unitId: "project",
520
+ message: `${largeFiles.length} planning file(s) exceed 100KB — largest: ${worst.path} (${worst.sizeKB}KB). Large files cause LLM context pressure.`,
521
+ file: worst.path,
522
+ fixable: false,
523
+ });
524
+ }
525
+ }
526
+ } catch {
527
+ // Non-fatal — large file scan failed
528
+ }
529
+
530
+ // ── Snapshot ref bloat ────────────────────────────────────────────────
531
+ // refs/gsd/snapshots/ accumulate over time. Prune to newest 5 per label
532
+ // when total count exceeds threshold.
533
+ try {
534
+ if (nativeIsRepo(basePath)) {
535
+ const refs = nativeForEachRef(basePath, "refs/gsd/snapshots/");
536
+ if (refs.length > 50) {
537
+ issues.push({
538
+ severity: "warning",
539
+ code: "snapshot_ref_bloat",
540
+ scope: "project",
541
+ unitId: "project",
542
+ message: `${refs.length} snapshot refs found under refs/gsd/snapshots/ — pruning to newest 5 per label will reclaim git storage`,
543
+ fixable: true,
544
+ });
545
+
546
+ if (shouldFix("snapshot_ref_bloat")) {
547
+ const byLabel = new Map<string, string[]>();
548
+ for (const ref of refs) {
549
+ const parts = ref.split("/");
550
+ const label = parts.slice(0, -1).join("/");
551
+ if (!byLabel.has(label)) byLabel.set(label, []);
552
+ byLabel.get(label)!.push(ref);
553
+ }
554
+ let pruned = 0;
555
+ for (const [, labelRefs] of byLabel) {
556
+ const sorted = labelRefs.sort();
557
+ for (const old of sorted.slice(0, -5)) {
558
+ try {
559
+ nativeUpdateRef(basePath, old);
560
+ pruned++;
561
+ } catch { /* skip */ }
562
+ }
563
+ }
564
+ if (pruned > 0) {
565
+ fixesApplied.push(`pruned ${pruned} old snapshot ref(s)`);
566
+ }
567
+ }
568
+ }
569
+ }
570
+ } catch {
571
+ // Non-fatal — snapshot ref check failed
572
+ }
573
+ }
574
+
575
+ /**
576
+ * Build STATE.md markdown content from derived state.
577
+ * Local helper used by checkRuntimeHealth for STATE.md drift detection and repair.
578
+ */
579
+ function buildStateMarkdownForCheck(state: Awaited<ReturnType<typeof deriveState>>): string {
580
+ const lines: string[] = [];
581
+ lines.push("# GSD State", "");
582
+
583
+ const activeMilestone = state.activeMilestone
584
+ ? `${state.activeMilestone.id}: ${state.activeMilestone.title}`
585
+ : "None";
586
+ const activeSlice = state.activeSlice
587
+ ? `${state.activeSlice.id}: ${state.activeSlice.title}`
588
+ : "None";
589
+
590
+ lines.push(`**Active Milestone:** ${activeMilestone}`);
591
+ lines.push(`**Active Slice:** ${activeSlice}`);
592
+ lines.push(`**Phase:** ${state.phase}`);
593
+ if (state.requirements) {
594
+ lines.push(`**Requirements Status:** ${state.requirements.active} active · ${state.requirements.validated} validated · ${state.requirements.deferred} deferred · ${state.requirements.outOfScope} out of scope`);
595
+ }
596
+ lines.push("");
597
+ lines.push("## Milestone Registry");
598
+
599
+ for (const entry of state.registry) {
600
+ const glyph = entry.status === "complete" ? "\u2705" : entry.status === "active" ? "\uD83D\uDD04" : entry.status === "parked" ? "\u23F8\uFE0F" : "\u2B1C";
601
+ lines.push(`- ${glyph} **${entry.id}:** ${entry.title}`);
602
+ }
603
+
604
+ lines.push("");
605
+ lines.push("## Recent Decisions");
606
+ if (state.recentDecisions.length > 0) {
607
+ for (const decision of state.recentDecisions) lines.push(`- ${decision}`);
608
+ } else {
609
+ lines.push("- None recorded");
610
+ }
611
+
612
+ lines.push("");
613
+ lines.push("## Blockers");
614
+ if (state.blockers.length > 0) {
615
+ for (const blocker of state.blockers) lines.push(`- ${blocker}`);
616
+ } else {
617
+ lines.push("- None");
618
+ }
619
+
620
+ lines.push("");
621
+ lines.push("## Next Action");
622
+ lines.push(state.nextAction || "None");
623
+ lines.push("");
624
+
625
+ return lines.join("\n");
626
+ }
@@ -487,7 +487,15 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
487
487
  demo: s.demo,
488
488
  }));
489
489
  } else {
490
- slices = parseLegacyRoadmap(roadmapContent).slices;
490
+ const activeMilestoneId = state.activeMilestone?.id;
491
+ const activeSliceId = state.activeSlice?.id;
492
+ slices = parseLegacyRoadmap(roadmapContent).slices.map(s => ({
493
+ ...s,
494
+ // Legacy roadmaps only encode done vs not-done. For doctor's
495
+ // missing-directory checks, treat every undone slice except the
496
+ // current active slice as effectively pending/unstarted.
497
+ pending: !s.done && (milestoneId !== activeMilestoneId || s.id !== activeSliceId),
498
+ }));
491
499
  }
492
500
  // Wrap in Roadmap-compatible shape for detectCircularDependencies
493
501
  const roadmap = { slices };
@@ -12,7 +12,7 @@
12
12
  "gsd_requirement_update", "gsd_milestone_generate_id"
13
13
  ],
14
14
  "commands": ["gsd", "kill", "worktree", "exit"],
15
- "hooks": ["session_start"],
15
+ "hooks": ["session_start", "session_switch"],
16
16
  "shortcuts": ["Ctrl+Alt+G"]
17
17
  }
18
18
  }