gsd-pi 2.43.0 → 2.44.0-dev.0b97ffd

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 (693) hide show
  1. package/README.md +30 -12
  2. package/dist/cli.js +13 -1
  3. package/dist/help-text.js +24 -0
  4. package/dist/resources/extensions/bg-shell/overlay.js +3 -0
  5. package/dist/resources/extensions/github-sync/sync.js +2 -1
  6. package/dist/resources/extensions/gsd/auto/loop.js +0 -2
  7. package/dist/resources/extensions/gsd/auto/phases.js +7 -12
  8. package/dist/resources/extensions/gsd/auto-dashboard.js +19 -18
  9. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +34 -19
  10. package/dist/resources/extensions/gsd/auto-dispatch.js +36 -21
  11. package/dist/resources/extensions/gsd/auto-post-unit.js +128 -14
  12. package/dist/resources/extensions/gsd/auto-prompts.js +202 -92
  13. package/dist/resources/extensions/gsd/auto-recovery.js +83 -135
  14. package/dist/resources/extensions/gsd/auto-start.js +10 -0
  15. package/dist/resources/extensions/gsd/auto-supervisor.js +14 -0
  16. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +4 -7
  17. package/dist/resources/extensions/gsd/auto-verification.js +5 -10
  18. package/dist/resources/extensions/gsd/auto-worktree.js +123 -30
  19. package/dist/resources/extensions/gsd/auto.js +1 -4
  20. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +611 -0
  21. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +28 -3
  22. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +7 -0
  23. package/dist/resources/extensions/gsd/commands/catalog.js +3 -1
  24. package/dist/resources/extensions/gsd/commands/handlers/ops.js +15 -1
  25. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +5 -0
  26. package/dist/resources/extensions/gsd/commands-handlers.js +1 -1
  27. package/dist/resources/extensions/gsd/commands-maintenance.js +78 -3
  28. package/dist/resources/extensions/gsd/dashboard-overlay.js +32 -31
  29. package/dist/resources/extensions/gsd/db-writer.js +95 -4
  30. package/dist/resources/extensions/gsd/dispatch-guard.js +35 -30
  31. package/dist/resources/extensions/gsd/doctor-checks.js +28 -11
  32. package/dist/resources/extensions/gsd/doctor-environment.js +28 -0
  33. package/dist/resources/extensions/gsd/doctor-types.js +0 -15
  34. package/dist/resources/extensions/gsd/doctor.js +46 -282
  35. package/dist/resources/extensions/gsd/file-watcher.js +5 -1
  36. package/dist/resources/extensions/gsd/files.js +14 -198
  37. package/dist/resources/extensions/gsd/git-service.js +4 -0
  38. package/dist/resources/extensions/gsd/gitignore.js +4 -0
  39. package/dist/resources/extensions/gsd/gsd-db.js +819 -197
  40. package/dist/resources/extensions/gsd/guided-flow.js +18 -8
  41. package/dist/resources/extensions/gsd/markdown-renderer.js +862 -0
  42. package/dist/resources/extensions/gsd/md-importer.js +182 -4
  43. package/dist/resources/extensions/gsd/native-git-bridge.js +10 -1
  44. package/dist/resources/extensions/gsd/parallel-eligibility.js +14 -19
  45. package/dist/resources/extensions/gsd/parallel-orchestrator.js +38 -0
  46. package/dist/resources/extensions/gsd/parsers-legacy.js +239 -0
  47. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  48. package/dist/resources/extensions/gsd/preferences-validation.js +9 -0
  49. package/dist/resources/extensions/gsd/preferences.js +1 -0
  50. package/dist/resources/extensions/gsd/prompts/complete-slice.md +22 -9
  51. package/dist/resources/extensions/gsd/prompts/discuss.md +2 -2
  52. package/dist/resources/extensions/gsd/prompts/execute-task.md +15 -5
  53. package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  54. package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  55. package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  56. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +6 -10
  57. package/dist/resources/extensions/gsd/prompts/plan-slice.md +4 -7
  58. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +3 -3
  59. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -7
  60. package/dist/resources/extensions/gsd/prompts/replan-slice.md +6 -6
  61. package/dist/resources/extensions/gsd/reactive-graph.js +33 -3
  62. package/dist/resources/extensions/gsd/skill-health.js +3 -1
  63. package/dist/resources/extensions/gsd/state.js +484 -30
  64. package/dist/resources/extensions/gsd/tools/complete-milestone.js +128 -0
  65. package/dist/resources/extensions/gsd/tools/complete-slice.js +244 -0
  66. package/dist/resources/extensions/gsd/tools/complete-task.js +204 -0
  67. package/dist/resources/extensions/gsd/tools/plan-milestone.js +205 -0
  68. package/dist/resources/extensions/gsd/tools/plan-slice.js +155 -0
  69. package/dist/resources/extensions/gsd/tools/plan-task.js +94 -0
  70. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +152 -0
  71. package/dist/resources/extensions/gsd/tools/replan-slice.js +146 -0
  72. package/dist/resources/extensions/gsd/triage-resolution.js +17 -1
  73. package/dist/resources/extensions/gsd/undo.js +197 -3
  74. package/dist/resources/extensions/gsd/visualizer-data.js +53 -16
  75. package/dist/resources/extensions/gsd/workspace-index.js +63 -39
  76. package/dist/web/standalone/.next/BUILD_ID +1 -1
  77. package/dist/web/standalone/.next/app-path-routes-manifest.json +18 -17
  78. package/dist/web/standalone/.next/build-manifest.json +4 -4
  79. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  80. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  81. package/dist/web/standalone/.next/required-server-files.json +4 -4
  82. package/dist/web/standalone/.next/routes-manifest.json +6 -0
  83. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  84. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  85. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  86. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  94. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  95. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  96. package/dist/web/standalone/.next/server/app/_not-found.rsc +4 -4
  97. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +4 -4
  98. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +4 -4
  100. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  102. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  103. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  110. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  137. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  143. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +5 -5
  148. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  154. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  155. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  157. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  159. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  161. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  163. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  164. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -0
  166. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -0
  167. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -0
  168. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
  169. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  170. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  171. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  172. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  173. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  175. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  176. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  177. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  178. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  179. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  180. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  181. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  182. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  183. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  184. package/dist/web/standalone/.next/server/app/index.html +1 -1
  185. package/dist/web/standalone/.next/server/app/index.rsc +5 -5
  186. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  187. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +5 -5
  188. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  189. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +4 -4
  190. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  191. package/dist/web/standalone/.next/server/app/page.js +2 -2
  192. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  193. package/dist/web/standalone/.next/server/app-paths-manifest.json +18 -17
  194. package/dist/web/standalone/.next/server/chunks/229.js +3 -3
  195. package/dist/web/standalone/.next/server/chunks/471.js +3 -3
  196. package/dist/web/standalone/.next/server/functions-config-manifest.json +1 -0
  197. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  198. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  199. package/dist/web/standalone/.next/server/middleware.js +2 -2
  200. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  201. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  202. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  203. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  204. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  205. package/dist/web/standalone/.next/static/alS4hoANx0TK4UVZY27da/_buildManifest.js +1 -0
  206. package/dist/web/standalone/.next/static/chunks/{4024.c195dc1fdd2adbea.js → 4024.0de81b543b28b9fe.js} +2 -2
  207. package/dist/web/standalone/.next/static/chunks/app/_global-error/{page-d07a2c023f1aef1e.js → page-d83ba70a25a85472.js} +1 -1
  208. package/dist/web/standalone/.next/static/chunks/app/_not-found/page-f2a7482d42a5614b.js +1 -0
  209. package/dist/web/standalone/.next/static/chunks/app/api/boot/{route-d07a2c023f1aef1e.js → route-d83ba70a25a85472.js} +1 -1
  210. package/dist/web/standalone/.next/static/chunks/app/api/bridge-terminal/input/{route-d07a2c023f1aef1e.js → route-d83ba70a25a85472.js} +1 -1
  211. package/dist/web/standalone/.next/static/chunks/app/api/bridge-terminal/resize/{route-d07a2c023f1aef1e.js → route-d83ba70a25a85472.js} +1 -1
  212. package/dist/web/standalone/.next/static/chunks/app/api/bridge-terminal/stream/route-d83ba70a25a85472.js +1 -0
  213. package/dist/web/standalone/.next/static/chunks/app/api/browse-directories/route-d83ba70a25a85472.js +1 -0
  214. package/dist/web/standalone/.next/static/chunks/app/api/captures/route-d83ba70a25a85472.js +1 -0
  215. package/dist/web/standalone/.next/static/chunks/app/api/cleanup/route-d83ba70a25a85472.js +1 -0
  216. package/dist/web/standalone/.next/static/chunks/app/api/dev-mode/route-d83ba70a25a85472.js +1 -0
  217. package/dist/web/standalone/.next/static/chunks/app/api/doctor/route-d83ba70a25a85472.js +1 -0
  218. package/dist/web/standalone/.next/static/chunks/app/api/export-data/route-d83ba70a25a85472.js +1 -0
  219. package/dist/web/standalone/.next/static/chunks/app/api/files/route-d83ba70a25a85472.js +1 -0
  220. package/dist/web/standalone/.next/static/chunks/app/api/forensics/route-d83ba70a25a85472.js +1 -0
  221. package/dist/web/standalone/.next/static/chunks/app/api/git/route-d83ba70a25a85472.js +1 -0
  222. package/dist/web/standalone/.next/static/chunks/app/api/history/route-d83ba70a25a85472.js +1 -0
  223. package/dist/web/standalone/.next/static/chunks/app/api/hooks/route-d83ba70a25a85472.js +1 -0
  224. package/dist/web/standalone/.next/static/chunks/app/api/inspect/route-d83ba70a25a85472.js +1 -0
  225. package/dist/web/standalone/.next/static/chunks/app/api/knowledge/route-d83ba70a25a85472.js +1 -0
  226. package/dist/web/standalone/.next/static/chunks/app/api/live-state/route-d83ba70a25a85472.js +1 -0
  227. package/dist/web/standalone/.next/static/chunks/app/api/onboarding/route-d83ba70a25a85472.js +1 -0
  228. package/dist/web/standalone/.next/static/chunks/app/api/preferences/route-d83ba70a25a85472.js +1 -0
  229. package/dist/web/standalone/.next/static/chunks/app/api/projects/route-d83ba70a25a85472.js +1 -0
  230. package/dist/web/standalone/.next/static/chunks/app/api/recovery/route-d83ba70a25a85472.js +1 -0
  231. package/dist/web/standalone/.next/static/chunks/app/api/remote-questions/route-d83ba70a25a85472.js +1 -0
  232. package/dist/web/standalone/.next/static/chunks/app/api/session/browser/route-d83ba70a25a85472.js +1 -0
  233. package/dist/web/standalone/.next/static/chunks/app/api/session/command/route-d83ba70a25a85472.js +1 -0
  234. package/dist/web/standalone/.next/static/chunks/app/api/session/events/route-d83ba70a25a85472.js +1 -0
  235. package/dist/web/standalone/.next/static/chunks/app/api/session/manage/route-d83ba70a25a85472.js +1 -0
  236. package/dist/web/standalone/.next/static/chunks/app/api/settings-data/route-d83ba70a25a85472.js +1 -0
  237. package/dist/web/standalone/.next/static/chunks/app/api/shutdown/route-d83ba70a25a85472.js +1 -0
  238. package/dist/web/standalone/.next/static/chunks/app/api/skill-health/route-d83ba70a25a85472.js +1 -0
  239. package/dist/web/standalone/.next/static/chunks/app/api/steer/route-d83ba70a25a85472.js +1 -0
  240. package/dist/web/standalone/.next/static/chunks/app/api/switch-root/route-d83ba70a25a85472.js +1 -0
  241. package/dist/web/standalone/.next/static/chunks/app/api/terminal/input/route-d83ba70a25a85472.js +1 -0
  242. package/dist/web/standalone/.next/static/chunks/app/api/terminal/resize/route-d83ba70a25a85472.js +1 -0
  243. package/dist/web/standalone/.next/static/chunks/app/api/terminal/sessions/route-d83ba70a25a85472.js +1 -0
  244. package/dist/web/standalone/.next/static/chunks/app/api/terminal/stream/route-d83ba70a25a85472.js +1 -0
  245. package/dist/web/standalone/.next/static/chunks/app/api/terminal/upload/route-d83ba70a25a85472.js +1 -0
  246. package/dist/web/standalone/.next/static/chunks/app/api/undo/route-d83ba70a25a85472.js +1 -0
  247. package/dist/web/standalone/.next/static/chunks/app/api/update/route-d83ba70a25a85472.js +1 -0
  248. package/dist/web/standalone/.next/static/chunks/app/api/visualizer/route-d83ba70a25a85472.js +1 -0
  249. package/dist/web/standalone/.next/static/chunks/app/layout-a16c7a7ecdf0c2cf.js +1 -0
  250. package/dist/web/standalone/.next/static/chunks/app/page-b9367c5ae13b99c6.js +1 -0
  251. package/dist/web/standalone/.next/static/chunks/{main-app-2f2ee7b85712c2bd.js → main-app-fdab67f7802d7832.js} +1 -1
  252. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/app-error-d83ba70a25a85472.js +1 -0
  253. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/forbidden-d83ba70a25a85472.js +1 -0
  254. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  255. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/not-found-d83ba70a25a85472.js +1 -0
  256. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/unauthorized-d83ba70a25a85472.js +1 -0
  257. package/dist/web/standalone/.next/static/chunks/{webpack-fa307370fcf9fb2c.js → webpack-9014b5adb127a98a.js} +1 -1
  258. package/dist/web/standalone/.next/static/css/8a727f372cf53002.css +1 -0
  259. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  260. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  261. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  262. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  263. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  264. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  265. package/dist/web/standalone/server.js +1 -1
  266. package/package.json +4 -4
  267. package/packages/pi-ai/dist/models.custom.d.ts +173 -0
  268. package/packages/pi-ai/dist/models.custom.d.ts.map +1 -0
  269. package/packages/pi-ai/dist/models.custom.js +170 -0
  270. package/packages/pi-ai/dist/models.custom.js.map +1 -0
  271. package/packages/pi-ai/dist/models.d.ts.map +1 -1
  272. package/packages/pi-ai/dist/models.js +16 -1
  273. package/packages/pi-ai/dist/models.js.map +1 -1
  274. package/packages/pi-ai/dist/models.test.d.ts +2 -0
  275. package/packages/pi-ai/dist/models.test.d.ts.map +1 -0
  276. package/packages/pi-ai/dist/models.test.js +67 -0
  277. package/packages/pi-ai/dist/models.test.js.map +1 -0
  278. package/packages/pi-ai/src/models.custom.ts +172 -0
  279. package/packages/pi-ai/src/models.test.ts +85 -0
  280. package/packages/pi-ai/src/models.ts +17 -1
  281. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +10 -3
  282. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  283. package/packages/pi-coding-agent/dist/core/agent-session.js +21 -34
  284. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  285. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +6 -8
  286. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  287. package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.d.ts +2 -2
  288. package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  289. package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.js.map +1 -1
  290. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +2 -2
  291. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
  292. package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
  293. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +4 -4
  294. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -1
  295. package/packages/pi-coding-agent/dist/core/extensions/index.d.ts +1 -1
  296. package/packages/pi-coding-agent/dist/core/extensions/index.d.ts.map +1 -1
  297. package/packages/pi-coding-agent/dist/core/extensions/index.js.map +1 -1
  298. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +6 -0
  299. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  300. package/packages/pi-coding-agent/dist/core/extensions/loader.js +80 -0
  301. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  302. package/packages/pi-coding-agent/dist/core/extensions/loader.test.js +63 -0
  303. package/packages/pi-coding-agent/dist/core/extensions/loader.test.js.map +1 -1
  304. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +24 -26
  305. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
  306. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +37 -0
  307. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  308. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  309. package/packages/pi-coding-agent/dist/core/fallback-resolver.d.ts.map +1 -1
  310. package/packages/pi-coding-agent/dist/core/fallback-resolver.js +2 -3
  311. package/packages/pi-coding-agent/dist/core/fallback-resolver.js.map +1 -1
  312. package/packages/pi-coding-agent/dist/core/fallback-resolver.test.js +12 -2
  313. package/packages/pi-coding-agent/dist/core/fallback-resolver.test.js.map +1 -1
  314. package/packages/pi-coding-agent/dist/core/fs-utils.test.js +29 -48
  315. package/packages/pi-coding-agent/dist/core/fs-utils.test.js.map +1 -1
  316. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts +38 -0
  317. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts.map +1 -0
  318. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js +192 -0
  319. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js.map +1 -0
  320. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts +5 -0
  321. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  322. package/packages/pi-coding-agent/dist/core/lsp/client.js +69 -21
  323. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  324. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.d.ts +2 -0
  325. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.d.ts.map +1 -0
  326. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +255 -0
  327. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -0
  328. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +15 -0
  329. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  330. package/packages/pi-coding-agent/dist/core/model-registry.js +40 -3
  331. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  332. package/packages/pi-coding-agent/dist/core/package-commands.d.ts +25 -0
  333. package/packages/pi-coding-agent/dist/core/package-commands.d.ts.map +1 -0
  334. package/packages/pi-coding-agent/dist/core/package-commands.js +253 -0
  335. package/packages/pi-coding-agent/dist/core/package-commands.js.map +1 -0
  336. package/packages/pi-coding-agent/dist/core/package-commands.test.d.ts +2 -0
  337. package/packages/pi-coding-agent/dist/core/package-commands.test.d.ts.map +1 -0
  338. package/packages/pi-coding-agent/dist/core/package-commands.test.js +225 -0
  339. package/packages/pi-coding-agent/dist/core/package-commands.test.js.map +1 -0
  340. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js +34 -44
  341. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js.map +1 -1
  342. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  343. package/packages/pi-coding-agent/dist/core/sdk.js +4 -0
  344. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  345. package/packages/pi-coding-agent/dist/core/session-manager.test.js +30 -34
  346. package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
  347. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +10 -12
  348. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -1
  349. package/packages/pi-coding-agent/dist/index.d.ts +3 -1
  350. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  351. package/packages/pi-coding-agent/dist/index.js +1 -0
  352. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  353. package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
  354. package/packages/pi-coding-agent/dist/main.js +11 -199
  355. package/packages/pi-coding-agent/dist/main.js.map +1 -1
  356. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts +6 -0
  357. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  358. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +21 -0
  359. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  360. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts +1 -1
  361. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  362. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +8 -15
  363. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
  364. package/packages/pi-coding-agent/dist/modes/print-mode.d.ts.map +1 -1
  365. package/packages/pi-coding-agent/dist/modes/print-mode.js +45 -34
  366. package/packages/pi-coding-agent/dist/modes/print-mode.js.map +1 -1
  367. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts +1 -0
  368. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  369. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js +7 -2
  370. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js.map +1 -1
  371. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  372. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +2 -1
  373. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  374. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js +43 -47
  375. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js.map +1 -1
  376. package/packages/pi-coding-agent/package.json +1 -1
  377. package/packages/pi-coding-agent/src/core/agent-session.ts +26 -37
  378. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +7 -7
  379. package/packages/pi-coding-agent/src/core/compaction/branch-summarization.ts +2 -2
  380. package/packages/pi-coding-agent/src/core/compaction/compaction.ts +3 -3
  381. package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +4 -4
  382. package/packages/pi-coding-agent/src/core/extensions/index.ts +5 -0
  383. package/packages/pi-coding-agent/src/core/extensions/loader.test.ts +96 -0
  384. package/packages/pi-coding-agent/src/core/extensions/loader.ts +89 -0
  385. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +26 -26
  386. package/packages/pi-coding-agent/src/core/extensions/types.ts +44 -0
  387. package/packages/pi-coding-agent/src/core/fallback-resolver.test.ts +15 -2
  388. package/packages/pi-coding-agent/src/core/fallback-resolver.ts +2 -3
  389. package/packages/pi-coding-agent/src/core/fs-utils.test.ts +31 -43
  390. package/packages/pi-coding-agent/src/core/lifecycle-hooks.ts +274 -0
  391. package/packages/pi-coding-agent/src/core/lsp/client.ts +83 -21
  392. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +288 -0
  393. package/packages/pi-coding-agent/src/core/model-registry.ts +39 -3
  394. package/packages/pi-coding-agent/src/core/package-commands.test.ts +240 -0
  395. package/packages/pi-coding-agent/src/core/package-commands.ts +310 -0
  396. package/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +40 -45
  397. package/packages/pi-coding-agent/src/core/sdk.ts +4 -0
  398. package/packages/pi-coding-agent/src/core/session-manager.test.ts +33 -33
  399. package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +17 -17
  400. package/packages/pi-coding-agent/src/index.ts +7 -0
  401. package/packages/pi-coding-agent/src/main.ts +11 -232
  402. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +20 -0
  403. package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +9 -16
  404. package/packages/pi-coding-agent/src/modes/print-mode.ts +42 -32
  405. package/packages/pi-coding-agent/src/modes/rpc/rpc-client.ts +8 -2
  406. package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +2 -1
  407. package/packages/pi-coding-agent/src/resources/extensions/memory/storage.test.ts +74 -74
  408. package/pkg/dist/modes/interactive/theme/theme.d.ts +1 -1
  409. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  410. package/pkg/dist/modes/interactive/theme/theme.js +8 -15
  411. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
  412. package/pkg/package.json +1 -1
  413. package/src/resources/extensions/bg-shell/overlay.ts +4 -0
  414. package/src/resources/extensions/github-sync/sync.ts +2 -1
  415. package/src/resources/extensions/gsd/auto/loop-deps.ts +0 -8
  416. package/src/resources/extensions/gsd/auto/loop.ts +0 -2
  417. package/src/resources/extensions/gsd/auto/phases.ts +7 -20
  418. package/src/resources/extensions/gsd/auto/types.ts +0 -1
  419. package/src/resources/extensions/gsd/auto-dashboard.ts +20 -16
  420. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +34 -19
  421. package/src/resources/extensions/gsd/auto-dispatch.ts +38 -21
  422. package/src/resources/extensions/gsd/auto-post-unit.ts +150 -15
  423. package/src/resources/extensions/gsd/auto-prompts.ts +186 -103
  424. package/src/resources/extensions/gsd/auto-recovery.ts +77 -142
  425. package/src/resources/extensions/gsd/auto-start.ts +14 -0
  426. package/src/resources/extensions/gsd/auto-supervisor.ts +14 -0
  427. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +6 -7
  428. package/src/resources/extensions/gsd/auto-verification.ts +4 -9
  429. package/src/resources/extensions/gsd/auto-worktree.ts +126 -30
  430. package/src/resources/extensions/gsd/auto.ts +0 -9
  431. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +675 -4
  432. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +31 -3
  433. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +7 -0
  434. package/src/resources/extensions/gsd/commands/catalog.ts +3 -1
  435. package/src/resources/extensions/gsd/commands/handlers/ops.ts +15 -1
  436. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +8 -0
  437. package/src/resources/extensions/gsd/commands-handlers.ts +1 -1
  438. package/src/resources/extensions/gsd/commands-maintenance.ts +86 -3
  439. package/src/resources/extensions/gsd/dashboard-overlay.ts +17 -13
  440. package/src/resources/extensions/gsd/db-writer.ts +105 -4
  441. package/src/resources/extensions/gsd/dispatch-guard.ts +32 -30
  442. package/src/resources/extensions/gsd/doctor-checks.ts +25 -11
  443. package/src/resources/extensions/gsd/doctor-environment.ts +31 -0
  444. package/src/resources/extensions/gsd/doctor-types.ts +0 -23
  445. package/src/resources/extensions/gsd/doctor.ts +45 -295
  446. package/src/resources/extensions/gsd/file-watcher.ts +4 -1
  447. package/src/resources/extensions/gsd/files.ts +16 -220
  448. package/src/resources/extensions/gsd/git-service.ts +4 -0
  449. package/src/resources/extensions/gsd/gitignore.ts +4 -0
  450. package/src/resources/extensions/gsd/gsd-db.ts +1157 -370
  451. package/src/resources/extensions/gsd/guided-flow.ts +20 -8
  452. package/src/resources/extensions/gsd/markdown-renderer.ts +1098 -0
  453. package/src/resources/extensions/gsd/md-importer.ts +211 -2
  454. package/src/resources/extensions/gsd/native-git-bridge.ts +12 -1
  455. package/src/resources/extensions/gsd/parallel-eligibility.ts +14 -18
  456. package/src/resources/extensions/gsd/parallel-orchestrator.ts +43 -0
  457. package/src/resources/extensions/gsd/parsers-legacy.ts +271 -0
  458. package/src/resources/extensions/gsd/preferences-types.ts +3 -0
  459. package/src/resources/extensions/gsd/preferences-validation.ts +9 -0
  460. package/src/resources/extensions/gsd/preferences.ts +1 -0
  461. package/src/resources/extensions/gsd/prompts/complete-slice.md +22 -9
  462. package/src/resources/extensions/gsd/prompts/discuss.md +2 -2
  463. package/src/resources/extensions/gsd/prompts/execute-task.md +15 -5
  464. package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  465. package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  466. package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  467. package/src/resources/extensions/gsd/prompts/plan-milestone.md +6 -10
  468. package/src/resources/extensions/gsd/prompts/plan-slice.md +4 -7
  469. package/src/resources/extensions/gsd/prompts/reactive-execute.md +3 -3
  470. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -7
  471. package/src/resources/extensions/gsd/prompts/replan-slice.md +6 -6
  472. package/src/resources/extensions/gsd/reactive-graph.ts +33 -3
  473. package/src/resources/extensions/gsd/skill-health.ts +2 -1
  474. package/src/resources/extensions/gsd/state.ts +547 -29
  475. package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +99 -99
  476. package/src/resources/extensions/gsd/tests/atomic-task-closeout.test.ts +8 -120
  477. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +14 -16
  478. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +20 -11
  479. package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +43 -57
  480. package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +11 -13
  481. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +600 -513
  482. package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +73 -75
  483. package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +34 -56
  484. package/src/resources/extensions/gsd/tests/auto-stash-merge.test.ts +121 -0
  485. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +540 -668
  486. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +165 -143
  487. package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +29 -52
  488. package/src/resources/extensions/gsd/tests/captures.test.ts +148 -176
  489. package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +32 -33
  490. package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +141 -143
  491. package/src/resources/extensions/gsd/tests/commands-inspect-open-db.test.ts +25 -25
  492. package/src/resources/extensions/gsd/tests/commands-logs.test.ts +81 -81
  493. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +39 -60
  494. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +375 -0
  495. package/src/resources/extensions/gsd/tests/complete-task.test.ts +387 -0
  496. package/src/resources/extensions/gsd/tests/context-store.test.ts +354 -367
  497. package/src/resources/extensions/gsd/tests/continue-here.test.ts +68 -72
  498. package/src/resources/extensions/gsd/tests/cost-projection.test.ts +92 -106
  499. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +27 -35
  500. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +0 -2
  501. package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +220 -237
  502. package/src/resources/extensions/gsd/tests/db-writer.test.ts +390 -420
  503. package/src/resources/extensions/gsd/tests/definition-loader.test.ts +76 -92
  504. package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +512 -0
  505. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +644 -84
  506. package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +78 -101
  507. package/src/resources/extensions/gsd/tests/derive-state.test.ts +192 -227
  508. package/src/resources/extensions/gsd/tests/detection.test.ts +232 -278
  509. package/src/resources/extensions/gsd/tests/dev-engine-wrapper.test.ts +30 -34
  510. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +192 -161
  511. package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +43 -49
  512. package/src/resources/extensions/gsd/tests/dispatch-uat-last-completed.test.ts +28 -32
  513. package/src/resources/extensions/gsd/tests/doctor-completion-deferral.test.ts +30 -90
  514. package/src/resources/extensions/gsd/tests/doctor-delimiter-fix.test.ts +34 -38
  515. package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +57 -80
  516. package/src/resources/extensions/gsd/tests/doctor-environment-worktree.test.ts +164 -0
  517. package/src/resources/extensions/gsd/tests/doctor-environment.test.ts +72 -97
  518. package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +55 -153
  519. package/src/resources/extensions/gsd/tests/doctor-git.test.ts +104 -145
  520. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +84 -106
  521. package/src/resources/extensions/gsd/tests/doctor-roadmap-summary-atomicity.test.ts +53 -97
  522. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +72 -93
  523. package/src/resources/extensions/gsd/tests/doctor.test.ts +109 -149
  524. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +123 -131
  525. package/src/resources/extensions/gsd/tests/exit-command.test.ts +20 -24
  526. package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +48 -57
  527. package/src/resources/extensions/gsd/tests/files-loadfile-eisdir.test.ts +5 -7
  528. package/src/resources/extensions/gsd/tests/flag-file-db.test.ts +278 -0
  529. package/src/resources/extensions/gsd/tests/freeform-decisions.test.ts +232 -0
  530. package/src/resources/extensions/gsd/tests/git-locale.test.ts +13 -27
  531. package/src/resources/extensions/gsd/tests/git-service.test.ts +291 -390
  532. package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +31 -39
  533. package/src/resources/extensions/gsd/tests/graph-operations.test.ts +63 -69
  534. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +255 -264
  535. package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +108 -119
  536. package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +440 -0
  537. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +229 -262
  538. package/src/resources/extensions/gsd/tests/headless-answers.test.ts +13 -13
  539. package/src/resources/extensions/gsd/tests/health-widget.test.ts +29 -37
  540. package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +81 -270
  541. package/src/resources/extensions/gsd/tests/init-wizard.test.ts +16 -18
  542. package/src/resources/extensions/gsd/tests/integration-edge.test.ts +41 -46
  543. package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +42 -53
  544. package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +75 -91
  545. package/src/resources/extensions/gsd/tests/integration-proof.test.ts +643 -0
  546. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +0 -3
  547. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +1161 -0
  548. package/src/resources/extensions/gsd/tests/md-importer.test.ts +101 -125
  549. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +45 -54
  550. package/src/resources/extensions/gsd/tests/memory-store.test.ts +81 -94
  551. package/src/resources/extensions/gsd/tests/migrate-command.test.ts +57 -66
  552. package/src/resources/extensions/gsd/tests/migrate-hierarchy.test.ts +429 -0
  553. package/src/resources/extensions/gsd/tests/migrate-parser.test.ts +161 -170
  554. package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +125 -141
  555. package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +107 -131
  556. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +89 -97
  557. package/src/resources/extensions/gsd/tests/migrate-writer.test.ts +127 -164
  558. package/src/resources/extensions/gsd/tests/must-have-parser.test.ts +81 -94
  559. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +35 -36
  560. package/src/resources/extensions/gsd/tests/overrides.test.ts +99 -106
  561. package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +40 -47
  562. package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +25 -28
  563. package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +66 -83
  564. package/src/resources/extensions/gsd/tests/park-edge-cases.test.ts +54 -77
  565. package/src/resources/extensions/gsd/tests/park-milestone.test.ts +68 -115
  566. package/src/resources/extensions/gsd/tests/parsers.test.ts +548 -612
  567. package/src/resources/extensions/gsd/tests/paths.test.ts +72 -87
  568. package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +176 -113
  569. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +7 -0
  570. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +179 -0
  571. package/src/resources/extensions/gsd/tests/plan-task.test.ts +145 -0
  572. package/src/resources/extensions/gsd/tests/planning-crossval.test.ts +305 -0
  573. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +77 -117
  574. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +139 -0
  575. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +56 -56
  576. package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +93 -119
  577. package/src/resources/extensions/gsd/tests/queue-order.test.ts +70 -82
  578. package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +42 -55
  579. package/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts +100 -0
  580. package/src/resources/extensions/gsd/tests/quick-branch-lifecycle.test.ts +45 -73
  581. package/src/resources/extensions/gsd/tests/reassess-handler.test.ts +325 -0
  582. package/src/resources/extensions/gsd/tests/reassess-prompt.test.ts +28 -38
  583. package/src/resources/extensions/gsd/tests/replan-handler.test.ts +410 -0
  584. package/src/resources/extensions/gsd/tests/replan-slice.test.ts +73 -80
  585. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +71 -74
  586. package/src/resources/extensions/gsd/tests/requirements.test.ts +70 -75
  587. package/src/resources/extensions/gsd/tests/retry-state-reset.test.ts +44 -66
  588. package/src/resources/extensions/gsd/tests/roadmap-parse-regression.test.ts +114 -181
  589. package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +1 -1
  590. package/src/resources/extensions/gsd/tests/rogue-file-detection.test.ts +296 -0
  591. package/src/resources/extensions/gsd/tests/rule-registry.test.ts +63 -65
  592. package/src/resources/extensions/gsd/tests/run-uat.test.ts +66 -128
  593. package/src/resources/extensions/gsd/tests/schema-v9-sequence.test.ts +176 -0
  594. package/src/resources/extensions/gsd/tests/session-lock-multipath.test.ts +18 -25
  595. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +37 -44
  596. package/src/resources/extensions/gsd/tests/shared-wal.test.ts +209 -0
  597. package/src/resources/extensions/gsd/tests/sqlite-unavailable-gate.test.ts +63 -0
  598. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +6 -8
  599. package/src/resources/extensions/gsd/tests/symlink-numbered-variants.test.ts +22 -28
  600. package/src/resources/extensions/gsd/tests/token-cost-display.test.ts +118 -0
  601. package/src/resources/extensions/gsd/tests/token-savings.test.ts +54 -56
  602. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +23 -25
  603. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +19 -13
  604. package/src/resources/extensions/gsd/tests/undo.test.ts +321 -1
  605. package/src/resources/extensions/gsd/tests/unique-milestone-ids.test.ts +66 -82
  606. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +46 -47
  607. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +1 -1
  608. package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +0 -142
  609. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -22
  610. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +84 -86
  611. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +41 -43
  612. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +94 -96
  613. package/src/resources/extensions/gsd/tests/windows-path-normalization.test.ts +11 -13
  614. package/src/resources/extensions/gsd/tests/worker-registry.test.ts +27 -29
  615. package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +50 -52
  616. package/src/resources/extensions/gsd/tests/worktree-bugfix.test.ts +10 -13
  617. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +14 -18
  618. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +38 -39
  619. package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +17 -21
  620. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +12 -5
  621. package/src/resources/extensions/gsd/tests/worktree-health.test.ts +25 -30
  622. package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +30 -37
  623. package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +15 -22
  624. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +59 -66
  625. package/src/resources/extensions/gsd/tests/worktree.test.ts +44 -50
  626. package/src/resources/extensions/gsd/tools/complete-milestone.ts +176 -0
  627. package/src/resources/extensions/gsd/tools/complete-slice.ts +300 -0
  628. package/src/resources/extensions/gsd/tools/complete-task.ts +245 -0
  629. package/src/resources/extensions/gsd/tools/plan-milestone.ts +249 -0
  630. package/src/resources/extensions/gsd/tools/plan-slice.ts +194 -0
  631. package/src/resources/extensions/gsd/tools/plan-task.ts +116 -0
  632. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +203 -0
  633. package/src/resources/extensions/gsd/tools/replan-slice.ts +192 -0
  634. package/src/resources/extensions/gsd/triage-resolution.ts +20 -1
  635. package/src/resources/extensions/gsd/types.ts +50 -0
  636. package/src/resources/extensions/gsd/undo.ts +247 -3
  637. package/src/resources/extensions/gsd/visualizer-data.ts +54 -17
  638. package/src/resources/extensions/gsd/workspace-index.ts +64 -46
  639. package/dist/resources/extensions/gsd/auto-observability.js +0 -56
  640. package/dist/resources/extensions/gsd/observability-validator.js +0 -422
  641. package/dist/resources/extensions/gsd/roadmap-mutations.js +0 -110
  642. package/dist/web/standalone/.next/static/VvclDCW6TAWjEyLU-EYL1/_buildManifest.js +0 -1
  643. package/dist/web/standalone/.next/static/chunks/app/_not-found/page-e07acdb7dd069836.js +0 -1
  644. package/dist/web/standalone/.next/static/chunks/app/api/bridge-terminal/stream/route-d07a2c023f1aef1e.js +0 -1
  645. package/dist/web/standalone/.next/static/chunks/app/api/browse-directories/route-d07a2c023f1aef1e.js +0 -1
  646. package/dist/web/standalone/.next/static/chunks/app/api/captures/route-d07a2c023f1aef1e.js +0 -1
  647. package/dist/web/standalone/.next/static/chunks/app/api/cleanup/route-d07a2c023f1aef1e.js +0 -1
  648. package/dist/web/standalone/.next/static/chunks/app/api/dev-mode/route-d07a2c023f1aef1e.js +0 -1
  649. package/dist/web/standalone/.next/static/chunks/app/api/doctor/route-d07a2c023f1aef1e.js +0 -1
  650. package/dist/web/standalone/.next/static/chunks/app/api/export-data/route-d07a2c023f1aef1e.js +0 -1
  651. package/dist/web/standalone/.next/static/chunks/app/api/files/route-d07a2c023f1aef1e.js +0 -1
  652. package/dist/web/standalone/.next/static/chunks/app/api/forensics/route-d07a2c023f1aef1e.js +0 -1
  653. package/dist/web/standalone/.next/static/chunks/app/api/git/route-d07a2c023f1aef1e.js +0 -1
  654. package/dist/web/standalone/.next/static/chunks/app/api/history/route-d07a2c023f1aef1e.js +0 -1
  655. package/dist/web/standalone/.next/static/chunks/app/api/hooks/route-d07a2c023f1aef1e.js +0 -1
  656. package/dist/web/standalone/.next/static/chunks/app/api/inspect/route-d07a2c023f1aef1e.js +0 -1
  657. package/dist/web/standalone/.next/static/chunks/app/api/knowledge/route-d07a2c023f1aef1e.js +0 -1
  658. package/dist/web/standalone/.next/static/chunks/app/api/live-state/route-d07a2c023f1aef1e.js +0 -1
  659. package/dist/web/standalone/.next/static/chunks/app/api/onboarding/route-d07a2c023f1aef1e.js +0 -1
  660. package/dist/web/standalone/.next/static/chunks/app/api/preferences/route-d07a2c023f1aef1e.js +0 -1
  661. package/dist/web/standalone/.next/static/chunks/app/api/projects/route-d07a2c023f1aef1e.js +0 -1
  662. package/dist/web/standalone/.next/static/chunks/app/api/recovery/route-d07a2c023f1aef1e.js +0 -1
  663. package/dist/web/standalone/.next/static/chunks/app/api/remote-questions/route-d07a2c023f1aef1e.js +0 -1
  664. package/dist/web/standalone/.next/static/chunks/app/api/session/browser/route-d07a2c023f1aef1e.js +0 -1
  665. package/dist/web/standalone/.next/static/chunks/app/api/session/command/route-d07a2c023f1aef1e.js +0 -1
  666. package/dist/web/standalone/.next/static/chunks/app/api/session/events/route-d07a2c023f1aef1e.js +0 -1
  667. package/dist/web/standalone/.next/static/chunks/app/api/session/manage/route-d07a2c023f1aef1e.js +0 -1
  668. package/dist/web/standalone/.next/static/chunks/app/api/settings-data/route-d07a2c023f1aef1e.js +0 -1
  669. package/dist/web/standalone/.next/static/chunks/app/api/shutdown/route-d07a2c023f1aef1e.js +0 -1
  670. package/dist/web/standalone/.next/static/chunks/app/api/skill-health/route-d07a2c023f1aef1e.js +0 -1
  671. package/dist/web/standalone/.next/static/chunks/app/api/steer/route-d07a2c023f1aef1e.js +0 -1
  672. package/dist/web/standalone/.next/static/chunks/app/api/terminal/input/route-d07a2c023f1aef1e.js +0 -1
  673. package/dist/web/standalone/.next/static/chunks/app/api/terminal/resize/route-d07a2c023f1aef1e.js +0 -1
  674. package/dist/web/standalone/.next/static/chunks/app/api/terminal/sessions/route-d07a2c023f1aef1e.js +0 -1
  675. package/dist/web/standalone/.next/static/chunks/app/api/terminal/stream/route-d07a2c023f1aef1e.js +0 -1
  676. package/dist/web/standalone/.next/static/chunks/app/api/terminal/upload/route-d07a2c023f1aef1e.js +0 -1
  677. package/dist/web/standalone/.next/static/chunks/app/api/undo/route-d07a2c023f1aef1e.js +0 -1
  678. package/dist/web/standalone/.next/static/chunks/app/api/update/route-d07a2c023f1aef1e.js +0 -1
  679. package/dist/web/standalone/.next/static/chunks/app/api/visualizer/route-d07a2c023f1aef1e.js +0 -1
  680. package/dist/web/standalone/.next/static/chunks/app/layout-745c6ed5fea5fb06.js +0 -1
  681. package/dist/web/standalone/.next/static/chunks/app/page-801b53eff6e83579.js +0 -1
  682. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/app-error-d07a2c023f1aef1e.js +0 -1
  683. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/forbidden-d07a2c023f1aef1e.js +0 -1
  684. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-e6255954dccfcf0a.js +0 -1
  685. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/not-found-d07a2c023f1aef1e.js +0 -1
  686. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/unauthorized-d07a2c023f1aef1e.js +0 -1
  687. package/dist/web/standalone/.next/static/css/123c0bb039697968.css +0 -1
  688. package/src/resources/extensions/gsd/auto-observability.ts +0 -74
  689. package/src/resources/extensions/gsd/observability-validator.ts +0 -456
  690. package/src/resources/extensions/gsd/roadmap-mutations.ts +0 -134
  691. package/src/resources/extensions/gsd/tests/doctor-task-done-missing-summary-slice-loop.test.ts +0 -174
  692. package/src/resources/extensions/gsd/tests/plan-quality-validator.test.ts +0 -474
  693. /package/dist/web/standalone/.next/static/{VvclDCW6TAWjEyLU-EYL1 → alS4hoANx0TK4UVZY27da}/_ssgManifest.js +0 -0
