gsd-pi 2.44.0-dev.d25d507 → 2.45.0-dev.6b9da3e

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 (245) hide show
  1. package/dist/resources/extensions/gsd/activity-log.js +7 -0
  2. package/dist/resources/extensions/gsd/auto/infra-errors.js +3 -0
  3. package/dist/resources/extensions/gsd/auto/phases.js +37 -36
  4. package/dist/resources/extensions/gsd/auto-prompts.js +24 -1
  5. package/dist/resources/extensions/gsd/auto-start.js +21 -2
  6. package/dist/resources/extensions/gsd/auto-timers.js +57 -3
  7. package/dist/resources/extensions/gsd/auto-worktree-sync.js +4 -0
  8. package/dist/resources/extensions/gsd/auto-worktree.js +9 -6
  9. package/dist/resources/extensions/gsd/auto.js +30 -3
  10. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +156 -0
  11. package/dist/resources/extensions/gsd/commands/catalog.js +7 -1
  12. package/dist/resources/extensions/gsd/commands/handlers/core.js +2 -0
  13. package/dist/resources/extensions/gsd/commands/handlers/ops.js +10 -0
  14. package/dist/resources/extensions/gsd/commands-mcp-status.js +187 -0
  15. package/dist/resources/extensions/gsd/db-writer.js +34 -16
  16. package/dist/resources/extensions/gsd/doctor.js +8 -0
  17. package/dist/resources/extensions/gsd/git-service.js +8 -3
  18. package/dist/resources/extensions/gsd/gsd-db.js +12 -1
  19. package/dist/resources/extensions/gsd/markdown-renderer.js +1 -1
  20. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -4
  21. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  22. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -6
  23. package/dist/resources/extensions/gsd/prompts/replan-slice.md +3 -14
  24. package/dist/resources/extensions/gsd/prompts/rethink.md +78 -0
  25. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +7 -37
  26. package/dist/resources/extensions/gsd/provider-error-pause.js +7 -0
  27. package/dist/resources/extensions/gsd/repo-identity.js +45 -7
  28. package/dist/resources/extensions/gsd/rethink.js +115 -0
  29. package/dist/resources/extensions/gsd/state.js +41 -3
  30. package/dist/resources/extensions/gsd/tools/plan-slice.js +1 -0
  31. package/dist/resources/extensions/gsd/tools/plan-task.js +1 -0
  32. package/dist/resources/extensions/gsd/tools/replan-slice.js +2 -0
  33. package/dist/resources/extensions/gsd/tools/validate-milestone.js +88 -0
  34. package/dist/resources/extensions/gsd/worktree-manager.js +32 -2
  35. package/dist/resources/extensions/gsd/worktree-resolver.js +6 -0
  36. package/dist/resources/extensions/mcp-client/index.js +14 -0
  37. package/dist/web/standalone/.next/BUILD_ID +1 -1
  38. package/dist/web/standalone/.next/app-path-routes-manifest.json +18 -18
  39. package/dist/web/standalone/.next/build-manifest.json +3 -3
  40. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  41. package/dist/web/standalone/.next/react-loadable-manifest.json +2 -2
  42. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  43. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  44. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_not-found/page.js +1 -1
  52. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  53. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  54. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  55. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  56. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  58. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  61. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  62. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  63. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  64. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  65. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  66. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  68. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  69. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  70. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  72. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  74. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  76. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  78. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  79. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  80. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/app/index.html +1 -1
  102. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  103. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  104. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  105. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  106. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +2 -2
  107. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  108. package/dist/web/standalone/.next/server/app/page.js +1 -1
  109. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  110. package/dist/web/standalone/.next/server/app-paths-manifest.json +18 -18
  111. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  112. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  113. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  114. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  115. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  116. package/dist/web/standalone/.next/static/chunks/4024.11ca5c01938e5948.js +9 -0
  117. package/dist/web/standalone/.next/static/chunks/{3721.bf31263de6d5fa46.js → 485.243af25f0cdf50d6.js} +2 -2
  118. package/dist/web/standalone/.next/static/chunks/app/{page-b9367c5ae13b99c6.js → page-6654a8cca61a3d1c.js} +1 -1
  119. package/dist/web/standalone/.next/static/chunks/webpack-0a4cd455ec4197d2.js +1 -0
  120. package/dist/web/standalone/.next/static/css/dd4ae3f58ac9b600.css +1 -0
  121. package/package.json +1 -1
  122. package/packages/native/dist/stream-process/index.js +2 -2
  123. package/packages/native/src/__tests__/stream-process.test.mjs +34 -0
  124. package/packages/native/src/stream-process/index.ts +2 -2
  125. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +3 -1
  126. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/auth-storage.js +15 -1
  128. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  129. package/packages/pi-coding-agent/dist/core/local-model-check.d.ts +15 -0
  130. package/packages/pi-coding-agent/dist/core/local-model-check.d.ts.map +1 -0
  131. package/packages/pi-coding-agent/dist/core/local-model-check.js +41 -0
  132. package/packages/pi-coding-agent/dist/core/local-model-check.js.map +1 -0
  133. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +11 -0
  134. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  135. package/packages/pi-coding-agent/dist/core/model-registry.js +20 -1
  136. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  137. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
  138. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  139. package/packages/pi-coding-agent/dist/core/settings-manager.js +6 -0
  140. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  141. package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
  142. package/packages/pi-coding-agent/dist/main.js +17 -0
  143. package/packages/pi-coding-agent/dist/main.js.map +1 -1
  144. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.d.ts +2 -0
  145. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.d.ts.map +1 -0
  146. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.js +32 -0
  147. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.js.map +1 -0
  148. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts +3 -1
  149. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  150. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +8 -1
  151. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  152. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  153. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  154. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +12 -0
  155. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  156. package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.d.ts +15 -0
  157. package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.d.ts.map +1 -0
  158. package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.js +40 -0
  159. package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.js.map +1 -0
  160. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  161. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +4 -1
  162. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  163. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts +5 -2
  164. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  165. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js +13 -2
  166. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js.map +1 -1
  167. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  168. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +17 -8
  169. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  170. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  171. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +7 -3
  172. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  173. package/packages/pi-coding-agent/package.json +1 -1
  174. package/packages/pi-coding-agent/src/core/auth-storage.ts +15 -1
  175. package/packages/pi-coding-agent/src/core/local-model-check.ts +45 -0
  176. package/packages/pi-coding-agent/src/core/model-registry.ts +21 -1
  177. package/packages/pi-coding-agent/src/core/settings-manager.ts +9 -0
  178. package/packages/pi-coding-agent/src/main.ts +19 -0
  179. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/timestamp.test.ts +38 -0
  180. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +10 -0
  181. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +15 -0
  182. package/packages/pi-coding-agent/src/modes/interactive/components/timestamp.ts +48 -0
  183. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +3 -1
  184. package/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts +18 -3
  185. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +16 -7
  186. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +8 -1
  187. package/pkg/package.json +1 -1
  188. package/src/resources/extensions/gsd/activity-log.ts +1 -0
  189. package/src/resources/extensions/gsd/auto/infra-errors.ts +3 -0
  190. package/src/resources/extensions/gsd/auto/phases.ts +46 -48
  191. package/src/resources/extensions/gsd/auto-prompts.ts +24 -1
  192. package/src/resources/extensions/gsd/auto-start.ts +25 -2
  193. package/src/resources/extensions/gsd/auto-timers.ts +64 -3
  194. package/src/resources/extensions/gsd/auto-worktree-sync.ts +5 -0
  195. package/src/resources/extensions/gsd/auto-worktree.ts +9 -6
  196. package/src/resources/extensions/gsd/auto.ts +37 -3
  197. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +148 -0
  198. package/src/resources/extensions/gsd/commands/catalog.ts +7 -1
  199. package/src/resources/extensions/gsd/commands/handlers/core.ts +2 -0
  200. package/src/resources/extensions/gsd/commands/handlers/ops.ts +10 -0
  201. package/src/resources/extensions/gsd/commands-mcp-status.ts +247 -0
  202. package/src/resources/extensions/gsd/db-writer.ts +39 -17
  203. package/src/resources/extensions/gsd/doctor.ts +7 -1
  204. package/src/resources/extensions/gsd/git-service.ts +6 -2
  205. package/src/resources/extensions/gsd/gsd-db.ts +16 -1
  206. package/src/resources/extensions/gsd/markdown-renderer.ts +1 -1
  207. package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -4
  208. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  209. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -6
  210. package/src/resources/extensions/gsd/prompts/replan-slice.md +3 -14
  211. package/src/resources/extensions/gsd/prompts/rethink.md +78 -0
  212. package/src/resources/extensions/gsd/prompts/validate-milestone.md +7 -37
  213. package/src/resources/extensions/gsd/provider-error-pause.ts +9 -0
  214. package/src/resources/extensions/gsd/repo-identity.ts +46 -7
  215. package/src/resources/extensions/gsd/rethink.ts +154 -0
  216. package/src/resources/extensions/gsd/state.ts +41 -1
  217. package/src/resources/extensions/gsd/tests/auto-pr-bugs.test.ts +88 -0
  218. package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +114 -0
  219. package/src/resources/extensions/gsd/tests/db-writer.test.ts +79 -0
  220. package/src/resources/extensions/gsd/tests/derive-state-db-disk-reconcile.test.ts +121 -0
  221. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +60 -0
  222. package/src/resources/extensions/gsd/tests/est-annotation-timeout.test.ts +120 -0
  223. package/src/resources/extensions/gsd/tests/infra-error.test.ts +20 -2
  224. package/src/resources/extensions/gsd/tests/inherited-repo-home-dir.test.ts +121 -0
  225. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +103 -0
  226. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +66 -0
  227. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +11 -7
  228. package/src/resources/extensions/gsd/tests/recovery-attempts-reset.test.ts +176 -0
  229. package/src/resources/extensions/gsd/tests/stop-auto-merge-back.test.ts +67 -0
  230. package/src/resources/extensions/gsd/tests/survivor-branch-complete.test.ts +108 -0
  231. package/src/resources/extensions/gsd/tests/terminated-transient.test.ts +49 -0
  232. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +2 -1
  233. package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +65 -0
  234. package/src/resources/extensions/gsd/tools/plan-slice.ts +2 -0
  235. package/src/resources/extensions/gsd/tools/plan-task.ts +2 -0
  236. package/src/resources/extensions/gsd/tools/replan-slice.ts +3 -0
  237. package/src/resources/extensions/gsd/tools/validate-milestone.ts +127 -0
  238. package/src/resources/extensions/gsd/worktree-manager.ts +43 -2
  239. package/src/resources/extensions/gsd/worktree-resolver.ts +7 -0
  240. package/src/resources/extensions/mcp-client/index.ts +20 -0
  241. package/dist/web/standalone/.next/static/chunks/4024.0de81b543b28b9fe.js +0 -9
  242. package/dist/web/standalone/.next/static/chunks/webpack-9014b5adb127a98a.js +0 -1
  243. package/dist/web/standalone/.next/static/css/8a727f372cf53002.css +0 -1
  244. /package/dist/web/standalone/.next/static/{tokoGmfkYfWf1_Yl_Gz7i → rzO54ZboyINyEt7cVM_uS}/_buildManifest.js +0 -0
  245. /package/dist/web/standalone/.next/static/{tokoGmfkYfWf1_Yl_Gz7i → rzO54ZboyINyEt7cVM_uS}/_ssgManifest.js +0 -0
