gsd-pi 2.77.0-dev.58d3d4d6c → 2.77.0-dev.cfd69e714

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 (429) hide show
  1. package/README.md +1 -1
  2. package/dist/claude-cli-check.js +5 -1
  3. package/dist/headless.js +49 -4
  4. package/dist/resource-loader.d.ts +40 -0
  5. package/dist/resource-loader.js +32 -13
  6. package/dist/resources/extensions/browser-tools/capture.js +9 -0
  7. package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
  8. package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
  9. package/dist/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
  10. package/dist/resources/extensions/browser-tools/tools/forms.js +5 -1
  11. package/dist/resources/extensions/browser-tools/tools/intent.js +5 -1
  12. package/dist/resources/extensions/claude-code-cli/readiness.js +5 -1
  13. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +481 -17
  14. package/dist/resources/extensions/gsd/auto/loop.js +43 -0
  15. package/dist/resources/extensions/gsd/auto/phases.js +15 -21
  16. package/dist/resources/extensions/gsd/auto/session.js +0 -2
  17. package/dist/resources/extensions/gsd/auto-dispatch.js +102 -24
  18. package/dist/resources/extensions/gsd/auto-model-selection.js +124 -4
  19. package/dist/resources/extensions/gsd/auto-post-unit.js +71 -64
  20. package/dist/resources/extensions/gsd/auto-prompts.js +329 -102
  21. package/dist/resources/extensions/gsd/auto-recovery.js +195 -23
  22. package/dist/resources/extensions/gsd/auto-start.js +34 -24
  23. package/dist/resources/extensions/gsd/auto-tool-tracking.js +47 -7
  24. package/dist/resources/extensions/gsd/auto-worktree.js +122 -26
  25. package/dist/resources/extensions/gsd/auto.js +31 -20
  26. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +9 -1
  27. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +209 -0
  28. package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +3 -6
  29. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +7 -3
  30. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +127 -9
  31. package/dist/resources/extensions/gsd/component-loader.js +447 -0
  32. package/dist/resources/extensions/gsd/component-types.js +69 -0
  33. package/dist/resources/extensions/gsd/detection.js +49 -1
  34. package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  35. package/dist/resources/extensions/gsd/gate-registry.js +2 -2
  36. package/dist/resources/extensions/gsd/git-constants.js +28 -1
  37. package/dist/resources/extensions/gsd/git-self-heal.js +27 -0
  38. package/dist/resources/extensions/gsd/git-service.js +126 -2
  39. package/dist/resources/extensions/gsd/gsd-db.js +6 -3
  40. package/dist/resources/extensions/gsd/guided-flow.js +17 -5
  41. package/dist/resources/extensions/gsd/memory-extractor.js +7 -1
  42. package/dist/resources/extensions/gsd/milestone-scope-classifier.js +299 -0
  43. package/dist/resources/extensions/gsd/model-cost-table.js +3 -0
  44. package/dist/resources/extensions/gsd/model-router.js +6 -0
  45. package/dist/resources/extensions/gsd/native-git-bridge.js +34 -4
  46. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
  47. package/dist/resources/extensions/gsd/prompts/plan-slice.md +15 -2
  48. package/dist/resources/extensions/gsd/safety/git-checkpoint.js +11 -0
  49. package/dist/resources/extensions/gsd/service-tier.js +5 -2
  50. package/dist/resources/extensions/gsd/session-lock.js +19 -10
  51. package/dist/resources/extensions/gsd/skill-manifest.js +168 -0
  52. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +278 -8
  53. package/dist/resources/extensions/gsd/state.js +44 -33
  54. package/dist/resources/extensions/gsd/sync-lock.js +98 -42
  55. package/dist/resources/extensions/gsd/unit-context-composer.js +147 -0
  56. package/dist/resources/extensions/gsd/unit-context-manifest.js +370 -0
  57. package/dist/resources/extensions/gsd/uok/gate-runner.js +53 -5
  58. package/dist/resources/extensions/gsd/workflow-mcp.js +6 -0
  59. package/dist/resources/extensions/gsd/worktree-manager.js +34 -8
  60. package/dist/resources/extensions/mcp-client/index.js +3 -1
  61. package/dist/resources/extensions/ollama/index.js +5 -1
  62. package/dist/resources/extensions/remote-questions/manager.js +11 -5
  63. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  64. package/dist/web/standalone/.next/BUILD_ID +1 -1
  65. package/dist/web/standalone/.next/app-path-routes-manifest.json +5 -5
  66. package/dist/web/standalone/.next/build-manifest.json +2 -2
  67. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  68. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  85. package/dist/web/standalone/.next/server/app/index.html +1 -1
  86. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app-paths-manifest.json +5 -5
  93. package/dist/web/standalone/.next/server/chunks/1926.js +1 -1
  94. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  95. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  97. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  98. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  99. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  100. package/package.json +2 -3
  101. package/packages/daemon/src/logger.ts +4 -3
  102. package/packages/mcp-server/dist/server.d.ts +24 -0
  103. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  104. package/packages/mcp-server/dist/server.js +88 -87
  105. package/packages/mcp-server/dist/server.js.map +1 -1
  106. package/packages/mcp-server/src/mcp-server.test.ts +25 -3
  107. package/packages/mcp-server/src/readers/graph.test.ts +87 -15
  108. package/packages/mcp-server/src/secure-env-collect.test.ts +232 -237
  109. package/packages/mcp-server/src/server.ts +131 -105
  110. package/packages/mcp-server/src/workflow-tools.test.ts +80 -39
  111. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  112. package/packages/native/package.json +1 -1
  113. package/packages/native/src/__tests__/_test-coverage-guard.test.mjs +98 -0
  114. package/packages/native/src/__tests__/module-compat.test.mjs +59 -27
  115. package/packages/native/src/__tests__/ps.test.mjs +14 -8
  116. package/packages/native/src/__tests__/stream-process.test.mjs +23 -2
  117. package/packages/native/src/__tests__/truncate.test.mjs +17 -2
  118. package/packages/pi-agent-core/src/agent-loop.test.ts +5 -15
  119. package/packages/pi-agent-core/src/agent.test.ts +96 -102
  120. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  121. package/packages/pi-ai/dist/models/capability-patches.d.ts.map +1 -1
  122. package/packages/pi-ai/dist/models/capability-patches.js +9 -2
  123. package/packages/pi-ai/dist/models/capability-patches.js.map +1 -1
  124. package/packages/pi-ai/dist/models/generated/index.d.ts +34 -0
  125. package/packages/pi-ai/dist/models/generated/index.d.ts.map +1 -1
  126. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts +17 -0
  127. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts.map +1 -1
  128. package/packages/pi-ai/dist/models/generated/openai-codex.js +17 -0
  129. package/packages/pi-ai/dist/models/generated/openai-codex.js.map +1 -1
  130. package/packages/pi-ai/dist/models/generated/openai.d.ts +17 -0
  131. package/packages/pi-ai/dist/models/generated/openai.d.ts.map +1 -1
  132. package/packages/pi-ai/dist/models/generated/openai.js +17 -0
  133. package/packages/pi-ai/dist/models/generated/openai.js.map +1 -1
  134. package/packages/pi-ai/dist/models.generated.test.js +43 -70
  135. package/packages/pi-ai/dist/models.generated.test.js.map +1 -1
  136. package/packages/pi-ai/dist/models.test.js +36 -11
  137. package/packages/pi-ai/dist/models.test.js.map +1 -1
  138. package/packages/pi-ai/scripts/generate-models.ts +44 -0
  139. package/packages/pi-ai/src/models/capability-patches.ts +10 -2
  140. package/packages/pi-ai/src/models/generated/openai-codex.ts +17 -0
  141. package/packages/pi-ai/src/models/generated/openai.ts +17 -0
  142. package/packages/pi-ai/src/models.generated.test.ts +46 -73
  143. package/packages/pi-ai/src/models.test.ts +48 -11
  144. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  145. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +96 -32
  146. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
  147. package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js +75 -12
  148. package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js.map +1 -1
  149. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +99 -31
  150. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
  151. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +5 -0
  152. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  153. package/packages/pi-coding-agent/dist/core/extensions/loader.js +61 -0
  154. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  155. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js +30 -4
  156. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js.map +1 -1
  157. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +17 -0
  158. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
  159. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js +76 -18
  160. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js.map +1 -1
  161. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  162. package/packages/pi-coding-agent/dist/core/retry-handler.js +2 -6
  163. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  164. package/packages/pi-coding-agent/dist/core/retry-handler.test.js +5 -1
  165. package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
  166. package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts +18 -0
  167. package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts.map +1 -0
  168. package/packages/pi-coding-agent/dist/core/retryable-error-regex.js +18 -0
  169. package/packages/pi-coding-agent/dist/core/retryable-error-regex.js.map +1 -0
  170. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +20 -0
  171. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  172. package/packages/pi-coding-agent/dist/core/system-prompt.js +16 -2
  173. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  174. package/packages/pi-coding-agent/dist/index.d.ts +1 -0
  175. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  176. package/packages/pi-coding-agent/dist/index.js +1 -0
  177. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  178. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js +20 -13
  179. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js.map +1 -1
  180. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  181. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +18 -3
  182. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  183. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +125 -0
  184. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
  185. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +2 -0
  186. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  187. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  188. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
  189. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  190. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +105 -13
  191. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  192. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts +2 -0
  193. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts.map +1 -0
  194. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js +130 -0
  195. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js.map +1 -0
  196. package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +113 -37
  197. package/packages/pi-coding-agent/src/core/agent-session-model-switch.test.ts +89 -17
  198. package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +112 -43
  199. package/packages/pi-coding-agent/src/core/extensions/loader.ts +58 -0
  200. package/packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts +35 -4
  201. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +20 -0
  202. package/packages/pi-coding-agent/src/core/resource-loader-cache-reset.test.ts +93 -28
  203. package/packages/pi-coding-agent/src/core/retry-handler.test.ts +5 -1
  204. package/packages/pi-coding-agent/src/core/retry-handler.ts +2 -8
  205. package/packages/pi-coding-agent/src/core/retryable-error-regex.ts +18 -0
  206. package/packages/pi-coding-agent/src/core/system-prompt.ts +35 -1
  207. package/packages/pi-coding-agent/src/index.ts +1 -0
  208. package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.test.ts +26 -20
  209. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +146 -1
  210. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +20 -3
  211. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +2 -0
  212. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +119 -13
  213. package/packages/pi-coding-agent/src/tests/system-prompt-skill-filter.test.ts +157 -0
  214. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  215. package/packages/pi-tui/dist/__tests__/autocomplete.test.js +18 -8
  216. package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
  217. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js +128 -17
  218. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js.map +1 -1
  219. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +36 -12
  220. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -1
  221. package/packages/pi-tui/dist/__tests__/tui.test.js +18 -30
  222. package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -1
  223. package/packages/pi-tui/dist/components/__tests__/input.test.js +10 -3
  224. package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
  225. package/packages/pi-tui/dist/components/__tests__/loader.test.js +53 -9
  226. package/packages/pi-tui/dist/components/__tests__/loader.test.js.map +1 -1
  227. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js +6 -2
  228. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js.map +1 -1
  229. package/packages/pi-tui/dist/components/editor.d.ts +14 -0
  230. package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
  231. package/packages/pi-tui/dist/components/editor.js +19 -0
  232. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  233. package/packages/pi-tui/dist/components/image.test.js +6 -5
  234. package/packages/pi-tui/dist/components/image.test.js.map +1 -1
  235. package/packages/pi-tui/dist/editor-component.d.ts +2 -0
  236. package/packages/pi-tui/dist/editor-component.d.ts.map +1 -1
  237. package/packages/pi-tui/dist/editor-component.js.map +1 -1
  238. package/packages/pi-tui/src/__tests__/autocomplete.test.ts +24 -8
  239. package/packages/pi-tui/src/__tests__/overlay-layout.test.ts +140 -17
  240. package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +41 -12
  241. package/packages/pi-tui/src/__tests__/tui.test.ts +18 -37
  242. package/packages/pi-tui/src/components/__tests__/input.test.ts +19 -3
  243. package/packages/pi-tui/src/components/__tests__/loader.test.ts +112 -35
  244. package/packages/pi-tui/src/components/__tests__/markdown-maxlines.test.ts +9 -2
  245. package/packages/pi-tui/src/components/editor.ts +22 -0
  246. package/packages/pi-tui/src/components/image.test.ts +10 -5
  247. package/packages/pi-tui/src/editor-component.ts +3 -0
  248. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  249. package/packages/rpc-client/dist/rpc-client.test.js +101 -51
  250. package/packages/rpc-client/dist/rpc-client.test.js.map +1 -1
  251. package/packages/rpc-client/src/rpc-client.test.ts +109 -52
  252. package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
  253. package/scripts/install.js +15 -1
  254. package/src/resources/extensions/browser-tools/capture.ts +12 -0
  255. package/src/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
  256. package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
  257. package/src/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
  258. package/src/resources/extensions/browser-tools/tools/forms.ts +5 -1
  259. package/src/resources/extensions/browser-tools/tools/intent.ts +5 -1
  260. package/src/resources/extensions/claude-code-cli/readiness.ts +5 -1
  261. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +518 -19
  262. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +919 -75
  263. package/src/resources/extensions/github-sync/tests/cli.test.ts +76 -7
  264. package/src/resources/extensions/github-sync/tests/templates.test.ts +33 -1
  265. package/src/resources/extensions/gsd/auto/loop.ts +47 -0
  266. package/src/resources/extensions/gsd/auto/phases.ts +16 -20
  267. package/src/resources/extensions/gsd/auto/session.ts +0 -2
  268. package/src/resources/extensions/gsd/auto-dispatch.ts +113 -24
  269. package/src/resources/extensions/gsd/auto-model-selection.ts +131 -4
  270. package/src/resources/extensions/gsd/auto-post-unit.ts +82 -73
  271. package/src/resources/extensions/gsd/auto-prompts.ts +330 -90
  272. package/src/resources/extensions/gsd/auto-recovery.ts +225 -24
  273. package/src/resources/extensions/gsd/auto-start.ts +54 -6
  274. package/src/resources/extensions/gsd/auto-tool-tracking.ts +51 -7
  275. package/src/resources/extensions/gsd/auto-worktree.ts +130 -26
  276. package/src/resources/extensions/gsd/auto.ts +43 -22
  277. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +9 -1
  278. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +221 -0
  279. package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +3 -7
  280. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +7 -3
  281. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +158 -9
  282. package/src/resources/extensions/gsd/component-loader.ts +598 -0
  283. package/src/resources/extensions/gsd/component-types.ts +362 -0
  284. package/src/resources/extensions/gsd/detection.ts +58 -1
  285. package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  286. package/src/resources/extensions/gsd/gate-registry.ts +2 -2
  287. package/src/resources/extensions/gsd/git-constants.ts +30 -1
  288. package/src/resources/extensions/gsd/git-self-heal.ts +31 -0
  289. package/src/resources/extensions/gsd/git-service.ts +133 -2
  290. package/src/resources/extensions/gsd/gsd-db.ts +6 -3
  291. package/src/resources/extensions/gsd/guided-flow.ts +20 -5
  292. package/src/resources/extensions/gsd/memory-extractor.ts +11 -3
  293. package/src/resources/extensions/gsd/milestone-scope-classifier.ts +366 -0
  294. package/src/resources/extensions/gsd/model-cost-table.ts +3 -0
  295. package/src/resources/extensions/gsd/model-router.ts +6 -0
  296. package/src/resources/extensions/gsd/native-git-bridge.ts +34 -4
  297. package/src/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
  298. package/src/resources/extensions/gsd/prompts/plan-slice.md +15 -2
  299. package/src/resources/extensions/gsd/safety/git-checkpoint.ts +15 -0
  300. package/src/resources/extensions/gsd/service-tier.ts +5 -2
  301. package/src/resources/extensions/gsd/session-lock.ts +20 -10
  302. package/src/resources/extensions/gsd/skill-manifest.ts +175 -0
  303. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +309 -8
  304. package/src/resources/extensions/gsd/state.ts +49 -44
  305. package/src/resources/extensions/gsd/sync-lock.ts +97 -39
  306. package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +270 -0
  307. package/src/resources/extensions/gsd/tests/auto-deterministic-error-classification-4973.test.ts +341 -0
  308. package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +264 -0
  309. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +94 -289
  310. package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +742 -0
  311. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +78 -0
  312. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +61 -0
  313. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +93 -0
  314. package/src/resources/extensions/gsd/tests/auto-retry-mcp-churn-fixes.test.ts +8 -197
  315. package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +15 -58
  316. package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +17 -21
  317. package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +263 -0
  318. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +25 -0
  319. package/src/resources/extensions/gsd/tests/complete-slice-composer.test.ts +192 -0
  320. package/src/resources/extensions/gsd/tests/complete-task.test.ts +16 -8
  321. package/src/resources/extensions/gsd/tests/component-loader.test.ts +589 -0
  322. package/src/resources/extensions/gsd/tests/component-types.test.ts +127 -0
  323. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +50 -1
  324. package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +3 -3
  325. package/src/resources/extensions/gsd/tests/derive-state-db-disk-reconcile.test.ts +40 -0
  326. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +91 -3
  327. package/src/resources/extensions/gsd/tests/derive-state.test.ts +4 -3
  328. package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +3 -2
  329. package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +139 -129
  330. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +9 -105
  331. package/src/resources/extensions/gsd/tests/gate-state-canonicalization.test.ts +102 -0
  332. package/src/resources/extensions/gsd/tests/gate-storage.test.ts +1 -1
  333. package/src/resources/extensions/gsd/tests/hook-key-parsing.test.ts +4 -55
  334. package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +7 -57
  335. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +20 -0
  336. package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +18 -2
  337. package/src/resources/extensions/gsd/tests/integration/queue-completed-milestone-perf.test.ts +10 -4
  338. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +144 -7
  339. package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +4 -0
  340. package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +2 -16
  341. package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +6 -9
  342. package/src/resources/extensions/gsd/tests/mcp-client-security.test.ts +8 -37
  343. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +5 -15
  344. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +227 -62
  345. package/src/resources/extensions/gsd/tests/milestone-scope-classifier.test.ts +187 -0
  346. package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +9 -1
  347. package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
  348. package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +6 -49
  349. package/src/resources/extensions/gsd/tests/notification-widget.test.ts +6 -3
  350. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +273 -133
  351. package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +301 -0
  352. package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +32 -1
  353. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +23 -24
  354. package/src/resources/extensions/gsd/tests/queue-auto-guard.test.ts +32 -0
  355. package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +75 -2
  356. package/src/resources/extensions/gsd/tests/reassess-default-optin.test.ts +132 -0
  357. package/src/resources/extensions/gsd/tests/recovery-attempts-reset.test.ts +8 -40
  358. package/src/resources/extensions/gsd/tests/regex-hardening.test.ts +136 -256
  359. package/src/resources/extensions/gsd/tests/research-milestone-composer.test.ts +114 -0
  360. package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +148 -0
  361. package/src/resources/extensions/gsd/tests/service-tier.test.ts +4 -0
  362. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +29 -0
  363. package/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts +55 -95
  364. package/src/resources/extensions/gsd/tests/single-writer-v3-tool-surface.test.ts +158 -0
  365. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +120 -1
  366. package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +112 -0
  367. package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +164 -1
  368. package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +5 -5
  369. package/src/resources/extensions/gsd/tests/structured-data-formatter.test.ts +11 -92
  370. package/src/resources/extensions/gsd/tests/survivor-branch-complete.test.ts +102 -101
  371. package/src/resources/extensions/gsd/tests/sync-lock.test.ts +31 -0
  372. package/src/resources/extensions/gsd/tests/test-helpers.test.ts +12 -61
  373. package/src/resources/extensions/gsd/tests/test-helpers.ts +21 -8
  374. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +61 -1
  375. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +8 -1
  376. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +355 -0
  377. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +258 -0
  378. package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +75 -0
  379. package/src/resources/extensions/gsd/tests/uok-gitops-wiring.test.ts +49 -26
  380. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +1 -0
  381. package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +144 -81
  382. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -54
  383. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +342 -277
  384. package/src/resources/extensions/gsd/tests/worker-model-override.test.ts +37 -29
  385. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +226 -266
  386. package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +103 -67
  387. package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +92 -90
  388. package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +238 -59
  389. package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +113 -161
  390. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +262 -0
  391. package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +186 -0
  392. package/src/resources/extensions/gsd/tests/write-gate.test.ts +7 -5
  393. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +80 -96
  394. package/src/resources/extensions/gsd/types.ts +3 -3
  395. package/src/resources/extensions/gsd/unit-context-composer.ts +218 -0
  396. package/src/resources/extensions/gsd/unit-context-manifest.ts +574 -0
  397. package/src/resources/extensions/gsd/uok/gate-runner.ts +65 -5
  398. package/src/resources/extensions/gsd/workflow-mcp.ts +6 -0
  399. package/src/resources/extensions/gsd/worktree-manager.ts +55 -7
  400. package/src/resources/extensions/mcp-client/index.ts +3 -1
  401. package/src/resources/extensions/mcp-client/tests/server-name-spaces.test.ts +70 -36
  402. package/src/resources/extensions/ollama/index.ts +5 -1
  403. package/src/resources/extensions/ollama/ollama-auth-mode.test.ts +123 -15
  404. package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +206 -19
  405. package/src/resources/extensions/remote-questions/manager.ts +36 -4
  406. package/src/resources/extensions/remote-questions/tests/command-polling.test.ts +200 -190
  407. package/src/resources/extensions/shared/tests/interview-preview.test.ts +11 -3
  408. package/src/resources/extensions/voice/tests/linux-ready.test.ts +129 -113
  409. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts +0 -2
  410. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts.map +0 -1
  411. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js +0 -289
  412. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js.map +0 -1
  413. package/packages/pi-ai/src/utils/oauth/oauth-providers.test.ts +0 -363
  414. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +0 -144
  415. package/src/resources/extensions/gsd/tests/complete-milestone-false-merge.test.ts +0 -157
  416. package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +0 -107
  417. package/src/resources/extensions/gsd/tests/find-missing-summaries-closed.test.ts +0 -48
  418. package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +0 -159
  419. package/src/resources/extensions/gsd/tests/forensics-db-completion.test.ts +0 -96
  420. package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +0 -79
  421. package/src/resources/extensions/gsd/tests/forensics-hook-key-parse.test.ts +0 -75
  422. package/src/resources/extensions/gsd/tests/forensics-journal.test.ts +0 -162
  423. package/src/resources/extensions/gsd/tests/forensics-worktree-telemetry.test.ts +0 -145
  424. package/src/resources/extensions/gsd/tests/gitignore-bg-shell.test.ts +0 -38
  425. package/src/resources/extensions/gsd/tests/gsd-no-project-error.test.ts +0 -73
  426. package/src/resources/extensions/gsd/tests/idle-watchdog-stall-override.test.ts +0 -130
  427. package/src/resources/extensions/gsd/tests/import-done-milestones.test.ts +0 -43
  428. /package/dist/web/standalone/.next/static/{Cev5xrAYA3ZGTRLyjR2fX → SvCJDZPQW104bR1KnBQg1}/_buildManifest.js +0 -0
  429. /package/dist/web/standalone/.next/static/{Cev5xrAYA3ZGTRLyjR2fX → SvCJDZPQW104bR1KnBQg1}/_ssgManifest.js +0 -0
