gsd-pi 2.44.0-dev.62b5d6c → 2.44.0-dev.73f2fd5

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 (326) hide show
  1. package/README.md +30 -12
  2. package/dist/resources/extensions/gsd/auto/infra-errors.js +3 -0
  3. package/dist/resources/extensions/gsd/auto/phases.js +36 -36
  4. package/dist/resources/extensions/gsd/auto-prompts.js +24 -1
  5. package/dist/resources/extensions/gsd/auto-start.js +10 -0
  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/bootstrap/system-context.js +46 -12
  12. package/dist/resources/extensions/gsd/commands/catalog.js +6 -1
  13. package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -0
  14. package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
  15. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +5 -0
  16. package/dist/resources/extensions/gsd/commands-mcp-status.js +187 -0
  17. package/dist/resources/extensions/gsd/db-writer.js +34 -16
  18. package/dist/resources/extensions/gsd/doctor.js +8 -0
  19. package/dist/resources/extensions/gsd/git-service.js +8 -3
  20. package/dist/resources/extensions/gsd/gsd-db.js +12 -1
  21. package/dist/resources/extensions/gsd/markdown-renderer.js +1 -1
  22. package/dist/resources/extensions/gsd/preferences.js +9 -1
  23. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -4
  24. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  25. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -6
  26. package/dist/resources/extensions/gsd/prompts/replan-slice.md +3 -14
  27. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +7 -37
  28. package/dist/resources/extensions/gsd/provider-error-pause.js +7 -0
  29. package/dist/resources/extensions/gsd/state.js +19 -2
  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-resolver.js +6 -0
  35. package/dist/resources/extensions/mcp-client/index.js +14 -0
  36. package/dist/web/standalone/.next/BUILD_ID +1 -1
  37. package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
  38. package/dist/web/standalone/.next/build-manifest.json +2 -2
  39. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  40. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  41. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  49. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/index.html +1 -1
  57. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
  64. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  65. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  66. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  67. package/package.json +1 -1
  68. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +3 -1
  69. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  70. package/packages/pi-coding-agent/dist/core/auth-storage.js +15 -1
  71. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  72. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +6 -8
  73. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  74. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +24 -26
  75. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
  76. package/packages/pi-coding-agent/dist/core/fs-utils.test.js +29 -48
  77. package/packages/pi-coding-agent/dist/core/fs-utils.test.js.map +1 -1
  78. package/packages/pi-coding-agent/dist/core/local-model-check.d.ts +15 -0
  79. package/packages/pi-coding-agent/dist/core/local-model-check.d.ts.map +1 -0
  80. package/packages/pi-coding-agent/dist/core/local-model-check.js +41 -0
  81. package/packages/pi-coding-agent/dist/core/local-model-check.js.map +1 -0
  82. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +11 -0
  83. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  84. package/packages/pi-coding-agent/dist/core/model-registry.js +20 -1
  85. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  86. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js +34 -44
  87. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js.map +1 -1
  88. package/packages/pi-coding-agent/dist/core/session-manager.test.js +30 -34
  89. package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
  90. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
  91. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  92. package/packages/pi-coding-agent/dist/core/settings-manager.js +6 -0
  93. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  94. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +10 -12
  95. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -1
  96. package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
  97. package/packages/pi-coding-agent/dist/main.js +17 -0
  98. package/packages/pi-coding-agent/dist/main.js.map +1 -1
  99. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.d.ts +2 -0
  100. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.d.ts.map +1 -0
  101. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.js +32 -0
  102. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.js.map +1 -0
  103. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts +3 -1
  104. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  105. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +8 -1
  106. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  108. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  109. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +12 -0
  110. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  111. package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.d.ts +15 -0
  112. package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.d.ts.map +1 -0
  113. package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.js +40 -0
  114. package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.js.map +1 -0
  115. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  116. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +4 -1
  117. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts +5 -2
  119. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  120. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js +13 -2
  121. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  123. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +17 -8
  124. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  125. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  126. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +7 -3
  127. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  128. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js +43 -47
  129. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js.map +1 -1
  130. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +7 -7
  131. package/packages/pi-coding-agent/src/core/auth-storage.ts +15 -1
  132. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +26 -26
  133. package/packages/pi-coding-agent/src/core/fs-utils.test.ts +31 -43
  134. package/packages/pi-coding-agent/src/core/local-model-check.ts +45 -0
  135. package/packages/pi-coding-agent/src/core/model-registry.ts +21 -1
  136. package/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +40 -45
  137. package/packages/pi-coding-agent/src/core/session-manager.test.ts +33 -33
  138. package/packages/pi-coding-agent/src/core/settings-manager.ts +9 -0
  139. package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +17 -17
  140. package/packages/pi-coding-agent/src/main.ts +19 -0
  141. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/timestamp.test.ts +38 -0
  142. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +10 -0
  143. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +15 -0
  144. package/packages/pi-coding-agent/src/modes/interactive/components/timestamp.ts +48 -0
  145. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +3 -1
  146. package/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts +18 -3
  147. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +16 -7
  148. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +8 -1
  149. package/packages/pi-coding-agent/src/resources/extensions/memory/storage.test.ts +74 -74
  150. package/src/resources/extensions/gsd/auto/infra-errors.ts +3 -0
  151. package/src/resources/extensions/gsd/auto/phases.ts +45 -48
  152. package/src/resources/extensions/gsd/auto-prompts.ts +24 -1
  153. package/src/resources/extensions/gsd/auto-start.ts +14 -0
  154. package/src/resources/extensions/gsd/auto-timers.ts +64 -3
  155. package/src/resources/extensions/gsd/auto-worktree-sync.ts +5 -0
  156. package/src/resources/extensions/gsd/auto-worktree.ts +9 -6
  157. package/src/resources/extensions/gsd/auto.ts +37 -3
  158. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +148 -0
  159. package/src/resources/extensions/gsd/bootstrap/system-context.ts +48 -11
  160. package/src/resources/extensions/gsd/commands/catalog.ts +6 -1
  161. package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -0
  162. package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
  163. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +8 -0
  164. package/src/resources/extensions/gsd/commands-mcp-status.ts +247 -0
  165. package/src/resources/extensions/gsd/db-writer.ts +39 -17
  166. package/src/resources/extensions/gsd/doctor.ts +7 -1
  167. package/src/resources/extensions/gsd/git-service.ts +6 -2
  168. package/src/resources/extensions/gsd/gsd-db.ts +16 -1
  169. package/src/resources/extensions/gsd/markdown-renderer.ts +1 -1
  170. package/src/resources/extensions/gsd/preferences.ts +11 -1
  171. package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -4
  172. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  173. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -6
  174. package/src/resources/extensions/gsd/prompts/replan-slice.md +3 -14
  175. package/src/resources/extensions/gsd/prompts/validate-milestone.md +7 -37
  176. package/src/resources/extensions/gsd/provider-error-pause.ts +9 -0
  177. package/src/resources/extensions/gsd/state.ts +19 -1
  178. package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +99 -99
  179. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +14 -16
  180. package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +43 -57
  181. package/src/resources/extensions/gsd/tests/auto-pr-bugs.test.ts +88 -0
  182. package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +11 -13
  183. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +465 -523
  184. package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +73 -75
  185. package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +34 -56
  186. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +533 -656
  187. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +165 -143
  188. package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +29 -52
  189. package/src/resources/extensions/gsd/tests/captures.test.ts +148 -176
  190. package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +32 -33
  191. package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +141 -143
  192. package/src/resources/extensions/gsd/tests/commands-inspect-open-db.test.ts +25 -25
  193. package/src/resources/extensions/gsd/tests/commands-logs.test.ts +81 -81
  194. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +38 -59
  195. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +228 -263
  196. package/src/resources/extensions/gsd/tests/complete-task.test.ts +250 -302
  197. package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +114 -0
  198. package/src/resources/extensions/gsd/tests/context-store.test.ts +354 -367
  199. package/src/resources/extensions/gsd/tests/continue-here.test.ts +68 -72
  200. package/src/resources/extensions/gsd/tests/cost-projection.test.ts +92 -106
  201. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +27 -35
  202. package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +220 -237
  203. package/src/resources/extensions/gsd/tests/db-writer.test.ts +465 -416
  204. package/src/resources/extensions/gsd/tests/definition-loader.test.ts +76 -92
  205. package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +68 -83
  206. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +210 -181
  207. package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +78 -101
  208. package/src/resources/extensions/gsd/tests/derive-state.test.ts +192 -227
  209. package/src/resources/extensions/gsd/tests/detection.test.ts +232 -278
  210. package/src/resources/extensions/gsd/tests/dev-engine-wrapper.test.ts +30 -34
  211. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +164 -180
  212. package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +43 -49
  213. package/src/resources/extensions/gsd/tests/dispatch-uat-last-completed.test.ts +28 -32
  214. package/src/resources/extensions/gsd/tests/doctor-completion-deferral.test.ts +27 -29
  215. package/src/resources/extensions/gsd/tests/doctor-delimiter-fix.test.ts +34 -38
  216. package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +54 -75
  217. package/src/resources/extensions/gsd/tests/doctor-environment-worktree.test.ts +21 -32
  218. package/src/resources/extensions/gsd/tests/doctor-environment.test.ts +72 -97
  219. package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +38 -44
  220. package/src/resources/extensions/gsd/tests/doctor-git.test.ts +104 -145
  221. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +84 -106
  222. package/src/resources/extensions/gsd/tests/doctor-roadmap-summary-atomicity.test.ts +54 -60
  223. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +72 -93
  224. package/src/resources/extensions/gsd/tests/doctor.test.ts +104 -134
  225. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +123 -131
  226. package/src/resources/extensions/gsd/tests/est-annotation-timeout.test.ts +120 -0
  227. package/src/resources/extensions/gsd/tests/exit-command.test.ts +20 -24
  228. package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +48 -57
  229. package/src/resources/extensions/gsd/tests/files-loadfile-eisdir.test.ts +5 -7
  230. package/src/resources/extensions/gsd/tests/flag-file-db.test.ts +30 -42
  231. package/src/resources/extensions/gsd/tests/freeform-decisions.test.ts +198 -206
  232. package/src/resources/extensions/gsd/tests/git-locale.test.ts +13 -27
  233. package/src/resources/extensions/gsd/tests/git-service.test.ts +285 -388
  234. package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +31 -39
  235. package/src/resources/extensions/gsd/tests/graph-operations.test.ts +63 -69
  236. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +255 -264
  237. package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +108 -119
  238. package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +81 -103
  239. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +229 -262
  240. package/src/resources/extensions/gsd/tests/headless-answers.test.ts +13 -13
  241. package/src/resources/extensions/gsd/tests/health-widget.test.ts +29 -37
  242. package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +81 -102
  243. package/src/resources/extensions/gsd/tests/infra-error.test.ts +20 -2
  244. package/src/resources/extensions/gsd/tests/init-wizard.test.ts +16 -18
  245. package/src/resources/extensions/gsd/tests/integration-edge.test.ts +41 -46
  246. package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +42 -53
  247. package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +75 -91
  248. package/src/resources/extensions/gsd/tests/integration-proof.test.ts +18 -18
  249. package/src/resources/extensions/gsd/tests/knowledge.test.ts +89 -0
  250. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +150 -194
  251. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +103 -0
  252. package/src/resources/extensions/gsd/tests/md-importer.test.ts +101 -125
  253. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +45 -54
  254. package/src/resources/extensions/gsd/tests/memory-store.test.ts +80 -93
  255. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +66 -0
  256. package/src/resources/extensions/gsd/tests/migrate-command.test.ts +57 -66
  257. package/src/resources/extensions/gsd/tests/migrate-hierarchy.test.ts +83 -93
  258. package/src/resources/extensions/gsd/tests/migrate-parser.test.ts +161 -170
  259. package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +125 -141
  260. package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +107 -131
  261. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +87 -96
  262. package/src/resources/extensions/gsd/tests/migrate-writer.test.ts +125 -164
  263. package/src/resources/extensions/gsd/tests/must-have-parser.test.ts +81 -94
  264. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +35 -36
  265. package/src/resources/extensions/gsd/tests/overrides.test.ts +99 -106
  266. package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +40 -47
  267. package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +25 -28
  268. package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +66 -83
  269. package/src/resources/extensions/gsd/tests/park-edge-cases.test.ts +54 -77
  270. package/src/resources/extensions/gsd/tests/park-milestone.test.ts +68 -115
  271. package/src/resources/extensions/gsd/tests/parsers.test.ts +546 -611
  272. package/src/resources/extensions/gsd/tests/paths.test.ts +72 -87
  273. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +77 -117
  274. package/src/resources/extensions/gsd/tests/preferences.test.ts +27 -0
  275. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +11 -7
  276. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +56 -56
  277. package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +93 -119
  278. package/src/resources/extensions/gsd/tests/queue-order.test.ts +70 -82
  279. package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +42 -55
  280. package/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts +100 -0
  281. package/src/resources/extensions/gsd/tests/quick-branch-lifecycle.test.ts +45 -73
  282. package/src/resources/extensions/gsd/tests/reassess-prompt.test.ts +28 -38
  283. package/src/resources/extensions/gsd/tests/replan-slice.test.ts +73 -80
  284. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +71 -74
  285. package/src/resources/extensions/gsd/tests/requirements.test.ts +70 -75
  286. package/src/resources/extensions/gsd/tests/retry-state-reset.test.ts +44 -66
  287. package/src/resources/extensions/gsd/tests/roadmap-parse-regression.test.ts +114 -181
  288. package/src/resources/extensions/gsd/tests/rule-registry.test.ts +63 -65
  289. package/src/resources/extensions/gsd/tests/run-uat.test.ts +66 -128
  290. package/src/resources/extensions/gsd/tests/session-lock-multipath.test.ts +18 -25
  291. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +37 -44
  292. package/src/resources/extensions/gsd/tests/shared-wal.test.ts +19 -26
  293. package/src/resources/extensions/gsd/tests/sqlite-unavailable-gate.test.ts +63 -0
  294. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +6 -8
  295. package/src/resources/extensions/gsd/tests/stop-auto-merge-back.test.ts +67 -0
  296. package/src/resources/extensions/gsd/tests/symlink-numbered-variants.test.ts +22 -28
  297. package/src/resources/extensions/gsd/tests/terminated-transient.test.ts +49 -0
  298. package/src/resources/extensions/gsd/tests/token-savings.test.ts +54 -56
  299. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +23 -25
  300. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +10 -11
  301. package/src/resources/extensions/gsd/tests/unique-milestone-ids.test.ts +66 -82
  302. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +46 -47
  303. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -22
  304. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +84 -86
  305. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +41 -43
  306. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +94 -96
  307. package/src/resources/extensions/gsd/tests/windows-path-normalization.test.ts +11 -13
  308. package/src/resources/extensions/gsd/tests/worker-registry.test.ts +27 -29
  309. package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +50 -52
  310. package/src/resources/extensions/gsd/tests/worktree-bugfix.test.ts +10 -13
  311. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +14 -18
  312. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +38 -39
  313. package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +17 -21
  314. package/src/resources/extensions/gsd/tests/worktree-health.test.ts +25 -30
  315. package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +30 -37
  316. package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +15 -22
  317. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +59 -66
  318. package/src/resources/extensions/gsd/tests/worktree.test.ts +44 -50
  319. package/src/resources/extensions/gsd/tools/plan-slice.ts +2 -0
  320. package/src/resources/extensions/gsd/tools/plan-task.ts +2 -0
  321. package/src/resources/extensions/gsd/tools/replan-slice.ts +3 -0
  322. package/src/resources/extensions/gsd/tools/validate-milestone.ts +127 -0
  323. package/src/resources/extensions/gsd/worktree-resolver.ts +7 -0
  324. package/src/resources/extensions/mcp-client/index.ts +20 -0
  325. /package/dist/web/standalone/.next/static/{fOnWQBjWXMKUs4bqTg530 → kxxAA66bah_yhPYqLBHE2}/_buildManifest.js +0 -0
  326. /package/dist/web/standalone/.next/static/{fOnWQBjWXMKUs4bqTg530 → kxxAA66bah_yhPYqLBHE2}/_ssgManifest.js +0 -0