@@ -0,0 +1,1161 @@
1
+ import * as path from 'node:path';
2
+ import * as os from 'node:os';
3
+ import * as fs from 'node:fs';
4
+ import {
5
+ openDatabase,
6
+ closeDatabase,
7
+ insertMilestone,
8
+ insertSlice,
9
+ insertTask,
10
+ insertArtifact,
11
+ getArtifact,
12
+ getAllMilestones,
13
+ getMilestoneSlices,
14
+ getSliceTasks,
15
+ updateSliceStatus,
16
+ _getAdapter,
17
+ } from '../gsd-db.ts';
18
+ import {
19
+ renderRoadmapCheckboxes,
20
+ renderPlanCheckboxes,
21
+ renderTaskSummary,
22
+ renderSliceSummary,
23
+ renderAllFromDb,
24
+ renderPlanFromDb,
25
+ renderTaskPlanFromDb,
26
+ detectStaleRenders,
27
+ repairStaleRenders,
28
+ } from '../markdown-renderer.ts';
29
+ import {
30
+ parseRoadmap,
31
+ parsePlan,
32
+ } from '../parsers-legacy.ts';
33
+ import {
34
+ parseSummary,
35
+ parseTaskPlanFile,
36
+ clearParseCache,
37
+ } from '../files.ts';
38
+ import { clearPathCache, _clearGsdRootCache } from '../paths.ts';
39
+ import { invalidateStateCache } from '../state.ts';
40
+ import { describe, test, beforeEach, afterEach } from 'node:test';
41
+ import assert from 'node:assert/strict';
42
+
43
+ // ═══════════════════════════════════════════════════════════════════════════
44
+ // Helpers
45
+ // ═══════════════════════════════════════════════════════════════════════════
46
+
47
+ function makeTmpDir(): string {
48
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-renderer-'));
49
+ fs.mkdirSync(path.join(dir, '.gsd'), { recursive: true });
50
+ return dir;
51
+ }
52
+
53
+ function cleanupDir(dir: string): void {
54
+ try {
55
+ fs.rmSync(dir, { recursive: true, force: true });
56
+ } catch { /* swallow */ }
57
+ }
58
+
59
+ function clearAllCaches(): void {
60
+ clearParseCache();
61
+ clearPathCache();
62
+ _clearGsdRootCache();
63
+ invalidateStateCache();
64
+ }
65
+
66
+ /**
67
+ * Create on-disk directory structure for a milestone/slice/task tree
68
+ * so that path resolvers work correctly.
69
+ */
70
+ function scaffoldDirs(tmpDir: string, mid: string, sliceIds: string[]): void {
71
+ const msDir = path.join(tmpDir, '.gsd', 'milestones', mid);
72
+ fs.mkdirSync(msDir, { recursive: true });
73
+
74
+ for (const sid of sliceIds) {
75
+ const sliceDir = path.join(msDir, 'slices', sid);
76
+ fs.mkdirSync(path.join(sliceDir, 'tasks'), { recursive: true });
77
+ }
78
+ }
79
+
80
+ // ─── Fixture: Roadmap Template ────────────────────────────────────────────
81
+
82
+ function makeRoadmapContent(slices: Array<{ id: string; title: string; done: boolean }>): string {
83
+ const lines: string[] = [];
84
+ lines.push('# M001 Roadmap');
85
+ lines.push('');
86
+ lines.push('**Vision:** Test milestone');
87
+ lines.push('');
88
+ lines.push('## Slices');
89
+ lines.push('');
90
+ for (const s of slices) {
91
+ const checkbox = s.done ? '[x]' : '[ ]';
92
+ lines.push(`- ${checkbox} **${s.id}: ${s.title}** \`risk:medium\` \`depends:[]\``);
93
+ }
94
+ lines.push('');
95
+ return lines.join('\n');
96
+ }
97
+
98
+ // ─── Fixture: Plan Template ───────────────────────────────────────────────
99
+
100
+ function makePlanContent(
101
+ sliceId: string,
102
+ tasks: Array<{ id: string; title: string; done: boolean }>,
103
+ ): string {
104
+ const lines: string[] = [];
105
+ lines.push(`# ${sliceId}: Test Slice`);
106
+ lines.push('');
107
+ lines.push('**Goal:** Test slice goal');
108
+ lines.push('**Demo:** Test demo');
109
+ lines.push('');
110
+ lines.push('## Must-Haves');
111
+ lines.push('');
112
+ lines.push('- Everything works');
113
+ lines.push('');
114
+ lines.push('## Tasks');
115
+ lines.push('');
116
+ for (const t of tasks) {
117
+ const checkbox = t.done ? '[x]' : '[ ]';
118
+ lines.push(`- ${checkbox} **${t.id}: ${t.title}** \`est:1h\``);
119
+ }
120
+ lines.push('');
121
+ return lines.join('\n');
122
+ }
123
+
124
+ // ─── Fixture: Task Summary Template ───────────────────────────────────────
125
+
126
+ function makeTaskSummaryContent(taskId: string): string {
127
+ return [
128
+ '---',
129
+ `id: ${taskId}`,
130
+ 'parent: S01',
131
+ 'milestone: M001',
132
+ 'duration: 45m',
133
+ 'verification_result: all-pass',
134
+ `completed_at: ${new Date().toISOString()}`,
135
+ 'blocker_discovered: false',
136
+ 'provides: []',
137
+ 'requires: []',
138
+ 'affects: []',
139
+ 'key_files:',
140
+ ' - src/test.ts',
141
+ 'key_decisions: []',
142
+ 'patterns_established: []',
143
+ 'drill_down_paths: []',
144
+ 'observability_surfaces: []',
145
+ '---',
146
+ '',
147
+ `# ${taskId}: Test Task Summary`,
148
+ '',
149
+ '**Implemented test functionality**',
150
+ '',
151
+ '## What Happened',
152
+ '',
153
+ 'Built the test feature.',
154
+ '',
155
+ '## Deviations',
156
+ '',
157
+ 'None.',
158
+ '',
159
+ '## Files Created/Modified',
160
+ '',
161
+ '- `src/test.ts` — main implementation',
162
+ '',
163
+ '## Verification Evidence',
164
+ '',
165
+ '| Command | Exit | Verdict | Duration |',
166
+ '|---------|------|---------|----------|',
167
+ '| `npm test` | 0 | ✅ pass | 2.1s |',
168
+ '',
169
+ ].join('\n');
170
+ }
171
+
172
+ // ═══════════════════════════════════════════════════════════════════════════
173
+ // DB Accessor Tests
174
+ // ═══════════════════════════════════════════════════════════════════════════
175
+
176
+ test('── markdown-renderer: DB accessor basics ──', () => {
177
+ openDatabase(':memory:');
178
+
179
+ // getAllMilestones — empty
180
+ const empty = getAllMilestones();
181
+ assert.deepStrictEqual(empty.length, 0, 'getAllMilestones returns empty when no milestones');
182
+
183
+ // Insert and retrieve
184
+ insertMilestone({ id: 'M001', title: 'Test MS', status: 'active' });
185
+ insertMilestone({ id: 'M002', title: 'Second MS', status: 'active' });
186
+
187
+ const all = getAllMilestones();
188
+ assert.deepStrictEqual(all.length, 2, 'getAllMilestones returns 2 milestones');
189
+ assert.deepStrictEqual(all[0].id, 'M001', 'first milestone is M001');
190
+ assert.deepStrictEqual(all[1].id, 'M002', 'second milestone is M002');
191
+ assert.deepStrictEqual(all[0].title, 'Test MS', 'milestone title correct');
192
+ assert.deepStrictEqual(all[0].status, 'active', 'milestone status correct');
193
+
194
+ // getMilestoneSlices — empty
195
+ const noSlices = getMilestoneSlices('M001');
196
+ assert.deepStrictEqual(noSlices.length, 0, 'getMilestoneSlices returns empty when no slices');
197
+
198
+ // Insert slices and retrieve
199
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Slice 1', status: 'complete' });
200
+ insertSlice({ id: 'S02', milestoneId: 'M001', title: 'Slice 2', status: 'pending' });
201
+ insertSlice({ id: 'S01', milestoneId: 'M002', title: 'M2 Slice', status: 'pending' });
202
+
203
+ const m1Slices = getMilestoneSlices('M001');
204
+ assert.deepStrictEqual(m1Slices.length, 2, 'M001 has 2 slices');
205
+ assert.deepStrictEqual(m1Slices[0].id, 'S01', 'first slice is S01');
206
+ assert.deepStrictEqual(m1Slices[0].status, 'complete', 'S01 status is complete');
207
+ assert.deepStrictEqual(m1Slices[1].id, 'S02', 'second slice is S02');
208
+ assert.deepStrictEqual(m1Slices[1].status, 'pending', 'S02 status is pending');
209
+
210
+ const m2Slices = getMilestoneSlices('M002');
211
+ assert.deepStrictEqual(m2Slices.length, 1, 'M002 has 1 slice');
212
+
213
+ closeDatabase();
214
+ });
215
+
216
+ test('── markdown-renderer: getArtifact accessor ──', () => {
217
+ openDatabase(':memory:');
218
+
219
+ // Not found
220
+ const missing = getArtifact('nonexistent/path');
221
+ assert.deepStrictEqual(missing, null, 'getArtifact returns null for missing path');
222
+
223
+ // Insert and retrieve
224
+ insertArtifact({
225
+ path: 'milestones/M001/M001-ROADMAP.md',
226
+ artifact_type: 'ROADMAP',
227
+ milestone_id: 'M001',
228
+ slice_id: null,
229
+ task_id: null,
230
+ full_content: '# Roadmap content',
231
+ });
232
+
233
+ const found = getArtifact('milestones/M001/M001-ROADMAP.md');
234
+ assert.ok(found !== null, 'getArtifact returns non-null for existing path');
235
+ assert.deepStrictEqual(found!.artifact_type, 'ROADMAP', 'artifact type correct');
236
+ assert.deepStrictEqual(found!.milestone_id, 'M001', 'milestone_id correct');
237
+ assert.deepStrictEqual(found!.full_content, '# Roadmap content', 'content correct');
238
+
239
+ closeDatabase();
240
+ });
241
+
242
+ // ═══════════════════════════════════════════════════════════════════════════
243
+ // Roadmap Checkbox Round-Trip
244
+ // ═══════════════════════════════════════════════════════════════════════════
245
+
246
+ test('── markdown-renderer: renderRoadmapCheckboxes round-trip ──', async () => {
247
+ const tmpDir = makeTmpDir();
248
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
249
+ openDatabase(dbPath);
250
+ clearAllCaches();
251
+
252
+ try {
253
+ scaffoldDirs(tmpDir, 'M001', ['S01', 'S02']);
254
+
255
+ // Seed DB with milestone and slices
256
+ insertMilestone({ id: 'M001', title: 'Test', status: 'active' });
257
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Core setup', status: 'complete' });
258
+ insertSlice({ id: 'S02', milestoneId: 'M001', title: 'Rendering', status: 'pending' });
259
+
260
+ // Write a roadmap file on disk with BOTH slices unchecked
261
+ const roadmapContent = makeRoadmapContent([
262
+ { id: 'S01', title: 'Core setup', done: false },
263
+ { id: 'S02', title: 'Rendering', done: false },
264
+ ]);
265
+ const roadmapPath = path.join(tmpDir, '.gsd', 'milestones', 'M001', 'M001-ROADMAP.md');
266
+ fs.writeFileSync(roadmapPath, roadmapContent);
267
+ clearAllCaches();
268
+
269
+ // Render — should set S01 [x] and leave S02 [ ]
270
+ const ok = await renderRoadmapCheckboxes(tmpDir, 'M001');
271
+ assert.ok(ok, 'renderRoadmapCheckboxes returns true');
272
+
273
+ // Read rendered file and parse
274
+ const rendered = fs.readFileSync(roadmapPath, 'utf-8');
275
+ clearAllCaches();
276
+ const parsed = parseRoadmap(rendered);
277
+
278
+ assert.deepStrictEqual(parsed.slices.length, 2, 'roadmap has 2 slices after render');
279
+
280
+ const s01 = parsed.slices.find(s => s.id === 'S01');
281
+ const s02 = parsed.slices.find(s => s.id === 'S02');
282
+ assert.ok(!!s01, 'S01 found in parsed roadmap');
283
+ assert.ok(!!s02, 'S02 found in parsed roadmap');
284
+ assert.ok(s01!.done, 'S01 is checked (done) after render');
285
+ assert.ok(!s02!.done, 'S02 is unchecked (pending) after render');
286
+
287
+ // Verify artifact stored in DB
288
+ const artifact = getArtifact('milestones/M001/M001-ROADMAP.md');
289
+ assert.ok(artifact !== null, 'roadmap artifact stored in DB after render');
290
+ assert.ok(artifact!.full_content.includes('[x] **S01:'), 'DB artifact has S01 checked');
291
+ assert.ok(artifact!.full_content.includes('[ ] **S02:'), 'DB artifact has S02 unchecked');
292
+ } finally {
293
+ closeDatabase();
294
+ cleanupDir(tmpDir);
295
+ }
296
+ });
297
+
298
+ test('── markdown-renderer: renderRoadmapCheckboxes bidirectional ──', async () => {
299
+ const tmpDir = makeTmpDir();
300
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
301
+ openDatabase(dbPath);
302
+ clearAllCaches();
303
+
304
+ try {
305
+ scaffoldDirs(tmpDir, 'M001', ['S01', 'S02']);
306
+
307
+ insertMilestone({ id: 'M001', title: 'Test', status: 'active' });
308
+ // S01 is PENDING in DB, but checked on disk — should be unchecked
309
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Core setup', status: 'pending' });
310
+ insertSlice({ id: 'S02', milestoneId: 'M001', title: 'Rendering', status: 'complete' });
311
+
312
+ // Write roadmap with S01 checked and S02 unchecked (opposite of DB state)
313
+ const roadmapContent = makeRoadmapContent([
314
+ { id: 'S01', title: 'Core setup', done: true },
315
+ { id: 'S02', title: 'Rendering', done: false },
316
+ ]);
317
+ const roadmapPath = path.join(tmpDir, '.gsd', 'milestones', 'M001', 'M001-ROADMAP.md');
318
+ fs.writeFileSync(roadmapPath, roadmapContent);
319
+ clearAllCaches();
320
+
321
+ const ok = await renderRoadmapCheckboxes(tmpDir, 'M001');
322
+ assert.ok(ok, 'bidirectional render returns true');
323
+
324
+ const rendered = fs.readFileSync(roadmapPath, 'utf-8');
325
+ clearAllCaches();
326
+ const parsed = parseRoadmap(rendered);
327
+
328
+ const s01 = parsed.slices.find(s => s.id === 'S01');
329
+ const s02 = parsed.slices.find(s => s.id === 'S02');
330
+ assert.ok(!s01!.done, 'S01 unchecked (DB says pending, was checked on disk)');
331
+ assert.ok(s02!.done, 'S02 checked (DB says complete, was unchecked on disk)');
332
+ } finally {
333
+ closeDatabase();
334
+ cleanupDir(tmpDir);
335
+ }
336
+ });
337
+
338
+ // ═══════════════════════════════════════════════════════════════════════════
339
+ // Plan Checkbox Round-Trip
340
+ // ═══════════════════════════════════════════════════════════════════════════
341
+
342
+ test('── markdown-renderer: renderPlanCheckboxes round-trip ──', async () => {
343
+ const tmpDir = makeTmpDir();
344
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
345
+ openDatabase(dbPath);
346
+ clearAllCaches();
347
+
348
+ try {
349
+ scaffoldDirs(tmpDir, 'M001', ['S01']);
350
+
351
+ insertMilestone({ id: 'M001', title: 'Test', status: 'active' });
352
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Slice', status: 'pending' });
353
+ insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', title: 'First task', status: 'done' });
354
+ insertTask({ id: 'T02', sliceId: 'S01', milestoneId: 'M001', title: 'Second task', status: 'done' });
355
+ insertTask({ id: 'T03', sliceId: 'S01', milestoneId: 'M001', title: 'Third task', status: 'pending' });
356
+
357
+ // Write plan with all tasks unchecked
358
+ const planContent = makePlanContent('S01', [
359
+ { id: 'T01', title: 'First task', done: false },
360
+ { id: 'T02', title: 'Second task', done: false },
361
+ { id: 'T03', title: 'Third task', done: false },
362
+ ]);
363
+ const planPath = path.join(tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'S01-PLAN.md');
364
+ fs.writeFileSync(planPath, planContent);
365
+ clearAllCaches();
366
+
367
+ const ok = await renderPlanCheckboxes(tmpDir, 'M001', 'S01');
368
+ assert.ok(ok, 'renderPlanCheckboxes returns true');
369
+
370
+ const rendered = fs.readFileSync(planPath, 'utf-8');
371
+ clearAllCaches();
372
+ const parsed = parsePlan(rendered);
373
+
374
+ assert.deepStrictEqual(parsed.tasks.length, 3, 'plan has 3 tasks after render');
375
+
376
+ const t01 = parsed.tasks.find(t => t.id === 'T01');
377
+ const t02 = parsed.tasks.find(t => t.id === 'T02');
378
+ const t03 = parsed.tasks.find(t => t.id === 'T03');
379
+ assert.ok(t01!.done, 'T01 checked (done in DB)');
380
+ assert.ok(t02!.done, 'T02 checked (done in DB)');
381
+ assert.ok(!t03!.done, 'T03 unchecked (pending in DB)');
382
+ } finally {
383
+ closeDatabase();
384
+ cleanupDir(tmpDir);
385
+ }
386
+ });
387
+
388
+ test('── markdown-renderer: renderPlanCheckboxes bidirectional ──', async () => {
389
+ const tmpDir = makeTmpDir();
390
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
391
+ openDatabase(dbPath);
392
+ clearAllCaches();
393
+
394
+ try {
395
+ scaffoldDirs(tmpDir, 'M001', ['S01']);
396
+
397
+ insertMilestone({ id: 'M001', title: 'Test', status: 'active' });
398
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Slice', status: 'pending' });
399
+ // T01 pending in DB but checked on disk
400
+ insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', title: 'First task', status: 'pending' });
401
+ insertTask({ id: 'T02', sliceId: 'S01', milestoneId: 'M001', title: 'Second task', status: 'done' });
402
+
403
+ const planContent = makePlanContent('S01', [
404
+ { id: 'T01', title: 'First task', done: true }, // checked but DB says pending
405
+ { id: 'T02', title: 'Second task', done: false }, // unchecked but DB says done
406
+ ]);
407
+ const planPath = path.join(tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'S01-PLAN.md');
408
+ fs.writeFileSync(planPath, planContent);
409
+ clearAllCaches();
410
+
411
+ const ok = await renderPlanCheckboxes(tmpDir, 'M001', 'S01');
412
+ assert.ok(ok, 'bidirectional plan render returns true');
413
+
414
+ const rendered = fs.readFileSync(planPath, 'utf-8');
415
+ clearAllCaches();
416
+ const parsed = parsePlan(rendered);
417
+
418
+ const t01 = parsed.tasks.find(t => t.id === 'T01');
419
+ const t02 = parsed.tasks.find(t => t.id === 'T02');
420
+ assert.ok(!t01!.done, 'T01 unchecked (DB says pending, was checked)');
421
+ assert.ok(t02!.done, 'T02 checked (DB says done, was unchecked)');
422
+ } finally {
423
+ closeDatabase();
424
+ cleanupDir(tmpDir);
425
+ }
426
+ });
427
+
428
+ test('── markdown-renderer: renderPlanFromDb creates parse-compatible slice plan + task plan files ──', async () => {
429
+ const tmpDir = makeTmpDir();
430
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
431
+ openDatabase(dbPath);
432
+ clearAllCaches();
433
+
434
+ try {
435
+ scaffoldDirs(tmpDir, 'M001', ['S02']);
436
+
437
+ insertMilestone({ id: 'M001', title: 'Milestone', status: 'active' });
438
+ insertSlice({
439
+ id: 'S02',
440
+ milestoneId: 'M001',
441
+ title: 'DB-backed planning',
442
+ status: 'pending',
443
+ demo: 'Rendered plans exist on disk.',
444
+ planning: {
445
+ goal: 'Render slice plans from DB state.',
446
+ successCriteria: '- Slice plan stays parse-compatible\n- Task plan files are regenerated',
447
+ proofLevel: 'integration',
448
+ integrationClosure: 'Wires DB planning rows to markdown artifacts.',
449
+ observabilityImpact: '- Run renderer contract tests\n- Inspect stale-render diagnostics on mismatch',
450
+ },
451
+ });
452
+ insertTask({
453
+ id: 'T01',
454
+ sliceId: 'S02',
455
+ milestoneId: 'M001',
456
+ title: 'Render slice plan',
457
+ status: 'pending',
458
+ planning: {
459
+ description: 'Implement the DB-backed slice plan renderer.',
460
+ estimate: '45m',
461
+ files: ['src/resources/extensions/gsd/markdown-renderer.ts'],
462
+ verify: 'node --test markdown-renderer.test.ts',
463
+ inputs: ['src/resources/extensions/gsd/markdown-renderer.ts'],
464
+ expectedOutput: ['src/resources/extensions/gsd/tests/markdown-renderer.test.ts'],
465
+ observabilityImpact: 'Renderer tests cover stale render failure paths.',
466
+ },
467
+ });
468
+ insertTask({
469
+ id: 'T02',
470
+ sliceId: 'S02',
471
+ milestoneId: 'M001',
472
+ title: 'Render task plan',
473
+ status: 'pending',
474
+ planning: {
475
+ description: 'Emit the task plan file with conservative frontmatter.',
476
+ estimate: '30m',
477
+ files: ['src/resources/extensions/gsd/files.ts'],
478
+ verify: 'node --test auto-recovery.test.ts',
479
+ inputs: ['src/resources/extensions/gsd/files.ts'],
480
+ expectedOutput: ['src/resources/extensions/gsd/tests/auto-recovery.test.ts'],
481
+ observabilityImpact: 'Missing task-plan files fail recovery verification.',
482
+ },
483
+ });
484
+
485
+ const rendered = await renderPlanFromDb(tmpDir, 'M001', 'S02');
486
+ assert.ok(fs.existsSync(rendered.planPath), 'slice plan written to disk');
487
+ assert.strictEqual(rendered.taskPlanPaths.length, 2, 'task plan paths returned for each task');
488
+ assert.ok(rendered.taskPlanPaths.every((p) => fs.existsSync(p)), 'all task plan files written to disk');
489
+
490
+ const planContent = fs.readFileSync(rendered.planPath, 'utf-8');
491
+ clearAllCaches();
492
+ const parsedPlan = parsePlan(planContent);
493
+ assert.strictEqual(parsedPlan.id, 'S02', 'rendered slice plan parses with correct slice id');
494
+ assert.strictEqual(parsedPlan.goal, 'Render slice plans from DB state.', 'rendered slice plan preserves goal');
495
+ assert.strictEqual(parsedPlan.demo, 'Rendered plans exist on disk.', 'rendered slice plan preserves demo');
496
+ assert.strictEqual(parsedPlan.mustHaves.length, 2, 'rendered slice plan exposes must-haves');
497
+ assert.strictEqual(parsedPlan.tasks.length, 2, 'rendered slice plan exposes all tasks');
498
+ assert.strictEqual(parsedPlan.tasks[0].id, 'T01', 'first task parses correctly');
499
+ assert.ok(parsedPlan.tasks[0].description.includes('DB-backed slice plan renderer'), 'task description preserved in slice plan');
500
+ assert.strictEqual(parsedPlan.tasks[0].files?.[0], 'src/resources/extensions/gsd/markdown-renderer.ts', 'files list preserved in slice plan');
501
+ assert.strictEqual(parsedPlan.tasks[0].verify, 'node --test markdown-renderer.test.ts', 'verify line preserved in slice plan');
502
+
503
+ const planArtifact = getArtifact('milestones/M001/slices/S02/S02-PLAN.md');
504
+ assert.ok(planArtifact !== null, 'slice plan artifact stored in DB');
505
+ assert.ok(planArtifact!.full_content.includes('## Tasks'), 'stored plan artifact contains task section');
506
+
507
+ const taskPlanPath = path.join(tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S02', 'tasks', 'T01-PLAN.md');
508
+ const taskPlanContent = fs.readFileSync(taskPlanPath, 'utf-8');
509
+ const taskPlanFile = parseTaskPlanFile(taskPlanContent);
510
+ assert.strictEqual(taskPlanFile.frontmatter.estimated_steps, 1, 'task plan frontmatter exposes estimated_steps');
511
+ assert.strictEqual(taskPlanFile.frontmatter.estimated_files, 1, 'task plan frontmatter exposes estimated_files');
512
+ assert.strictEqual(taskPlanFile.frontmatter.skills_used.length, 0, 'task plan frontmatter uses conservative empty skills list');
513
+ assert.match(taskPlanContent, /^# T01: Render slice plan/m, 'task plan renders task heading');
514
+ assert.match(taskPlanContent, /^## Inputs$/m, 'task plan renders Inputs section');
515
+ assert.match(taskPlanContent, /^## Expected Output$/m, 'task plan renders Expected Output section');
516
+ assert.match(taskPlanContent, /^## Verification$/m, 'task plan renders Verification section');
517
+
518
+ const taskArtifact = getArtifact('milestones/M001/slices/S02/tasks/T01-PLAN.md');
519
+ assert.ok(taskArtifact !== null, 'task plan artifact stored in DB');
520
+ assert.ok(taskArtifact!.full_content.includes('skills_used: []'), 'stored task plan artifact preserves conservative skills_used');
521
+ } finally {
522
+ closeDatabase();
523
+ cleanupDir(tmpDir);
524
+ }
525
+ });
526
+
527
+ test('── markdown-renderer: renderTaskPlanFromDb throws for missing task ──', async () => {
528
+ const tmpDir = makeTmpDir();
529
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
530
+ openDatabase(dbPath);
531
+ clearAllCaches();
532
+
533
+ try {
534
+ scaffoldDirs(tmpDir, 'M001', ['S02']);
535
+ insertMilestone({ id: 'M001', title: 'Milestone', status: 'active' });
536
+ insertSlice({ id: 'S02', milestoneId: 'M001', title: 'Slice', status: 'pending' });
537
+
538
+ let threw = false;
539
+ try {
540
+ await renderTaskPlanFromDb(tmpDir, 'M001', 'S02', 'T99');
541
+ } catch (error) {
542
+ threw = true;
543
+ assert.match(String((error as Error).message), /task M001\/S02\/T99 not found/, 'renderTaskPlanFromDb should fail clearly when task row is missing');
544
+ }
545
+ assert.ok(threw, 'renderTaskPlanFromDb throws when the task row is missing');
546
+ } finally {
547
+ closeDatabase();
548
+ cleanupDir(tmpDir);
549
+ }
550
+ });
551
+
552
+
553
+ // ═══════════════════════════════════════════════════════════════════════════
554
+ // Task Summary Rendering
555
+ // ═══════════════════════════════════════════════════════════════════════════
556
+
557
+ test('── markdown-renderer: renderTaskSummary round-trip ──', async () => {
558
+ const tmpDir = makeTmpDir();
559
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
560
+ openDatabase(dbPath);
561
+ clearAllCaches();
562
+
563
+ try {
564
+ scaffoldDirs(tmpDir, 'M001', ['S01']);
565
+
566
+ insertMilestone({ id: 'M001', title: 'Test', status: 'active' });
567
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Slice', status: 'pending' });
568
+
569
+ const summaryContent = makeTaskSummaryContent('T01');
570
+ insertTask({
571
+ id: 'T01',
572
+ sliceId: 'S01',
573
+ milestoneId: 'M001',
574
+ title: 'Test Task',
575
+ status: 'done',
576
+ fullSummaryMd: summaryContent,
577
+ });
578
+
579
+ const ok = await renderTaskSummary(tmpDir, 'M001', 'S01', 'T01');
580
+ assert.ok(ok, 'renderTaskSummary returns true');
581
+
582
+ // Verify file exists on disk
583
+ const summaryPath = path.join(
584
+ tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'tasks', 'T01-SUMMARY.md',
585
+ );
586
+ assert.ok(fs.existsSync(summaryPath), 'T01-SUMMARY.md written to disk');
587
+
588
+ // Parse and verify
589
+ const rendered = fs.readFileSync(summaryPath, 'utf-8');
590
+ clearAllCaches();
591
+ const parsed = parseSummary(rendered);
592
+ assert.deepStrictEqual(parsed.frontmatter.id, 'T01', 'parsed summary has correct id');
593
+ assert.deepStrictEqual(parsed.frontmatter.parent, 'S01', 'parsed summary has correct parent');
594
+ assert.deepStrictEqual(parsed.frontmatter.milestone, 'M001', 'parsed summary has correct milestone');
595
+ assert.deepStrictEqual(parsed.frontmatter.duration, '45m', 'parsed summary has correct duration');
596
+ assert.ok(parsed.title.includes('T01'), 'parsed summary title contains task ID');
597
+ assert.ok(parsed.whatHappened.includes('Built the test feature'), 'whatHappened content preserved');
598
+ } finally {
599
+ closeDatabase();
600
+ cleanupDir(tmpDir);
601
+ }
602
+ });
603
+
604
+ test('── markdown-renderer: renderTaskSummary skips empty ──', async () => {
605
+ const tmpDir = makeTmpDir();
606
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
607
+ openDatabase(dbPath);
608
+ clearAllCaches();
609
+
610
+ try {
611
+ scaffoldDirs(tmpDir, 'M001', ['S01']);
612
+
613
+ insertMilestone({ id: 'M001', title: 'Test', status: 'active' });
614
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Slice', status: 'pending' });
615
+ insertTask({
616
+ id: 'T01',
617
+ sliceId: 'S01',
618
+ milestoneId: 'M001',
619
+ title: 'Task without summary',
620
+ status: 'pending',
621
+ fullSummaryMd: '', // empty summary
622
+ });
623
+
624
+ const ok = await renderTaskSummary(tmpDir, 'M001', 'S01', 'T01');
625
+ assert.ok(!ok, 'renderTaskSummary returns false for empty summary');
626
+ } finally {
627
+ closeDatabase();
628
+ cleanupDir(tmpDir);
629
+ }
630
+ });
631
+
632
+ // ═══════════════════════════════════════════════════════════════════════════
633
+ // Slice Summary Rendering
634
+ // ═══════════════════════════════════════════════════════════════════════════
635
+
636
+ test('── markdown-renderer: renderSliceSummary round-trip ──', async () => {
637
+ const tmpDir = makeTmpDir();
638
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
639
+ openDatabase(dbPath);
640
+ clearAllCaches();
641
+
642
+ try {
643
+ scaffoldDirs(tmpDir, 'M001', ['S01']);
644
+
645
+ insertMilestone({ id: 'M001', title: 'Test', status: 'active' });
646
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Slice', status: 'complete' });
647
+
648
+ // Update slice with summary and UAT content
649
+ // Since insertSlice uses INSERT OR IGNORE, we need to set the content via raw adapter
650
+ const db = await import('../gsd-db.ts');
651
+ const adapter = db._getAdapter()!;
652
+ adapter.prepare(
653
+ `UPDATE slices SET full_summary_md = :sm, full_uat_md = :um WHERE milestone_id = 'M001' AND id = 'S01'`,
654
+ ).run({
655
+ ':sm': '---\nid: S01\nparent: M001\nmilestone: M001\nduration: 2h\nverification_result: all-pass\ncompleted_at: 2025-01-01\nblocker_discovered: false\nprovides: []\nrequires: []\naffects: []\nkey_files:\n - src/index.ts\nkey_decisions: []\npatterns_established: []\ndrill_down_paths: []\nobservability_surfaces: []\n---\n\n# S01: Test Slice Summary\n\n**Completed core functionality**\n\n## What Happened\n\nBuilt the slice.\n\n## Deviations\n\nNone.\n',
656
+ ':um': '# S01 UAT\n\n## UAT Type\n\n- UAT mode: artifact-driven\n\n## Checks\n\n- All tests pass\n',
657
+ });
658
+
659
+ const ok = await renderSliceSummary(tmpDir, 'M001', 'S01');
660
+ assert.ok(ok, 'renderSliceSummary returns true');
661
+
662
+ // Verify SUMMARY file
663
+ const summaryPath = path.join(
664
+ tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'S01-SUMMARY.md',
665
+ );
666
+ assert.ok(fs.existsSync(summaryPath), 'S01-SUMMARY.md written to disk');
667
+
668
+ const summaryContent = fs.readFileSync(summaryPath, 'utf-8');
669
+ assert.ok(summaryContent.includes('Test Slice Summary'), 'summary content correct');
670
+
671
+ // Verify UAT file
672
+ const uatPath = path.join(
673
+ tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'S01-UAT.md',
674
+ );
675
+ assert.ok(fs.existsSync(uatPath), 'S01-UAT.md written to disk');
676
+
677
+ const uatContent = fs.readFileSync(uatPath, 'utf-8');
678
+ assert.ok(uatContent.includes('artifact-driven'), 'UAT content correct');
679
+ } finally {
680
+ closeDatabase();
681
+ cleanupDir(tmpDir);
682
+ }
683
+ });
684
+
685
+ // ═══════════════════════════════════════════════════════════════════════════
686
+ // renderAllFromDb
687
+ // ═══════════════════════════════════════════════════════════════════════════
688
+
689
+ test('── markdown-renderer: renderAllFromDb produces all files ──', async () => {
690
+ const tmpDir = makeTmpDir();
691
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
692
+ openDatabase(dbPath);
693
+ clearAllCaches();
694
+
695
+ try {
696
+ // Setup: 2 milestones, M001 has 2 slices with tasks, M002 has 1 slice
697
+ scaffoldDirs(tmpDir, 'M001', ['S01', 'S02']);
698
+ scaffoldDirs(tmpDir, 'M002', ['S01']);
699
+
700
+ insertMilestone({ id: 'M001', title: 'First', status: 'active' });
701
+ insertMilestone({ id: 'M002', title: 'Second', status: 'active' });
702
+
703
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Core', status: 'complete' });
704
+ insertSlice({ id: 'S02', milestoneId: 'M001', title: 'Render', status: 'pending' });
705
+ insertSlice({ id: 'S01', milestoneId: 'M002', title: 'Future', status: 'pending' });
706
+
707
+ insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', title: 'DB', status: 'done', fullSummaryMd: makeTaskSummaryContent('T01') });
708
+ insertTask({ id: 'T01', sliceId: 'S02', milestoneId: 'M001', title: 'Renderer', status: 'pending' });
709
+ insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M002', title: 'Future task', status: 'pending' });
710
+
711
+ // Write roadmap and plan files on disk
712
+ const roadmap1 = makeRoadmapContent([
713
+ { id: 'S01', title: 'Core', done: false },
714
+ { id: 'S02', title: 'Render', done: false },
715
+ ]);
716
+ fs.writeFileSync(
717
+ path.join(tmpDir, '.gsd', 'milestones', 'M001', 'M001-ROADMAP.md'),
718
+ roadmap1,
719
+ );
720
+
721
+ const roadmap2 = makeRoadmapContent([
722
+ { id: 'S01', title: 'Future', done: false },
723
+ ]);
724
+ fs.writeFileSync(
725
+ path.join(tmpDir, '.gsd', 'milestones', 'M002', 'M002-ROADMAP.md'),
726
+ roadmap2,
727
+ );
728
+
729
+ const plan1 = makePlanContent('S01', [
730
+ { id: 'T01', title: 'DB', done: false },
731
+ ]);
732
+ fs.writeFileSync(
733
+ path.join(tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'S01-PLAN.md'),
734
+ plan1,
735
+ );
736
+
737
+ const plan2 = makePlanContent('S02', [
738
+ { id: 'T01', title: 'Renderer', done: false },
739
+ ]);
740
+ fs.writeFileSync(
741
+ path.join(tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S02', 'S02-PLAN.md'),
742
+ plan2,
743
+ );
744
+
745
+ const plan3 = makePlanContent('S01', [
746
+ { id: 'T01', title: 'Future task', done: false },
747
+ ]);
748
+ fs.writeFileSync(
749
+ path.join(tmpDir, '.gsd', 'milestones', 'M002', 'slices', 'S01', 'S01-PLAN.md'),
750
+ plan3,
751
+ );
752
+
753
+ clearAllCaches();
754
+
755
+ const result = await renderAllFromDb(tmpDir);
756
+
757
+ assert.ok(result.rendered > 0, 'renderAllFromDb rendered some files');
758
+ assert.deepStrictEqual(result.errors.length, 0, 'renderAllFromDb had no errors');
759
+
760
+ // Verify M001 roadmap has S01 checked
761
+ const m1Roadmap = fs.readFileSync(
762
+ path.join(tmpDir, '.gsd', 'milestones', 'M001', 'M001-ROADMAP.md'), 'utf-8',
763
+ );
764
+ clearAllCaches();
765
+ const parsed1 = parseRoadmap(m1Roadmap);
766
+ const s01 = parsed1.slices.find(s => s.id === 'S01');
767
+ assert.ok(s01!.done, 'M001 S01 checked after renderAll');
768
+
769
+ // Verify M001/S01 plan has T01 checked
770
+ const m1s1Plan = fs.readFileSync(
771
+ path.join(tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'S01-PLAN.md'), 'utf-8',
772
+ );
773
+ clearAllCaches();
774
+ const parsedPlan = parsePlan(m1s1Plan);
775
+ assert.ok(parsedPlan.tasks[0].done, 'M001/S01 T01 checked after renderAll');
776
+
777
+ // Verify task summary written
778
+ const taskSummaryPath = path.join(
779
+ tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'tasks', 'T01-SUMMARY.md',
780
+ );
781
+ assert.ok(fs.existsSync(taskSummaryPath), 'T01 summary written by renderAll');
782
+ } finally {
783
+ closeDatabase();
784
+ cleanupDir(tmpDir);
785
+ }
786
+ });
787
+
788
+ // ═══════════════════════════════════════════════════════════════════════════
789
+ // Graceful Degradation (Disk Fallback)
790
+ // ═══════════════════════════════════════════════════════════════════════════
791
+
792
+ test('── markdown-renderer: graceful fallback reads from disk when artifact not in DB ──', async () => {
793
+ const tmpDir = makeTmpDir();
794
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
795
+ openDatabase(dbPath);
796
+ clearAllCaches();
797
+
798
+ try {
799
+ scaffoldDirs(tmpDir, 'M001', ['S01']);
800
+
801
+ insertMilestone({ id: 'M001', title: 'Test', status: 'active' });
802
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Core', status: 'complete' });
803
+
804
+ // Write roadmap to disk but NOT in artifacts DB
805
+ const roadmapContent = makeRoadmapContent([
806
+ { id: 'S01', title: 'Core', done: false },
807
+ ]);
808
+ const roadmapPath = path.join(tmpDir, '.gsd', 'milestones', 'M001', 'M001-ROADMAP.md');
809
+ fs.writeFileSync(roadmapPath, roadmapContent);
810
+ clearAllCaches();
811
+
812
+ // Verify no artifact in DB
813
+ const before = getArtifact('milestones/M001/M001-ROADMAP.md');
814
+ assert.deepStrictEqual(before, null, 'artifact not in DB before render');
815
+
816
+ // Render — should read from disk, store in DB
817
+ const ok = await renderRoadmapCheckboxes(tmpDir, 'M001');
818
+ assert.ok(ok, 'render succeeds with disk fallback');
819
+
820
+ // Verify artifact now in DB (stored after reading from disk)
821
+ const after = getArtifact('milestones/M001/M001-ROADMAP.md');
822
+ assert.ok(after !== null, 'artifact stored in DB after disk fallback render');
823
+ assert.ok(after!.full_content.includes('[x] **S01:'), 'DB artifact reflects rendered state');
824
+ } finally {
825
+ closeDatabase();
826
+ cleanupDir(tmpDir);
827
+ }
828
+ });
829
+
830
+ // ═══════════════════════════════════════════════════════════════════════════
831
+ // stderr warnings (graceful degradation diagnostics)
832
+ // ═══════════════════════════════════════════════════════════════════════════
833
+
834
+ test('── markdown-renderer: stderr warning on missing content ──', async () => {
835
+ openDatabase(':memory:');
836
+
837
+ // No milestone/slices in DB, no files on disk — should return false and emit stderr
838
+ insertMilestone({ id: 'M001', title: 'Test', status: 'active' });
839
+ // No slices inserted — should warn about no slices
840
+
841
+ const ok = await renderRoadmapCheckboxes('/nonexistent/path', 'M001');
842
+ assert.ok(!ok, 'returns false when no slices in DB');
843
+
844
+ closeDatabase();
845
+ });
846
+
847
+ // ═══════════════════════════════════════════════════════════════════════════
848
+ // Stale Detection — Plan Checkbox Mismatch
849
+ // ═══════════════════════════════════════════════════════════════════════════
850
+
851
+ test('── markdown-renderer: detectStaleRenders finds plan checkbox mismatch ──', () => {
852
+ const tmpDir = makeTmpDir();
853
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
854
+ openDatabase(dbPath);
855
+ clearAllCaches();
856
+
857
+ try {
858
+ scaffoldDirs(tmpDir, 'M001', ['S01']);
859
+
860
+ insertMilestone({ id: 'M001', title: 'Test', status: 'active' });
861
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Slice', status: 'pending' });
862
+
863
+ // T01 is done, T02 is also done in DB
864
+ insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', title: 'First task', status: 'done' });
865
+ insertTask({ id: 'T02', sliceId: 'S01', milestoneId: 'M001', title: 'Second task', status: 'done' });
866
+
867
+ // Write plan with T01 checked but T02 unchecked
868
+ // T01 matches DB (done + checked) but T02 is stale (done but unchecked)
869
+ const planContent = makePlanContent('S01', [
870
+ { id: 'T01', title: 'First task', done: true },
871
+ { id: 'T02', title: 'Second task', done: false },
872
+ ]);
873
+ const planPath = path.join(tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'S01-PLAN.md');
874
+ fs.writeFileSync(planPath, planContent);
875
+ clearAllCaches();
876
+
877
+ // Render T01 to sync it, but leave T02 out of sync
878
+ // Actually, the plan was written with T01 already checked.
879
+ // The stale detection should find T02 as stale.
880
+ const stale = detectStaleRenders(tmpDir);
881
+
882
+ assert.ok(stale.length > 0, 'detectStaleRenders should find stale entries');
883
+ const t02Stale = stale.find(s => s.reason.includes('T02'));
884
+ assert.ok(!!t02Stale, 'should detect T02 as stale (done in DB, unchecked in plan)');
885
+ assert.ok(t02Stale!.reason.includes('done in DB but unchecked'), 'reason should explain the mismatch');
886
+
887
+ // T01 should NOT be stale — it's checked and done
888
+ const t01Stale = stale.find(s => s.reason.includes('T01'));
889
+ assert.deepStrictEqual(t01Stale, undefined, 'T01 should not be stale (done and checked)');
890
+ } finally {
891
+ closeDatabase();
892
+ cleanupDir(tmpDir);
893
+ }
894
+ });
895
+
896
+ // ═══════════════════════════════════════════════════════════════════════════
897
+ // Stale Repair — Plan Checkbox
898
+ // ═══════════════════════════════════════════════════════════════════════════
899
+
900
+ test('── markdown-renderer: repairStaleRenders fixes plan and second detect returns empty ──', async () => {
901
+ const tmpDir = makeTmpDir();
902
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
903
+ openDatabase(dbPath);
904
+ clearAllCaches();
905
+
906
+ try {
907
+ scaffoldDirs(tmpDir, 'M001', ['S01']);
908
+
909
+ insertMilestone({ id: 'M001', title: 'Test', status: 'active' });
910
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Slice', status: 'pending' });
911
+
912
+ insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', title: 'First task', status: 'done' });
913
+ insertTask({ id: 'T02', sliceId: 'S01', milestoneId: 'M001', title: 'Second task', status: 'done' });
914
+
915
+ // Write plan with both tasks unchecked (both are stale since DB says done)
916
+ const planContent = makePlanContent('S01', [
917
+ { id: 'T01', title: 'First task', done: false },
918
+ { id: 'T02', title: 'Second task', done: false },
919
+ ]);
920
+ const planPath = path.join(tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'S01-PLAN.md');
921
+ fs.writeFileSync(planPath, planContent);
922
+ clearAllCaches();
923
+
924
+ // Verify stale before repair
925
+ const staleBefore = detectStaleRenders(tmpDir);
926
+ assert.ok(staleBefore.length > 0, 'should have stale entries before repair');
927
+
928
+ // Repair
929
+ const repaired = await repairStaleRenders(tmpDir);
930
+ assert.ok(repaired > 0, 'repairStaleRenders should repair at least 1 file');
931
+
932
+ // After repair, detect again — should be empty
933
+ clearAllCaches();
934
+ const staleAfter = detectStaleRenders(tmpDir);
935
+ assert.deepStrictEqual(staleAfter.length, 0, 'detectStaleRenders should return empty after repair');
936
+
937
+ // Verify the plan file was actually updated
938
+ const repairedContent = fs.readFileSync(planPath, 'utf-8');
939
+ assert.ok(repairedContent.includes('[x] **T01:'), 'T01 should be checked after repair');
940
+ assert.ok(repairedContent.includes('[x] **T02:'), 'T02 should be checked after repair');
941
+ } finally {
942
+ closeDatabase();
943
+ cleanupDir(tmpDir);
944
+ }
945
+ });
946
+
947
+ // ═══════════════════════════════════════════════════════════════════════════
948
+ // Stale Detection — Roadmap Checkbox Mismatch
949
+ // ═══════════════════════════════════════════════════════════════════════════
950
+
951
+ test('── markdown-renderer: detectStaleRenders finds roadmap checkbox mismatch ──', () => {
952
+ const tmpDir = makeTmpDir();
953
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
954
+ openDatabase(dbPath);
955
+ clearAllCaches();
956
+
957
+ try {
958
+ scaffoldDirs(tmpDir, 'M001', ['S01', 'S02']);
959
+
960
+ insertMilestone({ id: 'M001', title: 'Test', status: 'active' });
961
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Core', status: 'complete' });
962
+ insertSlice({ id: 'S02', milestoneId: 'M001', title: 'Render', status: 'pending' });
963
+
964
+ // Write roadmap with both slices unchecked (S01 is stale — complete in DB but unchecked)
965
+ const roadmapContent = makeRoadmapContent([
966
+ { id: 'S01', title: 'Core', done: false },
967
+ { id: 'S02', title: 'Render', done: false },
968
+ ]);
969
+ const roadmapPath = path.join(tmpDir, '.gsd', 'milestones', 'M001', 'M001-ROADMAP.md');
970
+ fs.writeFileSync(roadmapPath, roadmapContent);
971
+ clearAllCaches();
972
+
973
+ const stale = detectStaleRenders(tmpDir);
974
+ const s01Stale = stale.find(s => s.reason.includes('S01'));
975
+ assert.ok(!!s01Stale, 'should detect S01 as stale (complete in DB, unchecked in roadmap)');
976
+
977
+ const s02Stale = stale.find(s => s.reason.includes('S02'));
978
+ assert.deepStrictEqual(s02Stale, undefined, 'S02 should not be stale (pending and unchecked — matches)');
979
+ } finally {
980
+ closeDatabase();
981
+ cleanupDir(tmpDir);
982
+ }
983
+ });
984
+
985
+ // ═══════════════════════════════════════════════════════════════════════════
986
+ // Stale Detection — Missing Task Summary
987
+ // ═══════════════════════════════════════════════════════════════════════════
988
+
989
+ test('── markdown-renderer: detectStaleRenders finds missing task summary ──', () => {
990
+ const tmpDir = makeTmpDir();
991
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
992
+ openDatabase(dbPath);
993
+ clearAllCaches();
994
+
995
+ try {
996
+ scaffoldDirs(tmpDir, 'M001', ['S01']);
997
+
998
+ insertMilestone({ id: 'M001', title: 'Test', status: 'active' });
999
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Slice', status: 'pending' });
1000
+
1001
+ // Task is done with full_summary_md, but no SUMMARY.md on disk
1002
+ const summaryContent = makeTaskSummaryContent('T01');
1003
+ insertTask({
1004
+ id: 'T01',
1005
+ sliceId: 'S01',
1006
+ milestoneId: 'M001',
1007
+ title: 'Task',
1008
+ status: 'done',
1009
+ fullSummaryMd: summaryContent,
1010
+ });
1011
+
1012
+ // Also write a plan so plan detection doesn't trigger (T01 is done but not checked)
1013
+ // We need a plan file so task plan detection works — but we specifically want to test
1014
+ // the missing summary case, so write plan with T01 checked
1015
+ const planContent = makePlanContent('S01', [
1016
+ { id: 'T01', title: 'Task', done: true },
1017
+ ]);
1018
+ const planPath = path.join(tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'S01-PLAN.md');
1019
+ fs.writeFileSync(planPath, planContent);
1020
+ clearAllCaches();
1021
+
1022
+ const stale = detectStaleRenders(tmpDir);
1023
+ const summaryStale = stale.find(s => s.reason.includes('SUMMARY.md missing'));
1024
+ assert.ok(!!summaryStale, 'should detect missing T01-SUMMARY.md');
1025
+ assert.ok(summaryStale!.reason.includes('T01'), 'reason should mention T01');
1026
+ } finally {
1027
+ closeDatabase();
1028
+ cleanupDir(tmpDir);
1029
+ }
1030
+ });
1031
+
1032
+ // ═══════════════════════════════════════════════════════════════════════════
1033
+ // Stale Repair — Missing Task Summary
1034
+ // ═══════════════════════════════════════════════════════════════════════════
1035
+
1036
+ test('── markdown-renderer: repairStaleRenders writes missing task summary ──', async () => {
1037
+ const tmpDir = makeTmpDir();
1038
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
1039
+ openDatabase(dbPath);
1040
+ clearAllCaches();
1041
+
1042
+ try {
1043
+ scaffoldDirs(tmpDir, 'M001', ['S01']);
1044
+
1045
+ insertMilestone({ id: 'M001', title: 'Test', status: 'active' });
1046
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Slice', status: 'pending' });
1047
+
1048
+ const summaryContent = makeTaskSummaryContent('T01');
1049
+ insertTask({
1050
+ id: 'T01',
1051
+ sliceId: 'S01',
1052
+ milestoneId: 'M001',
1053
+ title: 'Task',
1054
+ status: 'done',
1055
+ fullSummaryMd: summaryContent,
1056
+ });
1057
+
1058
+ // Write plan with T01 checked so plan detection doesn't trigger
1059
+ const planContent = makePlanContent('S01', [
1060
+ { id: 'T01', title: 'Task', done: true },
1061
+ ]);
1062
+ const planPath = path.join(tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'S01-PLAN.md');
1063
+ fs.writeFileSync(planPath, planContent);
1064
+ clearAllCaches();
1065
+
1066
+ // Repair
1067
+ const repaired = await repairStaleRenders(tmpDir);
1068
+ assert.ok(repaired > 0, 'should repair missing summary');
1069
+
1070
+ // Verify file written
1071
+ const summaryPath = path.join(
1072
+ tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'tasks', 'T01-SUMMARY.md',
1073
+ );
1074
+ assert.ok(fs.existsSync(summaryPath), 'T01-SUMMARY.md should exist after repair');
1075
+
1076
+ // Second detect should be empty
1077
+ clearAllCaches();
1078
+ const staleAfter = detectStaleRenders(tmpDir);
1079
+ const summaryStale = staleAfter.find(s => s.reason.includes('SUMMARY.md missing') && s.reason.includes('T01'));
1080
+ assert.deepStrictEqual(summaryStale, undefined, 'missing summary should be fixed after repair');
1081
+ } finally {
1082
+ closeDatabase();
1083
+ cleanupDir(tmpDir);
1084
+ }
1085
+ });
1086
+
1087
+ // ═══════════════════════════════════════════════════════════════════════════
1088
+ // Stale Repair — Idempotency
1089
+ // ═══════════════════════════════════════════════════════════════════════════
1090
+
1091
+ test('── markdown-renderer: repairStaleRenders idempotency — fully synced returns 0 ──', async () => {
1092
+ const tmpDir = makeTmpDir();
1093
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
1094
+ openDatabase(dbPath);
1095
+ clearAllCaches();
1096
+
1097
+ try {
1098
+ scaffoldDirs(tmpDir, 'M001', ['S01']);
1099
+
1100
+ insertMilestone({ id: 'M001', title: 'Test', status: 'active' });
1101
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Slice', status: 'pending' });
1102
+ insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', title: 'Task', status: 'done' });
1103
+
1104
+ // Write plan with T01 checked — matches DB
1105
+ const planContent = makePlanContent('S01', [
1106
+ { id: 'T01', title: 'Task', done: true },
1107
+ ]);
1108
+ const planPath = path.join(tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'S01-PLAN.md');
1109
+ fs.writeFileSync(planPath, planContent);
1110
+ clearAllCaches();
1111
+
1112
+ // No stale entries when everything is in sync (no summary to check since no fullSummaryMd)
1113
+ const repaired = await repairStaleRenders(tmpDir);
1114
+ assert.deepStrictEqual(repaired, 0, 'repairStaleRenders should return 0 on fully synced project');
1115
+ } finally {
1116
+ closeDatabase();
1117
+ cleanupDir(tmpDir);
1118
+ }
1119
+ });
1120
+
1121
+ // ═══════════════════════════════════════════════════════════════════════════
1122
+ // Stale Detection — Missing Slice Summary + UAT
1123
+ // ═══════════════════════════════════════════════════════════════════════════
1124
+
1125
+ test('── markdown-renderer: detectStaleRenders finds missing slice summary and UAT ──', () => {
1126
+ const tmpDir = makeTmpDir();
1127
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
1128
+ openDatabase(dbPath);
1129
+ clearAllCaches();
1130
+
1131
+ try {
1132
+ scaffoldDirs(tmpDir, 'M001', ['S01']);
1133
+
1134
+ insertMilestone({ id: 'M001', title: 'Test', status: 'active' });
1135
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Slice', status: 'pending' });
1136
+
1137
+ // Update slice to complete with content via raw adapter
1138
+ const adapter = _getAdapter()!;
1139
+ adapter.prepare(
1140
+ `UPDATE slices SET status = 'complete', full_summary_md = :sm, full_uat_md = :um WHERE milestone_id = 'M001' AND id = 'S01'`,
1141
+ ).run({
1142
+ ':sm': '---\nid: S01\nparent: M001\nmilestone: M001\n---\n\n# S01: Summary\n\nDone.\n',
1143
+ ':um': '# S01 UAT\n\nAll pass.\n',
1144
+ });
1145
+
1146
+ clearAllCaches();
1147
+
1148
+ const stale = detectStaleRenders(tmpDir);
1149
+ const summaryStale = stale.find(s => s.reason.includes('SUMMARY.md missing') && s.reason.includes('S01'));
1150
+ const uatStale = stale.find(s => s.reason.includes('UAT.md missing') && s.reason.includes('S01'));
1151
+
1152
+ assert.ok(!!summaryStale, 'should detect missing S01-SUMMARY.md');
1153
+ assert.ok(!!uatStale, 'should detect missing S01-UAT.md');
1154
+ } finally {
1155
+ closeDatabase();
1156
+ cleanupDir(tmpDir);
1157
+ }
1158
+ });
1159
+
1160
+ // ═══════════════════════════════════════════════════════════════════════════
1161
+