gsd-pi 2.29.0 → 2.30.0-dev.7e1bbce

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 (328) hide show
  1. package/README.md +24 -17
  2. package/dist/cli.js +51 -0
  3. package/dist/extension-registry.d.ts +63 -0
  4. package/dist/extension-registry.js +166 -0
  5. package/dist/headless.js +4 -0
  6. package/dist/help-text.js +35 -0
  7. package/dist/loader.js +10 -1
  8. package/dist/resource-loader.js +11 -1
  9. package/dist/resources/extensions/async-jobs/extension-manifest.json +13 -0
  10. package/dist/resources/extensions/bg-shell/extension-manifest.json +14 -0
  11. package/dist/resources/extensions/bg-shell/process-manager.ts +13 -0
  12. package/dist/resources/extensions/browser-tools/extension-manifest.json +37 -0
  13. package/dist/resources/extensions/context7/extension-manifest.json +12 -0
  14. package/dist/resources/extensions/google-search/extension-manifest.json +12 -0
  15. package/dist/resources/extensions/gsd/auto-dashboard.ts +31 -0
  16. package/dist/resources/extensions/gsd/auto-dispatch.ts +32 -3
  17. package/dist/resources/extensions/gsd/auto-post-unit.ts +45 -13
  18. package/dist/resources/extensions/gsd/auto-prompts.ts +40 -17
  19. package/dist/resources/extensions/gsd/auto-recovery.ts +18 -23
  20. package/dist/resources/extensions/gsd/auto-start.ts +18 -32
  21. package/dist/resources/extensions/gsd/auto-worktree.ts +21 -182
  22. package/dist/resources/extensions/gsd/auto.ts +2 -24
  23. package/dist/resources/extensions/gsd/captures.ts +4 -10
  24. package/dist/resources/extensions/gsd/commands-extensions.ts +328 -0
  25. package/dist/resources/extensions/gsd/commands-handlers.ts +22 -2
  26. package/dist/resources/extensions/gsd/commands-logs.ts +13 -14
  27. package/dist/resources/extensions/gsd/commands-prefs-wizard.ts +44 -14
  28. package/dist/resources/extensions/gsd/commands-workflow-templates.ts +544 -0
  29. package/dist/resources/extensions/gsd/commands.ts +108 -24
  30. package/dist/resources/extensions/gsd/dashboard-overlay.ts +2 -1
  31. package/dist/resources/extensions/gsd/detection.ts +2 -1
  32. package/dist/resources/extensions/gsd/doctor-checks.ts +49 -1
  33. package/dist/resources/extensions/gsd/doctor-types.ts +3 -1
  34. package/dist/resources/extensions/gsd/extension-manifest.json +18 -0
  35. package/dist/resources/extensions/gsd/forensics.ts +2 -2
  36. package/dist/resources/extensions/gsd/git-service.ts +3 -2
  37. package/dist/resources/extensions/gsd/gitignore.ts +9 -63
  38. package/dist/resources/extensions/gsd/gsd-db.ts +1 -165
  39. package/dist/resources/extensions/gsd/guided-flow.ts +8 -5
  40. package/dist/resources/extensions/gsd/index.ts +16 -3
  41. package/dist/resources/extensions/gsd/json-persistence.ts +16 -1
  42. package/dist/resources/extensions/gsd/md-importer.ts +3 -2
  43. package/dist/resources/extensions/gsd/mechanical-completion.ts +430 -0
  44. package/dist/resources/extensions/gsd/migrate/command.ts +3 -2
  45. package/dist/resources/extensions/gsd/migrate/writer.ts +2 -1
  46. package/dist/resources/extensions/gsd/migrate-external.ts +123 -0
  47. package/dist/resources/extensions/gsd/paths.ts +24 -2
  48. package/dist/resources/extensions/gsd/post-unit-hooks.ts +6 -5
  49. package/dist/resources/extensions/gsd/preferences-models.ts +7 -1
  50. package/dist/resources/extensions/gsd/preferences-validation.ts +2 -1
  51. package/dist/resources/extensions/gsd/preferences.ts +10 -5
  52. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +4 -2
  53. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  54. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +26 -2
  55. package/dist/resources/extensions/gsd/prompts/plan-slice.md +15 -1
  56. package/dist/resources/extensions/gsd/prompts/workflow-start.md +28 -0
  57. package/dist/resources/extensions/gsd/queue-order.ts +10 -11
  58. package/dist/resources/extensions/gsd/repo-identity.ts +148 -0
  59. package/dist/resources/extensions/gsd/resource-version.ts +99 -0
  60. package/dist/resources/extensions/gsd/roadmap-slices.ts +22 -7
  61. package/dist/resources/extensions/gsd/session-forensics.ts +4 -3
  62. package/dist/resources/extensions/gsd/session-lock.ts +53 -4
  63. package/dist/resources/extensions/gsd/session-status-io.ts +23 -41
  64. package/dist/resources/extensions/gsd/tests/activity-log.test.ts +2 -2
  65. package/dist/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
  66. package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +3 -3
  67. package/dist/resources/extensions/gsd/tests/auto-skip-loop.test.ts +1 -1
  68. package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +0 -58
  69. package/dist/resources/extensions/gsd/tests/doctor-runtime.test.ts +3 -4
  70. package/dist/resources/extensions/gsd/tests/extension-selector-separator.test.ts +60 -38
  71. package/dist/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +5 -18
  72. package/dist/resources/extensions/gsd/tests/git-service.test.ts +10 -37
  73. package/dist/resources/extensions/gsd/tests/knowledge.test.ts +4 -4
  74. package/dist/resources/extensions/gsd/tests/mechanical-completion.test.ts +356 -0
  75. package/dist/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +1 -1
  76. package/dist/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +1 -0
  77. package/dist/resources/extensions/gsd/tests/token-profile.test.ts +14 -16
  78. package/dist/resources/extensions/gsd/tests/workflow-templates.test.ts +173 -0
  79. package/dist/resources/extensions/gsd/triage-resolution.ts +2 -1
  80. package/dist/resources/extensions/gsd/types.ts +2 -0
  81. package/dist/resources/extensions/gsd/workflow-templates/bugfix.md +87 -0
  82. package/dist/resources/extensions/gsd/workflow-templates/dep-upgrade.md +74 -0
  83. package/dist/resources/extensions/gsd/workflow-templates/full-project.md +41 -0
  84. package/dist/resources/extensions/gsd/workflow-templates/hotfix.md +45 -0
  85. package/dist/resources/extensions/gsd/workflow-templates/refactor.md +83 -0
  86. package/dist/resources/extensions/gsd/workflow-templates/registry.json +85 -0
  87. package/dist/resources/extensions/gsd/workflow-templates/security-audit.md +73 -0
  88. package/dist/resources/extensions/gsd/workflow-templates/small-feature.md +81 -0
  89. package/dist/resources/extensions/gsd/workflow-templates/spike.md +69 -0
  90. package/dist/resources/extensions/gsd/workflow-templates.ts +241 -0
  91. package/dist/resources/extensions/gsd/worktree-command.ts +1 -11
  92. package/dist/resources/extensions/gsd/worktree-manager.ts +3 -2
  93. package/dist/resources/extensions/gsd/worktree.ts +42 -5
  94. package/dist/resources/extensions/mac-tools/extension-manifest.json +16 -0
  95. package/dist/resources/extensions/mcp-client/index.ts +459 -0
  96. package/dist/resources/extensions/mcporter/extension-manifest.json +12 -0
  97. package/dist/resources/extensions/remote-questions/discord-adapter.ts +8 -19
  98. package/dist/resources/extensions/remote-questions/extension-manifest.json +11 -0
  99. package/dist/resources/extensions/remote-questions/http-client.ts +76 -0
  100. package/dist/resources/extensions/remote-questions/slack-adapter.ts +11 -17
  101. package/dist/resources/extensions/remote-questions/telegram-adapter.ts +8 -19
  102. package/dist/resources/extensions/search-the-web/extension-manifest.json +13 -0
  103. package/dist/resources/extensions/slash-commands/extension-manifest.json +11 -0
  104. package/dist/resources/extensions/subagent/extension-manifest.json +13 -0
  105. package/dist/resources/extensions/ttsr/extension-manifest.json +11 -0
  106. package/dist/resources/extensions/universal-config/extension-manifest.json +13 -0
  107. package/dist/resources/extensions/voice/extension-manifest.json +12 -0
  108. package/dist/resources/skills/create-gsd-extension/SKILL.md +87 -0
  109. package/dist/resources/skills/create-gsd-extension/references/compaction-session-control.md +77 -0
  110. package/dist/resources/skills/create-gsd-extension/references/custom-commands.md +139 -0
  111. package/dist/resources/skills/create-gsd-extension/references/custom-rendering.md +108 -0
  112. package/dist/resources/skills/create-gsd-extension/references/custom-tools.md +183 -0
  113. package/dist/resources/skills/create-gsd-extension/references/custom-ui.md +490 -0
  114. package/dist/resources/skills/create-gsd-extension/references/events-reference.md +126 -0
  115. package/dist/resources/skills/create-gsd-extension/references/extension-lifecycle.md +64 -0
  116. package/dist/resources/skills/create-gsd-extension/references/extensionapi-reference.md +75 -0
  117. package/dist/resources/skills/create-gsd-extension/references/extensioncontext-reference.md +53 -0
  118. package/dist/resources/skills/create-gsd-extension/references/key-rules-gotchas.md +36 -0
  119. package/dist/resources/skills/create-gsd-extension/references/mode-behavior.md +32 -0
  120. package/dist/resources/skills/create-gsd-extension/references/model-provider-management.md +89 -0
  121. package/dist/resources/skills/create-gsd-extension/references/packaging-distribution.md +55 -0
  122. package/dist/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +90 -0
  123. package/dist/resources/skills/create-gsd-extension/references/state-management.md +70 -0
  124. package/dist/resources/skills/create-gsd-extension/references/system-prompt-modification.md +52 -0
  125. package/dist/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +51 -0
  126. package/dist/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +143 -0
  127. package/dist/resources/skills/create-gsd-extension/workflows/add-capability.md +57 -0
  128. package/dist/resources/skills/create-gsd-extension/workflows/create-extension.md +156 -0
  129. package/dist/resources/skills/create-gsd-extension/workflows/debug-extension.md +74 -0
  130. package/dist/resources/skills/create-skill/SKILL.md +184 -0
  131. package/dist/resources/skills/create-skill/references/api-security.md +226 -0
  132. package/dist/resources/skills/create-skill/references/be-clear-and-direct.md +531 -0
  133. package/dist/resources/skills/create-skill/references/common-patterns.md +595 -0
  134. package/dist/resources/skills/create-skill/references/core-principles.md +437 -0
  135. package/dist/resources/skills/create-skill/references/executable-code.md +175 -0
  136. package/dist/resources/skills/create-skill/references/gsd-skill-ecosystem.md +68 -0
  137. package/dist/resources/skills/create-skill/references/iteration-and-testing.md +474 -0
  138. package/dist/resources/skills/create-skill/references/recommended-structure.md +168 -0
  139. package/dist/resources/skills/create-skill/references/skill-structure.md +372 -0
  140. package/dist/resources/skills/create-skill/references/use-xml-tags.md +466 -0
  141. package/dist/resources/skills/create-skill/references/using-scripts.md +113 -0
  142. package/dist/resources/skills/create-skill/references/using-templates.md +112 -0
  143. package/dist/resources/skills/create-skill/references/workflows-and-validation.md +510 -0
  144. package/dist/resources/skills/create-skill/templates/router-skill.md +73 -0
  145. package/dist/resources/skills/create-skill/templates/simple-skill.md +33 -0
  146. package/dist/resources/skills/create-skill/workflows/add-reference.md +96 -0
  147. package/dist/resources/skills/create-skill/workflows/add-script.md +93 -0
  148. package/dist/resources/skills/create-skill/workflows/add-template.md +74 -0
  149. package/dist/resources/skills/create-skill/workflows/add-workflow.md +120 -0
  150. package/dist/resources/skills/create-skill/workflows/audit-skill.md +148 -0
  151. package/dist/resources/skills/create-skill/workflows/create-new-skill.md +196 -0
  152. package/dist/resources/skills/create-skill/workflows/get-guidance.md +121 -0
  153. package/dist/resources/skills/create-skill/workflows/upgrade-to-router.md +161 -0
  154. package/dist/resources/skills/create-skill/workflows/verify-skill.md +204 -0
  155. package/dist/resources/skills/react-best-practices/SKILL.md +1 -1
  156. package/dist/worktree-cli.d.ts +34 -0
  157. package/dist/worktree-cli.js +294 -0
  158. package/dist/worktree-name-gen.d.ts +7 -0
  159. package/dist/worktree-name-gen.js +44 -0
  160. package/package.json +1 -1
  161. package/packages/native/dist/native.d.ts +2 -0
  162. package/packages/native/dist/native.js +19 -5
  163. package/packages/native/src/native.ts +23 -9
  164. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  165. package/packages/pi-coding-agent/dist/core/extensions/loader.js +13 -0
  166. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  167. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  168. package/packages/pi-coding-agent/dist/core/lsp/client.js +3 -0
  169. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  170. package/packages/pi-coding-agent/package.json +1 -1
  171. package/packages/pi-coding-agent/src/core/extensions/loader.ts +13 -0
  172. package/packages/pi-coding-agent/src/core/lsp/client.ts +3 -0
  173. package/pkg/package.json +1 -1
  174. package/src/resources/extensions/async-jobs/extension-manifest.json +13 -0
  175. package/src/resources/extensions/bg-shell/extension-manifest.json +14 -0
  176. package/src/resources/extensions/bg-shell/process-manager.ts +13 -0
  177. package/src/resources/extensions/browser-tools/extension-manifest.json +37 -0
  178. package/src/resources/extensions/context7/extension-manifest.json +12 -0
  179. package/src/resources/extensions/google-search/extension-manifest.json +12 -0
  180. package/src/resources/extensions/gsd/auto-dashboard.ts +31 -0
  181. package/src/resources/extensions/gsd/auto-dispatch.ts +32 -3
  182. package/src/resources/extensions/gsd/auto-post-unit.ts +45 -13
  183. package/src/resources/extensions/gsd/auto-prompts.ts +40 -17
  184. package/src/resources/extensions/gsd/auto-recovery.ts +18 -23
  185. package/src/resources/extensions/gsd/auto-start.ts +18 -32
  186. package/src/resources/extensions/gsd/auto-worktree.ts +21 -182
  187. package/src/resources/extensions/gsd/auto.ts +2 -24
  188. package/src/resources/extensions/gsd/captures.ts +4 -10
  189. package/src/resources/extensions/gsd/commands-extensions.ts +328 -0
  190. package/src/resources/extensions/gsd/commands-handlers.ts +22 -2
  191. package/src/resources/extensions/gsd/commands-logs.ts +13 -14
  192. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +44 -14
  193. package/src/resources/extensions/gsd/commands-workflow-templates.ts +544 -0
  194. package/src/resources/extensions/gsd/commands.ts +108 -24
  195. package/src/resources/extensions/gsd/dashboard-overlay.ts +2 -1
  196. package/src/resources/extensions/gsd/detection.ts +2 -1
  197. package/src/resources/extensions/gsd/doctor-checks.ts +49 -1
  198. package/src/resources/extensions/gsd/doctor-types.ts +3 -1
  199. package/src/resources/extensions/gsd/extension-manifest.json +18 -0
  200. package/src/resources/extensions/gsd/forensics.ts +2 -2
  201. package/src/resources/extensions/gsd/git-service.ts +3 -2
  202. package/src/resources/extensions/gsd/gitignore.ts +9 -63
  203. package/src/resources/extensions/gsd/gsd-db.ts +1 -165
  204. package/src/resources/extensions/gsd/guided-flow.ts +8 -5
  205. package/src/resources/extensions/gsd/index.ts +16 -3
  206. package/src/resources/extensions/gsd/json-persistence.ts +16 -1
  207. package/src/resources/extensions/gsd/md-importer.ts +3 -2
  208. package/src/resources/extensions/gsd/mechanical-completion.ts +430 -0
  209. package/src/resources/extensions/gsd/migrate/command.ts +3 -2
  210. package/src/resources/extensions/gsd/migrate/writer.ts +2 -1
  211. package/src/resources/extensions/gsd/migrate-external.ts +123 -0
  212. package/src/resources/extensions/gsd/paths.ts +24 -2
  213. package/src/resources/extensions/gsd/post-unit-hooks.ts +6 -5
  214. package/src/resources/extensions/gsd/preferences-models.ts +7 -1
  215. package/src/resources/extensions/gsd/preferences-validation.ts +2 -1
  216. package/src/resources/extensions/gsd/preferences.ts +10 -5
  217. package/src/resources/extensions/gsd/prompts/discuss-headless.md +4 -2
  218. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  219. package/src/resources/extensions/gsd/prompts/plan-milestone.md +26 -2
  220. package/src/resources/extensions/gsd/prompts/plan-slice.md +15 -1
  221. package/src/resources/extensions/gsd/prompts/workflow-start.md +28 -0
  222. package/src/resources/extensions/gsd/queue-order.ts +10 -11
  223. package/src/resources/extensions/gsd/repo-identity.ts +148 -0
  224. package/src/resources/extensions/gsd/resource-version.ts +99 -0
  225. package/src/resources/extensions/gsd/roadmap-slices.ts +22 -7
  226. package/src/resources/extensions/gsd/session-forensics.ts +4 -3
  227. package/src/resources/extensions/gsd/session-lock.ts +53 -4
  228. package/src/resources/extensions/gsd/session-status-io.ts +23 -41
  229. package/src/resources/extensions/gsd/tests/activity-log.test.ts +2 -2
  230. package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
  231. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +3 -3
  232. package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +1 -1
  233. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +0 -58
  234. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +3 -4
  235. package/src/resources/extensions/gsd/tests/extension-selector-separator.test.ts +60 -38
  236. package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +5 -18
  237. package/src/resources/extensions/gsd/tests/git-service.test.ts +10 -37
  238. package/src/resources/extensions/gsd/tests/knowledge.test.ts +4 -4
  239. package/src/resources/extensions/gsd/tests/mechanical-completion.test.ts +356 -0
  240. package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +1 -1
  241. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +1 -0
  242. package/src/resources/extensions/gsd/tests/token-profile.test.ts +14 -16
  243. package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +173 -0
  244. package/src/resources/extensions/gsd/triage-resolution.ts +2 -1
  245. package/src/resources/extensions/gsd/types.ts +2 -0
  246. package/src/resources/extensions/gsd/workflow-templates/bugfix.md +87 -0
  247. package/src/resources/extensions/gsd/workflow-templates/dep-upgrade.md +74 -0
  248. package/src/resources/extensions/gsd/workflow-templates/full-project.md +41 -0
  249. package/src/resources/extensions/gsd/workflow-templates/hotfix.md +45 -0
  250. package/src/resources/extensions/gsd/workflow-templates/refactor.md +83 -0
  251. package/src/resources/extensions/gsd/workflow-templates/registry.json +85 -0
  252. package/src/resources/extensions/gsd/workflow-templates/security-audit.md +73 -0
  253. package/src/resources/extensions/gsd/workflow-templates/small-feature.md +81 -0
  254. package/src/resources/extensions/gsd/workflow-templates/spike.md +69 -0
  255. package/src/resources/extensions/gsd/workflow-templates.ts +241 -0
  256. package/src/resources/extensions/gsd/worktree-command.ts +1 -11
  257. package/src/resources/extensions/gsd/worktree-manager.ts +3 -2
  258. package/src/resources/extensions/gsd/worktree.ts +42 -5
  259. package/src/resources/extensions/mac-tools/extension-manifest.json +16 -0
  260. package/src/resources/extensions/mcp-client/index.ts +459 -0
  261. package/src/resources/extensions/mcporter/extension-manifest.json +12 -0
  262. package/src/resources/extensions/remote-questions/discord-adapter.ts +8 -19
  263. package/src/resources/extensions/remote-questions/extension-manifest.json +11 -0
  264. package/src/resources/extensions/remote-questions/http-client.ts +76 -0
  265. package/src/resources/extensions/remote-questions/slack-adapter.ts +11 -17
  266. package/src/resources/extensions/remote-questions/telegram-adapter.ts +8 -19
  267. package/src/resources/extensions/search-the-web/extension-manifest.json +13 -0
  268. package/src/resources/extensions/slash-commands/extension-manifest.json +11 -0
  269. package/src/resources/extensions/subagent/extension-manifest.json +13 -0
  270. package/src/resources/extensions/ttsr/extension-manifest.json +11 -0
  271. package/src/resources/extensions/universal-config/extension-manifest.json +13 -0
  272. package/src/resources/extensions/voice/extension-manifest.json +12 -0
  273. package/src/resources/skills/create-gsd-extension/SKILL.md +87 -0
  274. package/src/resources/skills/create-gsd-extension/references/compaction-session-control.md +77 -0
  275. package/src/resources/skills/create-gsd-extension/references/custom-commands.md +139 -0
  276. package/src/resources/skills/create-gsd-extension/references/custom-rendering.md +108 -0
  277. package/src/resources/skills/create-gsd-extension/references/custom-tools.md +183 -0
  278. package/src/resources/skills/create-gsd-extension/references/custom-ui.md +490 -0
  279. package/src/resources/skills/create-gsd-extension/references/events-reference.md +126 -0
  280. package/src/resources/skills/create-gsd-extension/references/extension-lifecycle.md +64 -0
  281. package/src/resources/skills/create-gsd-extension/references/extensionapi-reference.md +75 -0
  282. package/src/resources/skills/create-gsd-extension/references/extensioncontext-reference.md +53 -0
  283. package/src/resources/skills/create-gsd-extension/references/key-rules-gotchas.md +36 -0
  284. package/src/resources/skills/create-gsd-extension/references/mode-behavior.md +32 -0
  285. package/src/resources/skills/create-gsd-extension/references/model-provider-management.md +89 -0
  286. package/src/resources/skills/create-gsd-extension/references/packaging-distribution.md +55 -0
  287. package/src/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +90 -0
  288. package/src/resources/skills/create-gsd-extension/references/state-management.md +70 -0
  289. package/src/resources/skills/create-gsd-extension/references/system-prompt-modification.md +52 -0
  290. package/src/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +51 -0
  291. package/src/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +143 -0
  292. package/src/resources/skills/create-gsd-extension/workflows/add-capability.md +57 -0
  293. package/src/resources/skills/create-gsd-extension/workflows/create-extension.md +156 -0
  294. package/src/resources/skills/create-gsd-extension/workflows/debug-extension.md +74 -0
  295. package/src/resources/skills/create-skill/SKILL.md +184 -0
  296. package/src/resources/skills/create-skill/references/api-security.md +226 -0
  297. package/src/resources/skills/create-skill/references/be-clear-and-direct.md +531 -0
  298. package/src/resources/skills/create-skill/references/common-patterns.md +595 -0
  299. package/src/resources/skills/create-skill/references/core-principles.md +437 -0
  300. package/src/resources/skills/create-skill/references/executable-code.md +175 -0
  301. package/src/resources/skills/create-skill/references/gsd-skill-ecosystem.md +68 -0
  302. package/src/resources/skills/create-skill/references/iteration-and-testing.md +474 -0
  303. package/src/resources/skills/create-skill/references/recommended-structure.md +168 -0
  304. package/src/resources/skills/create-skill/references/skill-structure.md +372 -0
  305. package/src/resources/skills/create-skill/references/use-xml-tags.md +466 -0
  306. package/src/resources/skills/create-skill/references/using-scripts.md +113 -0
  307. package/src/resources/skills/create-skill/references/using-templates.md +112 -0
  308. package/src/resources/skills/create-skill/references/workflows-and-validation.md +510 -0
  309. package/src/resources/skills/create-skill/templates/router-skill.md +73 -0
  310. package/src/resources/skills/create-skill/templates/simple-skill.md +33 -0
  311. package/src/resources/skills/create-skill/workflows/add-reference.md +96 -0
  312. package/src/resources/skills/create-skill/workflows/add-script.md +93 -0
  313. package/src/resources/skills/create-skill/workflows/add-template.md +74 -0
  314. package/src/resources/skills/create-skill/workflows/add-workflow.md +120 -0
  315. package/src/resources/skills/create-skill/workflows/audit-skill.md +148 -0
  316. package/src/resources/skills/create-skill/workflows/create-new-skill.md +196 -0
  317. package/src/resources/skills/create-skill/workflows/get-guidance.md +121 -0
  318. package/src/resources/skills/create-skill/workflows/upgrade-to-router.md +161 -0
  319. package/src/resources/skills/create-skill/workflows/verify-skill.md +204 -0
  320. package/src/resources/skills/react-best-practices/SKILL.md +1 -1
  321. package/dist/resources/extensions/gsd/auto-worktree-sync.ts +0 -198
  322. package/dist/resources/extensions/gsd/tests/worktree-db-integration.test.ts +0 -205
  323. package/dist/resources/extensions/gsd/tests/worktree-db.test.ts +0 -442
  324. package/dist/resources/extensions/mcporter/index.ts +0 -525
  325. package/src/resources/extensions/gsd/auto-worktree-sync.ts +0 -198
  326. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +0 -205
  327. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +0 -442
  328. package/src/resources/extensions/mcporter/index.ts +0 -525