@@ -0,0 +1,132 @@
1
+ // GSD-2 — ADR-003 §4 behavior contract: reassess-roadmap is opt-in.
2
+ // Companion to (eventually replacing) the source-grep assertions in
3
+ // token-profile.test.ts. This file verifies the dispatch rule's guard
4
+ // behavior directly rather than inspecting source text.
5
+
6
+ import test from "node:test";
7
+ import assert from "node:assert/strict";
8
+ import { mkdirSync, readFileSync, rmSync } from "node:fs";
9
+ import { tmpdir } from "node:os";
10
+ import { join } from "node:path";
11
+ import { randomUUID } from "node:crypto";
12
+
13
+ import {
14
+ DISPATCH_RULES,
15
+ setReassessmentCheckerForTest,
16
+ type DispatchAction,
17
+ type DispatchContext,
18
+ } from "../auto-dispatch.ts";
19
+ import { resolveProfileDefaults } from "../preferences-models.ts";
20
+ import type { GSDState } from "../types.ts";
21
+ import type { GSDPreferences } from "../preferences.ts";
22
+
23
+ const REASSESS_RULE_NAME = "reassess-roadmap (post-completion)";
24
+
25
+ function makeIsolatedBase(): string {
26
+ const base = join(tmpdir(), `gsd-reassess-default-${randomUUID()}`);
27
+ mkdirSync(join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks"), { recursive: true });
28
+ return base;
29
+ }
30
+
31
+ function makeCtx(prefs: GSDPreferences | undefined, basePath: string): DispatchContext {
32
+ const state: GSDState = {
33
+ phase: "executing",
34
+ activeMilestone: { id: "M001", title: "Test" },
35
+ activeSlice: { id: "S01", title: "First" },
36
+ activeTask: null,
37
+ recentDecisions: [],
38
+ blockers: [],
39
+ nextAction: "",
40
+ registry: [{ id: "M001", title: "Test", status: "active" }],
41
+ };
42
+ return { basePath, mid: "M001", midTitle: "Test", state, prefs };
43
+ }
44
+
45
+ function reassessRule() {
46
+ const rule = DISPATCH_RULES.find(r => r.name === REASSESS_RULE_NAME);
47
+ assert.ok(rule, `dispatch rule "${REASSESS_RULE_NAME}" must exist`);
48
+ return rule!;
49
+ }
50
+
51
+ const guardCases: Array<{ name: string; prefs: GSDPreferences | undefined; message?: string }> = [
52
+ {
53
+ name: "prefs is undefined (new default)",
54
+ prefs: undefined,
55
+ message: "default behavior must be opt-in — no prefs means no reassess dispatch",
56
+ },
57
+ {
58
+ name: "prefs.phases is undefined",
59
+ prefs: {} as GSDPreferences,
60
+ },
61
+ {
62
+ name: "phases.reassess_after_slice is explicitly false",
63
+ prefs: { phases: { reassess_after_slice: false } } as unknown as GSDPreferences,
64
+ },
65
+ {
66
+ name: "phases.skip_reassess is true (short-circuit guard preserved)",
67
+ prefs: { phases: { skip_reassess: true, reassess_after_slice: true } } as unknown as GSDPreferences,
68
+ message: "skip_reassess must win over reassess_after_slice",
69
+ },
70
+ ];
71
+
72
+ for (const { name, prefs, message } of guardCases) {
73
+ test(`ADR-003 §4: reassess-roadmap does NOT dispatch when ${name}`, async (t) => {
74
+ const base = makeIsolatedBase();
75
+ t.after(() => { try { rmSync(base, { recursive: true, force: true }); } catch {} });
76
+
77
+ let checkerCalls = 0;
78
+ const restoreChecker = setReassessmentCheckerForTest(async () => {
79
+ checkerCalls++;
80
+ return { sliceId: "S01" };
81
+ });
82
+ t.after(restoreChecker);
83
+
84
+ const result = await reassessRule().match(makeCtx(prefs, base));
85
+ assert.strictEqual(result, null, message);
86
+ assert.strictEqual(checkerCalls, 0, "preference guards must short-circuit before reassessment detection");
87
+ });
88
+ }
89
+
90
+ test("ADR-003 §4: reassess-roadmap opt-in path dispatches after reassessment detection", async (t) => {
91
+ const base = makeIsolatedBase();
92
+ t.after(() => { try { rmSync(base, { recursive: true, force: true }); } catch {} });
93
+
94
+ const checkerCalls: Array<{ basePath: string; mid: string; activeSliceId: string | undefined }> = [];
95
+ const restoreChecker = setReassessmentCheckerForTest(async (checkerBase, checkerMid, state) => {
96
+ checkerCalls.push({ basePath: checkerBase, mid: checkerMid, activeSliceId: state.activeSlice?.id });
97
+ return { sliceId: "S01" };
98
+ });
99
+ t.after(restoreChecker);
100
+
101
+ const prefs = { phases: { reassess_after_slice: true } } as unknown as GSDPreferences;
102
+ const result: DispatchAction | null = await reassessRule().match(makeCtx(prefs, base));
103
+
104
+ assert.deepStrictEqual(checkerCalls, [{ basePath: base, mid: "M001", activeSliceId: "S01" }]);
105
+ assert.ok(result, "opt-in path should return a dispatch action when reassessment is needed");
106
+ assert.strictEqual(result.action, "dispatch");
107
+ if (result.action !== "dispatch") assert.fail("expected dispatch action");
108
+ assert.strictEqual(result.unitType, "reassess-roadmap");
109
+ assert.strictEqual(result.unitId, "M001/S01");
110
+ assert.match(result.prompt, /reassess/i);
111
+ });
112
+
113
+ test("ADR-003 §4: burn-max profile opts into dedicated reassess-roadmap dispatch", () => {
114
+ const defaults = resolveProfileDefaults("burn-max");
115
+ assert.strictEqual(defaults.phases?.reassess_after_slice, true);
116
+ assert.strictEqual(defaults.phases?.skip_reassess, false);
117
+ });
118
+
119
+ test("ADR-003 §4: plan-slice prompt and MCP tool agree on reassess sliceChanges shape", () => {
120
+ const planSlicePrompt = readFileSync(new URL("../prompts/plan-slice.md", import.meta.url), "utf8");
121
+ assert.match(planSlicePrompt, /gsd_reassess_roadmap/);
122
+ assert.match(planSlicePrompt, /sliceChanges\.modified/);
123
+ assert.match(planSlicePrompt, /sliceChanges\.added/);
124
+ assert.match(planSlicePrompt, /sliceChanges\.removed/);
125
+
126
+ const dbToolsSource = readFileSync(new URL("../bootstrap/db-tools.ts", import.meta.url), "utf8");
127
+ assert.match(dbToolsSource, /name:\s*"gsd_reassess_roadmap"/);
128
+ assert.match(dbToolsSource, /sliceChanges:\s*Type\.Object/);
129
+ assert.match(dbToolsSource, /modified:\s*Type\.Array/);
130
+ assert.match(dbToolsSource, /added:\s*Type\.Array/);
131
+ assert.match(dbToolsSource, /removed:\s*Type\.Array/);
132
+ });
@@ -81,46 +81,14 @@ try {
81
81
  );
82
82
  }