@@ -64,17 +64,12 @@ export async function buildBeforeAgentStartResult(
64
64
  }
65
65
  }
66
66
 
67
- let knowledgeBlock = "";
68
- const knowledgePath = resolveGsdRootFile(process.cwd(), "KNOWLEDGE");
69
- if (existsSync(knowledgePath)) {
70
- try {
71
- const content = readFileSync(knowledgePath, "utf-8").trim();
72
- if (content) {
73
- knowledgeBlock = `\n\n[PROJECT KNOWLEDGE — Rules, patterns, and lessons learned]\n\n${content}`;
74
- }
75
- } catch {
76
- // skip
77
- }
67
+ const { block: knowledgeBlock, globalSizeKb } = loadKnowledgeBlock(gsdHome, process.cwd());
68
+ if (globalSizeKb > 4) {
69
+ ctx.ui.notify(
70
+ `GSD: ~/.gsd/agent/KNOWLEDGE.md is ${globalSizeKb.toFixed(1)}KB — consider trimming to keep system prompt lean.`,
71
+ "warning",
72
+ );
78
73
  }
79
74
 
80
75
  let memoryBlock = "";
@@ -126,6 +121,48 @@ export async function buildBeforeAgentStartResult(
126
121
  };
127
122
  }
128
123
 