@@ -16,6 +16,8 @@ All relevant context has been preloaded below — the roadmap, all slice summari
16
16
 
17
17
  {{inlinedContext}}
18
18
 
19
+ {{skillActivation}}
20
+
19
21
  ## Validation Steps
20
22
 
21
23
  1. For each **success criterion** in `{{roadmapPath}}`, check whether slice summaries and UAT results provide evidence that it was met. Record pass/fail per criterion.
@@ -25,47 +27,15 @@ All relevant context has been preloaded below — the roadmap, all slice summari
25
27
  5. Determine a verdict:
26
28
  - `pass` — all criteria met, all slices delivered, no gaps
27
29
  - `needs-attention` — minor gaps that do not block completion (document them)
28
- - `needs-remediation` — material gaps found; add remediation slices to the roadmap
29
-
30
- ## Output
31
-
32
- Write `{{validationPath}}` with this structure:
33
-
34
- ```markdown
35
- ---
36
- verdict: <pass|needs-attention|needs-remediation>
37
- remediation_round: {{remediationRound}}
38
- ---
39
-
40
- # Milestone Validation: {{milestoneId}}
30
+ - `needs-remediation` — material gaps found; remediation slices must be added to the roadmap
41
31
 
42
- ## Success Criteria Checklist
43
- - [x] Criterion 1 — evidence: ...
44
- - [ ] Criterion 2 — gap: ...
32
+ ## Persist Validation
45
33
 
46
- ## Slice Delivery Audit
47
- | Slice | Claimed | Delivered | Status |
48
- |-------|---------|-----------|--------|
49
- | S01 | ... | ... | pass |
50
-
51
- ## Cross-Slice Integration
52
- (any boundary mismatches)
53
-
54
- ## Requirement Coverage
55
- (any unaddressed requirements)
56
-
57
- ## Verdict Rationale
58
- (why this verdict was chosen)
59
-
60
- ## Remediation Plan
61
- (only if verdict is needs-remediation — list new slices to add to the roadmap)
62
- ```
34
+ **Persist validation results through `gsd_validate_milestone`.** Call it with: `milestoneId`, `verdict`, `remediationRound`, `successCriteriaChecklist`, `sliceDeliveryAudit`, `crossSliceIntegration`, `requirementCoverage`, `verdictRationale`, and `remediationPlan` (if verdict is `needs-remediation`). The tool writes the validation to the DB and renders VALIDATION.md to disk.
63
35
 