83
83
 
84
- // ═══ Verify the BUG scenario: omitting recoveryAttempts carries it over ═══
85
-
86
- {
87
- console.log("\n=== #2322: demonstrates bug omitting recoveryAttempts carries it over ===");
88
-
89
- const unitType = "execute-task";
90
- const unitId = "M001/S01/T02";
91
- const startedAt1 = Date.now() - 10000;
92
-
93
- // First dispatch
94
- writeUnitRuntimeRecord(base, unitType, unitId, startedAt1, {
95
- phase: "dispatched",
96
- });
97
-
98
- // Timeout bumps recoveryAttempts to 1
99
- writeUnitRuntimeRecord(base, unitType, unitId, startedAt1, {
100
- recoveryAttempts: 1,
101
- lastRecoveryReason: "hard",
102
- });
103
-
104
- // Re-dispatch WITHOUT resetting recoveryAttempts (the bug)
105
- const startedAt2 = Date.now();
106
- writeUnitRuntimeRecord(base, unitType, unitId, startedAt2, {
107
- phase: "dispatched",
108
- wrapupWarningSent: false,
109
- timeoutAt: null,
110
- lastProgressAt: startedAt2,
111
- progressCount: 0,
112
- lastProgressKind: "dispatch",
113
- // recoveryAttempts: NOT included — this is the bug
114
- });
115
-
116
- const afterBuggyRedispatch = readUnitRuntimeRecord(base, unitType, unitId);
117
- // This DEMONSTRATES the bug: recoveryAttempts is still 1
118
- assertEq(
119
- afterBuggyRedispatch?.recoveryAttempts,
120
- 1,
121
- "BUG DEMO: recoveryAttempts carries over when not explicitly reset",
122
- );
123
- }
84
+ // Note (#4837): the previous "BUG DEMO" block was tautological it only
85
+ // asserted that writeUnitRuntimeRecord merges partial updates into the
86
+ // stored record (which is the function's documented behaviour and is
87
+ // already covered by unit-runtime.test.ts). Proving merge semantics here
88
+ // added zero coverage of the #2322 fix. The first block above verifies the
89
+ // fix itself (explicit recoveryAttempts: 0 on re-dispatch resets the
90
+ // counter); the block below verifies the end-to-end budget-reset
91
+ // invariant.
124
92
 
