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
@@ -4,7 +4,13 @@ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { tmpdir } from "node:os";
6
6
  import { loadSkills } from "@gsd/pi-coding-agent";
7
- import { buildSkillActivationBlock } from "../auto-prompts.js";
7
+ import {
8
+ buildPlanMilestonePrompt,
9
+ buildResearchMilestonePrompt,
10
+ buildSkillActivationBlock,
11
+ } from "../auto-prompts.js";
12
+ import { warnIfManifestHasMissingSkills } from "../skill-manifest.js";
13
+ import { _resetLogs, drainLogs, setStderrLoggingEnabled } from "../workflow-logger.js";
8
14
  import type { GSDPreferences } from "../preferences.js";
9
15
 
10
16
  function makeTempBase(): string {
@@ -25,6 +31,11 @@ function loadOnlyTestSkills(base: string): void {
25
31
  loadSkills({ cwd: base, includeDefaults: false, skillPaths: [join(base, "skills")] });
26
32
  }
27
33
 
34
+ function writeProjectPreferences(base: string, preferences: string): void {
35
+ mkdirSync(join(base, ".gsd"), { recursive: true });
36
+ writeFileSync(join(base, ".gsd", "PREFERENCES.md"), `---\n${preferences}---\n`);
37
+ }
38
+
28
39
  function buildBlock(
29
40
  base: string,
30
41
  params: Partial<Parameters<typeof buildSkillActivationBlock>[0]> = {},
@@ -231,3 +242,111 @@ test("buildSkillActivationBlock allows valid skill names and rejects invalid one
231
242
  cleanup(base);
232
243
  }
233
244
  });