64
36
  If verdict is `needs-remediation`:
65
- - Add new slices to `{{roadmapPath}}` with unchecked `[ ]` status
66
- - These slices will be planned and executed before validation re-runs
67
-
68
- **You MUST write `{{validationPath}}` before finishing.**
37
+ - After calling `gsd_validate_milestone`, use `gsd_reassess_roadmap` to add remediation slices. Pass `milestoneId`, a synthetic `completedSliceId` (e.g. "VALIDATION"), `verdict: "roadmap-adjusted"`, `assessment` text, and `sliceChanges` with the new slices in the `added` array. The tool persists the changes to the DB and re-renders ROADMAP.md.
38
+ - These remediation slices will be planned and executed before validation re-runs.
69
39
 
70
40
  **File system safety:** When scanning milestone directories for evidence, use `ls` or `find` to list directory contents first — never pass a directory path (e.g. `tasks/`, `slices/`) directly to the `read` tool. The `read` tool only accepts file paths, not directories.
71
41
 
@@ -19,6 +19,11 @@ export function classifyProviderError(errorMsg: string): {
19
19
  const isRateLimit = /rate.?limit|too many requests|429/i.test(errorMsg);
20
20
  const isServerError = /internal server error|500|502|503|overloaded|server_error|api_error|service.?unavailable/i.test(errorMsg);
21
21
 
22
+ // Connection/process errors — transient, auto-resume after brief backoff (#2309).
23
+ // These indicate the process was killed, the connection was reset, or a network
24
+ // blip occurred. They are NOT permanent failures.
25
+ const isConnectionError = /terminated|connection.?reset|connection.?refused|other side closed|fetch failed|network.?(?:is\s+)?unavailable|ECONNREFUSED|ECONNRESET|EPIPE/i.test(errorMsg);
26
+
22
27
  // Permanent errors — never auto-resume
23
28
  const isPermanent = /auth|unauthorized|forbidden|invalid.*key|invalid.*api|billing|quota exceeded|account/i.test(errorMsg);
24
29
 
@@ -37,6 +42,10 @@ export function classifyProviderError(errorMsg: string): {
37
42
  return { isTransient: true, isRateLimit: false, suggestedDelayMs: 30_000 }; // 30s for server errors
38
43
  }
39
44
 
45
+ if (isConnectionError) {
46
+ return { isTransient: true, isRateLimit: false, suggestedDelayMs: 15_000 }; // 15s for connection errors
47
+ }
48
+
40
49
  // Unknown error — treat as permanent (user reviews)
41
50
  return { isTransient: false, isRateLimit: false, suggestedDelayMs: 0 };
42
51
  }