@@ -17,7 +17,7 @@
17
17
  */
18
18
 
19
19
  import { createRequire } from "node:module";
20
- import { existsSync, readFileSync, mkdirSync, unlinkSync } from "node:fs";
20
+ import { existsSync, readFileSync, mkdirSync, unlinkSync, rmSync, statSync } from "node:fs";
21
21
  import { join, dirname } from "node:path";
22
22
  import { gsdRoot } from "./paths.js";
23
23
  import { atomicWriteSync } from "./atomic-write.js";
@@ -92,11 +92,12 @@ export function acquireSessionLock(basePath: string): SessionLockResult {
92
92
  return acquireFallbackLock(basePath, lp, lockData);
93
93
  }
94
94
 
95
+ const gsdDir = gsdRoot(basePath);
96
+
95
97
  try {
96
98
  // Try to acquire an exclusive OS-level lock on the lock file.
97
99
  // We lock the directory (gsdRoot) since proper-lockfile works best
98
100
  // on directories, and the lock file itself may not exist yet.
99
- const gsdDir = gsdRoot(basePath);
100
101
  mkdirSync(gsdDir, { recursive: true });
101
102
 
102
103
  const release = lockfile.lockSync(gsdDir, {
@@ -109,16 +110,53 @@ export function acquireSessionLock(basePath: string): SessionLockResult {
109
110
  _lockedPath = basePath;
110
111
  _lockPid = process.pid;
111
112
 
113
+ // Safety net: clean up lock dir on process exit if _releaseFunction
114
+ // wasn't called (e.g., normal exit after clean completion) (#1245).
115
+ const lockDirForCleanup = join(gsdDir + ".lock");
116
+ process.once("exit", () => {
117
+ try {
118
+ if (_releaseFunction) { _releaseFunction(); _releaseFunction = null; }
119
+ } catch { /* best-effort */ }
120
+ try {
121
+ if (existsSync(lockDirForCleanup)) rmSync(lockDirForCleanup, { recursive: true, force: true });
122
+ } catch { /* best-effort */ }
123
+ });
124
+
112
125
  // Write the informational lock data
113
126
  atomicWriteSync(lp, JSON.stringify(lockData, null, 2));
114
127
 
115
128
  return { acquired: true };
116
129
  } catch (err) {
117
- // Lock is held by another process
130
+ // Lock is held by another process — or the .gsd.lock/ directory is stranded.
131
+ // Check: if auto.lock is gone and no process is alive, the lock dir is stale.
118
132
  const existingData = readExistingLockData(lp);
119
133
  const existingPid = existingData?.pid;
134
+
135
+ // If no lock file or no alive process, try to clean up and re-acquire (#1245)
136
+ if (!existingData || (existingPid && !isPidAlive(existingPid))) {
137
+ try {
138
+ const lockDir = join(gsdDir + ".lock");
139
+ if (existsSync(lockDir)) rmSync(lockDir, { recursive: true, force: true });
140
+ if (existsSync(lp)) unlinkSync(lp);
141
+
142
+ // Retry acquisition after cleanup
143
+ const release = lockfile.lockSync(gsdDir, {
144
+ realpath: false,
145
+ stale: 300_000,
146
+ update: 10_000,
147
+ });
148
+ _releaseFunction = release;
149
+ _lockedPath = basePath;
150
+ _lockPid = process.pid;
151
+ atomicWriteSync(lp, JSON.stringify(lockData, null, 2));
152
+ return { acquired: true };
153
+ } catch {
154
+ // Retry also failed — fall through to the error path
155
+ }
156
+ }
157
+
120
158
  const reason = existingPid
121
- ? `Another auto-mode session (PID ${existingPid}) is already running on this project.`
159
+ ? `Another auto-mode session (PID ${existingPid}) appears to be running.\nStop it with \`kill ${existingPid}\` before starting a new session.`
122
160
  : `Another auto-mode session is already running on this project.`;
123
161
 
124
162
  return { acquired: false, reason, existingPid };
@@ -233,6 +271,17 @@ export function releaseSessionLock(basePath: string): void {
233
271
  // Non-fatal
234
272
  }
235
273
 
274
+ // Remove the proper-lockfile directory (.gsd.lock/) if it exists.
275
+ // proper-lockfile creates this directory as the OS-level lock mechanism.
276
+ // If the process exits without calling _releaseFunction (SIGKILL, crash),
277
+ // this directory is stranded and blocks the next session (#1245).
278
+ try {
279
+ const lockDir = join(gsdRoot(basePath) + ".lock");
280
+ if (existsSync(lockDir)) rmSync(lockDir, { recursive: true, force: true });
281
+ } catch {
282
+ // Non-fatal
283
+ }
284
+
236
285
  _lockedPath = null;
237
286
  _lockPid = 0;
238
287
  }
@@ -11,9 +11,6 @@
11
11
  */
12
12
 
13
13
  import {
14
- writeFileSync,
15
- readFileSync,
16
- renameSync,
17
14
  unlinkSync,
18
15
  readdirSync,
19
16
  mkdirSync,
@@ -21,6 +18,7 @@ import {
21
18
  } from "node:fs";
22
19
  import { join } from "node:path";
23
20
  import { gsdRoot } from "./paths.js";
21
+ import { loadJsonFileOrNull, writeJsonFileAtomic } from "./json-persistence.js";
24
22
 
25
23
  // ─── Types ─────────────────────────────────────────────────────────────────
26
24
 
@@ -49,9 +47,16 @@ export interface SignalMessage {
49
47
  const PARALLEL_DIR = "parallel";
50
48
  const STATUS_SUFFIX = ".status.json";
51
49
  const SIGNAL_SUFFIX = ".signal.json";
52
- const TMP_SUFFIX = ".tmp";
53
50
  const DEFAULT_STALE_TIMEOUT_MS = 30_000;
54
51
 
52
+ function isSessionStatus(data: unknown): data is SessionStatus {
53
+ return data !== null && typeof data === "object" && "milestoneId" in data && "pid" in data;
54
+ }
55
+
56
+ function isSignalMessage(data: unknown): data is SignalMessage {
57
+ return data !== null && typeof data === "object" && "signal" in data && "sentAt" in data;
58
+ }
59
+
55
60
  // ─── Helpers ───────────────────────────────────────────────────────────────
56
61
 
57
62
  function parallelDir(basePath: string): string {
@@ -86,25 +91,13 @@ function isPidAlive(pid: number): boolean {
86
91
 
87
92
  /** Write session status atomically (write to .tmp, then rename). */
88
93
  export function writeSessionStatus(basePath: string, status: SessionStatus): void {
89
- try {
90
- ensureParallelDir(basePath);
91
- const dest = statusPath(basePath, status.milestoneId);
92
- const tmp = dest + TMP_SUFFIX;
93
- writeFileSync(tmp, JSON.stringify(status, null, 2), "utf-8");
94
- renameSync(tmp, dest);
95
- } catch { /* non-fatal */ }
94
+ ensureParallelDir(basePath);
95
+ writeJsonFileAtomic(statusPath(basePath, status.milestoneId), status);
96
96
  }
97
97
 
98
98
  /** Read a specific milestone's session status. */
99
99
  export function readSessionStatus(basePath: string, milestoneId: string): SessionStatus | null {
100
- try {
101
- const p = statusPath(basePath, milestoneId);
102
- if (!existsSync(p)) return null;
103
- const raw = readFileSync(p, "utf-8");
104
- return JSON.parse(raw) as SessionStatus;
105
- } catch {
106
- return null;
107
- }
100
+ return loadJsonFileOrNull(statusPath(basePath, milestoneId), isSessionStatus);
108
101
  }
109
102
 
110
103
  /** Read all session status files from .gsd/parallel/. */
@@ -114,13 +107,10 @@ export function readAllSessionStatuses(basePath: string): SessionStatus[] {
114
107
 
115
108
  const results: SessionStatus[] = [];
116
109
  try {
117
- const entries = readdirSync(dir);
118
- for (const entry of entries) {
110
+ for (const entry of readdirSync(dir)) {
119
111
  if (!entry.endsWith(STATUS_SUFFIX)) continue;
120
- try {
121
- const raw = readFileSync(join(dir, entry), "utf-8");
122
- results.push(JSON.parse(raw) as SessionStatus);
123
- } catch { /* skip corrupt files */ }
112
+ const status = loadJsonFileOrNull(join(dir, entry), isSessionStatus);
113
+ if (status) results.push(status);
124
114
  }
125
115
  } catch { /* non-fatal */ }
126
116
  return results;
@@ -138,27 +128,19 @@ export function removeSessionStatus(basePath: string, milestoneId: string): void
138
128
 
139
129
  /** Write a signal file for a worker to consume. */
140
130
  export function sendSignal(basePath: string, milestoneId: string, signal: SessionSignal): void {
141
- try {
142
- ensureParallelDir(basePath);
143
- const dest = signalPath(basePath, milestoneId);
144
- const tmp = dest + TMP_SUFFIX;
145
- const msg: SignalMessage = { signal, sentAt: Date.now(), from: "coordinator" };
146
- writeFileSync(tmp, JSON.stringify(msg, null, 2), "utf-8");
147
- renameSync(tmp, dest);
148
- } catch { /* non-fatal */ }
131
+ ensureParallelDir(basePath);
132
+ const msg: SignalMessage = { signal, sentAt: Date.now(), from: "coordinator" };
133
+ writeJsonFileAtomic(signalPath(basePath, milestoneId), msg);
149
134
  }
150
135
 
151
136
  /** Read and delete a signal file (atomic consume). Returns null if no signal pending. */
152
137
  export function consumeSignal(basePath: string, milestoneId: string): SignalMessage | null {
153
- try {
154
- const p = signalPath(basePath, milestoneId);
155
- if (!existsSync(p)) return null;
156
- const raw = readFileSync(p, "utf-8");
157
- unlinkSync(p);
158
- return JSON.parse(raw) as SignalMessage;
159
- } catch {
160
- return null;
138
+ const p = signalPath(basePath, milestoneId);
139
+ const msg = loadJsonFileOrNull(p, isSignalMessage);
140
+ if (msg) {
141
+ try { unlinkSync(p); } catch { /* non-fatal */ }
161
142
  }
143
+ return msg;
162
144
  }
163
145
 
164
146
  // ─── Stale Detection ───────────────────────────────────────────────────────
@@ -6,7 +6,7 @@
6
6
 
7
7
  import test from "node:test";
8
8
  import assert from "node:assert/strict";
9
- import { existsSync, mkdtempSync, mkdirSync, readdirSync, rmSync, utimesSync, writeFileSync, readFileSync } from "node:fs";
9
+ import { existsSync, mkdtempSync, mkdirSync, readdirSync, realpathSync, rmSync, utimesSync, writeFileSync, readFileSync } from "node:fs";
10
10
  import { join, dirname } from "node:path";
11
11
  import { tmpdir } from "node:os";
12
12
  import { fileURLToPath } from "node:url";
@@ -18,7 +18,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
18
18
  // ── Helpers ──────────────────────────────────────────────────────────────────
19
19
 
20
20
  function createTmpDir(): string {
21
- return mkdtempSync(join(tmpdir(), "gsd-activity-test-"));
21
+ return realpathSync(mkdtempSync(join(tmpdir(), "gsd-activity-test-")));
22
22
  }
23
23
 
24
24
  function writeActivityFile(dir: string, seq: string, name: string): string {
@@ -5,7 +5,7 @@ import {
5
5
  getBudgetAlertLevel,
6
6
  getBudgetEnforcementAction,
7
7
  getNewBudgetAlertLevel,
8
- } from "../auto.js";
8
+ } from "../auto-budget.js";
9
9
 
10
10
  test("getBudgetAlertLevel returns the expected threshold bucket", () => {
11
11
  assert.equal(getBudgetAlertLevel(0.10), 0);
@@ -308,8 +308,8 @@ test("loadPersistedKeys unions keys from project root and worktree", () => {
308
308
  });
309
309
 
310
310
  test("completed-units.json set-union merge produces correct result", () => {
311
- // Verify that a manual set-union merge (as done in syncStateToProjectRoot)
312
- // correctly merges two JSON arrays of keys.
311
+ // Verify that a manual set-union merge correctly merges two JSON arrays
312
+ // of completed-unit keys.
313
313
  const projectRoot = makeTmpBase();
314
314
  const worktree = makeTmpBase();
315
315
  try {
@@ -320,7 +320,7 @@ test("completed-units.json set-union merge produces correct result", () => {
320
320
  writeFileSync(prKeysFile, JSON.stringify(["a", "b"]));
321
321
  writeFileSync(wtKeysFile, JSON.stringify(["b", "c", "d"]));
322
322
 
323
- // Perform the same merge logic used in syncStateToProjectRoot
323
+ // Perform a set-union merge of two JSON key arrays
324
324
  const srcKeys: string[] = JSON.parse(readFileSync(wtKeysFile, "utf8"));
325
325
  let dstKeys: string[] = [];
326
326
  if (existsSync(prKeysFile)) {
@@ -17,8 +17,8 @@ import { tmpdir } from "node:os";
17
17
  import {
18
18
  _getUnitConsecutiveSkips,
19
19
  _resetUnitConsecutiveSkips,
20
- MAX_CONSECUTIVE_SKIPS,
21
20
  } from "../auto.ts";
21
+ import { MAX_CONSECUTIVE_SKIPS } from "../auto/session.ts";
22
22
  import { persistCompletedKey, removePersistedKey, loadPersistedKeys } from "../auto-recovery.ts";
23
23
  import { createTestContext } from "./test-helpers.ts";
24
24
 
@@ -153,64 +153,6 @@ async function main(): Promise<void> {
153
153
  // After teardown, originalBase should be null
154
154
  assertEq(getAutoWorktreeOriginalBase(), null, "no split-brain: originalBase cleared");
155
155
 
156
- // ─── #778: reconcile plan checkboxes on re-attach ─────────────────
157
- console.log("\n=== #778: reconcile plan checkboxes on re-attach ===");
158
- {
159
- // Simulate: T01 [x] was committed to milestone branch, T02 [x] was
160
- // written to project root by syncStateToProjectRoot() but the
161
- // auto-commit crashed before it fired. On restart the worktree is
162
- // re-created from the milestone branch HEAD (T02 still [ ]).
163
- // reconcilePlanCheckboxes should forward-apply T02 [x] from the root.
164
-
165
- const planRelPath = join(".gsd", "milestones", "M004", "slices", "S01", "S01-PLAN.md");
166
- const planDir = join(tempDir, ".gsd", "milestones", "M004", "slices", "S01");
167
- const { mkdirSync: mkdir, writeFileSync: write, readFileSync: read } = await import("node:fs");
168
-
169
- // Plan on integration branch (project root): T01 [x], T02 [x]
170
- mkdir(planDir, { recursive: true });
171
- write(
172
- join(tempDir, planRelPath),
173
- "# S01 Plan\n- [x] **T01:** task one\n- [x] **T02:** task two\n- [ ] **T03:** task three\n",
174
- );
175
-
176
- // Write integration-branch plan to git so milestone branch starts from it
177
- run(`git add .`, tempDir);
178
- run(`git commit -m "add plan with T01 and T02 checked" --allow-empty`, tempDir);
179
-
180
- // Create milestone branch with only T01 [x] (simulating crash before T02 commit)
181
- const milestoneBranch = "milestone/M004";
182
- run(`git checkout -b ${milestoneBranch}`, tempDir);
183
- mkdir(planDir, { recursive: true });
184
- write(
185
- join(tempDir, planRelPath),
186
- "# S01 Plan\n- [x] **T01:** task one\n- [ ] **T02:** task two\n- [ ] **T03:** task three\n",
187
- );
188
- run(`git add .`, tempDir);
189
- run(`git commit -m "milestone: only T01 checked"`, tempDir);
190
- run(`git checkout main`, tempDir);
191
-
192
- // Restore project root plan (T01+T02 [x]) — simulates syncStateToProjectRoot
193
- write(
194
- join(tempDir, planRelPath),
195
- "# S01 Plan\n- [x] **T01:** task one\n- [x] **T02:** task two\n- [ ] **T03:** task three\n",
196
- );
197
-
198
- // Create worktree re-attached to existing milestone branch (T02 still [ ] in branch)
199
- const wtPath = createAutoWorktree(tempDir, "M004");
200
-
201
- try {
202
- const wtPlanPath = join(wtPath, planRelPath);
203
- assertTrue(existsSync(wtPlanPath), "plan file exists in worktree after re-attach");
204
-
205
- const wtPlan = read(wtPlanPath, "utf-8");
206
- assertTrue(wtPlan.includes("- [x] **T02:"), "T02 should be [x] after reconciliation (was [ ] on branch)");
207
- assertTrue(wtPlan.includes("- [x] **T01:"), "T01 stays [x]");
208
- assertTrue(wtPlan.includes("- [ ] **T03:"), "T03 stays [ ] (not in root either)");
209
- } finally {
210
- teardownAutoWorktree(tempDir, "M004");
211
- }
212
- }
213
-
214
156
  } finally {
215
157
  // Always restore cwd and clean up
216
158
  process.chdir(savedCwd);
@@ -231,15 +231,14 @@ None
231
231
  const detect = await runGSDDoctor(dir);
232
232
  const gitignoreIssues = detect.issues.filter(i => i.code === "gitignore_missing_patterns");
233
233
  assertTrue(gitignoreIssues.length > 0, "detects missing gitignore patterns");
234
- assertTrue(gitignoreIssues[0]?.message.includes(".gsd/activity/"), "message lists missing patterns");
234
+ assertTrue(gitignoreIssues[0]?.message.includes(".gsd"), "message lists missing .gsd pattern");
235
235
 
236
236
  const fixed = await runGSDDoctor(dir, { fix: true });
237
237
  assertTrue(fixed.fixesApplied.some(f => f.includes("added missing GSD runtime patterns")), "fix adds patterns");
238
238
 
239
- // Verify patterns were added
239
+ // Verify .gsd entry was added (external state symlink)
240
240
  const content = readFileSync(join(dir, ".gitignore"), "utf-8");
241
- assertTrue(content.includes(".gsd/activity/"), "gitignore now has activity pattern");
242
- assertTrue(content.includes(".gsd/auto.lock"), "gitignore now has auto.lock pattern");
241
+ assertTrue(content.includes(".gsd"), "gitignore now has .gsd entry");
243
242
  }
244
243
  } else {
245
244
  console.log("\n=== gitignore_missing_patterns (skipped on Windows) ===");
@@ -1,4 +1,6 @@
1
- // Tests for the SEPARATOR_PREFIX convention used by ExtensionSelectorComponent.
1
+ // Tests for the SEPARATOR_PREFIX convention used by ExtensionSelectorComponent
2
+ // and the two-step provider→model picker in configureModels.
3
+ //
2
4
  // We cannot import the component directly in node:test because its transitive
3
5
  // dependency (countdown-timer.ts) uses TypeScript parameter properties which
4
6
  // are unsupported under --experimental-strip-types. Instead we duplicate the
@@ -69,16 +71,17 @@ describe("separator detection", () => {
69
71
  });
70
72
  });
71
73
 
72
- describe("model grouping", () => {
73
- test("groups models by provider with separator headers", () => {
74
- // Simulate the grouping logic from configureModels
75
- const availableModels = [
76
- { id: "claude-opus-4-6", provider: "anthropic" },
77
- { id: "gpt-4o", provider: "openai" },
78
- { id: "claude-sonnet-4-5", provider: "anthropic" },
79
- { id: "o3-mini", provider: "openai" },
80
- ];
74
+ describe("two-step provider→model picker", () => {
75
+ // Simulate the grouping logic from configureModels
76
+ const availableModels = [
77
+ { id: "claude-opus-4-6", provider: "anthropic" },
78
+ { id: "gpt-4o", provider: "openai" },
79
+ { id: "claude-sonnet-4-5", provider: "anthropic" },
80
+ { id: "o3-mini", provider: "openai" },
81
+ { id: "claude-haiku-4-5", provider: "anthropic" },
82
+ ];
81
83
 
84
+ function buildProviderGroups() {
82
85
  const byProvider = new Map<string, typeof availableModels>();
83
86
  for (const m of availableModels) {
84
87
  let group = byProvider.get(m.provider);
@@ -89,34 +92,53 @@ describe("model grouping", () => {
89
92
  group.push(m);
90
93
  }
91
94
  const providers = Array.from(byProvider.keys()).sort((a, b) => a.localeCompare(b));
92
-
93
- const modelOptions: string[] = [];
94
- for (const provider of providers) {
95
- const group = byProvider.get(provider)!;
96
- modelOptions.push(`${SEPARATOR_PREFIX} ${provider} (${group.length}) ${SEPARATOR_PREFIX}`);
97
- for (const m of group) {
98
- modelOptions.push(`${m.id} · ${m.provider}`);
99
- }
95
+ for (const group of byProvider.values()) {
96
+ group.sort((a, b) => a.id.localeCompare(b.id));
100
97
  }
101
- modelOptions.push("(keep current)", "(clear)");
102
-
103
- // Verify structure
104
- assert.strictEqual(modelOptions[0], `${SEPARATOR_PREFIX} anthropic (2) ${SEPARATOR_PREFIX}`);
105
- assert.strictEqual(modelOptions[1], "claude-opus-4-6 · anthropic");
106
- assert.strictEqual(modelOptions[2], "claude-sonnet-4-5 · anthropic");
107
- assert.strictEqual(modelOptions[3], `${SEPARATOR_PREFIX} openai (2) ${SEPARATOR_PREFIX}`);
108
- assert.strictEqual(modelOptions[4], "gpt-4o · openai");
109
- assert.strictEqual(modelOptions[5], "o3-mini · openai");
110
- assert.strictEqual(modelOptions[6], "(keep current)");
111
- assert.strictEqual(modelOptions[7], "(clear)");
112
-
113
- // Verify separators are correctly detected
114
- assert.ok(isSeparator(modelOptions, 0));
115
- assert.ok(!isSeparator(modelOptions, 1));
116
- assert.ok(isSeparator(modelOptions, 3));
117
- assert.ok(!isSeparator(modelOptions, 6));
118
-
119
- // Verify first selectable is index 1, not the separator at 0
120
- assert.strictEqual(nextSelectable(modelOptions, 0, 1), 1);
98
+ return { byProvider, providers };
99
+ }
100
+
101
+ test("provider menu lists providers with model counts", () => {
102
+ const { providers, byProvider } = buildProviderGroups();
103
+ const providerOptions = providers.map(p => {
104
+ const count = byProvider.get(p)!.length;
105
+ return `${p} (${count} models)`;
106
+ });
107
+ providerOptions.push("(keep current)", "(clear)", "(type manually)");
108
+
109
+ assert.strictEqual(providerOptions[0], "anthropic (3 models)");
110
+ assert.strictEqual(providerOptions[1], "openai (2 models)");
111
+ assert.strictEqual(providerOptions[2], "(keep current)");
112
+ assert.strictEqual(providerOptions[3], "(clear)");
113
+ assert.strictEqual(providerOptions[4], "(type manually)");
114
+ });
115
+
116
+ test("model menu for a provider is sorted alphabetically", () => {
117
+ const { byProvider } = buildProviderGroups();
118
+ const anthropicModels = byProvider.get("anthropic")!;
119
+ const modelOptions = anthropicModels.map(m => m.id);
120
+
121
+ assert.strictEqual(modelOptions[0], "claude-haiku-4-5");
122
+ assert.strictEqual(modelOptions[1], "claude-opus-4-6");
123
+ assert.strictEqual(modelOptions[2], "claude-sonnet-4-5");
124
+ });
125
+
126
+ test("provider name is extracted correctly from choice string", () => {
127
+ const choice = "anthropic (3 models)";
128
+ const providerName = choice.replace(/ \(\d+ models?\)$/, "");
129
+ assert.strictEqual(providerName, "anthropic");
130
+
131
+ const singleChoice = "ollama (1 model)";
132
+ const singleProvider = singleChoice.replace(/ \(\d+ models?\)$/, "");
133
+ assert.strictEqual(singleProvider, "ollama");
134
+ });
135
+
136
+ test("openai models are sorted within their group", () => {
137
+ const { byProvider } = buildProviderGroups();
138
+ const openaiModels = byProvider.get("openai")!;
139
+ const modelOptions = openaiModels.map(m => m.id);
140
+
141
+ assert.strictEqual(modelOptions[0], "gpt-4o");
142
+ assert.strictEqual(modelOptions[1], "o3-mini");
121
143
  });
122
144
  });
@@ -311,7 +311,7 @@ async function main(): Promise<void> {
311
311
  // Test 2: Uncommitted .gsd/ planning files are available in worktree
312
312
  //
313
313
  // When auto-mode starts, .gsd/ files may be untracked/uncommitted.
314
- // copyPlanningArtifacts should carry them into the worktree even if
314
+ // Planning artifacts should be carried into the worktree even if
315
315
  // they weren't committed on the feature branch.
316
316
  // ================================================================
317
317
  console.log("\n=== Untracked planning files copied to worktree ===");
@@ -341,23 +341,10 @@ async function main(): Promise<void> {
341
341
  const wtPath = createAutoWorktree(repo, milestoneId);
342
342
  tempDirs.push(wtPath);
343
343
 
344
- // Planning files should exist in the worktree (via copyPlanningArtifacts)
345
- assertTrue(
346
- existsSync(join(wtPath, ".gsd", "milestones", milestoneId, `${milestoneId}-ROADMAP.md`)),
347
- "ROADMAP.md copied to worktree",
348
- );
349
- assertTrue(
350
- existsSync(join(wtPath, ".gsd", "milestones", milestoneId, "slices", "S01", "S01-PLAN.md")),
351
- "S01-PLAN.md copied to worktree",
352
- );
353
- assertTrue(
354
- existsSync(join(wtPath, ".gsd", "PROJECT.md")),
355
- "PROJECT.md copied to worktree",
356
- );
357
- assertTrue(
358
- existsSync(join(wtPath, ".gsd", "DECISIONS.md")),
359
- "DECISIONS.md copied to worktree",
360
- );
344
+ // With external state, worktree .gsd is a symlink to shared state.
345
+ // Verify symlink was created (planning files are shared, not copied).
346
+ const wtGsd = join(wtPath, ".gsd");
347
+ assertTrue(existsSync(wtGsd), "worktree .gsd exists (symlink or dir)");
361
348
 
362
349
  // Clean up: chdir back before teardown
363
350
  process.chdir(savedCwd);
@@ -1167,53 +1167,26 @@ async function main(): Promise<void> {
1167
1167
  rmSync(repo, { recursive: true, force: true });
1168
1168
  }
1169
1169
 
1170
- // ─── ensureGitignore: commit_docs false adds blanket .gsd/ ──────────
1170
+ // ─── ensureGitignore: always adds .gsd to gitignore ──────────────────
1171
1171
 
1172
- console.log("\n=== ensureGitignore: commit_docs false ===");
1172
+ console.log("\n=== ensureGitignore: adds .gsd entry ===");
1173
1173
 
1174
1174
  {
1175
1175
  const { ensureGitignore } = await import("../gitignore.ts");
1176
- const repo = mkdtempSync(join(tmpdir(), "gsd-gitignore-commit-docs-"));
1176
+ const repo = mkdtempSync(join(tmpdir(), "gsd-gitignore-external-state-"));
1177
1177
 
1178
- // When commit_docs is false, should add blanket .gsd/ to gitignore
1179
- const modified = ensureGitignore(repo, { commitDocs: false });
1180
- assertTrue(modified, "commit_docs=false: gitignore was modified");
1178
+ // Should add .gsd to gitignore (external state dir is a symlink)
1179
+ const modified = ensureGitignore(repo);
1180
+ assertTrue(modified, "ensureGitignore: gitignore was modified");
1181
1181
 
1182
1182
  const { readFileSync } = await import("node:fs");
1183
1183
  const content = readFileSync(join(repo, ".gitignore"), "utf-8");
1184
- assertTrue(content.includes(".gsd/"), "commit_docs=false: .gitignore contains blanket .gsd/");
1185
- assertTrue(content.includes("commit_docs: false"), "commit_docs=false: .gitignore contains explanatory comment");
1186
-
1187
- // Should NOT contain individual runtime patterns (those are subsumed by blanket .gsd/)
1188
- // But it's OK if it does — the blanket .gsd/ covers everything
1184
+ const lines = content.split("\n").map(l => l.trim()).filter(l => l && !l.startsWith("#"));
1185
+ assertTrue(lines.includes(".gsd"), "ensureGitignore: .gitignore contains .gsd");
1189
1186
 
1190
1187
  // Idempotent — calling again doesn't add duplicates
1191
- const modified2 = ensureGitignore(repo, { commitDocs: false });
1192
- assertTrue(!modified2, "commit_docs=false: second call is idempotent");
1193
-
1194
- rmSync(repo, { recursive: true, force: true });
1195
- }
1196
-
1197
- // ─── ensureGitignore: commit_docs true removes blanket .gsd/ ────────
1198
-
1199
- console.log("\n=== ensureGitignore: commit_docs true self-heals ===");
1200
-
1201
- {
1202
- const { ensureGitignore } = await import("../gitignore.ts");
1203
- const repo = mkdtempSync(join(tmpdir(), "gsd-gitignore-selfheal-"));
1204
-
1205
- // Start with a gitignore that has a blanket .gsd/ (e.g., user switched setting)
1206
- writeFileSync(join(repo, ".gitignore"), ".gsd/\n");
1207
-
1208
- const modified = ensureGitignore(repo, { commitDocs: true });
1209
- assertTrue(modified, "commit_docs=true: gitignore was modified");
1210
-
1211
- const { readFileSync } = await import("node:fs");
1212
- const content = readFileSync(join(repo, ".gitignore"), "utf-8");
1213
- // Blanket .gsd/ should be removed
1214
- const lines = content.split("\n").map(l => l.trim()).filter(l => l && !l.startsWith("#"));
1215
- assertTrue(!lines.includes(".gsd/"), "commit_docs=true: blanket .gsd/ was removed");
1216
- assertTrue(!lines.includes(".gsd"), "commit_docs=true: blanket .gsd was removed");
1188
+ const modified2 = ensureGitignore(repo);
1189
+ assertTrue(!modified2, "ensureGitignore: second call is idempotent");
1217
1190
 
1218
1191
  rmSync(repo, { recursive: true, force: true });
1219
1192
  }
@@ -10,7 +10,7 @@
10
10
 
11
11
  import test from 'node:test';
12
12
  import assert from 'node:assert/strict';
13
- import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync } from 'node:fs';
13
+ import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync, realpathSync } from 'node:fs';
14
14
  import { join } from 'node:path';
15
15
  import { tmpdir } from 'node:os';
16
16
  import { GSD_ROOT_FILES, resolveGsdRootFile } from '../paths.ts';
@@ -27,7 +27,7 @@ test('knowledge: KNOWLEDGE key exists in GSD_ROOT_FILES', () => {
27
27
  // ─── resolveGsdRootFile resolves KNOWLEDGE.md ───────────────────────────────
28
28
 
29
29
  test('knowledge: resolveGsdRootFile returns canonical path when KNOWLEDGE.md exists', () => {
30
- const tmp = mkdtempSync(join(tmpdir(), 'gsd-knowledge-'));
30
+ const tmp = realpathSync(mkdtempSync(join(tmpdir(), 'gsd-knowledge-')));
31
31
  const gsdDir = join(tmp, '.gsd');
32
32
  mkdirSync(gsdDir, { recursive: true });
33
33
  writeFileSync(join(gsdDir, 'KNOWLEDGE.md'), '# Project Knowledge\n');
@@ -39,7 +39,7 @@ test('knowledge: resolveGsdRootFile returns canonical path when KNOWLEDGE.md exi
39
39
  });
40
40
 
41
41
  test('knowledge: resolveGsdRootFile resolves when legacy knowledge.md exists', () => {
42
- const tmp = mkdtempSync(join(tmpdir(), 'gsd-knowledge-'));
42
+ const tmp = realpathSync(mkdtempSync(join(tmpdir(), 'gsd-knowledge-')));
43
43
  const gsdDir = join(tmp, '.gsd');
44
44
  mkdirSync(gsdDir, { recursive: true });
45
45
  writeFileSync(join(gsdDir, 'knowledge.md'), '# Project Knowledge\n');
@@ -58,7 +58,7 @@ test('knowledge: resolveGsdRootFile resolves when legacy knowledge.md exists', (
58
58
  });
59
59
 
60
60
  test('knowledge: resolveGsdRootFile returns canonical path when file does not exist', () => {
61
- const tmp = mkdtempSync(join(tmpdir(), 'gsd-knowledge-'));
61
+ const tmp = realpathSync(mkdtempSync(join(tmpdir(), 'gsd-knowledge-')));
62
62
  const gsdDir = join(tmp, '.gsd');
63
63
  mkdirSync(gsdDir, { recursive: true });
64
64