125
93
  // ═══ Hard timeout maxRecoveryAttempts=1 — second dispatch must get full budget ═══
126
94
 
@@ -1,281 +1,161 @@
1
- // Regex-hardening tests for S02/T02 — proves all 12 regex/parser sites
2
- // accept both M001 (classic) and M001-abc123 (unique) milestone ID formats.
3
- //
4
- // Sections:
5
- // (a) Directory scanning regex findMilestoneIds pattern
6
- // (b) Title-strip regex milestone title cleanup
7
- // (c) SLICE_BRANCH_RE branch name parsing (with/without worktree prefix)
8
- // (d) Milestone detection regex hasExistingMilestones pattern
9
- // (e) MILESTONE_CONTEXT_REcontext write-gate filename match
10
- // (f) Prompt dispatch regexes — executeMatch and resumeMatch capture
11
- // (g) milestoneIdSort — mixed-format ordering
12
- // (h) extractMilestoneSeq numeric extraction from both formats
13
-
14
- import { test } from 'node:test';
1
+ /**
2
+ * regex-hardening.test.ts verifies production regexes accept both the
3
+ * legacy (M001) and unique (M001-abc123) milestone ID formats.
4
+ *
5
+ * The previous version of this file advertised 12 parser sites but
6
+ * only 3 tested imports (SLICE_BRANCH_RE, MILESTONE_ID_RE helpers).
7
+ * The remaining 9 sections (a, b, d, e, f) declared local `const
8
+ * *_RE = ...` copies of production regexes and asserted against the
9
+ * copies a bug in the real regex would not fail those tests. See
10
+ * #4835.
11
+ *
12
+ * This rewrite imports every production pattern it exercises. Four
13
+ * call sites whose regexes are inline at the use site (state.ts:313
14
+ * title-strip, workspace-index.ts:80 title extraction, worktree-
15
+ * command.ts hasExistingMilestones, and the prompt dispatch regexes
16
+ * in index.ts) are intentionally NOT reimplemented here — they should
17
+ * be covered by behaviour tests of their parent functions, not by
18
+ * regex-copy assertions. A follow-up issue tracks extracting those
19
+ * regexes to a shared patterns module so they can be tested directly.
20
+ */
21
+
22
+ import test from "node:test";
23
+ import assert from "node:assert/strict";
15
24
 