@@ -104,16 +104,17 @@ export function readRepoMeta(externalPath: string): RepoMeta | null {
104
104
  * Returns true when ALL of:
105
105
  * 1. basePath is inside a git repo (git rev-parse succeeds)
106
106
  * 2. The resolved git root is a proper ancestor of basePath
107
- * 3. There is no `.gsd` directory at the git root (the parent project
108
- * has not been initialised with GSD)
107
+ * 3. There is no *project* `.gsd` directory at the git root or any
108
+ * intermediate ancestor (the parent project has not been
109
+ * initialised with GSD)
109
110
  *
110
111
  * When true, the caller should run `git init` at basePath so that
111
112
  * `repoIdentity()` produces a hash unique to this directory, preventing
112
113
  * cross-project state leaks (#1639).
113
114
  *
114
- * When the git root already has `.gsd`, the directory is a legitimate
115
- * subdirectory of an existing GSD project — `cd src/ && /gsd` should
116
- * still load the parent project's milestones.
115
+ * When the git root already has a project `.gsd`, the directory is a
116
+ * legitimate subdirectory of an existing GSD project — `cd src/ && /gsd`
117
+ * should still load the parent project's milestones.
117
118
  */
118
119
  export function isInheritedRepo(basePath: string): boolean {
119
120
  try {
@@ -124,12 +125,12 @@ export function isInheritedRepo(basePath: string): boolean {
124
125
 
125
126
  // The git root is a proper ancestor. Check whether it already has .gsd
126
127
  // (i.e. the parent project was initialised with GSD).
127
- if (existsSync(join(root, ".gsd"))) return false;
128
+ if (isProjectGsd(join(root, ".gsd"))) return false;
128
129
 
129
130
  // Also walk up from basePath to the git root checking for .gsd
130
131
  let dir = normalizedBase;
131
132
  while (dir !== normalizedRoot && dir !== dirname(dir)) {
132
- if (existsSync(join(dir, ".gsd"))) return false;
133
+ if (isProjectGsd(join(dir, ".gsd"))) return false;
133
134
  dir = dirname(dir);
134
135
  }
135
136
 
@@ -139,6 +140,44 @@ export function isInheritedRepo(basePath: string): boolean {
139
140
  }
140
141
  }
141
142
 
143
+ /**
144
+ * Distinguish a *project* `.gsd` from the global `~/.gsd` state directory.
145
+ *
146
+ * A project `.gsd` is either:
147
+ * - A symlink to an external state directory (normal post-migration layout)
148
+ * - A legacy real directory that is NOT the global GSD home
149
+ *
150
+ * When the user's home directory is itself a git repo (e.g. dotfile managers),
151
+ * `~/.gsd` exists but is the global state directory — not a project `.gsd`.
152
+ * Treating it as a project `.gsd` would cause isInheritedRepo() to wrongly
153
+ * conclude that subdirectories are part of the home "project" (#2393).
154
+ */
155
+ function isProjectGsd(gsdPath: string): boolean {
156
+ if (!existsSync(gsdPath)) return false;
157
+
158
+ try {
159
+ const stat = lstatSync(gsdPath);
160
+
161
+ // Symlinks are always project .gsd (created by ensureGsdSymlink).
162
+ if (stat.isSymbolicLink()) return true;
163
+
164
+ // For real directories, check that this isn't the global GSD home.
165
+ // Recompute gsdHome dynamically so env overrides (GSD_HOME) are
166
+ // picked up at call time, not just at module load time.
167
+ if (stat.isDirectory()) {
168
+ const currentGsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
169
+ const normalizedGsdPath = canonicalizeExistingPath(gsdPath);
170
+ const normalizedGsdHome = canonicalizeExistingPath(currentGsdHome);
171
+ if (normalizedGsdPath === normalizedGsdHome) return false;
172
+ return true;
173
+ }
174
+ } catch {
175
+ // lstat failed — treat as no .gsd present
176
+ }
177
+
178
+ return false;
179
+ }
180
+
142
181
  // ─── Repo Identity ──────────────────────────────────────────────────────────
143
182
 
144
183
  /**
@@ -0,0 +1,154 @@
1
+ /**
2
+ * GSD Rethink — Conversational project reorganization.
3
+ *
4
+ * Collects a snapshot of all milestones (status, dependencies, slice progress,
5
+ * queue order) and dispatches a prompt that turns Claude into a reorganization
6
+ * assistant. Claude can then reorder, park, unpark, discard, or add milestones
7
+ * through conversation.
8
+ */
9
+
10
+ import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
11
+ import { existsSync } from "node:fs";
12
+
13
+ import { isAutoActive } from "./auto.js";
14
+ import { deriveState } from "./state.js";
15
+ import { gsdRoot } from "./paths.js";
16
+ import { findMilestoneIds } from "./milestone-ids.js";
17
+ import { loadQueueOrder, validateQueueOrder } from "./queue-order.js";
18
+ import { isParked, getParkedReason } from "./milestone-actions.js";
19
+ import { getMilestoneSlices, isDbAvailable } from "./gsd-db.js";
20
+ import { buildExistingMilestonesContext } from "./guided-flow-queue.js";
21
+ import { loadPrompt } from "./prompt-loader.js";
22
+
23
+ // ─── Entry Point ──────────────────────────────────────────────────────────────
24
+
25
+ export async function handleRethink(
26
+ _args: string,
27
+ ctx: ExtensionCommandContext,
28
+ pi: ExtensionAPI,
29
+ ): Promise<void> {
30
+ if (isAutoActive()) {
31
+ ctx.ui.notify("Cannot rethink while auto-mode is active. Stop auto-mode first.", "error");
32
+ return;
33
+ }
34
+
35
+ const basePath = process.cwd();
36
+ const root = gsdRoot(basePath);
37
+ if (!existsSync(root)) {
38
+ ctx.ui.notify("No GSD project found. Run /gsd init first.", "warning");
39
+ return;
40
+ }
41
+
42
+ ctx.ui.notify("Building project snapshot for rethink...", "info");
43
+
44
+ const state = await deriveState(basePath);
45
+ const milestoneIds = findMilestoneIds(basePath);
46
+
47
+ if (milestoneIds.length === 0) {
48
+ ctx.ui.notify("No milestones exist yet. Nothing to rethink.", "warning");
49
+ return;
50
+ }
51
+
52
+ const queueOrder = loadQueueOrder(basePath);
53
+ const rethinkData = buildRethinkData(basePath, milestoneIds, state, queueOrder);
54
+ const existingMilestonesContext = await buildExistingMilestonesContext(basePath, milestoneIds, state);
55
+
56
+ const content = loadPrompt("rethink", {
57
+ rethinkData,
58
+ existingMilestonesContext,
59
+ });
60
+
61
+ pi.sendMessage(
62
+ { customType: "gsd-rethink", content, display: false },
63
+ { triggerTurn: true },
64
+ );
65
+ }
66
+
67
+ // ─── Data Builder ─────────────────────────────────────────────────────────────
68
+
69
+ function buildRethinkData(
70
+ basePath: string,
71
+ milestoneIds: string[],
72
+ state: Awaited<ReturnType<typeof deriveState>>,
73
+ queueOrder: string[] | null,
74
+ ): string {
75
+ const lines: string[] = [];
76
+ const dbAvailable = isDbAvailable();
77
+
78
+ // ── Summary stats ───────────────────────────────────────────────────
79
+ const counts = { complete: 0, active: 0, pending: 0, parked: 0 };
80
+ for (const entry of state.registry) {
81
+ if (entry.status in counts) counts[entry.status as keyof typeof counts]++;
82
+ }
83
+
84
+ lines.push("### Summary");
85
+ lines.push(`${counts.complete} complete, ${counts.active} active, ${counts.pending} pending, ${counts.parked} parked — ${milestoneIds.length} total`);
86
+ lines.push(`Queue order source: ${queueOrder ? "explicit QUEUE-ORDER.json" : "default numeric (by ID)"}`);
87
+ if (state.activeMilestone) {
88
+ lines.push(`Active milestone: ${state.activeMilestone}`);
89
+ }
90
+ lines.push("");
91
+
92
+ // ── Milestone table ─────────────────────────────────────────────────
93
+ lines.push("### Execution Order");
94
+ lines.push("");
95
+ lines.push("| # | ID | Title | Status | Dependencies | Slices |");
96
+ lines.push("|---|-----|-------|--------|--------------|--------|");
97
+
98
+ for (let i = 0; i < milestoneIds.length; i++) {
99
+ const mid = milestoneIds[i];
100
+ const entry = state.registry.find(m => m.id === mid);
101
+ const title = entry?.title ?? mid;
102
+ const status = entry?.status ?? "unknown";
103
+ const deps = entry?.dependsOn?.length ? entry.dependsOn.join(", ") : "—";
104
+
105
+ let sliceInfo = "—";
106
+ if (dbAvailable && status !== "complete") {
107
+ const slices = getMilestoneSlices(mid);
108
+ if (slices.length > 0) {
109
+ const done = slices.filter(s => s.status === "complete").length;
110
+ sliceInfo = `${done}/${slices.length} complete`;
111
+ }
112
+ }
113
+
114
+ // Add parked reason if applicable
115
+ let statusDisplay = status;
116
+ if (status === "parked") {
117
+ const reason = getParkedReason(basePath, mid);
118
+ if (reason) statusDisplay = `parked (${reason})`;
119
+ }
120
+
121
+ lines.push(`| ${i + 1} | ${mid} | ${title} | ${statusDisplay} | ${deps} | ${sliceInfo} |`);
122
+ }
123
+
124
+ // ── Dependency validation ───────────────────────────────────────────
125
+ const pendingIds = milestoneIds.filter(mid => {
126
+ const entry = state.registry.find(m => m.id === mid);
127
+ return entry?.status !== "complete";
128
+ });
129
+
130
+ const completedIds = new Set(
131
+ state.registry.filter(m => m.status === "complete").map(m => m.id),
132
+ );
133
+
134
+ const depsMap = new Map<string, string[]>();
135
+ for (const entry of state.registry) {
136
+ if (entry.dependsOn?.length) {
137
+ depsMap.set(entry.id, entry.dependsOn);
138
+ }
139
+ }
140
+
141
+ if (pendingIds.length > 0 && depsMap.size > 0) {
142
+ const validation = validateQueueOrder(pendingIds, depsMap, completedIds);
143
+
144
+ if (validation.violations.length > 0) {
145
+ lines.push("");
146
+ lines.push("### Dependency Issues");
147
+ for (const v of validation.violations) {
148
+ lines.push(`- **${v.type}**: ${v.message}`);
149
+ }
150
+ }
151
+ }
152
+
153
+ return lines.join("\n");
154
+ }
@@ -48,6 +48,7 @@ import {
48
48
  getSliceTasks,
49
49
  getReplanHistory,
50
50
  getSlice,
51
+ insertMilestone,
51
52
  type MilestoneRow,
52
53
  type SliceRow,
53
54
  type TaskRow,
@@ -257,7 +258,46 @@ function isStatusDone(status: string): boolean {
257
258
  export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
258
259
  const requirements = parseRequirementCounts(await loadFile(resolveGsdRootFile(basePath, "REQUIREMENTS")));
259
260
 
260
- const allMilestones = getAllMilestones();
261
+ let allMilestones = getAllMilestones();
262
+
263
+ // Incremental disk→DB sync: milestone directories created outside the DB
264
+ // write path (via /gsd queue, manual mkdir, or complete-milestone writing the
265
+ // next CONTEXT.md) are never inserted by the initial migration guard in
266
+ // auto-start.ts because that guard only runs when gsd.db doesn't exist yet.
267
+ // Reconcile here so deriveStateFromDb never silently misses queued milestones.
268
+ // insertMilestone uses INSERT OR IGNORE, so this is safe to call every time.
269
+ const dbIdSet = new Set(allMilestones.map(m => m.id));
270
+ const diskIds = findMilestoneIds(basePath);
271
+ let synced = false;
272
+ for (const diskId of diskIds) {
273
+ if (!dbIdSet.has(diskId) && !isGhostMilestone(basePath, diskId)) {
274
+ insertMilestone({ id: diskId, status: 'active' });
275
+ synced = true;
276
+ }
277
+ }
278
+ if (synced) allMilestones = getAllMilestones();
279
+
280
+ // Reconcile: discover milestones that exist on disk but are missing from
281
+ // the DB. This happens when milestones were created before the DB migration
282
+ // or were manually added to the filesystem. Without this, disk-only
283
+ // milestones are invisible after migration (#2416).
284
+ const dbMilestoneIds = new Set(allMilestones.map(m => m.id));
285
+ const diskMilestoneIds = findMilestoneIds(basePath);
286
+ for (const diskId of diskMilestoneIds) {
287
+ if (!dbMilestoneIds.has(diskId)) {
288
+ // Synthesize a minimal MilestoneRow for the disk-only milestone.
289
+ // Title and status will be resolved from disk files in the loop below.
290
+ allMilestones.push({
291
+ id: diskId,
292
+ title: diskId,
293
+ status: 'active',
294
+ depends_on: [] as string[],
295
+ created_at: new Date().toISOString(),
296
+ } as MilestoneRow);
297
+ }
298
+ }
299
+ // Re-sort so milestones are in canonical order after injection
300
+ allMilestones.sort((a, b) => milestoneIdSort(a.id, b.id));
261
301
 
262
302
  // Parallel worker isolation: when locked, filter to just the locked milestone
263
303
  const milestoneLock = process.env.GSD_MILESTONE_LOCK;
@@ -0,0 +1,88 @@
1
+ /**
2
+ * auto-pr-bugs.test.ts — Regression tests for #2302.
3
+ *
4
+ * Three interacting bugs prevented auto_pr from ever creating a PR:
5
+ * 1. auto_pr was gated on `pushed` (which requires auto_push)
6
+ * 2. Milestone branch was not pushed to remote before PR creation
7
+ * 3. createDraftPR in git-service.ts lacked --head/--base parameters
8
+ */
9
+
10
+ import test from "node:test";
11
+ import assert from "node:assert/strict";
12
+ import { readFileSync } from "node:fs";
13
+ import { join } from "node:path";
14
+
15
+ // ─── Bug 1: auto_pr should not depend on auto_push / pushed flag ────────────
16
+
17
+ const autoWorktreeSrcPath = join(import.meta.dirname, "..", "auto-worktree.ts");
18
+ const autoWorktreeSrc = readFileSync(autoWorktreeSrcPath, "utf-8");
19
+
20
+ test("#2302 bug 1: auto_pr condition should not require pushed flag", () => {
21
+ // Find the auto_pr block in mergeMilestoneToMain
22
+ const autoPrIdx = autoWorktreeSrc.indexOf("auto_pr");
23
+ assert.ok(autoPrIdx !== -1, "auto_pr reference exists in auto-worktree.ts");
24
+
25
+ // Get context around the auto_pr check
26
+ const lineStart = autoWorktreeSrc.lastIndexOf("\n", autoPrIdx) + 1;
27
+ const lineEnd = autoWorktreeSrc.indexOf("\n", autoPrIdx);
28
+ const autoPrLine = autoWorktreeSrc.slice(lineStart, lineEnd);
29
+
30
+ // The condition should NOT include `&& pushed`
31
+ assert.ok(
32
+ !autoPrLine.includes("&& pushed"),
33
+ "auto_pr condition should not be gated on pushed flag (auto_push dependency)",
34
+ );
35
+ });
36
+
37
+ // ─── Bug 2: phases.ts should not duplicate PR creation ──────────────────────
38
+
39
+ const phasesSrcPath = join(import.meta.dirname, "..", "auto", "phases.ts");
40
+ const phasesSrc = readFileSync(phasesSrcPath, "utf-8");
41
+
42
+ test("#2302 bug 2: phases.ts should not call createDraftPR (handled by mergeMilestoneToMain)", () => {
43
+ // After fix, phases.ts should not import or call createDraftPR because
44
+ // PR creation is handled inside mergeMilestoneToMain in auto-worktree.ts
45
+ const createDraftPRCalls = phasesSrc.match(/createDraftPR\(/g) || [];
46
+
47
+ assert.equal(
48
+ createDraftPRCalls.length,
49
+ 0,
50
+ "phases.ts should not call createDraftPR — it's handled by mergeMilestoneToMain",
51
+ );
52
+ });
53
+
54
+ // ─── Bug 3: createDraftPR should accept head and base branch parameters ─────
55
+
56
+ const gitServiceSrcPath = join(import.meta.dirname, "..", "git-service.ts");
57
+ const gitServiceSrc = readFileSync(gitServiceSrcPath, "utf-8");
58
+
59
+ test("#2302 bug 3: createDraftPR should accept head and base branch parameters", () => {
60
+ // Find the createDraftPR function signature
61
+ const fnIdx = gitServiceSrc.indexOf("function createDraftPR");
62
+ assert.ok(fnIdx !== -1, "createDraftPR function exists");
63
+
64
+ // Get the function signature (up to the closing paren)
65
+ const sigEnd = gitServiceSrc.indexOf(")", fnIdx);
66
+ const signature = gitServiceSrc.slice(fnIdx, sigEnd);
67
+
68
+ // Should have head and base parameters
69
+ assert.ok(
70
+ signature.includes("head") || signature.includes("branch"),
71
+ "createDraftPR should accept a head/branch parameter",
72
+ );
73
+ });
74
+
75
+ test("#2302 bug 3: createDraftPR should pass --head and --base to gh pr create", () => {
76
+ const fnIdx = gitServiceSrc.indexOf("function createDraftPR");
77
+ const fnEnd = gitServiceSrc.indexOf("\n}", fnIdx);
78
+ const fnBody = gitServiceSrc.slice(fnIdx, fnEnd);
79
+
80
+ assert.ok(
81
+ fnBody.includes("--head"),
82
+ "createDraftPR should pass --head to gh pr create",
83
+ );
84
+ assert.ok(
85
+ fnBody.includes("--base"),
86
+ "createDraftPR should pass --base to gh pr create",
87
+ );
88
+ });
@@ -0,0 +1,114 @@
1
+ /**
2
+ * completed-units-metrics-sync.test.ts — Regression tests for #2313.
3
+ *
4
+ * 1. completed-units.json should be archived (not wiped) on milestone transition
5
+ * 2. metrics.json should be in the worktree → project root sync file list
6
+ */
7
+
8
+ import test from "node:test";
9
+ import assert from "node:assert/strict";
10
+ import { readFileSync, mkdtempSync, mkdirSync, writeFileSync, existsSync, cpSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import { tmpdir } from "node:os";
13
+
14
+ // ─── Bug 1: completed-units.json should be archived, not wiped ─────────────
15
+
16
+ const phasesSrcPath = join(import.meta.dirname, "..", "auto", "phases.ts");
17
+ const phasesSrc = readFileSync(phasesSrcPath, "utf-8");
18
+
19
+ test("#2313: completed-units.json should not be blindly wiped to [] on milestone transition", () => {
20
+ // The milestone transition block should NOT write an empty array to completed-units.json
21
+ // without first archiving the existing data. Look for the archive/rename pattern.
22
+ const transitionIdx = phasesSrc.indexOf("Milestone transition");
23
+ assert.ok(transitionIdx !== -1, "Milestone transition section exists");
24
+
25
+ // Find the completed-units handling block
26
+ const completedUnitsIdx = phasesSrc.indexOf("completed-units", transitionIdx);
27
+ assert.ok(completedUnitsIdx !== -1, "completed-units handling exists in transition");
28
+
29
+ // Get a window around the completed-units handling (1200 chars to
30
+ // accommodate CRLF line endings on Windows which inflate byte offsets).
31
+ const windowStart = Math.max(0, completedUnitsIdx - 300);
32
+ const windowEnd = Math.min(phasesSrc.length, completedUnitsIdx + 900);
33
+ const window = phasesSrc.slice(windowStart, windowEnd).toLowerCase();
34
+
35
+ // Should archive/rename the old file before resetting
36
+ const hasArchive = window.includes("archive") ||
37
+ window.includes("rename") ||
38
+ window.includes("cpsync") ||
39
+ window.includes("safecopy") ||
40
+ window.includes("completed-units-");
41
+
42
+ assert.ok(
43
+ hasArchive,
44
+ "completed-units.json should be archived before reset during milestone transition",
45
+ );
46
+ });
47
+
48
+ // ─── Bug 2: metrics.json should be in the sync file lists ──────────────────
49
+
50
+ test("#2313: syncStateToProjectRoot should sync metrics.json", () => {
51
+ const syncSrcPath = join(import.meta.dirname, "..", "auto-worktree-sync.ts");
52
+ const syncSrc = readFileSync(syncSrcPath, "utf-8");
53
+
54
+ // syncStateToProjectRoot should copy metrics.json from worktree to project root
55
+ assert.ok(
56
+ syncSrc.includes("metrics.json"),
57
+ "auto-worktree-sync.ts should reference metrics.json for sync",
58
+ );
59
+ });
60
+
61
+ test("#2313: syncWorktreeStateBack should include metrics.json in root files list", () => {
62
+ const autoWorktreeSrcPath = join(import.meta.dirname, "..", "auto-worktree.ts");
63
+ const autoWorktreeSrc = readFileSync(autoWorktreeSrcPath, "utf-8");
64
+
65
+ // Find the rootFiles array in syncWorktreeStateBack
66
+ const syncBackIdx = autoWorktreeSrc.indexOf("syncWorktreeStateBack");
67
+ assert.ok(syncBackIdx !== -1, "syncWorktreeStateBack exists");
68
+
69
+ const rootFilesIdx = autoWorktreeSrc.indexOf("rootFiles", syncBackIdx);
70
+ assert.ok(rootFilesIdx !== -1, "rootFiles list exists in syncWorktreeStateBack");
71
+
72
+ // Get the rootFiles array content
73
+ const arrayStart = autoWorktreeSrc.indexOf("[", rootFilesIdx);
74
+ const arrayEnd = autoWorktreeSrc.indexOf("]", arrayStart);
75
+ const rootFilesBlock = autoWorktreeSrc.slice(arrayStart, arrayEnd);
76
+
77
+ assert.ok(
78
+ rootFilesBlock.includes("metrics.json"),
79
+ "metrics.json should be in syncWorktreeStateBack rootFiles list",
80
+ );
81
+ });
82
+
83
+ // ─── Functional test: completed-units archive ────────────────────────────────
84
+
85
+ test("#2313: functional — completed-units archive creates milestone-specific file", () => {
86
+ const tmpBase = mkdtempSync(join(tmpdir(), "gsd-completed-units-"));
87
+ const gsdDir = join(tmpBase, ".gsd");
88
+ mkdirSync(gsdDir, { recursive: true });
89
+
90
+ // Simulate existing completed-units.json with data
91
+ const existing = [
92
+ { type: "task", id: "T01" },
93
+ { type: "slice", id: "S01" },
94
+ ];
95
+ const completedKeysPath = join(gsdDir, "completed-units.json");
96
+ writeFileSync(completedKeysPath, JSON.stringify(existing, null, 2));
97
+
98
+ // Simulate the archive behavior: copy to milestone-specific file
99
+ const milestoneId = "M001";
100
+ const archivePath = join(gsdDir, `completed-units-${milestoneId}.json`);
101
+ cpSync(completedKeysPath, archivePath);
102
+
103
+ // Reset the main file
104
+ writeFileSync(completedKeysPath, JSON.stringify([], null, 2));
105
+
106
+ // Verify archive exists with original data
107
+ assert.ok(existsSync(archivePath), "archive file should exist");
108
+ const archived = JSON.parse(readFileSync(archivePath, "utf-8"));
109
+ assert.deepEqual(archived, existing, "archived data should match original");
110
+
111
+ // Verify main file is reset
112
+ const current = JSON.parse(readFileSync(completedKeysPath, "utf-8"));
113
+ assert.deepEqual(current, [], "current completed-units should be empty after transition");
114
+ });
@@ -483,6 +483,85 @@ describe('db-writer', () => {
483
483
  }
484
484
  });
485
485
 
486
+ test('saveArtifactToDb — shrinkage guard preserves larger existing file', async () => {
487
+ const tmpDir = makeTmpDir();
488
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
489
+ openDatabase(dbPath);
490
+
491
+ try {
492
+ const fullContent = '# Full Research\n\n' + 'x'.repeat(20000) + '\n';
493
+ const abbreviatedContent = '# Summary\n\nShort version.\n';
494
+
495
+ // Pre-create the file with full content (simulating a prior `write` tool call)
496
+ const relPath = 'milestones/M001/M001-RESEARCH.md';
497
+ const filePath = path.join(tmpDir, '.gsd', relPath);
498
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
499
+ fs.writeFileSync(filePath, fullContent);
500
+
501
+ // Call saveArtifactToDb with abbreviated content — should trigger shrinkage guard
502
+ await saveArtifactToDb({
503
+ path: relPath,
504
+ artifact_type: 'RESEARCH',
505
+ content: abbreviatedContent,
506
+ milestone_id: 'M001',
507
+ }, tmpDir);
508
+
509
+ // Disk file should be preserved (not overwritten)
510
+ assert.deepStrictEqual(
511
+ fs.readFileSync(filePath, 'utf-8'),
512
+ fullContent,
513
+ 'disk file preserved — shrinkage guard prevented overwrite',
514
+ );
515
+
516
+ // DB should contain the full disk content, not the abbreviated content
517
+ const adapter = _getAdapter();
518
+ const row = adapter!
519
+ .prepare('SELECT full_content FROM artifacts WHERE path = ?')
520
+ .get(relPath);
521
+ assert.deepStrictEqual(
522
+ row!['full_content'],
523
+ fullContent,
524
+ 'DB stores the richer disk content instead of abbreviated content',
525
+ );
526
+ } finally {
527
+ closeDatabase();
528
+ cleanupDir(tmpDir);
529
+ }
530
+ });
531
+
532
+ test('saveArtifactToDb — allows overwrite when new content is similar size', async () => {
533
+ const tmpDir = makeTmpDir();
534
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
535
+ openDatabase(dbPath);
536
+
537
+ try {
538
+ const oldContent = '# Summary v1\n\nOriginal content here.\n';
539
+ const newContent = '# Summary v2\n\nUpdated content here with more details.\n';
540
+
541
+ const relPath = 'milestones/M001/M001-SUMMARY.md';
542
+ const filePath = path.join(tmpDir, '.gsd', relPath);
543
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
544
+ fs.writeFileSync(filePath, oldContent);
545
+
546
+ await saveArtifactToDb({
547
+ path: relPath,
548
+ artifact_type: 'SUMMARY',
549
+ content: newContent,
550
+ milestone_id: 'M001',
551
+ }, tmpDir);
552
+
553
+ // Disk file should be updated (new content is >=50% of old size)
554
+ assert.deepStrictEqual(
555
+ fs.readFileSync(filePath, 'utf-8'),
556
+ newContent,
557
+ 'disk file updated when new content is similar size',
558
+ );
559
+ } finally {
560
+ closeDatabase();
561
+ cleanupDir(tmpDir);
562
+ }
563
+ });
564
+
486
565
  // ═══════════════════════════════════════════════════════════════════════════
487
566
  // Full Round-Trip: DB → Markdown → Parse → Compare
488
567
  // ═══════════════════════════════════════════════════════════════════════════