gsd-pi 2.59.0 → 2.60.0-dev.2580e65

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 (354) hide show
  1. package/dist/resources/extensions/ask-user-questions.js +7 -4
  2. package/dist/resources/extensions/gsd/auto/phases.js +62 -1
  3. package/dist/resources/extensions/gsd/auto-dashboard.js +21 -8
  4. package/dist/resources/extensions/gsd/auto-dispatch.js +6 -3
  5. package/dist/resources/extensions/gsd/auto-model-selection.js +57 -3
  6. package/dist/resources/extensions/gsd/auto-post-unit.js +43 -3
  7. package/dist/resources/extensions/gsd/auto-prompts.js +49 -20
  8. package/dist/resources/extensions/gsd/auto-recovery.js +37 -18
  9. package/dist/resources/extensions/gsd/auto-start.js +9 -5
  10. package/dist/resources/extensions/gsd/auto-timers.js +11 -5
  11. package/dist/resources/extensions/gsd/auto-unit-closeout.js +5 -3
  12. package/dist/resources/extensions/gsd/auto-verification.js +3 -2
  13. package/dist/resources/extensions/gsd/auto-worktree.js +120 -55
  14. package/dist/resources/extensions/gsd/auto.js +39 -17
  15. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +6 -3
  16. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +72 -2
  17. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +4 -10
  18. package/dist/resources/extensions/gsd/bootstrap/journal-tools.js +2 -1
  19. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +58 -5
  20. package/dist/resources/extensions/gsd/bootstrap/system-context.js +11 -10
  21. package/dist/resources/extensions/gsd/captures.js +54 -1
  22. package/dist/resources/extensions/gsd/commands/catalog.js +2 -0
  23. package/dist/resources/extensions/gsd/commands-codebase.js +48 -21
  24. package/dist/resources/extensions/gsd/commands-inspect.js +2 -1
  25. package/dist/resources/extensions/gsd/commands-maintenance.js +32 -19
  26. package/dist/resources/extensions/gsd/complexity-classifier.js +9 -5
  27. package/dist/resources/extensions/gsd/context-masker.js +68 -0
  28. package/dist/resources/extensions/gsd/custom-verification.js +3 -2
  29. package/dist/resources/extensions/gsd/docs/preferences-reference.md +7 -0
  30. package/dist/resources/extensions/gsd/gsd-db.js +35 -15
  31. package/dist/resources/extensions/gsd/guided-flow.js +19 -9
  32. package/dist/resources/extensions/gsd/init-wizard.js +12 -0
  33. package/dist/resources/extensions/gsd/markdown-renderer.js +11 -9
  34. package/dist/resources/extensions/gsd/md-importer.js +5 -4
  35. package/dist/resources/extensions/gsd/milestone-actions.js +3 -2
  36. package/dist/resources/extensions/gsd/milestone-ids.js +2 -1
  37. package/dist/resources/extensions/gsd/model-router.js +199 -45
  38. package/dist/resources/extensions/gsd/parallel-merge.js +5 -3
  39. package/dist/resources/extensions/gsd/parallel-orchestrator.js +26 -14
  40. package/dist/resources/extensions/gsd/phase-anchor.js +56 -0
  41. package/dist/resources/extensions/gsd/preferences-types.js +2 -0
  42. package/dist/resources/extensions/gsd/preferences-validation.js +91 -0
  43. package/dist/resources/extensions/gsd/preferences.js +15 -3
  44. package/dist/resources/extensions/gsd/prompt-loader.js +3 -2
  45. package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -0
  46. package/dist/resources/extensions/gsd/prompts/rethink.md +7 -0
  47. package/dist/resources/extensions/gsd/prompts/triage-captures.md +6 -1
  48. package/dist/resources/extensions/gsd/rethink.js +5 -2
  49. package/dist/resources/extensions/gsd/rule-registry.js +7 -6
  50. package/dist/resources/extensions/gsd/safe-fs.js +6 -8
  51. package/dist/resources/extensions/gsd/state.js +1 -1
  52. package/dist/resources/extensions/gsd/status-guards.js +4 -3
  53. package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -2
  54. package/dist/resources/extensions/gsd/tools/complete-slice.js +3 -2
  55. package/dist/resources/extensions/gsd/tools/complete-task.js +3 -2
  56. package/dist/resources/extensions/gsd/tools/plan-milestone.js +3 -2
  57. package/dist/resources/extensions/gsd/tools/plan-slice.js +3 -2
  58. package/dist/resources/extensions/gsd/tools/plan-task.js +2 -1
  59. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +4 -4
  60. package/dist/resources/extensions/gsd/tools/reopen-slice.js +2 -1
  61. package/dist/resources/extensions/gsd/tools/reopen-task.js +2 -1
  62. package/dist/resources/extensions/gsd/tools/replan-slice.js +2 -1
  63. package/dist/resources/extensions/gsd/tools/validate-milestone.js +2 -1
  64. package/dist/resources/extensions/gsd/triage-resolution.js +135 -1
  65. package/dist/resources/extensions/gsd/triage-ui.js +12 -3
  66. package/dist/resources/extensions/gsd/workflow-events.js +2 -1
  67. package/dist/resources/extensions/gsd/workflow-logger.js +37 -4
  68. package/dist/resources/extensions/gsd/workflow-migration.js +14 -12
  69. package/dist/resources/extensions/gsd/workflow-projections.js +2 -2
  70. package/dist/resources/extensions/gsd/workflow-reconcile.js +2 -2
  71. package/dist/resources/extensions/gsd/worktree-manager.js +26 -14
  72. package/dist/resources/extensions/shared/interview-ui.js +3 -1
  73. package/dist/resources/skills/btw/SKILL.md +42 -0
  74. package/dist/web/standalone/.next/BUILD_ID +1 -1
  75. package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
  76. package/dist/web/standalone/.next/build-manifest.json +3 -3
  77. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  78. package/dist/web/standalone/.next/required-server-files.json +3 -3
  79. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  80. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  81. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  82. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  90. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  91. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  92. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  93. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  94. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  96. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  106. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  118. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  137. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  143. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  146. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  152. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  155. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  157. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  159. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  161. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  163. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  164. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  166. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  168. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  169. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  170. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  171. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  172. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  173. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  175. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  176. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  177. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  178. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  179. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  180. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  181. package/dist/web/standalone/.next/server/app/index.html +1 -1
  182. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  183. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  184. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  185. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  186. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  187. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  188. package/dist/web/standalone/.next/server/app/page.js +2 -2
  189. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  190. package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
  191. package/dist/web/standalone/.next/server/chunks/2229.js +1 -1
  192. package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
  193. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  194. package/dist/web/standalone/.next/server/middleware.js +2 -2
  195. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  196. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  197. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  198. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  199. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  200. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  201. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  202. package/dist/web/standalone/.next/static/chunks/app/page-0c485498795110d6.js +1 -0
  203. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  204. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  205. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  206. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  207. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  208. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  209. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  210. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  211. package/dist/web/standalone/server.js +1 -1
  212. package/package.json +1 -1
  213. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  214. package/packages/pi-coding-agent/dist/core/extensions/loader.js +5 -0
  215. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  216. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +2 -1
  217. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  218. package/packages/pi-coding-agent/dist/core/extensions/runner.js +16 -0
  219. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  220. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +26 -0
  221. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  222. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  223. package/packages/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -1
  224. package/packages/pi-coding-agent/dist/core/lsp/config.js +6 -1
  225. package/packages/pi-coding-agent/dist/core/lsp/config.js.map +1 -1
  226. package/packages/pi-coding-agent/dist/core/lsp/defaults.json +2 -2
  227. package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.d.ts +2 -0
  228. package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.d.ts.map +1 -0
  229. package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.js +47 -0
  230. package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.js.map +1 -0
  231. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts +1 -0
  232. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  233. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +6 -0
  234. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  235. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.d.ts +2 -0
  236. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.d.ts.map +1 -0
  237. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +122 -0
  238. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -0
  239. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +1 -0
  240. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  241. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +30 -0
  242. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  243. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
  244. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +1 -7
  245. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  246. package/packages/pi-coding-agent/package.json +1 -1
  247. package/packages/pi-coding-agent/src/core/extensions/loader.ts +6 -0
  248. package/packages/pi-coding-agent/src/core/extensions/runner.ts +19 -0
  249. package/packages/pi-coding-agent/src/core/extensions/types.ts +26 -0
  250. package/packages/pi-coding-agent/src/core/lsp/config.ts +7 -1
  251. package/packages/pi-coding-agent/src/core/lsp/defaults.json +2 -2
  252. package/packages/pi-coding-agent/src/core/lsp/lsp-legacy-alias.test.ts +70 -0
  253. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +156 -0
  254. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +7 -0
  255. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +38 -0
  256. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +1 -8
  257. package/pkg/package.json +1 -1
  258. package/src/resources/extensions/ask-user-questions.ts +7 -3
  259. package/src/resources/extensions/gsd/auto/phases.ts +70 -1
  260. package/src/resources/extensions/gsd/auto-dashboard.ts +22 -8
  261. package/src/resources/extensions/gsd/auto-dispatch.ts +7 -3
  262. package/src/resources/extensions/gsd/auto-model-selection.ts +77 -6
  263. package/src/resources/extensions/gsd/auto-post-unit.ts +52 -5
  264. package/src/resources/extensions/gsd/auto-prompts.ts +54 -20
  265. package/src/resources/extensions/gsd/auto-recovery.ts +38 -18
  266. package/src/resources/extensions/gsd/auto-start.ts +10 -9
  267. package/src/resources/extensions/gsd/auto-timers.ts +12 -5
  268. package/src/resources/extensions/gsd/auto-unit-closeout.ts +6 -2
  269. package/src/resources/extensions/gsd/auto-verification.ts +3 -6
  270. package/src/resources/extensions/gsd/auto-worktree.ts +121 -55
  271. package/src/resources/extensions/gsd/auto.ts +40 -17
  272. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +4 -3
  273. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +80 -2
  274. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +4 -16
  275. package/src/resources/extensions/gsd/bootstrap/journal-tools.ts +2 -1
  276. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +61 -4
  277. package/src/resources/extensions/gsd/bootstrap/system-context.ts +11 -10
  278. package/src/resources/extensions/gsd/captures.ts +71 -2
  279. package/src/resources/extensions/gsd/commands/catalog.ts +2 -0
  280. package/src/resources/extensions/gsd/commands-codebase.ts +52 -20
  281. package/src/resources/extensions/gsd/commands-inspect.ts +2 -1
  282. package/src/resources/extensions/gsd/commands-maintenance.ts +28 -19
  283. package/src/resources/extensions/gsd/complexity-classifier.ts +10 -5
  284. package/src/resources/extensions/gsd/context-masker.ts +74 -0
  285. package/src/resources/extensions/gsd/custom-verification.ts +3 -2
  286. package/src/resources/extensions/gsd/docs/preferences-reference.md +7 -0
  287. package/src/resources/extensions/gsd/gsd-db.ts +14 -16
  288. package/src/resources/extensions/gsd/guided-flow.ts +9 -8
  289. package/src/resources/extensions/gsd/init-wizard.ts +12 -0
  290. package/src/resources/extensions/gsd/markdown-renderer.ts +11 -17
  291. package/src/resources/extensions/gsd/md-importer.ts +5 -4
  292. package/src/resources/extensions/gsd/milestone-actions.ts +3 -2
  293. package/src/resources/extensions/gsd/milestone-ids.ts +2 -1
  294. package/src/resources/extensions/gsd/model-router.ts +245 -56
  295. package/src/resources/extensions/gsd/parallel-merge.ts +5 -3
  296. package/src/resources/extensions/gsd/parallel-orchestrator.ts +18 -14
  297. package/src/resources/extensions/gsd/phase-anchor.ts +71 -0
  298. package/src/resources/extensions/gsd/preferences-types.ts +22 -0
  299. package/src/resources/extensions/gsd/preferences-validation.ts +83 -0
  300. package/src/resources/extensions/gsd/preferences.ts +16 -3
  301. package/src/resources/extensions/gsd/prompt-loader.ts +3 -2
  302. package/src/resources/extensions/gsd/prompts/execute-task.md +2 -0
  303. package/src/resources/extensions/gsd/prompts/rethink.md +7 -0
  304. package/src/resources/extensions/gsd/prompts/triage-captures.md +6 -1
  305. package/src/resources/extensions/gsd/rethink.ts +5 -2
  306. package/src/resources/extensions/gsd/rule-registry.ts +7 -6
  307. package/src/resources/extensions/gsd/safe-fs.ts +6 -5
  308. package/src/resources/extensions/gsd/state.ts +1 -1
  309. package/src/resources/extensions/gsd/status-guards.ts +4 -3
  310. package/src/resources/extensions/gsd/tests/capability-router.test.ts +347 -0
  311. package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +63 -0
  312. package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +27 -2
  313. package/src/resources/extensions/gsd/tests/context-masker.test.ts +122 -0
  314. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +4 -4
  315. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +1188 -0
  316. package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +841 -0
  317. package/src/resources/extensions/gsd/tests/model-router.test.ts +488 -2
  318. package/src/resources/extensions/gsd/tests/phase-anchor.test.ts +83 -0
  319. package/src/resources/extensions/gsd/tests/preferences.test.ts +62 -0
  320. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +21 -0
  321. package/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts +284 -0
  322. package/src/resources/extensions/gsd/tests/status-guards.test.ts +4 -0
  323. package/src/resources/extensions/gsd/tests/stop-backtrack.test.ts +216 -0
  324. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +1 -1
  325. package/src/resources/extensions/gsd/tests/workflow-logger-audit.test.ts +120 -0
  326. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +6 -6
  327. package/src/resources/extensions/gsd/tools/complete-milestone.ts +3 -6
  328. package/src/resources/extensions/gsd/tools/complete-slice.ts +3 -6
  329. package/src/resources/extensions/gsd/tools/complete-task.ts +3 -6
  330. package/src/resources/extensions/gsd/tools/plan-milestone.ts +3 -6
  331. package/src/resources/extensions/gsd/tools/plan-slice.ts +3 -6
  332. package/src/resources/extensions/gsd/tools/plan-task.ts +2 -3
  333. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +4 -6
  334. package/src/resources/extensions/gsd/tools/reopen-slice.ts +2 -3
  335. package/src/resources/extensions/gsd/tools/reopen-task.ts +2 -3
  336. package/src/resources/extensions/gsd/tools/replan-slice.ts +2 -3
  337. package/src/resources/extensions/gsd/tools/validate-milestone.ts +2 -3
  338. package/src/resources/extensions/gsd/triage-resolution.ts +151 -1
  339. package/src/resources/extensions/gsd/triage-ui.ts +12 -3
  340. package/src/resources/extensions/gsd/types.ts +1 -0
  341. package/src/resources/extensions/gsd/workflow-events.ts +2 -1
  342. package/src/resources/extensions/gsd/workflow-logger.ts +52 -5
  343. package/src/resources/extensions/gsd/workflow-migration.ts +14 -12
  344. package/src/resources/extensions/gsd/workflow-projections.ts +2 -2
  345. package/src/resources/extensions/gsd/workflow-reconcile.ts +2 -2
  346. package/src/resources/extensions/gsd/worktree-manager.ts +16 -14
  347. package/src/resources/extensions/shared/interview-ui.ts +3 -1
  348. package/src/resources/extensions/shared/tests/interview-notes-loop.test.ts +144 -0
  349. package/src/resources/skills/btw/SKILL.md +42 -0
  350. package/dist/web/standalone/.next/static/chunks/app/page-62be3b5fa91e4c8f.js +0 -1
  351. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  352. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  353. /package/dist/web/standalone/.next/static/{DGvT_c5Vb7Wu3X-fEOVUU → ogyMN7M-3bGGuRY08L5HR}/_buildManifest.js +0 -0
  354. /package/dist/web/standalone/.next/static/{DGvT_c5Vb7Wu3X-fEOVUU → ogyMN7M-3bGGuRY08L5HR}/_ssgManifest.js +0 -0