124
+ export function loadKnowledgeBlock(gsdHomeDir: string, cwd: string): { block: string; globalSizeKb: number } {
125
+ // 1. Global knowledge (~/.gsd/agent/KNOWLEDGE.md) — cross-project, user-maintained
126
+ let globalKnowledge = "";
127
+ let globalSizeKb = 0;
128
+ const globalKnowledgePath = join(gsdHomeDir, "agent", "KNOWLEDGE.md");
129
+ if (existsSync(globalKnowledgePath)) {
130
+ try {
131
+ const content = readFileSync(globalKnowledgePath, "utf-8").trim();
132
+ if (content) {
133
+ globalSizeKb = Buffer.byteLength(content, "utf-8") / 1024;
134
+ globalKnowledge = content;
135
+ }
136
+ } catch {
137
+ // skip
138
+ }
139
+ }
140
+
141
+ // 2. Project knowledge (.gsd/KNOWLEDGE.md) — project-specific
142
+ let projectKnowledge = "";
143
+ const knowledgePath = resolveGsdRootFile(cwd, "KNOWLEDGE");
144
+ if (existsSync(knowledgePath)) {
145
+ try {
146
+ const content = readFileSync(knowledgePath, "utf-8").trim();
147
+ if (content) projectKnowledge = content;
148
+ } catch {
149
+ // skip
150
+ }
151
+ }
152
+
153
+ if (!globalKnowledge && !projectKnowledge) {
154
+ return { block: "", globalSizeKb: 0 };
155
+ }
156
+
157
+ const parts: string[] = [];
158
+ if (globalKnowledge) parts.push(`## Global Knowledge\n\n${globalKnowledge}`);
159
+ if (projectKnowledge) parts.push(`## Project Knowledge\n\n${projectKnowledge}`);
160
+ return {
161
+ block: `\n\n[KNOWLEDGE — Rules, patterns, and lessons learned]\n\n${parts.join("\n\n")}`,
162
+ globalSizeKb,
163
+ };
164
+ }
165
+
129
166
  function buildWorktreeContextBlock(): string {
130
167
  const worktreeName = getActiveWorktreeName();
131
168
  const worktreeMainCwd = getWorktreeOriginalCwd();
@@ -15,7 +15,7 @@ export interface GsdCommandDefinition {
15
15
  type CompletionMap = Record<string, readonly GsdCommandDefinition[]>;
16
16
 
17
17
  export const GSD_COMMAND_DESCRIPTION =
18
- "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|queue|quick|discuss|capture|triage|dispatch|history|undo|undo-task|reset-slice|rate|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|logs|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|park|unpark|init|setup|inspect|extensions|update|fast";
18
+ "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|queue|quick|discuss|capture|triage|dispatch|history|undo|undo-task|reset-slice|rate|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|logs|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|park|unpark|init|setup|inspect|extensions|update|fast|mcp";
19
19
 
20
20
  export const TOP_LEVEL_SUBCOMMANDS: readonly GsdCommandDefinition[] = [
21
21
  { cmd: "help", desc: "Categorized command reference with descriptions" },
@@ -68,6 +68,7 @@ export const TOP_LEVEL_SUBCOMMANDS: readonly GsdCommandDefinition[] = [
68
68
  { cmd: "templates", desc: "List available workflow templates" },
69
69
  { cmd: "extensions", desc: "Manage extensions (list, enable, disable, info)" },
70
70
  { cmd: "fast", desc: "Toggle OpenAI service tier (on/off/flex/status)" },
71
+ { cmd: "mcp", desc: "MCP server status and connectivity check (status, check <server>)" },
71
72
  { cmd: "workflow", desc: "Custom workflow lifecycle (new, run, list, validate, pause, resume)" },
72
73
  ];
73
74
 
@@ -187,6 +188,10 @@ const NESTED_COMPLETIONS: CompletionMap = {
187
188
  { cmd: "flex", desc: "Flex tier (0.5x cost, slower)" },
188
189
  { cmd: "status", desc: "Show current service tier setting" },
189
190
  ],
191
+ mcp: [
192
+ { cmd: "status", desc: "Show all MCP server statuses (default)" },
193
+ { cmd: "check", desc: "Detailed status for a specific server" },
194
+ ],
190
195
  doctor: [
191
196
  { cmd: "fix", desc: "Auto-fix detected issues" },
192
197
  { cmd: "heal", desc: "AI-driven deep healing" },
@@ -53,6 +53,7 @@ export function showHelp(ctx: ExtensionCommandContext): void {
53
53
  " /gsd hooks Show post-unit hook configuration",
54
54
  " /gsd extensions Manage extensions [list|enable|disable|info]",
55
55
  " /gsd fast Toggle OpenAI service tier [on|off|flex|status]",
56
+ " /gsd mcp MCP server status and connectivity [status|check <server>]",
56
57
  "",
57
58
  "MAINTENANCE",
58
59
  " /gsd doctor Diagnose and repair .gsd/ state [audit|fix|heal] [scope]",
@@ -191,6 +191,11 @@ Examples:
191
191
  await handleFast(trimmed.replace(/^fast\s*/, "").trim(), ctx);
192
192
  return true;
193
193
  }
194
+ if (trimmed === "mcp" || trimmed.startsWith("mcp ")) {
195
+ const { handleMcpStatus } = await import("../../commands-mcp-status.js");
196
+ await handleMcpStatus(trimmed.replace(/^mcp\s*/, "").trim(), ctx);
197
+ return true;
198
+ }
194
199
  if (trimmed === "extensions" || trimmed.startsWith("extensions ")) {
195
200
  const { handleExtensions } = await import("../../commands-extensions.js");
196
201
  await handleExtensions(trimmed.replace(/^extensions\s*/, "").trim(), ctx);
@@ -188,6 +188,14 @@ export async function handleWorkflowCommand(trimmed: string, ctx: ExtensionComma
188
188
  return true;
189
189
  }
190
190
  if (trimmed === "quick" || trimmed.startsWith("quick ")) {
191
+ if (isAutoActive()) {
192
+ ctx.ui.notify(
193
+ "/gsd quick cannot run while auto-mode is active.\n" +
194
+ "Stop auto-mode first with /gsd stop, then run /gsd quick.",
195
+ "error",
196
+ );
197
+ return true;
198
+ }
191
199
  await handleQuick(trimmed.replace(/^quick\s*/, "").trim(), ctx, pi);
192
200
  return true;
193
201
  }
@@ -0,0 +1,247 @@
1
+ /**
2
+ * MCP Status — `/gsd mcp` command handler.
3
+ *
4
+ * Shows configured MCP servers, their connection status, and available tools.
5
+ *
6
+ * Subcommands:
7
+ * /gsd mcp — Overview of all servers (alias: /gsd mcp status)
8
+ * /gsd mcp status — Same as bare /gsd mcp
9
+ * /gsd mcp check <srv> — Detailed status for a specific server
10
+ */
11
+
12
+ import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
13
+
14
+ import { existsSync, readFileSync } from "node:fs";
15
+ import { join } from "node:path";
16
+
17
+ // ─── Types ──────────────────────────────────────────────────────────────────
18
+
19
+ export interface McpServerStatus {
20
+ name: string;
21
+ transport: "stdio" | "http" | "unknown";
22
+ connected: boolean;
23
+ toolCount: number;
24
+ error: string | undefined;
25
+ }
26
+
27
+ export interface McpServerDetail extends McpServerStatus {
28
+ tools: string[];
29
+ }
30
+
31
+ // ─── Config reader (standalone — does not import mcp-client internals) ──────
32
+
33
+ interface McpServerRawConfig {
34
+ name: string;
35
+ transport: "stdio" | "http" | "unknown";
36
+ command?: string;
37
+ args?: string[];
38
+ url?: string;
39
+ }
40
+
41
+ function readMcpConfigs(): McpServerRawConfig[] {
42
+ const servers: McpServerRawConfig[] = [];
43
+ const seen = new Set<string>();
44
+ const configPaths = [
45
+ join(process.cwd(), ".mcp.json"),
46
+ join(process.cwd(), ".gsd", "mcp.json"),
47
+ ];
48
+
49
+ for (const configPath of configPaths) {
50
+ try {
51
+ if (!existsSync(configPath)) continue;
52
+ const raw = readFileSync(configPath, "utf-8");
53
+ const data = JSON.parse(raw) as Record<string, unknown>;
54
+ const mcpServers = (data.mcpServers ?? data.servers) as
55
+ | Record<string, Record<string, unknown>>
56
+ | undefined;
57
+ if (!mcpServers || typeof mcpServers !== "object") continue;
58
+
59
+ for (const [name, config] of Object.entries(mcpServers)) {
60
+ if (seen.has(name)) continue;
61
+ seen.add(name);
62
+
63
+ const hasCommand = typeof config.command === "string";
64
+ const hasUrl = typeof config.url === "string";
65
+ const transport: McpServerRawConfig["transport"] = hasCommand
66
+ ? "stdio"
67
+ : hasUrl
68
+ ? "http"
69
+ : "unknown";
70
+
71
+ servers.push({
72
+ name,
73
+ transport,
74
+ ...(hasCommand && {
75
+ command: config.command as string,
76
+ args: Array.isArray(config.args) ? (config.args as string[]) : undefined,
77
+ }),
78
+ ...(hasUrl && { url: config.url as string }),
79
+ });
80
+ }
81
+ } catch {
82
+ // Non-fatal — config file may not exist or be malformed
83
+ }
84
+ }
85
+
86
+ return servers;
87
+ }
88
+
89
+ // ─── Formatters (exported for testing) ──────────────────────────────────────
90
+
91
+ export function formatMcpStatusReport(servers: McpServerStatus[]): string {
92
+ if (servers.length === 0) {
93
+ return [
94
+ "No MCP servers configured.",
95
+ "",
96
+ "Add servers to .mcp.json or .gsd/mcp.json to enable MCP integrations.",
97
+ "See: https://modelcontextprotocol.io/quickstart",
98
+ ].join("\n");
99
+ }
100
+
101
+ const lines: string[] = [`MCP Server Status — ${servers.length} server(s)\n`];
102
+
103
+ for (const s of servers) {
104
+ const icon = s.error ? "✗" : s.connected ? "✓" : "○";
105
+ const status = s.error
106
+ ? `error: ${s.error}`
107
+ : s.connected
108
+ ? `connected — ${s.toolCount} tools`
109
+ : "disconnected";
110
+ lines.push(` ${icon} ${s.name} (${s.transport}) — ${status}`);
111
+ }
112
+
113
+ lines.push("");
114
+ lines.push("Use /gsd mcp check <server> for details on a specific server.");
115
+ lines.push("Use mcp_discover to connect and list tools for a server.");
116
+
117
+ return lines.join("\n");
118
+ }
119
+
120
+ export function formatMcpServerDetail(server: McpServerDetail): string {
121
+ const lines: string[] = [`MCP Server: ${server.name}\n`];
122
+
123
+ lines.push(` Transport: ${server.transport}`);
124
+
125
+ if (server.error) {
126
+ lines.push(` Status: error`);
127
+ lines.push(` Error: ${server.error}`);
128
+ } else if (server.connected) {
129
+ lines.push(` Status: connected`);
130
+ lines.push(` Tools: ${server.toolCount}`);
131
+ if (server.tools.length > 0) {
132
+ lines.push("");
133
+ lines.push(" Available tools:");
134
+ for (const tool of server.tools) {
135
+ lines.push(` - ${tool}`);
136
+ }
137
+ }
138
+ } else {
139
+ lines.push(` Status: disconnected`);
140
+ lines.push("");
141
+ lines.push(` Run mcp_discover("${server.name}") to connect and list tools.`);
142
+ }
143
+
144
+ return lines.join("\n");
145
+ }
146
+
147
+ // ─── Command handler ────────────────────────────────────────────────────────
148
+
149
+ /**
150
+ * Handle `/gsd mcp [status|check <server>]`.
151
+ */
152
+ export async function handleMcpStatus(
153
+ args: string,
154
+ ctx: ExtensionCommandContext,
155
+ ): Promise<void> {
156
+ const trimmed = args.trim().toLowerCase();
157
+ const configs = readMcpConfigs();
158
+
159
+ // /gsd mcp check <server>
160
+ if (trimmed.startsWith("check ")) {
161
+ const serverName = args.trim().slice("check ".length).trim();
162
+ const config = configs.find((c) => c.name === serverName);
163
+ if (!config) {
164
+ const available = configs.map((c) => c.name).join(", ") || "(none)";
165
+ ctx.ui.notify(
166
+ `Unknown MCP server: "${serverName}"\n\nAvailable: ${available}`,
167
+ "warning",
168
+ );
169
+ return;
170
+ }
171
+
172
+ // Try to get connection/tool info from the mcp-client module if available
173
+ let connected = false;
174
+ let toolNames: string[] = [];
175
+ let error: string | undefined;
176
+ try {
177
+ const mcpClient = await import("../mcp-client/index.js");
178
+ // Access the module's connection state if exported; fall back gracefully
179
+ const mod = mcpClient as Record<string, unknown>;
180
+ if (typeof mod.getConnectionStatus === "function") {
181
+ const status = (mod.getConnectionStatus as (name: string) => { connected: boolean; tools: string[]; error?: string })(serverName);
182
+ connected = status.connected;
183
+ toolNames = status.tools;
184
+ error = status.error;
185
+ }
186
+ } catch {
187
+ // mcp-client may not expose status helpers — that's fine
188
+ }
189
+
190
+ ctx.ui.notify(
191
+ formatMcpServerDetail({
192
+ name: config.name,
193
+ transport: config.transport,
194
+ connected,
195
+ toolCount: toolNames.length,
196
+ tools: toolNames,
197
+ error,
198
+ }),
199
+ "info",
200
+ );
201
+ return;
202
+ }
203
+
204
+ // /gsd mcp or /gsd mcp status
205
+ if (!trimmed || trimmed === "status") {
206
+ // Build status for each server
207
+ const statuses: McpServerStatus[] = [];
208
+
209
+ for (const config of configs) {
210
+ let connected = false;
211
+ let toolCount = 0;
212
+ let error: string | undefined;
213
+
214
+ try {
215
+ const mcpClient = await import("../mcp-client/index.js");
216
+ const mod = mcpClient as Record<string, unknown>;
217
+ if (typeof mod.getConnectionStatus === "function") {
218
+ const status = (mod.getConnectionStatus as (name: string) => { connected: boolean; tools: string[]; error?: string })(config.name);
219
+ connected = status.connected;
220
+ toolCount = status.tools.length;
221
+ error = status.error;
222
+ }
223
+ } catch {
224
+ // Fall back to unknown state
225
+ }
226
+
227
+ statuses.push({
228
+ name: config.name,
229
+ transport: config.transport,
230
+ connected,
231
+ toolCount,
232
+ error,
233
+ });
234
+ }
235
+
236
+ ctx.ui.notify(formatMcpStatusReport(statuses), "info");
237
+ return;
238
+ }
239
+
240
+ // Unknown subcommand
241
+ ctx.ui.notify(
242
+ "Usage: /gsd mcp [status|check <server>]\n\n" +
243
+ " status Show all MCP server statuses (default)\n" +
244
+ " check <server> Detailed status for a specific server",
245
+ "warning",
246
+ );
247
+ }
@@ -9,7 +9,7 @@
9
9
  // parseDecisionsTable() and parseRequirementsSections() with field fidelity.
10
10
 
11
11
  import { join, resolve } from 'node:path';
12
- import { readFileSync, existsSync } from 'node:fs';
12
+ import { readFileSync, existsSync, statSync } from 'node:fs';
13
13
  import type { Decision, Requirement } from './types.js';
14
14
  import { resolveGsdRootFile } from './paths.js';
15
15
  import { saveFile } from './files.js';
@@ -428,30 +428,52 @@ export async function saveArtifactToDb(
428
428
  try {
429
429
  const db = await import('./gsd-db.js');
430
430
 
431
+ // Guard against path traversal before any reads/writes
432
+ const gsdDir = resolve(basePath, '.gsd');
433
+ const fullPath = resolve(basePath, '.gsd', opts.path);
434
+ if (!fullPath.startsWith(gsdDir)) {
435
+ throw new GSDError(GSD_IO_ERROR, `saveArtifactToDb: path escapes .gsd/ directory: ${opts.path}`);
436
+ }
437
+
438
+ // Shrinkage guard: if the file already exists and the new content is
439
+ // significantly smaller (<50%), preserve the richer file on disk and
440
+ // store its content in the DB instead of the abbreviated version.
441
+ let dbContent = opts.content;
442
+ let skipDiskWrite = false;
443
+ if (existsSync(fullPath)) {
444
+ const existingSize = statSync(fullPath).size;
445
+ const newSize = Buffer.byteLength(opts.content, 'utf-8');
446
+ if (existingSize > 0 && newSize < existingSize * 0.5) {
447
+ process.stderr.write(
448
+ `gsd-db: saveArtifactToDb — new content (${newSize}B) is <50% of existing file ` +
449
+ `(${existingSize}B) at ${opts.path}. Preserving disk file to prevent data loss.\n`,
450
+ );
451
+ dbContent = readFileSync(fullPath, 'utf-8');
452
+ skipDiskWrite = true;
453
+ }
454
+ }
455
+
431
456
  db.insertArtifact({
432
457
  path: opts.path,
433
458
  artifact_type: opts.artifact_type,
434
459
  milestone_id: opts.milestone_id ?? null,
435
460
  slice_id: opts.slice_id ?? null,
436
461
  task_id: opts.task_id ?? null,
437
- full_content: opts.content,
462
+ full_content: dbContent,
438
463
  });
439
464
 
440
- // Write the file to disk (guard against path traversal)
441
- const gsdDir = resolve(basePath, '.gsd');
442
- const fullPath = resolve(basePath, '.gsd', opts.path);
443
- if (!fullPath.startsWith(gsdDir)) {
444
- throw new GSDError(GSD_IO_ERROR, `saveArtifactToDb: path escapes .gsd/ directory: ${opts.path}`);
445
- }
446
- try {
447
- await saveFile(fullPath, opts.content);
448
- } catch (diskErr) {
449
- process.stderr.write(
450
- `gsd-db: saveArtifactToDb — disk write failed, rolling back DB row: ${(diskErr as Error).message}\n`,
451
- );
452
- const rollbackAdapter = db._getAdapter();
453
- rollbackAdapter?.prepare('DELETE FROM artifacts WHERE path = :path').run({ ':path': opts.path });
454
- throw diskErr;
465
+ // Write the file to disk (only if we're not preserving a richer existing file)
466
+ if (!skipDiskWrite) {
467
+ try {
468
+ await saveFile(fullPath, opts.content);
469
+ } catch (diskErr) {
470
+ process.stderr.write(
471
+ `gsd-db: saveArtifactToDb — disk write failed, rolling back DB row: ${(diskErr as Error).message}\n`,
472
+ );
473
+ const rollbackAdapter = db._getAdapter();
474
+ rollbackAdapter?.prepare('DELETE FROM artifacts WHERE path = :path').run({ ':path': opts.path });
475
+ throw diskErr;
476
+ }
455
477
  }
456
478
  // Invalidate file-read caches so deriveState() sees the updated markdown.
457
479
  // Do NOT clear the artifacts table — we just wrote to it intentionally.
@@ -470,7 +470,7 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
470
470
  if (!roadmapContent) continue;
471
471
 
472
472
  // Normalize slices: prefer DB, fall back to parser
473
- type NormSlice = RoadmapSliceEntry;
473
+ type NormSlice = RoadmapSliceEntry & { pending?: boolean };
474
474
  let slices: NormSlice[];
475
475
  if (isDbAvailable()) {
476
476
  const dbSlices = getMilestoneSlices(milestoneId);
@@ -478,6 +478,7 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
478
478
  id: s.id,
479
479
  title: s.title,
480
480
  done: s.status === "complete",
481
+ pending: s.status === "pending",
481
482
  risk: (s.risk || "medium") as RoadmapSliceEntry["risk"],
482
483
  depends: s.depends,
483
484
  demo: s.demo,
@@ -564,6 +565,9 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
564
565
 
565
566
  const slicePath = resolveSlicePath(basePath, milestoneId, slice.id);
566
567
  if (!slicePath) {
568
+ // Pending slices haven't been planned yet — directories are created
569
+ // lazily by ensurePreconditions() at dispatch time. Skip them.
570
+ if (slice.pending) continue;
567
571
  const expectedPath = relSlicePath(basePath, milestoneId, slice.id);
568
572
  issues.push({
569
573
  severity: slice.done ? "warning" : "error",
@@ -586,6 +590,8 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
586
590
 
587
591
  const tasksDir = resolveTasksDir(basePath, milestoneId, slice.id);
588
592
  if (!tasksDir) {
593
+ // Pending slices haven't been planned yet — tasks/ is created on demand.
594
+ if (slice.pending) continue;
589
595
  issues.push({
590
596
  severity: slice.done ? "warning" : "error",
591
597
  code: "missing_tasks_dir",
@@ -684,13 +684,17 @@ export function createDraftPR(
684
684
  milestoneId: string,
685
685
  title: string,
686
686
  body: string,
687
+ opts?: { head?: string; base?: string },
687
688
  ): string | null {
688
689
  try {
689
- const result = execFileSync("gh", [
690
+ const args = [
690
691
  "pr", "create", "--draft",
691
692
  "--title", title,
692
693
  "--body", body,
693
- ], { cwd: basePath, encoding: "utf8", timeout: 30000, env: GIT_NO_PROMPT_ENV });
694
+ ];
695
+ if (opts?.head) args.push("--head", opts.head);
696
+ if (opts?.base) args.push("--base", opts.base);
697
+ const result = execFileSync("gh", args, { cwd: basePath, encoding: "utf8", timeout: 30000, env: GIT_NO_PROMPT_ENV });
694
698
  return result.trim();
695
699
  } catch {
696
700
  return null;
@@ -301,6 +301,7 @@ function initSchema(db: DbAdapter, fileBacked: boolean): void {
301
301
  inputs TEXT NOT NULL DEFAULT '[]',
302
302
  expected_output TEXT NOT NULL DEFAULT '[]',
303
303
  observability_impact TEXT NOT NULL DEFAULT '',
304
+ full_plan_md TEXT NOT NULL DEFAULT '',
304
305
  sequence INTEGER DEFAULT 0, -- DEAD CODE: no tool exposes sequence — always 0
305
306
  PRIMARY KEY (milestone_id, slice_id, id),
306
307
  FOREIGN KEY (milestone_id, slice_id) REFERENCES slices(milestone_id, id)
@@ -616,6 +617,15 @@ function migrateSchema(db: DbAdapter): void {
616
617
  });
617
618
  }
618
619
 
620
+ if (currentVersion < 11) {
621
+ ensureColumn(db, "tasks", "full_plan_md", `ALTER TABLE tasks ADD COLUMN full_plan_md TEXT NOT NULL DEFAULT ''`);
622
+
623
+ db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
624
+ ":version": 11,
625
+ ":applied_at": new Date().toISOString(),
626
+ });
627
+ }
628
+
619
629
  db.exec("COMMIT");
620
630
  } catch (err) {
621
631
  db.exec("ROLLBACK");
@@ -923,6 +933,7 @@ export interface TaskPlanningRecord {
923
933
  inputs: string[];
924
934
  expectedOutput: string[];
925
935
  observabilityImpact: string;
936
+ fullPlanMd?: string;
926
937
  }
927
938
 
928
939
  export function insertMilestone(m: {
@@ -1163,7 +1174,8 @@ export function upsertTaskPlanning(milestoneId: string, sliceId: string, taskId:
1163
1174
  verify = COALESCE(:verify, verify),
1164
1175
  inputs = COALESCE(:inputs, inputs),
1165
1176
  expected_output = COALESCE(:expected_output, expected_output),
1166
- observability_impact = COALESCE(:observability_impact, observability_impact)
1177
+ observability_impact = COALESCE(:observability_impact, observability_impact),
1178
+ full_plan_md = COALESCE(:full_plan_md, full_plan_md)
1167
1179
  WHERE milestone_id = :milestone_id AND slice_id = :slice_id AND id = :id`,
1168
1180
  ).run({
1169
1181
  ":milestone_id": milestoneId,
@@ -1177,6 +1189,7 @@ export function upsertTaskPlanning(milestoneId: string, sliceId: string, taskId:
1177
1189
  ":inputs": planning.inputs ? JSON.stringify(planning.inputs) : null,
1178
1190
  ":expected_output": planning.expectedOutput ? JSON.stringify(planning.expectedOutput) : null,
1179
1191
  ":observability_impact": planning.observabilityImpact ?? null,
1192
+ ":full_plan_md": planning.fullPlanMd ?? null,
1180
1193
  });
1181
1194
  }
1182
1195
 
@@ -1268,6 +1281,7 @@ export interface TaskRow {
1268
1281
  inputs: string[];
1269
1282
  expected_output: string[];
1270
1283
  observability_impact: string;
1284
+ full_plan_md: string;
1271
1285
  sequence: number;
1272
1286
  }
1273
1287
 
@@ -1296,6 +1310,7 @@ function rowToTask(row: Record<string, unknown>): TaskRow {
1296
1310
  inputs: JSON.parse((row["inputs"] as string) || "[]"),
1297
1311
  expected_output: JSON.parse((row["expected_output"] as string) || "[]"),
1298
1312
  observability_impact: (row["observability_impact"] as string) ?? "",
1313
+ full_plan_md: (row["full_plan_md"] as string) ?? "",
1299
1314
  sequence: (row["sequence"] as number) ?? 0,
1300
1315
  };
1301
1316
  }
@@ -387,7 +387,7 @@ export async function renderTaskPlanFromDb(
387
387
  mkdirSync(tasksDir, { recursive: true });
388
388
  const absPath = join(tasksDir, buildTaskFileName(taskId, "PLAN"));
389
389
  const artifactPath = toArtifactPath(absPath, basePath);
390
- const content = renderTaskPlanMarkdown(task);
390
+ const content = task.full_plan_md.trim() ? task.full_plan_md : renderTaskPlanMarkdown(task);
391
391
 
392
392
  await writeAndStore(absPath, artifactPath, content, {
393
393
  artifact_type: "PLAN",
@@ -196,6 +196,13 @@ function loadPreferencesFile(path: string, scope: "global" | "project"): LoadedG
196
196
  };
197
197
  }
198
198
 
199
+ let _warnedUnrecognizedFormat = false;
200
+
201
+ /** @internal Reset the warn-once flag — exported for testing only. */
202
+ export function _resetParseWarningFlag(): void {
203
+ _warnedUnrecognizedFormat = false;
204
+ }
205
+
199
206
  /** @internal Exported for testing only */
200
207
  export function parsePreferencesMarkdown(content: string): GSDPreferences | null {
201
208
  // Use indexOf instead of [\s\S]*? regex to avoid backtracking (#468)
@@ -214,7 +221,10 @@ export function parsePreferencesMarkdown(content: string): GSDPreferences | null
214
221
  return parseHeadingListFormat(content);
215
222
  }
216
223
 
217
- console.warn("[parsePreferencesMarkdown] preferences.md exists but uses an unrecognized format — skipping.");
224
+ if (!_warnedUnrecognizedFormat) {
225
+ _warnedUnrecognizedFormat = true;
226
+ console.warn("[parsePreferencesMarkdown] preferences.md exists but uses an unrecognized format — skipping.");
227
+ }
218
228
  return null;
219
229
  }
220
230
 
@@ -21,8 +21,8 @@ Then:
21
21
  4. Verify each **success criterion** from the milestone definition in `{{roadmapPath}}`. For each criterion, confirm it was met with specific evidence from slice summaries, test results, or observable behavior. List any criterion that was NOT met.
22
22
  5. Verify the milestone's **definition of done** — all slices are `[x]`, all slice summaries exist, and any cross-slice integration points work correctly.
23
23
  6. Validate **requirement status transitions**. For each requirement that changed status during this milestone, confirm the transition is supported by evidence. Requirements can move between Active, Validated, Deferred, Blocked, or Out of Scope — but only with proof.
24
- 7. Write `{{milestoneSummaryPath}}` using the milestone-summary template. Fill all frontmatter fields and narrative sections. The `requirement_outcomes` field must list every requirement that changed status with `from_status`, `to_status`, and `proof`.
25
- 8. Update `.gsd/REQUIREMENTS.md` if any requirement status transitions were validated in step 5.
24
+ 7. **Persist completion through `gsd_complete_milestone`.** Call it with: `milestoneId`, `title`, `oneLiner`, `narrative`, `successCriteriaResults`, `definitionOfDoneResults`, `requirementOutcomes`, `keyDecisions`, `keyFiles`, `lessonsLearned`, `followUps`, `deviations`. The tool updates the milestone status in the DB, renders `{{milestoneSummaryPath}}`, and validates all slices are complete before proceeding.
25
+ 8. Update `.gsd/REQUIREMENTS.md` if any requirement status transitions were validated in step 6.
26
26
  9. Update `.gsd/PROJECT.md` to reflect milestone completion and current project state.
27
27
  10. Review all slice summaries for cross-cutting lessons, patterns, or gotchas that emerged during this milestone. Append any non-obvious, reusable insights to `.gsd/KNOWLEDGE.md`.
28
28
  11. Do not commit manually — the system auto-commits your changes after this unit completes.
@@ -31,6 +31,4 @@ Then:
31
31
 
32
32
  **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.
33
33
 
34
- **You MUST write `{{milestoneSummaryPath}}` AND update PROJECT.md before finishing.**
35
-
36
34
  When done, say: "Milestone {{milestoneId}} complete."
@@ -63,7 +63,7 @@ Then:
63
63
  - a matching task plan file with description, steps, must-haves, verification, inputs, and expected output
64
64
  - **Inputs and Expected Output must list concrete backtick-wrapped file paths** (e.g. `` `src/types.ts` ``). These are machine-parsed to derive task dependencies — vague prose without paths breaks parallel execution. Every task must have at least one output file path.
65
65
  - Observability Impact section **only if the task touches runtime boundaries, async flows, or error paths** — omit it otherwise
66
- 6. **Persist planning state through DB-backed tools.** Call `gsd_plan_slice` with the full slice planning payload (goal, demo, must-haves, verification, tasks, and metadata). Then call `gsd_plan_task` for each task to persist its planning fields. These tools write to the DB and render `{{outputPath}}` and `{{slicePath}}/tasks/T##-PLAN.md` files automatically. Do **not** rely on direct `PLAN.md` writes as the source of truth; the DB-backed tools are the canonical write path for slice and task planning state.
66
+ 6. **Persist planning state through `gsd_plan_slice`.** Call it with the full slice planning payload (goal, demo, must-haves, verification, tasks, and metadata). The tool inserts all tasks in the same transaction, writes to the DB, and renders `{{outputPath}}` and `{{slicePath}}/tasks/T##-PLAN.md` files automatically. Do **not** call `gsd_plan_task` separately — `gsd_plan_slice` handles task persistence. Do **not** rely on direct `PLAN.md` writes as the source of truth; the DB-backed tool is the canonical write path for slice and task planning state.
67
67
  7. **Self-audit the plan.** Walk through each check — if any fail, fix the plan files before moving on:
68
68
  - **Completion semantics:** If every task were completed exactly as written, the slice goal/demo should actually be true.
69
69
  - **Requirement coverage:** Every must-have in the slice maps to at least one task. No must-have is orphaned. If `REQUIREMENTS.md` exists, every Active requirement this slice owns maps to at least one task.