245
+
246
+ // ─── Per-unit-type skill manifest (RFC #4779) ─────────────────────────────────
247
+
248
+ test("buildSkillActivationBlock: explicit always_use_skills bypass the unit-type manifest", () => {
249
+ const base = makeTempBase();
250
+ try {
251
+ // write-docs is in the research-milestone manifest; swiftui is not.
252
+ // Both are in always_use_skills — a user-explicit source — so BOTH
253
+ // should activate regardless of the manifest. User intent wins over
254
+ // unit-type defaults. See RFC #4779 and skill-manifest.ts rationale.
255
+ writeSkill(base, "write-docs", "Use when writing docs or RFCs.");
256
+ writeSkill(base, "swiftui", "Use for SwiftUI views.");
257
+ loadOnlyTestSkills(base);
258
+
259
+ const result = buildBlock(base, { unitType: "research-milestone" }, {
260
+ always_use_skills: ["write-docs", "swiftui"],
261
+ });
262
+
263
+ assert.match(result, /Call Skill\(\{ skill: 'write-docs' \}\)/);
264
+ assert.match(result, /Call Skill\(\{ skill: 'swiftui' \}\)/);
265
+ } finally {
266
+ cleanup(base);
267
+ }
268
+ });
269
+
270
+ test("buildSkillActivationBlock falls through to all skills for unknown unit type", () => {
271
+ const base = makeTempBase();
272
+ try {
273
+ writeSkill(base, "swiftui", "Use for SwiftUI views.");
274
+ loadOnlyTestSkills(base);
275
+
276
+ const result = buildBlock(base, { unitType: "unknown-unit-type" }, {
277
+ always_use_skills: ["swiftui"],
278
+ });
279
+
280
+ // Unknown unit type = wildcard fallback (pre-manifest behavior).
281
+ assert.match(result, /Call Skill\(\{ skill: 'swiftui' \}\)/);
282
+ } finally {
283
+ cleanup(base);
284
+ }
285
+ });
286
+
287
+ test("buildSkillActivationBlock without unitType preserves pre-manifest behavior", () => {
288
+ const base = makeTempBase();
289
+ try {
290
+ writeSkill(base, "swiftui", "Use for SwiftUI views.");
291
+ loadOnlyTestSkills(base);
292
+
293
+ // No unitType param — filter should no-op.
294
+ const result = buildBlock(base, {}, {
295
+ always_use_skills: ["swiftui"],
296
+ });
297
+
298
+ assert.match(result, /Call Skill\(\{ skill: 'swiftui' \}\)/);
299
+ } finally {
300
+ cleanup(base);
301
+ }
302
+ });
303
+
304
+ test("milestone prompt builders propagate always_use_skills through buildSkillActivationBlock", async () => {
305
+ const base = makeTempBase();
306
+ try {
307
+ // Both skills are in always_use_skills — explicit user intent bypasses
308
+ // the unit-type manifest, so both activate in both milestone flows.
309
+ writeSkill(base, "write-docs", "Use when writing docs or RFCs.");
310
+ writeSkill(base, "swiftui", "Use for SwiftUI views.");
311
+ writeProjectPreferences(base, "always_use_skills:\n - write-docs\n - swiftui\n");
312
+ loadOnlyTestSkills(base);
313
+
314
+ const researchPrompt = await buildResearchMilestonePrompt("M001", "Test", base);
315
+ assert.match(researchPrompt, /Call Skill\(\{ skill: 'write-docs' \}\)/);
316
+ assert.match(researchPrompt, /Call Skill\(\{ skill: 'swiftui' \}\)/);
317
+
318
+ const planPrompt = await buildPlanMilestonePrompt("M001", "Test", base);
319
+ assert.match(planPrompt, /Call Skill\(\{ skill: 'write-docs' \}\)/);
320
+ assert.match(planPrompt, /Call Skill\(\{ skill: 'swiftui' \}\)/);
321
+ } finally {
322
+ cleanup(base);
323
+ }
324
+ });
325
+
326
+ test("skill manifest strict warnings require GSD_SKILL_MANIFEST_STRICT=1", (t) => {
327
+ const previousStrict = process.env.GSD_SKILL_MANIFEST_STRICT;
328
+ const previousStderr = setStderrLoggingEnabled(false);
329
+ t.after(() => {
330
+ if (previousStrict === undefined) {
331
+ delete process.env.GSD_SKILL_MANIFEST_STRICT;
332
+ } else {
333
+ process.env.GSD_SKILL_MANIFEST_STRICT = previousStrict;
334
+ }
335
+ setStderrLoggingEnabled(previousStderr);
336
+ _resetLogs();
337
+ });
338
+
339
+ process.env.GSD_SKILL_MANIFEST_STRICT = "0";
340
+ _resetLogs();
341
+ warnIfManifestHasMissingSkills("research-milestone", new Set());
342
+ assert.equal(drainLogs().length, 0, "strict=0 must preserve silent behavior");
343
+
344
+ process.env.GSD_SKILL_MANIFEST_STRICT = "1";
345
+ _resetLogs();
346
+ warnIfManifestHasMissingSkills("research-milestone", new Set());
347
+ const logs = drainLogs();
348
+ assert.ok(
349
+ logs.some(log => log.message.includes("skill-manifest: references uninstalled skill")),
350
+ "strict=1 should warn about missing manifest entries",
351
+ );
352
+ });
@@ -0,0 +1,112 @@
1
+ // GSD2 + skill-manifest.test — unit coverage for the skill manifest resolver
2
+ //
3
+ // Focused tests for `resolveSkillManifest` and `filterSkillsByManifest`.
4
+ // Covers the wildcard semantics, the newly seeded unit-type entries
5
+ // (complete-milestone, validate-milestone, reassess-roadmap, research-slice,
6
+ // plan-slice, refine-slice, replan-slice, run-uat), and the deliberate
7
+ // wildcard fallback for the execute-task hot path (RFC #4779).
8
+
9
+ import test from "node:test";
10
+ import assert from "node:assert/strict";
11
+
12
+ import {
13
+ resolveSkillManifest,
14
+ filterSkillsByManifest,
15
+ } from "../skill-manifest.js";
16
+
17
+ const NEWLY_WIRED_UNIT_TYPES = [
18
+ "complete-milestone",
19
+ "validate-milestone",
20
+ "reassess-roadmap",
21
+ "research-slice",
22
+ "plan-slice",
23
+ "refine-slice",
24
+ "replan-slice",
25
+ "run-uat",
26
+ ] as const;
27
+
28
+ test("resolveSkillManifest returns null for undefined unit type (wildcard)", () => {
29
+ assert.equal(resolveSkillManifest(undefined), null);
30
+ });
31
+
32
+ test("resolveSkillManifest returns null for unknown unit types (wildcard fallback)", () => {
33
+ assert.equal(resolveSkillManifest("nonexistent-unit-type"), null);
34
+ });
35
+
36
+ test("resolveSkillManifest returns null for execute-task (intentional wildcard)", () => {
37
+ // execute-task is the implementation hot path; allowlisting it requires
38
+ // per-task skill hints from task-plan frontmatter. Documented in
39
+ // skill-manifest.ts — regression guard.
40
+ assert.equal(resolveSkillManifest("execute-task"), null);
41
+ });
42
+
43
+ for (const unitType of NEWLY_WIRED_UNIT_TYPES) {
44
+ test(`resolveSkillManifest returns a non-empty allowlist for '${unitType}'`, () => {
45
+ const allowlist = resolveSkillManifest(unitType);
46
+ assert.ok(allowlist !== null, `${unitType} should resolve to an allowlist, not wildcard`);
47
+ assert.ok(allowlist.length > 0, `${unitType} allowlist should not be empty`);
48
+ // Every entry must be lowercase (normalized).
49
+ for (const name of allowlist) {
50
+ assert.equal(name, name.toLowerCase(), `${unitType} entry '${name}' should be lowercase`);
51
+ }
52
+ });
53
+ }
54
+
55
+ test("resolveSkillManifest: slice-level manifests include decompose-into-slices", () => {
56
+ // Planning-shaped slice flows all benefit from the decomposition skill.
57
+ // Sanity-check a representative entry from each.
58
+ for (const unitType of ["research-slice", "plan-slice", "refine-slice", "replan-slice"] as const) {
59
+ const allowlist = resolveSkillManifest(unitType);
60
+ assert.ok(
61
+ allowlist?.includes("decompose-into-slices"),
62
+ `${unitType} should list decompose-into-slices`,
63
+ );
64
+ }
65
+ });
66
+
67
+ test("resolveSkillManifest: validation / completion flows include verify-before-complete", () => {
68
+ for (const unitType of ["complete-milestone", "validate-milestone", "run-uat"] as const) {
69
+ const allowlist = resolveSkillManifest(unitType);
70
+ assert.ok(
71
+ allowlist?.includes("verify-before-complete"),
72
+ `${unitType} should list verify-before-complete`,
73
+ );
74
+ }
75
+ });
76
+
77
+ test("filterSkillsByManifest: pass-through when unit type is unknown", () => {
78
+ const skills = [{ name: "swiftui" }, { name: "solidity-security" }];
79
+ const result = filterSkillsByManifest(skills, "nonexistent-unit-type");
80
+ assert.deepEqual(result, skills);
81
+ });
82
+
83
+ test("filterSkillsByManifest: pass-through when unitType is undefined", () => {
84
+ const skills = [{ name: "swiftui" }];
85
+ const result = filterSkillsByManifest(skills, undefined);
86
+ assert.deepEqual(result, skills);
87
+ });
88
+
89
+ test("filterSkillsByManifest: restricts to allowlisted names for known unit type", () => {
90
+ // research-slice allowlists include decompose-into-slices but not swiftui.
91
+ const skills = [
92
+ { name: "decompose-into-slices" },
93
+ { name: "swiftui" },
94
+ { name: "write-docs" },
95
+ ];
96
+ const result = filterSkillsByManifest(skills, "research-slice");
97
+ const names = result.map(s => s.name);
98
+ assert.ok(names.includes("decompose-into-slices"));
99
+ assert.ok(names.includes("write-docs"));
100
+ assert.ok(!names.includes("swiftui"));
101
+ });
102
+
103
+ test("filterSkillsByManifest: matching is case-insensitive via normalize", () => {
104
+ const skills = [
105
+ { name: "Write-Docs" }, // different case from manifest entry
106
+ { name: "SWIFTUI" },
107
+ ];
108
+ const result = filterSkillsByManifest(skills, "research-milestone");
109
+ const names = result.map(s => s.name);
110
+ assert.ok(names.includes("Write-Docs"));
111
+ assert.ok(!names.includes("SWIFTUI"));
112
+ });
@@ -6,13 +6,83 @@
6
6
 