16
25
  import {
17
26
  MILESTONE_ID_RE,
18
27
  extractMilestoneSeq,
19
28
  milestoneIdSort,
20
- } from '../guided-flow.ts';
21
-
22
- import { SLICE_BRANCH_RE } from '../worktree.ts';
23
- import { createTestContext } from './test-helpers.ts';
24
-
25
-
26
- const { assertEq, assertTrue, report } = createTestContext();
27
- // ─── Tests ─────────────────────────────────────────────────────────────────
28
-
29
- async function main(): Promise<void> {
30
- console.log('regex-hardening tests');
31
-
32
- // (a) Directory scanning regex — used in state.ts, workspace-index.ts, files.ts
33
- // Pattern: /^(M\d+(?:-[a-z0-9]{6})?)/
34
- {
35
- console.log(' (a) Directory scanning regex');
36
- const DIR_SCAN_RE = /^(M\d+(?:-[a-z0-9]{6})?)/;
37
-
38
- // Classic format matches
39
- assertTrue(DIR_SCAN_RE.test('M001'), 'dir scan matches M001');
40
- assertTrue(DIR_SCAN_RE.test('M042'), 'dir scan matches M042');
41
- assertTrue(DIR_SCAN_RE.test('M999'), 'dir scan matches M999');
42
- assertEq(('M001' as string).match(DIR_SCAN_RE)?.[1], 'M001', 'captures M001');
43
-
44
- // Unique format matches
45
- assertTrue(DIR_SCAN_RE.test('M001-abc123'), 'dir scan matches M001-abc123');
46
- assertTrue(DIR_SCAN_RE.test('M042-z9a8b7'), 'dir scan matches M042-z9a8b7');
47
- assertEq(('M001-abc123' as string).match(DIR_SCAN_RE)?.[1], 'M001-abc123', 'captures M001-abc123 from dir name');
48
-
49
- // Rejects
50
- assertTrue(!DIR_SCAN_RE.test('S01'), 'dir scan rejects S01');
51
- assertTrue(!DIR_SCAN_RE.test('X001'), 'dir scan rejects X001');
52
- assertTrue(!DIR_SCAN_RE.test('.DS_Store'), 'dir scan rejects .DS_Store');
53
- assertTrue(!DIR_SCAN_RE.test('notes'), 'dir scan rejects notes');
54
- }
55
-
56
- // (b) Title-strip regex — used in state.ts, workspace-index.ts
57
- // Pattern: /^M\d+(?:-[a-z0-9]{6})?[^:]*:\s*/
58
- {
59
- console.log(' (b) Title-strip regex');
60
- const TITLE_STRIP_RE = /^M\d+(?:-[a-z0-9]{6})?[^:]*:\s*/;
61
-
62
- // Classic format strip
63
- assertEq('M001: Title'.replace(TITLE_STRIP_RE, ''), 'Title', 'strips M001: Title → Title');
64
- assertEq('M042: Payment Integration'.replace(TITLE_STRIP_RE, ''), 'Payment Integration', 'strips M042: Payment Integration');
29
+ } from "../guided-flow.ts";
30
+ import { SLICE_BRANCH_RE } from "../worktree.ts";
31
+ import { MILESTONE_CONTEXT_RE } from "../bootstrap/write-gate.ts";
65
32
 
66
- // Unique format strip
67
- assertEq('M001-abc123: Title'.replace(TITLE_STRIP_RE, ''), 'Title', 'strips M001-abc123: Title → Title');
68
- assertEq('M042-z9a8b7: Dashboard'.replace(TITLE_STRIP_RE, ''), 'Dashboard', 'strips M042-z9a8b7: Dashboard');
33
+ // ─── MILESTONE_ID_RE ──────────────────────────────────────────────────────
69
34
 
70
- // Em dash in title — current format (M001: Title) correctly preserves em dash in title body
71
- assertEq(
72
- 'M001: Foundation — Build Core'.replace(TITLE_STRIP_RE, ''),
73
- 'Foundation — Build Core',
74
- 'strips M001: prefix and preserves em dash in title body',
75
- );
76
- assertEq(
77
- 'M001-abc123: Foundation — Build Core'.replace(TITLE_STRIP_RE, ''),
78
- 'Foundation — Build Core',
79
- 'strips M001-abc123: prefix and preserves em dash in title body (unique format)',
80
- );
81
-
82
- // Edge case: dash-style separator (M001 — Title: Subtitle preserves colon in body)
83
- assertEq(
84
- 'M001 — Unique Milestone IDs: Foo'.replace(TITLE_STRIP_RE, ''),
85
- 'Foo',
86
- 'strips M001 — Unique Milestone IDs: Foo → Foo (first colon consumed)',
87
- );
88
-
89
- // Edge case: colon inside title body preserved
90
- assertEq(
91
- 'M001: Note: important'.replace(TITLE_STRIP_RE, ''),
92
- 'Note: important',
93
- 'preserves colons in title body',
94
- );
95
-
96
- // No match — leaves non-milestone strings alone
97
- assertEq('S01: Slice Title'.replace(TITLE_STRIP_RE, ''), 'S01: Slice Title', 'does not strip S01 prefix');
98
- }
99
-
100
- // (c) SLICE_BRANCH_RE — from worktree.ts
101
- // Pattern: /^gsd\/(?:([a-zA-Z0-9_-]+)\/)?(M\d+(?:-[a-z0-9]{6})?)\/(S\d+)$/
102
- {
103
- console.log(' (c) SLICE_BRANCH_RE');
104
-
105
- // Classic format — no worktree prefix
106
- {
107
- const m = 'gsd/M001/S01'.match(SLICE_BRANCH_RE);
108
- assertTrue(m !== null, 'matches gsd/M001/S01');
109
- assertEq(m?.[1], undefined, 'no worktree prefix for gsd/M001/S01');
110
- assertEq(m?.[2], 'M001', 'captures M001');
111
- assertEq(m?.[3], 'S01', 'captures S01');
112
- }
113
-
114
- // Unique format — no worktree prefix
115
- {
116
- const m = 'gsd/M001-abc123/S01'.match(SLICE_BRANCH_RE);
117
- assertTrue(m !== null, 'matches gsd/M001-abc123/S01');
118
- assertEq(m?.[1], undefined, 'no worktree prefix for unique format');
119
- assertEq(m?.[2], 'M001-abc123', 'captures M001-abc123');
120
- assertEq(m?.[3], 'S01', 'captures S01');
121
- }
122
-
123
- // Classic format — with worktree prefix
124
- {
125
- const m = 'gsd/worktree/M001/S01'.match(SLICE_BRANCH_RE);
126
- assertTrue(m !== null, 'matches gsd/worktree/M001/S01');
127
- assertEq(m?.[1], 'worktree', 'captures worktree prefix');
128
- assertEq(m?.[2], 'M001', 'captures M001 with worktree');
129
- assertEq(m?.[3], 'S01', 'captures S01 with worktree');
130
- }
131
-
132
- // Unique format — with worktree prefix
133
- {
134
- const m = 'gsd/worktree/M001-abc123/S01'.match(SLICE_BRANCH_RE);
135
- assertTrue(m !== null, 'matches gsd/worktree/M001-abc123/S01');
136
- assertEq(m?.[1], 'worktree', 'captures worktree prefix with unique format');
137
- assertEq(m?.[2], 'M001-abc123', 'captures M001-abc123 with worktree');
138
- assertEq(m?.[3], 'S01', 'captures S01 with worktree and unique format');
139
- }
140
-
141
- // Rejects
142
- assertTrue(!SLICE_BRANCH_RE.test('gsd/S01'), 'rejects gsd/S01 (no milestone)');
143
- assertTrue(!SLICE_BRANCH_RE.test('main'), 'rejects main');
144
- assertTrue(!SLICE_BRANCH_RE.test('gsd/M001'), 'rejects gsd/M001 (no slice)');
145
- assertTrue(!SLICE_BRANCH_RE.test('feature/M001/S01'), 'rejects feature/ prefix');
146
- }
147
-
148
- // (d) Milestone detection regex — used in worktree-command.ts (hasExistingMilestones)
149
- // Pattern: /^M\d+(?:-[a-z0-9]{6})?/
150
- {
151
- console.log(' (d) Milestone detection regex');
152
- const MILESTONE_DETECT_RE = /^M\d+(?:-[a-z0-9]{6})?/;
35
+ test("MILESTONE_ID_RE accepts classic M001 format", () => {
36
+ assert.ok(MILESTONE_ID_RE.test("M001"));
37
+ assert.ok(MILESTONE_ID_RE.test("M042"));
38
+ assert.ok(MILESTONE_ID_RE.test("M999"));
39
+ });
153
40
 
154
- // Classic format matches
155
- assertTrue(MILESTONE_DETECT_RE.test('M001'), 'detect matches M001');
156
- assertTrue(MILESTONE_DETECT_RE.test('M042'), 'detect matches M042');
41
+ test("MILESTONE_ID_RE accepts unique M001-abc123 format", () => {
42
+ assert.ok(MILESTONE_ID_RE.test("M001-abc123"));
43
+ assert.ok(MILESTONE_ID_RE.test("M042-z9a8b7"));
44
+ });
157
45
 
158
- // Unique format matches
159
- assertTrue(MILESTONE_DETECT_RE.test('M001-abc123'), 'detect matches M001-abc123');
160
- assertTrue(MILESTONE_DETECT_RE.test('M042-z9a8b7'), 'detect matches M042-z9a8b7');
46
+ test("MILESTONE_ID_RE rejects non-milestone strings", () => {
47
+ assert.ok(!MILESTONE_ID_RE.test("S01"));
48
+ assert.ok(!MILESTONE_ID_RE.test("X001"));
49
+ assert.ok(!MILESTONE_ID_RE.test("notes"));
50
+ assert.ok(!MILESTONE_ID_RE.test(".DS_Store"));
51
+ assert.ok(!MILESTONE_ID_RE.test(""));
52
+ // Must be a bare id — not a prefix match.
53
+ assert.ok(!MILESTONE_ID_RE.test("M001-ABCDEF"), "uppercase suffix rejected");
54
+ assert.ok(!MILESTONE_ID_RE.test("M001 "), "trailing space rejected");
55
+ });
161
56
 
162
- // Rejects
163
- assertTrue(!MILESTONE_DETECT_RE.test('S01'), 'detect rejects S01');
164
- assertTrue(!MILESTONE_DETECT_RE.test('notes'), 'detect rejects notes');
165
- assertTrue(!MILESTONE_DETECT_RE.test('.DS_Store'), 'detect rejects .DS_Store');
57
+ // ─── SLICE_BRANCH_RE ──────────────────────────────────────────────────────
58
+
59
+ test("SLICE_BRANCH_RE captures milestone + slice without worktree prefix", () => {
60
+ for (const { input, expectMid } of [
61
+ { input: "gsd/M001/S01", expectMid: "M001" },
62
+ { input: "gsd/M001-abc123/S01", expectMid: "M001-abc123" },
63
+ ]) {
64
+ const m = input.match(SLICE_BRANCH_RE);
65
+ assert.ok(m, `should match ${input}`);
66
+ assert.equal(m?.[1], undefined, "no worktree prefix");
67
+ assert.equal(m?.[2], expectMid);
68
+ assert.equal(m?.[3], "S01");
166
69
  }
70
+ });
167
71
 
168
- // (e) MILESTONE_CONTEXT_RE used in index.ts (write-gate)
169
- // Pattern: /M\d+(?:-[a-z0-9]{6})?-CONTEXT\.md$/
170
- {
171
- console.log(' (e) MILESTONE_CONTEXT_RE');
172
- const CONTEXT_RE = /M\d+(?:-[a-z0-9]{6})?-CONTEXT\.md$/;
173
-
174
- // Classic format matches
175
- assertTrue(CONTEXT_RE.test('M001-CONTEXT.md'), 'context matches M001-CONTEXT.md');
176
- assertTrue(CONTEXT_RE.test('.gsd/milestones/M001/M001-CONTEXT.md'), 'context matches full path classic format');
177
-
178
- // Unique format matches
179
- assertTrue(CONTEXT_RE.test('M001-abc123-CONTEXT.md'), 'context matches M001-abc123-CONTEXT.md');
180
- assertTrue(CONTEXT_RE.test('.gsd/milestones/M001-abc123/M001-abc123-CONTEXT.md'), 'context matches full path unique format');
181
-
182
- // Rejects
183
- assertTrue(!CONTEXT_RE.test('M001-ROADMAP.md'), 'context rejects M001-ROADMAP.md');
184
- assertTrue(!CONTEXT_RE.test('M001-SUMMARY.md'), 'context rejects M001-SUMMARY.md');
185
- assertTrue(!CONTEXT_RE.test('CONTEXT.md'), 'context rejects bare CONTEXT.md');
72
+ test("SLICE_BRANCH_RE captures worktree prefix when present", () => {
73
+ for (const { input, expectMid } of [
74
+ { input: "gsd/worktree/M001/S01", expectMid: "M001" },
75
+ { input: "gsd/worktree/M001-abc123/S01", expectMid: "M001-abc123" },
76
+ ]) {
77
+ const m = input.match(SLICE_BRANCH_RE);
78
+ assert.ok(m, `should match ${input}`);
79
+ assert.equal(m?.[1], "worktree");
80
+ assert.equal(m?.[2], expectMid);
81
+ assert.equal(m?.[3], "S01");
186
82
  }
83
+ });
187
84
 
188
- // (f) Prompt dispatch regexes used in index.ts (executeMatch, resumeMatch)
189
- {
190
- console.log(' (f) Prompt dispatch regexes');
191
- const EXECUTE_RE = /Execute the next task:\s+(T\d+)\s+\("([^"]+)"\)\s+in slice\s+(S\d+)\s+of milestone\s+(M\d+(?:-[a-z0-9]{6})?)/i;
192
- const RESUME_RE = /Resume interrupted work\.[\s\S]*?slice\s+(S\d+)\s+of milestone\s+(M\d+(?:-[a-z0-9]{6})?)/i;
193
-
194
- // Execute — classic format
195
- {
196
- const prompt = 'Execute the next task: T01 ("Write tests") in slice S01 of milestone M001';
197
- const m = prompt.match(EXECUTE_RE);
198
- assertTrue(m !== null, 'execute matches classic format');
199
- assertEq(m?.[1], 'T01', 'execute captures T01');
200
- assertEq(m?.[3], 'S01', 'execute captures S01');
201
- assertEq(m?.[4], 'M001', 'execute captures M001');
202
- }
203
-
204
- // Execute — unique format
205
- {
206
- const prompt = 'Execute the next task: T02 ("Build feature") in slice S03 of milestone M001-abc123';
207
- const m = prompt.match(EXECUTE_RE);
208
- assertTrue(m !== null, 'execute matches unique format');
209
- assertEq(m?.[1], 'T02', 'execute captures T02 (unique format)');
210
- assertEq(m?.[3], 'S03', 'execute captures S03 (unique format)');
211
- assertEq(m?.[4], 'M001-abc123', 'execute captures M001-abc123');
212
- }
213
-
214
- // Resume — classic format
215
- {
216
- const prompt = 'Resume interrupted work.\nContinuing slice S02 of milestone M001';
217
- const m = prompt.match(RESUME_RE);
218
- assertTrue(m !== null, 'resume matches classic format');
219
- assertEq(m?.[1], 'S02', 'resume captures S02');
220
- assertEq(m?.[2], 'M001', 'resume captures M001');
221
- }
222
-
223
- // Resume — unique format
224
- {
225
- const prompt = 'Resume interrupted work.\nContinuing slice S01 of milestone M042-z9a8b7';
226
- const m = prompt.match(RESUME_RE);
227
- assertTrue(m !== null, 'resume matches unique format');
228
- assertEq(m?.[1], 'S01', 'resume captures S01 (unique format)');
229
- assertEq(m?.[2], 'M042-z9a8b7', 'resume captures M042-z9a8b7');
230
- }
231
- }
85
+ test("SLICE_BRANCH_RE rejects malformed inputs", () => {
86
+ assert.ok(!SLICE_BRANCH_RE.test("gsd/S01"), "no milestone");
87
+ assert.ok(!SLICE_BRANCH_RE.test("main"), "non-gsd branch");
88
+ assert.ok(!SLICE_BRANCH_RE.test("gsd/M001"), "no slice");
89
+ assert.ok(!SLICE_BRANCH_RE.test("feature/M001/S01"), "wrong prefix");
90
+ });
232
91
 
233
- // (g) milestoneIdSort — mixed-format ordering
234
- {
235
- console.log(' (g) milestoneIdSort');
236
- const mixed = ['M002-abc123', 'M001', 'M001-xyz789'];
237
- const sorted = [...mixed].sort(milestoneIdSort);
238
- assertEq(sorted, ['M001', 'M001-xyz789', 'M002-abc123'], 'sorts mixed IDs by sequence number');
92
+ // ─── MILESTONE_CONTEXT_RE ────────────────────────────────────────────────
93
+
94
+ test("MILESTONE_CONTEXT_RE matches legacy and unique CONTEXT.md names", () => {
95
+ assert.ok(MILESTONE_CONTEXT_RE.test("M001-CONTEXT.md"));
96
+ assert.ok(MILESTONE_CONTEXT_RE.test("M001-abc123-CONTEXT.md"));
97
+ assert.ok(
98
+ MILESTONE_CONTEXT_RE.test(".gsd/milestones/M001/M001-CONTEXT.md"),
99
+ "full path legacy format",
100
+ );
101
+ assert.ok(
102
+ MILESTONE_CONTEXT_RE.test(".gsd/milestones/M001-abc123/M001-abc123-CONTEXT.md"),
103
+ "full path unique format",
104
+ );
105
+ });
239
106
 
240
- // Stable within same seq preserves insertion order
241
- const sameSorted = ['M001-abc123', 'M001'].sort(milestoneIdSort);
242
- assertEq(sameSorted[0], 'M001-abc123', 'same seq preserves order (first)');
243
- assertEq(sameSorted[1], 'M001', 'same seq preserves order (second)');
107
+ test("MILESTONE_CONTEXT_RE rejects non-CONTEXT artifact names", () => {
108
+ assert.ok(!MILESTONE_CONTEXT_RE.test("M001-ROADMAP.md"));
109
+ assert.ok(!MILESTONE_CONTEXT_RE.test("M001-SUMMARY.md"));
110
+ assert.ok(!MILESTONE_CONTEXT_RE.test("CONTEXT.md"), "bare name without milestone prefix");
111
+ });
244
112
 
245
- // Classic format only
246
- const oldOnly = ['M003', 'M001', 'M002'];
247
- assertEq([...oldOnly].sort(milestoneIdSort), ['M001', 'M002', 'M003'], 'sorts classic-format IDs');
113
+ // ─── extractMilestoneSeq ──────────────────────────────────────────────────
248
114
 
249
- // Unique format only
250
- const newOnly = ['M003-abc123', 'M001-def456', 'M002-ghi789'];
251
- assertEq([...newOnly].sort(milestoneIdSort), ['M001-def456', 'M002-ghi789', 'M003-abc123'], 'sorts unique-format IDs');
252
- }
115
+ test("extractMilestoneSeq returns numeric sequence for both formats", () => {
116
+ assert.equal(extractMilestoneSeq("M001"), 1);
117
+ assert.equal(extractMilestoneSeq("M042"), 42);
118
+ assert.equal(extractMilestoneSeq("M999"), 999);
119
+ assert.equal(extractMilestoneSeq("M001-abc123"), 1);
120
+ assert.equal(extractMilestoneSeq("M042-z9a8b7"), 42);
121
+ assert.equal(extractMilestoneSeq("M100-xyz789"), 100);
122
+ });
253
123
 
254
- // (h) extractMilestoneSeq numeric extraction from both formats
255
- {
256
- console.log(' (h) extractMilestoneSeq');
124
+ test("extractMilestoneSeq returns 0 (not NaN) for invalid inputs", () => {
125
+ assert.equal(extractMilestoneSeq(""), 0);
126
+ assert.equal(extractMilestoneSeq("notes"), 0);
127
+ assert.equal(extractMilestoneSeq("S01"), 0);
128
+ // Specific regression: the parseInt(slice(1)) implementation returned
129
+ // NaN on inputs like "M001-abc123" because parseInt stopped at the
130
+ // dash but then the rest of the logic treated the result as a number.
131
+ // Current impl returns a real number.
132
+ assert.ok(!Number.isNaN(extractMilestoneSeq("M001-abc123")));
133
+ });
257
134
 
258
- // Classic format
259
- assertEq(extractMilestoneSeq('M001'), 1, 'M001 → 1');
260
- assertEq(extractMilestoneSeq('M042'), 42, 'M042 → 42');
261
- assertEq(extractMilestoneSeq('M999'), 999, 'M999 → 999');
135
+ // ─── milestoneIdSort ──────────────────────────────────────────────────────
262
136
 
263
- // Unique format confirms dispatch-guard refactor correctness
264
- assertEq(extractMilestoneSeq('M001-abc123'), 1, 'M001-abc123 → 1');
265
- assertEq(extractMilestoneSeq('M042-z9a8b7'), 42, 'M042-z9a8b7 → 42');
266
- assertEq(extractMilestoneSeq('M100-xyz789'), 100, 'M100-xyz789 → 100');
137
+ test("milestoneIdSort orders by numeric sequence across both formats", () => {
138
+ const mixed = ["M002-abc123", "M001", "M001-xyz789"];
139
+ assert.deepEqual(
140
+ [...mixed].sort(milestoneIdSort),
141
+ ["M001", "M001-xyz789", "M002-abc123"],
142
+ "mixed formats sort by seq number",
143
+ );
267
144
 
268
- // Invalid 0 (not NaN — the old parseInt(slice(1)) bug)
269
- assertEq(extractMilestoneSeq(''), 0, 'empty → 0');
270
- assertEq(extractMilestoneSeq('notes'), 0, 'notes → 0');
271
- assertEq(extractMilestoneSeq('S01'), 0, 'S01 → 0');
272
- assertTrue(!Number.isNaN(extractMilestoneSeq('M001-abc123')), 'unique format does not return NaN');
273
- assertTrue(!Number.isNaN(extractMilestoneSeq('M001-ABCDEF')), 'invalid format does not return NaN');
274
- }
145
+ const legacy = ["M003", "M001", "M002"];
146
+ assert.deepEqual([...legacy].sort(milestoneIdSort), ["M001", "M002", "M003"]);
275
147
 
276
- report();
277
- }
148
+ const unique = ["M003-abc123", "M001-def456", "M002-ghi789"];
149
+ assert.deepEqual(
150
+ [...unique].sort(milestoneIdSort),
151
+ ["M001-def456", "M002-ghi789", "M003-abc123"],
152
+ );
153
+ });
278
154
 
279
- test('regex-hardening: all 12 sites accept both formats', async () => {
280
- await main();
155
+ test("milestoneIdSort preserves input order for same-sequence ids", () => {
156
+ // sort is stable per ECMAScript 2019+ when the comparator returns 0.
157
+ const sameSeq = ["M001-abc123", "M001"];
158
+ const sorted = [...sameSeq].sort(milestoneIdSort);
159
+ assert.equal(sorted[0], "M001-abc123");
160
+ assert.equal(sorted[1], "M001");
281
161
  });