@@ -739,6 +739,27 @@ test("config source-level: hydration skips api_key entries with empty keys", ()
739
739
  );
740
740
  });
741
741
 
742
+ test("ask-user-questions source-level: tryRemoteQuestions is called before the hasUI guard", () => {
743
+ // Regression test for #3480 — remote questions were silently skipped in interactive
744
+ // mode because tryRemoteQuestions was gated behind `if (!ctx.hasUI)`.
745
+ // The fix moved the remote call before that guard so configured channels
746
+ // (Telegram/Slack/Discord) fire regardless of UI availability.
747
+ const src = readFileSync(
748
+ join(__dirname, "..", "..", "ask-user-questions.ts"),
749
+ "utf-8",
750
+ );
751
+
752
+ const remoteCallIdx = src.indexOf("tryRemoteQuestions(params.questions");
753
+ const hasUIGuardIdx = src.indexOf("if (!ctx.hasUI)");
754
+
755
+ assert.ok(remoteCallIdx !== -1, "tryRemoteQuestions call should exist in ask-user-questions.ts");
756
+ assert.ok(hasUIGuardIdx !== -1, "!ctx.hasUI guard should exist in ask-user-questions.ts");
757
+ assert.ok(
758
+ remoteCallIdx < hasUIGuardIdx,
759
+ "tryRemoteQuestions must be called before the !ctx.hasUI guard — otherwise remote questions are skipped in interactive mode",
760
+ );
761
+ });
762
+
742
763
  test("config source-level: removeProviderToken uses auth.remove not auth.set with empty key", () => {
743
764
  const commandSrc = readFileSync(
744
765
  join(__dirname, "..", "..", "remote-questions", "remote-command.ts"),
@@ -0,0 +1,284 @@
1
+ /**
2
+ * Verify that catch blocks across GSD source files use the centralized
3
+ * workflow-logger (logWarning/logError) instead of raw process.stderr.write,
4
+ * console.error, or being completely empty (#3348, #3345).
5
+ *
6
+ * Two tests:
7
+ * 1. Auto-mode files must have zero empty catch blocks (fully migrated).
8
+ * 2. All GSD files must not use raw stderr/console in catch blocks.
9
+ */
10
+
11
+ import { describe, test } from "node:test";
12
+ import assert from "node:assert/strict";
13
+ import { readFileSync, readdirSync, statSync } from "node:fs";
14
+ import { join, dirname, relative } from "node:path";
15
+ import { fileURLToPath } from "node:url";
16
+
17
+ const __dirname = dirname(fileURLToPath(import.meta.url));
18
+ const gsdDir = join(__dirname, "..");
19
+
20
+ /** Files exempt from the raw-stderr/console check */
21
+ const EXEMPT_FILES = new Set([
22
+ "workflow-logger.ts", // The logger itself
23
+ "debug-logger.ts", // Separate opt-in debug system
24
+ ]);
25
+
26
+ /**
27
+ * Files that have been fully migrated to workflow-logger and must not
28
+ * regress to empty catch blocks. Covers auto-mode, tools, bootstrap,
29
+ * and core infrastructure files.
30
+ */
31
+ const MIGRATED_FILES = new Set([
32
+ // auto-mode (detected dynamically below)
33
+ // tools/
34
+ "tools/complete-task.ts",
35
+ "tools/complete-slice.ts",
36
+ "tools/complete-milestone.ts",
37
+ "tools/plan-milestone.ts",
38
+ "tools/plan-slice.ts",
39
+ "tools/plan-task.ts",
40
+ "tools/reassess-roadmap.ts",
41
+ "tools/reopen-task.ts",
42
+ "tools/reopen-slice.ts",
43
+ "tools/replan-slice.ts",
44
+ "tools/validate-milestone.ts",
45
+ // bootstrap/
46
+ "bootstrap/agent-end-recovery.ts",
47
+ "bootstrap/system-context.ts",
48
+ "bootstrap/db-tools.ts",
49
+ "bootstrap/dynamic-tools.ts",
50
+ "bootstrap/journal-tools.ts",
51
+ // core infrastructure
52
+ "gsd-db.ts",
53
+ "workflow-logger.ts",
54
+ "workflow-reconcile.ts",
55
+ "workflow-migration.ts",
56
+ "workflow-projections.ts",
57
+ "workflow-events.ts",
58
+ "worktree-manager.ts",
59
+ "parallel-orchestrator.ts",
60
+ "parallel-merge.ts",
61
+ "guided-flow.ts",
62
+ "preferences.ts",
63
+ "commands-maintenance.ts",
64
+ "commands-inspect.ts",
65
+ "safe-fs.ts",
66
+ "markdown-renderer.ts",
67
+ "md-importer.ts",
68
+ "milestone-actions.ts",
69
+ "milestone-ids.ts",
70
+ "rule-registry.ts",
71
+ "custom-verification.ts",
72
+ "prompt-loader.ts",
73
+ "auto-verification.ts",
74
+ ]);
75
+
76
+ /** Patterns that indicate a catch block already uses workflow-logger */
77
+ const LOGGER_PATTERNS = [
78
+ /logWarning\s*\(/,
79
+ /logError\s*\(/,
80
+ ];
81
+
82
+ function getAutoModeFiles(): string[] {
83
+ const files: string[] = [];
84
+
85
+ // Top-level auto*.ts files
86
+ for (const f of readdirSync(gsdDir)) {
87
+ if (f.startsWith("auto") && f.endsWith(".ts") && !f.endsWith(".test.ts")) {
88
+ files.push(join(gsdDir, f));
89
+ }
90
+ }
91
+
92
+ // auto/ subdirectory
93
+ const autoSubDir = join(gsdDir, "auto");
94
+ for (const f of readdirSync(autoSubDir)) {
95
+ if (f.endsWith(".ts") && !f.endsWith(".test.ts")) {
96
+ files.push(join(autoSubDir, f));
97
+ }
98
+ }
99
+
100
+ return files;
101
+ }
102
+
103
+ function getGsdSourceFiles(): string[] {
104
+ const files: string[] = [];
105
+
106
+ function walk(dir: string): void {
107
+ for (const entry of readdirSync(dir)) {
108
+ const full = join(dir, entry);
109
+ if (entry === "tests" || entry === "node_modules") continue;
110
+ try {
111
+ const st = statSync(full);
112
+ if (st.isDirectory()) {
113
+ walk(full);
114
+ } else if (entry.endsWith(".ts") && !entry.endsWith(".test.ts") && !entry.endsWith(".d.ts")) {
115
+ files.push(full);
116
+ }
117
+ } catch {
118
+ continue;
119
+ }
120
+ }
121
+ }
122
+
123
+ walk(gsdDir);
124
+ return files;
125
+ }
126
+
127
+ /**
128
+ * Scan a file for empty catch blocks — catches whose body contains
129
+ * only whitespace and/or comments but no executable statements.
130
+ */
131
+ function findEmptyCatches(filePath: string): Array<{ line: number; text: string }> {
132
+ const content = readFileSync(filePath, "utf-8");
133
+ const lines = content.split("\n");
134
+ const results: Array<{ line: number; text: string }> = [];
135
+
136
+ for (let i = 0; i < lines.length; i++) {
137
+ const line = lines[i];
138
+
139
+ // Match catch block opening
140
+ if (!/\}\s*catch\s*(\([^)]*\))?\s*\{/.test(line)) continue;
141
+
142
+ // Inline single-line catch: } catch { ... }
143
+ const inlineMatch = line.match(/\}\s*catch\s*(\([^)]*\))?\s*\{(.*)\}\s*;?\s*$/);
144
+ if (inlineMatch) {
145
+ const body = inlineMatch[2].trim();
146
+ const stripped = body.replace(/\/\*.*?\*\//g, "").replace(/\/\/.*/g, "").trim();
147
+ if (!stripped) {
148
+ results.push({ line: i + 1, text: line.trim() });
149
+ }
150
+ continue;
151
+ }
152
+
153
+ // Multi-line catch — scan until matching }
154
+ let j = i + 1;
155
+ let depth = 1;
156
+ const bodyLines: string[] = [];
157
+ while (j < lines.length && depth > 0) {
158
+ for (const ch of lines[j]) {
159
+ if (ch === "{") depth++;
160
+ else if (ch === "}") depth--;
161
+ }
162
+ bodyLines.push(lines[j].trim());
163
+ j++;
164
+ }
165
+
166
+ const meaningful = bodyLines.slice(0, -1).filter(
167
+ (l) => l && !l.startsWith("//") && !l.startsWith("/*") && !l.startsWith("*") && l !== "}",
168
+ );
169
+
170
+ if (meaningful.length === 0) {
171
+ results.push({ line: i + 1, text: line.trim() });
172
+ }
173
+ }
174
+
175
+ return results;
176
+ }
177
+
178
+ /**
179
+ * Scan a file for catch blocks that use raw process.stderr.write or
180
+ * console.error/warn instead of workflow-logger.
181
+ */
182
+ function findRawStderrCatches(filePath: string): Array<{ line: number; text: string }> {
183
+ const content = readFileSync(filePath, "utf-8");
184
+ const lines = content.split("\n");
185
+ const results: Array<{ line: number; text: string }> = [];
186
+
187
+ for (let i = 0; i < lines.length; i++) {
188
+ const line = lines[i];
189
+ if (!/\}\s*catch\s*(\([^)]*\))?\s*\{/.test(line)) continue;
190
+
191
+ // Inline single-line catch
192
+ const inlineMatch = line.match(/\}\s*catch\s*(\([^)]*\))?\s*\{(.*)\}\s*;?\s*$/);
193
+ if (inlineMatch) {
194
+ const body = inlineMatch[2];
195
+ if (!LOGGER_PATTERNS.some((p) => p.test(body))) {
196
+ if (/process\.stderr\.write/.test(body) || /console\.(error|warn)/.test(body)) {
197
+ results.push({ line: i + 1, text: line.trim() });
198
+ }
199
+ }
200
+ continue;
201
+ }
202
+
203
+ // Multi-line catch
204
+ let j = i + 1;
205
+ let depth = 1;
206
+ const bodyLines: string[] = [];
207
+ while (j < lines.length && depth > 0) {
208
+ for (const ch of lines[j]) {
209
+ if (ch === "{") depth++;
210
+ else if (ch === "}") depth--;
211
+ }
212
+ bodyLines.push(lines[j]);
213
+ j++;
214
+ }
215
+
216
+ const bodyText = bodyLines.slice(0, -1).join("\n");
217
+ if (!LOGGER_PATTERNS.some((p) => p.test(bodyText))) {
218
+ if (/process\.stderr\.write/.test(bodyText) || /console\.(error|warn)/.test(bodyText)) {
219
+ results.push({ line: i + 1, text: line.trim() });
220
+ }
221
+ }
222
+ }
223
+
224
+ return results;
225
+ }
226
+
227
+ describe("workflow-logger coverage (#3348)", () => {
228
+ test("no empty catch blocks remain in migrated files", () => {
229
+ // Combine auto-mode files + explicitly migrated files
230
+ const autoFiles = getAutoModeFiles();
231
+ const allFiles = getGsdSourceFiles();
232
+ const migratedPaths = new Set(autoFiles);
233
+ for (const file of allFiles) {
234
+ const rel = relative(gsdDir, file);
235
+ if (MIGRATED_FILES.has(rel)) {
236
+ migratedPaths.add(file);
237
+ }
238
+ }
239
+
240
+ assert.ok(migratedPaths.size > 0, "should find migrated source files");
241
+
242
+ const violations: string[] = [];
243
+ for (const file of migratedPaths) {
244
+ const rel = relative(gsdDir, file);
245
+ const basename = rel.split("/").pop()!;
246
+ // gsd-db.ts has intentionally silent provider probes
247
+ if (basename === "gsd-db.ts" || basename === "session-lock.ts") continue;
248
+
249
+ const empties = findEmptyCatches(file);
250
+ for (const empty of empties) {
251
+ violations.push(`${rel}:${empty.line} — ${empty.text}`);
252
+ }
253
+ }
254
+
255
+ assert.equal(
256
+ violations.length,
257
+ 0,
258
+ `Found ${violations.length} empty catch block(s) in migrated files:\n${violations.join("\n")}`,
259
+ );
260
+ });
261
+
262
+ test("catch blocks use workflow-logger instead of raw stderr/console", () => {
263
+ const files = getGsdSourceFiles();
264
+ assert.ok(files.length > 0, "should find GSD source files");
265
+
266
+ const violations: string[] = [];
267
+ for (const file of files) {
268
+ const rel = relative(gsdDir, file);
269
+ const basename = rel.split("/").pop()!;
270
+ if (EXEMPT_FILES.has(basename)) continue;
271
+
272
+ const issues = findRawStderrCatches(file);
273
+ for (const issue of issues) {
274
+ violations.push(`${rel}:${issue.line} — ${issue.text}`);
275
+ }
276
+ }
277
+
278
+ assert.equal(
279
+ violations.length,
280
+ 0,
281
+ `Found ${violations.length} catch block(s) using raw stderr/console instead of workflow-logger:\n${violations.join("\n")}`,
282
+ );
283
+ });
284
+ });
@@ -13,6 +13,10 @@ test('isClosedStatus: "done" returns true', () => {
13
13
  assert.equal(isClosedStatus('done'), true);
14
14
  });