7
7
  import { describe, it } from "node:test";
8
8
  import assert from "node:assert/strict";
9
- import { readFileSync } from "node:fs";
9
+ import { execFileSync, spawn } from "node:child_process";
10
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
10
11
  import { join, dirname } from "node:path";
12
+ import { tmpdir } from "node:os";
11
13
  import { fileURLToPath } from "node:url";
14
+ import { restoreSliceState } from "../slice-parallel-orchestrator.ts";
12
15
 
13
16
  const __dirname = dirname(fileURLToPath(import.meta.url));
14
17
  const gsdDir = join(__dirname, "..");
15
18
 
19
+ function readLinuxProcessStartFingerprint(pid: number): string | null {
20
+ try {
21
+ const stat = readFileSync(`/proc/${pid}/stat`, "utf-8");
22
+ const afterCommand = stat.slice(stat.lastIndexOf(")") + 2).trim();
23
+ const fields = afterCommand.split(/\s+/);
24
+ const startTimeTicks = fields[19];
25
+ return startTimeTicks ? `linux-stat:${startTimeTicks}` : null;
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+
31
+ function readPsProcessStartFingerprint(pid: number): string | null {
32
+ try {
33
+ const raw = execFileSync("ps", ["-p", String(pid), "-o", "lstart="], {
34
+ stdio: ["ignore", "pipe", "ignore"],
35
+ encoding: "utf-8",
36
+ }).trim().replace(/\s+/g, " ");
37
+ return raw ? `ps-lstart:${raw}` : null;
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+
43
+ function readProcessStartFingerprint(pid: number): string | null {
44
+ return readLinuxProcessStartFingerprint(pid) ?? readPsProcessStartFingerprint(pid);
45
+ }
46
+
47
+ function makeTempProject(): string {
48
+ const basePath = mkdtempSync(join(tmpdir(), "gsd-slice-parallel-"));
49
+ mkdirSync(join(basePath, ".gsd"), { recursive: true });
50
+ return basePath;
51
+ }
52
+
53
+ function writeSliceOrchestratorState(
54
+ basePath: string,
55
+ worker: {
56
+ pid: number;
57
+ workerToken?: string;
58
+ processStartFingerprint?: string | null;
59
+ },
60
+ ): void {
61
+ writeFileSync(
62
+ join(basePath, ".gsd", "slice-orchestrator.json"),
63
+ JSON.stringify({
64
+ active: true,
65
+ workers: [{
66
+ milestoneId: "M900",
67
+ sliceId: "S01",
68
+ pid: worker.pid,
69
+ workerToken: worker.workerToken,
70
+ processStartFingerprint: worker.processStartFingerprint,
71
+ worktreePath: join(basePath, ".gsd", "worktrees", "M900-S01"),
72
+ startedAt: Date.now(),
73
+ state: "running",
74
+ completedUnits: 0,
75
+ cost: 0,
76
+ }],
77
+ totalCost: 0,
78
+ maxWorkers: 1,
79
+ startedAt: Date.now(),
80
+ basePath,
81
+ }),
82
+ "utf-8",
83
+ );
84
+ }
85
+
16
86
  describe("slice-parallel-orchestrator structural tests", () => {
17
87
  it("orchestrator uses GSD_SLICE_LOCK env var", () => {
18
88
  const source = readFileSync(join(gsdDir, "slice-parallel-orchestrator.ts"), "utf-8");
@@ -46,6 +116,99 @@ describe("slice-parallel-orchestrator structural tests", () => {
46
116
  "Orchestrator must also pass GSD_MILESTONE_LOCK for milestone context",
47
117
  );
48
118
  });
119
+
120
+ it("recovery preserves terminal workers for coordinator-side collection", () => {
121
+ const source = readFileSync(join(gsdDir, "slice-parallel-orchestrator.ts"), "utf-8");
122
+ assert.ok(
123
+ source.includes('} else if (w.state === "running")') &&
124
+ source.includes("survivors.push(w);"),
125
+ "Recovery must only prune dead running workers, not stopped/error workers",
126
+ );
127
+ });
128
+
129
+ it("recovered PID-only workers are validated before signaling", () => {
130
+ const source = readFileSync(join(gsdDir, "slice-parallel-orchestrator.ts"), "utf-8");
131
+ assert.ok(
132
+ source.includes("isRecoveredSliceWorkerAlive(worker)") &&
133
+ source.includes('process.kill(worker.pid, "SIGTERM")'),
134
+ "stopSliceParallel must validate recovered worker identity before signaling a PID",
135
+ );
136
+ });
137
+
138
+ it("persists worker identity for crash recovery", () => {
139
+ const source = readFileSync(join(gsdDir, "slice-parallel-orchestrator.ts"), "utf-8");
140
+ assert.ok(
141
+ source.includes("workerToken") &&
142
+ source.includes("processStartFingerprint") &&
143
+ source.includes("GSD_SLICE_WORKER_TOKEN"),
144
+ "Orchestrator must persist stable worker identity metadata for recovered workers",
145
+ );
146
+ });
147
+
148
+ it("spawn failures remove stale worker state and worktree", () => {
149
+ const source = readFileSync(join(gsdDir, "slice-parallel-orchestrator.ts"), "utf-8");
150
+ assert.ok(
151
+ source.includes("sliceState.workers.delete(slice.id)") &&
152
+ source.includes("removeWorktree(basePath, wtName, { deleteBranch: true, force: true })"),
153
+ "Failed slice worker spawns must remove stale worker state and clean up the worktree",
154
+ );
155
+ });
156
+ });
157
+
158
+ describe("slice-parallel-orchestrator recovery identity", () => {
159
+ it("rejects a live PID when the process start fingerprint does not match", () => {
160
+ const basePath = makeTempProject();
161
+ try {
162
+ writeSliceOrchestratorState(basePath, {
163
+ pid: process.pid,
164
+ processStartFingerprint: "mismatched-fingerprint",
165
+ });
166
+
167
+ const restored = restoreSliceState(basePath);
168
+ assert.equal(restored, null, "mismatched fingerprint is treated as a dead worker");
169
+ assert.equal(
170
+ existsSync(join(basePath, ".gsd", "slice-orchestrator.json")),
171
+ false,
172
+ "state file is removed when no recovered worker identity validates",
173
+ );
174
+ } finally {
175
+ rmSync(basePath, { recursive: true, force: true });
176
+ }
177
+ });
178
+
179
+ it("keeps a recovered worker when PID, token, and process start fingerprint match", async () => {
180
+ const basePath = makeTempProject();
181
+ const token = `test-token-${Date.now()}`;
182
+ const child = spawn(
183
+ process.execPath,
184
+ ["-e", "setTimeout(() => {}, 30000)"],
185
+ {
186
+ env: { ...process.env, GSD_SLICE_WORKER_TOKEN: token },
187
+ stdio: "ignore",
188
+ },
189
+ );
190
+
191
+ try {
192
+ assert.ok(child.pid, "child process has a pid");
193
+ await new Promise((resolve) => setTimeout(resolve, 50));
194
+ const fingerprint = readProcessStartFingerprint(child.pid!);
195
+ if (!fingerprint) return;
196
+
197
+ writeSliceOrchestratorState(basePath, {
198
+ pid: child.pid!,
199
+ workerToken: token,
200
+ processStartFingerprint: fingerprint,
201
+ });
202
+
203
+ const restored = restoreSliceState(basePath);
204
+ assert.ok(restored, "matching worker identity is restored");
205
+ assert.equal(restored.workers.length, 1);
206
+ assert.equal(restored.workers[0].pid, child.pid);
207
+ } finally {
208
+ child.kill("SIGTERM");
209
+ rmSync(basePath, { recursive: true, force: true });
210
+ }
211
+ });
49
212
  });
50
213
 
51
214
  describe("slice_parallel preference gating", () => {
@@ -835,9 +835,9 @@ describe("state-machine-full-walkthrough", () => {
835
835
  assert.ok(state.blockers.length > 0, "should have blockers");
836
836
  });
837
837
 
838
- test("no eligible slice (all deps unmet) → fallback picks slice with most deps satisfied", async () => {
838
+ test("no eligible slice (all deps unmet) → blocked", async () => {
839
839
  const base = createFixtureBase();
840
- // S01 depends on S00 which doesn't exist — fallback picks S01 anyway
840
+ // S01 depends on S00 which doesn't exist.
841
841
  writeRoadmap(base, "M001", [
842
842
  "# M001: Test Milestone",
843
843
  "",
@@ -851,9 +851,9 @@ describe("state-machine-full-walkthrough", () => {
851
851
  invalidateStateCache();
852
852
  const state = await deriveState(base);
853
853
 
854
- // With partial-dep fallback, S01 is picked despite unmet dep on S00
855
- assert.equal(state.phase, "planning");
856
- assert.equal(state.activeSlice?.id, "S01");
854
+ assert.equal(state.phase, "blocked");
855
+ assert.equal(state.activeSlice, null);
856
+ assert.ok(state.blockers.some(b => b.includes("No slice eligible")));
857
857
  });
858
858
  });
859
859
 
@@ -271,96 +271,15 @@ describe("structured-data-formatter: measureSavings", () => {
271
271
  });
272
272
 
273
273
  // ---------------------------------------------------------------------------
274
- // Realistic token savings measurement
274
+ // Dropped (#4836): the previous "realistic savings" suite asserted that the
275
+ // compact formatter beat hand-authored "typical markdown" baselines by 30%+.
276
+ // Those baselines were written by the test author to make the assertion pass
277
+ // — they are not the format GSD actually emits anywhere else, so shifting
278
+ // the compact output by any amount could be absorbed by padding the baseline.
279
+ // There was no regression signal.
280
+ //
281
+ // The unit tests above already pin the compact format's structure byte for
282
+ // byte; a genuine regression changes one of those exact-string assertions.
283
+ // If a size-budget guarantee is needed later, capture a real production
284
+ // baseline into a fixture file and assert against a checked-in byte count.
275
285
  // ---------------------------------------------------------------------------
276
-
277
- describe("structured-data-formatter: realistic savings", () => {
278
- it("decisions compact format saves 30%+ vs markdown table", () => {
279
- const decisions = [sampleDecision, sampleDecision2];
280
-
281
- // Simulate a typical markdown table
282
- const markdownTable = [
283
- "| ID | When | Scope | Decision | Choice | Rationale | Revisable |",
284
- "|------|------------|--------------|-------------------------|------------------------|--------------------------|-----------|",
285
- "| D001 | M001/S01 | architecture | Use SQLite for storage | WAL mode, single-writer | Built-in, no external deps | yes |",
286
- "| D002 | M001/S02 | testing | Unit test all parsers | node:test framework | Fast, zero-dependency | no |",
287
- ].join("\n");
288
-
289
- const compactOutput = formatDecisionsCompact(decisions);
290
- const savings = measureSavings(compactOutput, markdownTable);
291
- assert.ok(
292
- savings >= 30,
293
- `expected >=30% savings, got ${savings.toFixed(1)}%`,
294
- );
295
- });
296
-
297
- it("requirements compact format saves 30%+ vs markdown sections", () => {
298
- const requirements = [sampleRequirement, sampleRequirement2];
299
-
300
- // Simulate verbose markdown format with all fields
301
- const markdownSections = [
302
- "## R001",
303
- "",
304
- "- **Class:** functional",
305
- "- **Status:** active",
306
- "- **Description:** Response latency < 200ms for API endpoints",
307
- "- **Why:** Critical for user experience",
308
- "- **Source:** architecture review",
309
- "- **Primary Owner:** S01",
310
- "- **Supporting Slices:** S02, S03",
311
- "- **Validation:** Load test confirms P99 < 200ms",
312
- "- **Notes:** Monitor in production",
313
- "",
314
- "## R002",
315
- "",
316
- "- **Class:** non-functional",
317
- "- **Status:** active",
318
- "- **Description:** Data consistency across writes",
319
- "- **Why:** Prevents data loss",
320
- "- **Source:** data team review",
321
- "- **Primary Owner:** S02",
322
- "- **Supporting Slices:** S01",
323
- "- **Validation:** Integration test suite",
324
- "- **Notes:** Requires WAL mode",
325
- ].join("\n");
326
-
327
- const compactOutput = formatRequirementsCompact(requirements);
328
- const savings = measureSavings(compactOutput, markdownSections);
329
- assert.ok(
330
- savings >= 30,
331
- `expected >=30% savings, got ${savings.toFixed(1)}%`,
332
- );
333
- });
334
-
335
- it("task plan compact format saves 30%+ vs markdown sections", () => {
336
- const tasks = [sampleTaskDone, sampleTaskPending];
337
-
338
- // Simulate verbose markdown task format
339
- const markdownTasks = [
340
- "## T01 - Database schema",
341
- "",
342
- "- **Status:** Done",
343
- "- **Estimate:** 30m",
344
- "- **Description:** Create tables for decisions and requirements",
345
- "- **Files:**",
346
- " - src/db.ts",
347
- " - src/schema.ts",
348
- "",
349
- "## T02 - API endpoints",
350
- "",
351
- "- **Status:** Pending",
352
- "- **Estimate:** 1h",
353
- "- **Description:** REST endpoints for CRUD operations",
354
- "- **Files:**",
355
- " - src/api.ts",
356
- "- **Verify:** npm test",
357
- ].join("\n");
358
-
359
- const compactOutput = formatTaskPlanCompact(tasks);
360
- const savings = measureSavings(compactOutput, markdownTasks);
361
- assert.ok(
362
- savings >= 30,
363
- `expected >=30% savings, got ${savings.toFixed(1)}%`,
364
- );
365
- });
366
- });