15
15
 
16
+ test('isClosedStatus: "skipped" returns true', () => {
17
+ assert.equal(isClosedStatus('skipped'), true);
18
+ });
19
+
16
20
  test('isClosedStatus: "pending" returns false', () => {
17
21
  assert.equal(isClosedStatus('pending'), false);
18
22
  });
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Unit tests for stop/backtrack capture classifications and milestone regression (#3487).
3
+ *
4
+ * Tests:
5
+ * - "stop" and "backtrack" are valid classification types
6
+ * - loadStopCaptures returns unexecuted stop+backtrack captures
7
+ * - loadBacktrackCaptures returns only backtrack captures
8
+ * - revertExecutorResolvedCaptures reverts silenced captures
9
+ * - executeBacktrack writes trigger and regression markers
10
+ * - readBacktrackTrigger parses trigger file
11
+ */
12
+
13
+ import test from "node:test";
14
+ import assert from "node:assert/strict";
15
+ import { mkdirSync, readFileSync, writeFileSync, rmSync, existsSync } from "node:fs";
16
+ import { join } from "node:path";
17
+ import { tmpdir } from "node:os";
18
+ import { isClosedStatus } from "../status-guards.ts";
19
+ import {
20
+ appendCapture,
21
+ loadAllCaptures,
22
+ loadStopCaptures,
23
+ loadBacktrackCaptures,
24
+ markCaptureResolved,
25
+ revertExecutorResolvedCaptures,
26
+ hasPendingCaptures,
27
+ } from "../captures.ts";
28
+ import {
29
+ executeBacktrack,
30
+ readBacktrackTrigger,
31
+ } from "../triage-resolution.ts";
32
+
33
+ function makeTempDir(prefix: string): string {
34
+ const dir = join(
35
+ tmpdir(),
36
+ `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`,
37
+ );
38
+ mkdirSync(dir, { recursive: true });
39
+ return dir;
40
+ }
41
+
42
+ function setupGsdDir(tmp: string): void {
43
+ mkdirSync(join(tmp, ".gsd"), { recursive: true });
44
+ }
45
+
46
+ // ─── Classification Types ─────────────────────────────────────────────────────
47
+
48
+ test("stop is a valid classification", () => {
49
+ const tmp = makeTempDir("stop-class");
50
+ setupGsdDir(tmp);
51
+ const id = appendCapture(tmp, "stop running immediately");
52
+ markCaptureResolved(tmp, id, "stop", "Halt auto-mode", "User said stop", "M005");
53
+ const all = loadAllCaptures(tmp);
54
+ const cap = all.find(c => c.id === id);
55
+ assert.equal(cap?.classification, "stop");
56
+ rmSync(tmp, { recursive: true, force: true });
57
+ });
58
+
59
+ test("backtrack is a valid classification", () => {
60
+ const tmp = makeTempDir("bt-class");
61
+ setupGsdDir(tmp);
62
+ const id = appendCapture(tmp, "restart from M003");
63
+ markCaptureResolved(tmp, id, "backtrack", "Backtrack to M003", "User wants to restart", "M005");
64
+ const all = loadAllCaptures(tmp);
65
+ const cap = all.find(c => c.id === id);
66
+ assert.equal(cap?.classification, "backtrack");
67
+ rmSync(tmp, { recursive: true, force: true });
68
+ });
69
+
70
+ // ─── loadStopCaptures ─────────────────────────────────────────────────────────
71
+
72
+ test("loadStopCaptures returns unexecuted stop and backtrack captures", () => {
73
+ const tmp = makeTempDir("load-stop");
74
+ setupGsdDir(tmp);
75
+ const stopId = appendCapture(tmp, "halt execution");
76
+ const btId = appendCapture(tmp, "go back to M003");
77
+ const noteId = appendCapture(tmp, "just a note");
78
+ markCaptureResolved(tmp, stopId, "stop", "Halt", "User stop", "M005");
79
+ markCaptureResolved(tmp, btId, "backtrack", "Backtrack to M003", "User backtrack", "M005");
80
+ markCaptureResolved(tmp, noteId, "note", "Info only", "Not actionable", "M005");
81
+
82
+ const stops = loadStopCaptures(tmp);
83
+ assert.equal(stops.length, 2);
84
+ assert.ok(stops.some(c => c.classification === "stop"));
85
+ assert.ok(stops.some(c => c.classification === "backtrack"));
86
+ rmSync(tmp, { recursive: true, force: true });
87
+ });
88
+
89
+ test("loadBacktrackCaptures returns only backtrack captures", () => {
90
+ const tmp = makeTempDir("load-bt");
91
+ setupGsdDir(tmp);
92
+ const stopId = appendCapture(tmp, "halt execution");
93
+ const btId = appendCapture(tmp, "go back to M003");
94
+ markCaptureResolved(tmp, stopId, "stop", "Halt", "User stop", "M005");
95
+ markCaptureResolved(tmp, btId, "backtrack", "Backtrack to M003", "User backtrack", "M005");
96
+
97
+ const bts = loadBacktrackCaptures(tmp);
98
+ assert.equal(bts.length, 1);
99
+ assert.equal(bts[0].classification, "backtrack");
100
+ rmSync(tmp, { recursive: true, force: true });
101
+ });
102
+
103
+ // ─── revertExecutorResolvedCaptures ───────────────────────────────────────────
104
+
105
+ test("revertExecutorResolvedCaptures reverts captures resolved without classification", () => {
106
+ const tmp = makeTempDir("revert-exec");
107
+ setupGsdDir(tmp);
108
+ const id = appendCapture(tmp, "stop everything");
109
+
110
+ // Simulate an executor writing Status: resolved directly (no classification)
111
+ const capPath = join(tmp, ".gsd", "CAPTURES.md");
112
+ let content = readFileSync(capPath, "utf-8");
113
+ content = content.replace("**Status:** pending", "**Status:** resolved");
114
+ writeFileSync(capPath, content, "utf-8");
115
+
116
+ // Verify it's now "resolved" without classification
117
+ assert.equal(hasPendingCaptures(tmp), false);
118
+
119
+ // Revert should detect and fix it
120
+ const reverted = revertExecutorResolvedCaptures(tmp);
121
+ assert.equal(reverted, 1);
122
+
123
+ // Should be pending again
124
+ assert.equal(hasPendingCaptures(tmp), true);
125
+ rmSync(tmp, { recursive: true, force: true });
126
+ });
127
+
128
+ test("revertExecutorResolvedCaptures does NOT revert properly triaged captures", () => {
129
+ const tmp = makeTempDir("revert-skip");
130
+ setupGsdDir(tmp);
131
+ const id = appendCapture(tmp, "restart from M003");
132
+ markCaptureResolved(tmp, id, "backtrack", "Backtrack to M003", "User wants restart", "M005");
133
+
134
+ // This capture was properly triaged — should NOT be reverted
135
+ const reverted = revertExecutorResolvedCaptures(tmp);
136
+ assert.equal(reverted, 0);
137
+ rmSync(tmp, { recursive: true, force: true });
138
+ });
139
+
140
+ // ─── executeBacktrack ─────────────────────────────────────────────────────────
141
+
142
+ test("executeBacktrack writes trigger and regression markers", () => {
143
+ const tmp = makeTempDir("exec-bt");
144
+ setupGsdDir(tmp);
145
+
146
+ // Create target milestone directory
147
+ mkdirSync(join(tmp, ".gsd", "milestones", "M003"), { recursive: true });
148
+
149
+ const targetMid = executeBacktrack(tmp, "M005", {
150
+ id: "CAP-test123",
151
+ text: "restart from M003 — milestones after 2 failed",
152
+ timestamp: new Date().toISOString(),
153
+ status: "resolved",
154
+ classification: "backtrack",
155
+ resolution: "Backtrack to M003",
156
+ rationale: "User directive",
157
+ });
158
+
159
+ assert.equal(targetMid, "M003");
160
+
161
+ // Check trigger file exists
162
+ const triggerPath = join(tmp, ".gsd", "BACKTRACK-TRIGGER.md");
163
+ assert.ok(existsSync(triggerPath));
164
+ const triggerContent = readFileSync(triggerPath, "utf-8");
165
+ assert.ok(triggerContent.includes("M005"));
166
+ assert.ok(triggerContent.includes("M003"));
167
+
168
+ // Check regression marker exists on target milestone
169
+ const regressionPath = join(tmp, ".gsd", "milestones", "M003", "M003-REGRESSION.md");
170
+ assert.ok(existsSync(regressionPath));
171
+ const regressionContent = readFileSync(regressionPath, "utf-8");
172
+ assert.ok(regressionContent.includes("M005"));
173
+ rmSync(tmp, { recursive: true, force: true });
174
+ });
175
+
176
+ // ─── readBacktrackTrigger ─────────────────────────────────────────────────────
177
+
178
+ test("readBacktrackTrigger parses trigger file", () => {
179
+ const tmp = makeTempDir("read-bt");
180
+ setupGsdDir(tmp);
181
+ mkdirSync(join(tmp, ".gsd", "milestones", "M003"), { recursive: true });
182
+
183
+ executeBacktrack(tmp, "M005", {
184
+ id: "CAP-abc",
185
+ text: "go back to M003",
186
+ timestamp: new Date().toISOString(),
187
+ status: "resolved",
188
+ classification: "backtrack",
189
+ resolution: "Backtrack to M003",
190
+ rationale: "Regression",
191
+ });
192
+
193
+ const trigger = readBacktrackTrigger(tmp);
194
+ assert.ok(trigger);
195
+ assert.equal(trigger.target, "M003");
196
+ assert.equal(trigger.from, "M005");
197
+ rmSync(tmp, { recursive: true, force: true });
198
+ });
199
+
200
+ test("readBacktrackTrigger returns null when no trigger exists", () => {
201
+ const tmp = makeTempDir("no-bt");
202
+ setupGsdDir(tmp);
203
+ const trigger = readBacktrackTrigger(tmp);
204
+ assert.equal(trigger, null);
205
+ rmSync(tmp, { recursive: true, force: true });
206
+ });
207
+
208
+ // ─── Slice Skip Status (#3477) ──────────────────────────────────────────────
209
+
210
+ test("isClosedStatus treats 'skipped' as closed", () => {
211
+ assert.equal(isClosedStatus("skipped"), true);
212
+ assert.equal(isClosedStatus("complete"), true);
213
+ assert.equal(isClosedStatus("done"), true);
214
+ assert.equal(isClosedStatus("pending"), false);
215
+ assert.equal(isClosedStatus("active"), false);
216
+ });
@@ -45,7 +45,7 @@ console.log('\n── Tool naming: registration count ──');
45
45
  const pi = makeMockPi();
46
46
  registerDbTools(pi);
47
47
 
48
- assert.deepStrictEqual(pi.tools.length, 29, 'Should register exactly 29 tools (14 canonical + 14 aliases + 1 gate tool)');
48
+ assert.deepStrictEqual(pi.tools.length, 30, 'Should register exactly 30 tools (14 canonical + 14 aliases + 1 gate tool + 1 gsd_skip_slice)');
49
49
 
50
50
  // ─── Both names exist for each pair ──────────────────────────────────────────
51
51
 
@@ -0,0 +1,120 @@
1
+ // GSD Extension — Workflow Logger Audit Persistence Tests
2
+ // Validates error-only persistence, sanitization, and warning ephemeral behavior.
3
+
4
+ import { describe, test, beforeEach, afterEach } from "node:test";
5
+ import assert from "node:assert/strict";
6
+ import { mkdtempSync, mkdirSync, readFileSync, existsSync, rmSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { tmpdir } from "node:os";
9
+
10
+ import {
11
+ logWarning,
12
+ logError,
13
+ setLogBasePath,
14
+ _resetLogs,
15
+ peekLogs,
16
+ drainLogs,
17
+ } from "../workflow-logger.ts";
18
+
19
+ function createTempProject(): string {
20
+ const tmp = mkdtempSync(join(tmpdir(), "gsd-wflog-test-"));
21
+ mkdirSync(join(tmp, ".gsd"), { recursive: true });
22
+ return tmp;
23
+ }
24
+
25
+ function readAuditLines(basePath: string): Record<string, unknown>[] {
26
+ const auditPath = join(basePath, ".gsd", "audit-log.jsonl");
27
+ if (!existsSync(auditPath)) return [];
28
+ const content = readFileSync(auditPath, "utf-8").trim();
29
+ if (!content) return [];
30
+ return content.split("\n").map((line) => JSON.parse(line));
31
+ }
32
+
33
+ describe("workflow-logger audit persistence", () => {
34
+ let tmp: string;
35
+
36
+ beforeEach(() => {
37
+ tmp = createTempProject();
38
+ _resetLogs();
39
+ setLogBasePath(tmp);
40
+ });
41
+
42
+ afterEach(() => {
43
+ _resetLogs();
44
+ setLogBasePath(null as unknown as string);
45
+ rmSync(tmp, { recursive: true, force: true });
46
+ });
47
+
48
+ test("logError persists to audit-log.jsonl", () => {
49
+ logError("engine", "something broke");
50
+ const lines = readAuditLines(tmp);
51
+ assert.equal(lines.length, 1);
52
+ assert.equal(lines[0].severity, "error");
53
+ assert.equal(lines[0].component, "engine");
54
+ });
55
+
56
+ test("logWarning does NOT persist to audit-log.jsonl", () => {
57
+ logWarning("engine", "something fishy");
58
+ const lines = readAuditLines(tmp);
59
+ assert.equal(lines.length, 0, "warnings must not be persisted to audit log");
60
+ });
61
+
62
+ test("logWarning still appears in in-memory buffer", () => {
63
+ logWarning("recovery", "probe miss");
64
+ const entries = peekLogs();
65
+ assert.equal(entries.length, 1);
66
+ assert.equal(entries[0].severity, "warn");
67
+ assert.equal(entries[0].component, "recovery");
68
+ });
69
+
70
+ test("persisted error messages are truncated at 200 chars", () => {
71
+ const longMessage = "x".repeat(300);
72
+ logError("engine", longMessage);
73
+ const lines = readAuditLines(tmp);
74
+ assert.equal(lines.length, 1);
75
+ const msg = lines[0].message as string;
76
+ assert.ok(msg.length <= 215, `message should be truncated, got ${msg.length} chars`);
77
+ assert.ok(msg.endsWith("…[truncated]"));
78
+ });
79
+
80
+ test("persisted errors have context filtered to safe allowlist", () => {
81
+ logError("tool", "tool failed", {
82
+ fn: "saveDecisionToDb",
83
+ tool: "gsd_decision_save",
84
+ error: "SQLITE_BUSY: database is locked",
85
+ file: "/home/user/project/gsd.db",
86
+ });
87
+ const lines = readAuditLines(tmp);
88
+ assert.equal(lines.length, 1);
89
+ const ctx = lines[0].context as Record<string, string>;
90
+ assert.ok(ctx, "context should exist");
91
+ assert.equal(ctx.fn, "saveDecisionToDb");
92
+ assert.equal(ctx.tool, "gsd_decision_save");
93
+ assert.equal(ctx.error, undefined, "error key must be stripped from persisted context");
94
+ assert.equal(ctx.file, undefined, "file key must be stripped from persisted context");
95
+ });
96
+
97
+ test("persisted errors omit context when no safe keys present", () => {
98
+ logError("bootstrap", "ensureDbOpen failed", {
99
+ error: "ENOENT",
100
+ cwd: "/home/user/project",
101
+ });
102
+ const lines = readAuditLines(tmp);
103
+ assert.equal(lines.length, 1);
104
+ assert.equal(lines[0].context, undefined, "context should be omitted when no safe keys match");
105
+ });
106
+
107
+ test("mixed warnings and errors only persist errors", () => {
108
+ logWarning("recovery", "main not found");
109
+ logWarning("recovery", "master not found");
110
+ logError("engine", "fatal failure");
111
+ logWarning("prompt", "cache miss");
112
+
113
+ const lines = readAuditLines(tmp);
114
+ assert.equal(lines.length, 1, "only the error should be persisted");
115
+ assert.equal(lines[0].severity, "error");
116
+
117
+ const buffered = drainLogs();
118
+ assert.equal(buffered.length, 4, "all entries should be in the in-memory buffer");
119
+ });
120
+ });