gsd-pi 2.18.0 → 2.20.0

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 (289) hide show
  1. package/README.md +5 -1
  2. package/dist/cli.js +3 -3
  3. package/dist/onboarding.d.ts +3 -1
  4. package/dist/onboarding.js +77 -3
  5. package/dist/remote-questions-config.d.ts +1 -1
  6. package/dist/resources/extensions/google-search/index.ts +164 -47
  7. package/dist/resources/extensions/gsd/auto-dashboard.ts +14 -2
  8. package/dist/resources/extensions/gsd/auto-prompts.ts +148 -39
  9. package/dist/resources/extensions/gsd/auto-worktree.ts +93 -9
  10. package/dist/resources/extensions/gsd/auto.ts +690 -39
  11. package/dist/resources/extensions/gsd/captures.ts +384 -0
  12. package/dist/resources/extensions/gsd/commands.ts +654 -36
  13. package/dist/resources/extensions/gsd/complexity-classifier.ts +322 -0
  14. package/dist/resources/extensions/gsd/context-budget.ts +243 -0
  15. package/dist/resources/extensions/gsd/context-store.ts +195 -0
  16. package/dist/resources/extensions/gsd/dashboard-overlay.ts +51 -3
  17. package/dist/resources/extensions/gsd/db-writer.ts +341 -0
  18. package/dist/resources/extensions/gsd/debug-logger.ts +178 -0
  19. package/dist/resources/extensions/gsd/dispatch-guard.ts +0 -1
  20. package/dist/resources/extensions/gsd/docs/preferences-reference.md +54 -0
  21. package/dist/resources/extensions/gsd/doctor-proactive.ts +286 -0
  22. package/dist/resources/extensions/gsd/doctor.ts +283 -2
  23. package/dist/resources/extensions/gsd/export.ts +81 -2
  24. package/dist/resources/extensions/gsd/files.ts +39 -9
  25. package/dist/resources/extensions/gsd/git-service.ts +6 -0
  26. package/dist/resources/extensions/gsd/gsd-db.ts +752 -0
  27. package/dist/resources/extensions/gsd/guided-flow.ts +26 -1
  28. package/dist/resources/extensions/gsd/history.ts +0 -1
  29. package/dist/resources/extensions/gsd/index.ts +277 -1
  30. package/dist/resources/extensions/gsd/md-importer.ts +526 -0
  31. package/dist/resources/extensions/gsd/metrics.ts +84 -0
  32. package/dist/resources/extensions/gsd/model-cost-table.ts +65 -0
  33. package/dist/resources/extensions/gsd/model-router.ts +256 -0
  34. package/dist/resources/extensions/gsd/notifications.ts +0 -1
  35. package/dist/resources/extensions/gsd/post-unit-hooks.ts +72 -2
  36. package/dist/resources/extensions/gsd/preferences.ts +198 -150
  37. package/dist/resources/extensions/gsd/prompt-loader.ts +45 -9
  38. package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -5
  39. package/dist/resources/extensions/gsd/prompts/heal-skill.md +45 -0
  40. package/dist/resources/extensions/gsd/prompts/plan-slice.md +5 -1
  41. package/dist/resources/extensions/gsd/prompts/quick-task.md +48 -0
  42. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
  43. package/dist/resources/extensions/gsd/prompts/replan-slice.md +8 -0
  44. package/dist/resources/extensions/gsd/prompts/system.md +2 -1
  45. package/dist/resources/extensions/gsd/prompts/triage-captures.md +62 -0
  46. package/dist/resources/extensions/gsd/quick.ts +156 -0
  47. package/dist/resources/extensions/gsd/skill-discovery.ts +5 -3
  48. package/dist/resources/extensions/gsd/skill-health.ts +417 -0
  49. package/dist/resources/extensions/gsd/skill-telemetry.ts +127 -0
  50. package/dist/resources/extensions/gsd/state.ts +30 -0
  51. package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
  52. package/dist/resources/extensions/gsd/tests/captures.test.ts +438 -0
  53. package/dist/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
  54. package/dist/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
  55. package/dist/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  56. package/dist/resources/extensions/gsd/tests/context-store.test.ts +462 -0
  57. package/dist/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
  58. package/dist/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
  59. package/dist/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
  60. package/dist/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
  61. package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
  62. package/dist/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
  63. package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
  64. package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
  65. package/dist/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
  66. package/dist/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -0
  67. package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
  68. package/dist/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
  69. package/dist/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
  70. package/dist/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
  71. package/dist/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
  72. package/dist/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
  73. package/dist/resources/extensions/gsd/tests/metrics.test.ts +197 -0
  74. package/dist/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +144 -0
  75. package/dist/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
  76. package/dist/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
  77. package/dist/resources/extensions/gsd/tests/model-router.test.ts +167 -0
  78. package/dist/resources/extensions/gsd/tests/parsers.test.ts +40 -0
  79. package/dist/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
  80. package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
  81. package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
  82. package/dist/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
  83. package/dist/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
  84. package/dist/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
  85. package/dist/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
  86. package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +488 -1
  87. package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
  88. package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
  89. package/dist/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
  90. package/dist/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
  91. package/dist/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
  92. package/dist/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
  93. package/dist/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
  94. package/dist/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
  95. package/dist/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
  96. package/dist/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
  97. package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +290 -0
  98. package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
  99. package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +478 -0
  100. package/dist/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  101. package/dist/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  102. package/dist/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
  103. package/dist/resources/extensions/gsd/triage-resolution.ts +200 -0
  104. package/dist/resources/extensions/gsd/triage-ui.ts +175 -0
  105. package/dist/resources/extensions/gsd/types.ts +29 -0
  106. package/dist/resources/extensions/gsd/undo.ts +0 -1
  107. package/dist/resources/extensions/gsd/unit-runtime.ts +5 -1
  108. package/dist/resources/extensions/gsd/visualizer-data.ts +505 -0
  109. package/dist/resources/extensions/gsd/visualizer-overlay.ts +337 -0
  110. package/dist/resources/extensions/gsd/visualizer-views.ts +755 -0
  111. package/dist/resources/extensions/gsd/worktree-command.ts +18 -0
  112. package/dist/resources/extensions/gsd/worktree-manager.ts +11 -4
  113. package/dist/resources/extensions/remote-questions/config.ts +4 -2
  114. package/dist/resources/extensions/remote-questions/discord-adapter.ts +35 -4
  115. package/dist/resources/extensions/remote-questions/format.ts +166 -14
  116. package/dist/resources/extensions/remote-questions/manager.ts +14 -4
  117. package/dist/resources/extensions/remote-questions/remote-command.ts +100 -4
  118. package/dist/resources/extensions/remote-questions/slack-adapter.ts +58 -2
  119. package/dist/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
  120. package/dist/resources/extensions/remote-questions/types.ts +2 -1
  121. package/dist/resources/extensions/ttsr/ttsr-manager.ts +26 -0
  122. package/dist/resources/extensions/voice/index.ts +4 -3
  123. package/package.json +1 -1
  124. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/agent-session.js +12 -1
  126. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  128. package/packages/pi-coding-agent/dist/core/extensions/loader.js +5 -0
  129. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  130. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts +6 -0
  131. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/lsp/client.js +25 -0
  133. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts +2 -0
  135. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
  136. package/packages/pi-coding-agent/dist/core/lsp/index.js +106 -3
  137. package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
  138. package/packages/pi-coding-agent/dist/core/lsp/lsp.md +6 -0
  139. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +35 -0
  140. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -1
  141. package/packages/pi-coding-agent/dist/core/lsp/types.js +6 -0
  142. package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -1
  143. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts +3 -1
  144. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -1
  145. package/packages/pi-coding-agent/dist/core/lsp/utils.js +45 -0
  146. package/packages/pi-coding-agent/dist/core/lsp/utils.js.map +1 -1
  147. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -0
  148. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  149. package/packages/pi-coding-agent/dist/core/settings-manager.js +43 -11
  150. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  151. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  152. package/packages/pi-coding-agent/dist/core/system-prompt.js +7 -1
  153. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  154. package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
  155. package/packages/pi-coding-agent/dist/core/tools/edit.js +5 -0
  156. package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
  157. package/packages/pi-coding-agent/dist/core/tools/index.d.ts +2 -0
  158. package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
  159. package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
  160. package/packages/pi-coding-agent/dist/core/tools/write.js +5 -0
  161. package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
  162. package/packages/pi-coding-agent/src/core/agent-session.ts +13 -1
  163. package/packages/pi-coding-agent/src/core/extensions/loader.ts +6 -0
  164. package/packages/pi-coding-agent/src/core/lsp/client.ts +26 -0
  165. package/packages/pi-coding-agent/src/core/lsp/index.ts +157 -2
  166. package/packages/pi-coding-agent/src/core/lsp/lsp.md +6 -0
  167. package/packages/pi-coding-agent/src/core/lsp/types.ts +53 -0
  168. package/packages/pi-coding-agent/src/core/lsp/utils.ts +56 -0
  169. package/packages/pi-coding-agent/src/core/settings-manager.ts +41 -11
  170. package/packages/pi-coding-agent/src/core/system-prompt.ts +7 -1
  171. package/packages/pi-coding-agent/src/core/tools/edit.ts +3 -0
  172. package/packages/pi-coding-agent/src/core/tools/write.ts +3 -0
  173. package/src/resources/extensions/google-search/index.ts +164 -47
  174. package/src/resources/extensions/gsd/auto-dashboard.ts +14 -2
  175. package/src/resources/extensions/gsd/auto-prompts.ts +148 -39
  176. package/src/resources/extensions/gsd/auto-worktree.ts +93 -9
  177. package/src/resources/extensions/gsd/auto.ts +690 -39
  178. package/src/resources/extensions/gsd/captures.ts +384 -0
  179. package/src/resources/extensions/gsd/commands.ts +654 -36
  180. package/src/resources/extensions/gsd/complexity-classifier.ts +322 -0
  181. package/src/resources/extensions/gsd/context-budget.ts +243 -0
  182. package/src/resources/extensions/gsd/context-store.ts +195 -0
  183. package/src/resources/extensions/gsd/dashboard-overlay.ts +51 -3
  184. package/src/resources/extensions/gsd/db-writer.ts +341 -0
  185. package/src/resources/extensions/gsd/debug-logger.ts +178 -0
  186. package/src/resources/extensions/gsd/dispatch-guard.ts +0 -1
  187. package/src/resources/extensions/gsd/docs/preferences-reference.md +54 -0
  188. package/src/resources/extensions/gsd/doctor-proactive.ts +286 -0
  189. package/src/resources/extensions/gsd/doctor.ts +283 -2
  190. package/src/resources/extensions/gsd/export.ts +81 -2
  191. package/src/resources/extensions/gsd/files.ts +39 -9
  192. package/src/resources/extensions/gsd/git-service.ts +6 -0
  193. package/src/resources/extensions/gsd/gsd-db.ts +752 -0
  194. package/src/resources/extensions/gsd/guided-flow.ts +26 -1
  195. package/src/resources/extensions/gsd/history.ts +0 -1
  196. package/src/resources/extensions/gsd/index.ts +277 -1
  197. package/src/resources/extensions/gsd/md-importer.ts +526 -0
  198. package/src/resources/extensions/gsd/metrics.ts +84 -0
  199. package/src/resources/extensions/gsd/model-cost-table.ts +65 -0
  200. package/src/resources/extensions/gsd/model-router.ts +256 -0
  201. package/src/resources/extensions/gsd/notifications.ts +0 -1
  202. package/src/resources/extensions/gsd/post-unit-hooks.ts +72 -2
  203. package/src/resources/extensions/gsd/preferences.ts +198 -150
  204. package/src/resources/extensions/gsd/prompt-loader.ts +45 -9
  205. package/src/resources/extensions/gsd/prompts/execute-task.md +3 -5
  206. package/src/resources/extensions/gsd/prompts/heal-skill.md +45 -0
  207. package/src/resources/extensions/gsd/prompts/plan-slice.md +5 -1
  208. package/src/resources/extensions/gsd/prompts/quick-task.md +48 -0
  209. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
  210. package/src/resources/extensions/gsd/prompts/replan-slice.md +8 -0
  211. package/src/resources/extensions/gsd/prompts/system.md +2 -1
  212. package/src/resources/extensions/gsd/prompts/triage-captures.md +62 -0
  213. package/src/resources/extensions/gsd/quick.ts +156 -0
  214. package/src/resources/extensions/gsd/skill-discovery.ts +5 -3
  215. package/src/resources/extensions/gsd/skill-health.ts +417 -0
  216. package/src/resources/extensions/gsd/skill-telemetry.ts +127 -0
  217. package/src/resources/extensions/gsd/state.ts +30 -0
  218. package/src/resources/extensions/gsd/templates/preferences.md +1 -0
  219. package/src/resources/extensions/gsd/tests/captures.test.ts +438 -0
  220. package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
  221. package/src/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
  222. package/src/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  223. package/src/resources/extensions/gsd/tests/context-store.test.ts +462 -0
  224. package/src/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
  225. package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
  226. package/src/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
  227. package/src/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
  228. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
  229. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
  230. package/src/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
  231. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
  232. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
  233. package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -0
  234. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
  235. package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
  236. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
  237. package/src/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
  238. package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
  239. package/src/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
  240. package/src/resources/extensions/gsd/tests/metrics.test.ts +197 -0
  241. package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +144 -0
  242. package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
  243. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
  244. package/src/resources/extensions/gsd/tests/model-router.test.ts +167 -0
  245. package/src/resources/extensions/gsd/tests/parsers.test.ts +40 -0
  246. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
  247. package/src/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
  248. package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
  249. package/src/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
  250. package/src/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
  251. package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
  252. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
  253. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +488 -1
  254. package/src/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
  255. package/src/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
  256. package/src/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
  257. package/src/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
  258. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
  259. package/src/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
  260. package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
  261. package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
  262. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
  263. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
  264. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +290 -0
  265. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
  266. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +478 -0
  267. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  268. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  269. package/src/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
  270. package/src/resources/extensions/gsd/triage-resolution.ts +200 -0
  271. package/src/resources/extensions/gsd/triage-ui.ts +175 -0
  272. package/src/resources/extensions/gsd/types.ts +29 -0
  273. package/src/resources/extensions/gsd/undo.ts +0 -1
  274. package/src/resources/extensions/gsd/unit-runtime.ts +5 -1
  275. package/src/resources/extensions/gsd/visualizer-data.ts +505 -0
  276. package/src/resources/extensions/gsd/visualizer-overlay.ts +337 -0
  277. package/src/resources/extensions/gsd/visualizer-views.ts +755 -0
  278. package/src/resources/extensions/gsd/worktree-command.ts +18 -0
  279. package/src/resources/extensions/gsd/worktree-manager.ts +11 -4
  280. package/src/resources/extensions/remote-questions/config.ts +4 -2
  281. package/src/resources/extensions/remote-questions/discord-adapter.ts +35 -4
  282. package/src/resources/extensions/remote-questions/format.ts +166 -14
  283. package/src/resources/extensions/remote-questions/manager.ts +14 -4
  284. package/src/resources/extensions/remote-questions/remote-command.ts +100 -4
  285. package/src/resources/extensions/remote-questions/slack-adapter.ts +58 -2
  286. package/src/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
  287. package/src/resources/extensions/remote-questions/types.ts +2 -1
  288. package/src/resources/extensions/ttsr/ttsr-manager.ts +26 -0
  289. package/src/resources/extensions/voice/index.ts +4 -3
@@ -2,8 +2,11 @@ import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from "
2
2
  import { homedir } from "node:os";
3
3
  import { isAbsolute, join } from "node:path";
4
4
  import { getAgentDir } from "@gsd/pi-coding-agent";
5
+ import { parse as parseYaml } from "yaml";
5
6
  import type { GitPreferences } from "./git-service.js";
6
7
  import type { PostUnitHookConfig, PreDispatchHookConfig, BudgetEnforcementMode, NotificationPreferences, TokenProfile, InlineLevel, PhaseSkipPreferences } from "./types.js";
8
+ import type { DynamicRoutingConfig } from "./model-router.js";
9
+ import { defaultRoutingConfig } from "./model-router.js";
7
10
  import { VALID_BRANCH_NAME } from "./git-service.js";
8
11
 
9
12
  const GLOBAL_PREFERENCES_PATH = join(homedir(), ".gsd", "preferences.md");
@@ -15,9 +18,40 @@ const GLOBAL_PREFERENCES_PATH_UPPERCASE = join(homedir(), ".gsd", "PREFERENCES.m
15
18
  const PROJECT_PREFERENCES_PATH_UPPERCASE = join(process.cwd(), ".gsd", "PREFERENCES.md");
16
19
  const SKILL_ACTIONS = new Set(["use", "prefer", "avoid"]);
17
20
 
21
+ // ─── Workflow Modes ──────────────────────────────────────────────────────────
22
+
23
+ export type WorkflowMode = "solo" | "team";
24
+
25
+ /** Default preference values for each workflow mode. */
26
+ const MODE_DEFAULTS: Record<WorkflowMode, Partial<GSDPreferences>> = {
27
+ solo: {
28
+ git: {
29
+ auto_push: true,
30
+ push_branches: false,
31
+ pre_merge_check: false,
32
+ merge_strategy: "squash",
33
+ isolation: "worktree",
34
+ commit_docs: true,
35
+ },
36
+ unique_milestone_ids: false,
37
+ },
38
+ team: {
39
+ git: {
40
+ auto_push: false,
41
+ push_branches: true,
42
+ pre_merge_check: true,
43
+ merge_strategy: "squash",
44
+ isolation: "worktree",
45
+ commit_docs: true,
46
+ },
47
+ unique_milestone_ids: true,
48
+ },
49
+ };
50
+
18
51
  /** All recognized top-level keys in GSDPreferences. Used to detect typos / stale config. */
19
52
  const KNOWN_PREFERENCE_KEYS = new Set<string>([
20
53
  "version",
54
+ "mode",
21
55
  "always_use_skills",
22
56
  "prefer_skills",
23
57
  "avoid_skills",
@@ -25,6 +59,7 @@ const KNOWN_PREFERENCE_KEYS = new Set<string>([
25
59
  "custom_instructions",
26
60
  "models",
27
61
  "skill_discovery",
62
+ "skill_staleness_days",
28
63
  "auto_supervisor",
29
64
  "uat_dispatch",
30
65
  "unique_milestone_ids",
@@ -36,8 +71,10 @@ const KNOWN_PREFERENCE_KEYS = new Set<string>([
36
71
  "git",
37
72
  "post_unit_hooks",
38
73
  "pre_dispatch_hooks",
74
+ "dynamic_routing",
39
75
  "token_profile",
40
76
  "phases",
77
+ "auto_visualize",
41
78
  ]);
42
79
 
43
80
  export interface GSDSkillRule {
@@ -102,7 +139,7 @@ export interface AutoSupervisorConfig {
102
139
  }
103
140
 
104
141
  export interface RemoteQuestionsConfig {
105
- channel: "slack" | "discord";
142
+ channel: "slack" | "discord" | "telegram";
106
143
  channel_id: string | number;
107
144
  timeout_minutes?: number; // clamped to 1-30
108
145
  poll_interval_seconds?: number; // clamped to 2-30
@@ -110,6 +147,7 @@ export interface RemoteQuestionsConfig {
110
147
 
111
148
  export interface GSDPreferences {
112
149
  version?: number;
150
+ mode?: WorkflowMode;
113
151
  always_use_skills?: string[];
114
152
  prefer_skills?: string[];
115
153
  avoid_skills?: string[];
@@ -117,6 +155,7 @@ export interface GSDPreferences {
117
155
  custom_instructions?: string[];
118
156
  models?: GSDModelConfig | GSDModelConfigV2;
119
157
  skill_discovery?: SkillDiscoveryMode;
158
+ skill_staleness_days?: number; // Skills unused for N days get deprioritized (#599). 0 = disabled. Default: 60.
120
159
  auto_supervisor?: AutoSupervisorConfig;
121
160
  uat_dispatch?: boolean;
122
161
  unique_milestone_ids?: boolean;
@@ -128,8 +167,10 @@ export interface GSDPreferences {
128
167
  git?: GitPreferences;
129
168
  post_unit_hooks?: PostUnitHookConfig[];
130
169
  pre_dispatch_hooks?: PreDispatchHookConfig[];
170
+ dynamic_routing?: DynamicRoutingConfig;
131
171
  token_profile?: TokenProfile;
132
172
  phases?: PhaseSkipPreferences;
173
+ auto_visualize?: boolean;
133
174
  }
134
175
 
135
176
  export interface LoadedGSDPreferences {
@@ -163,25 +204,49 @@ export function loadProjectGSDPreferences(): LoadedGSDPreferences | null {
163
204
  ?? loadPreferencesFile(PROJECT_PREFERENCES_PATH_UPPERCASE, "project");
164
205
  }
165
206
 
207
+ /**
208
+ * Apply mode defaults as the lowest-priority layer.
209
+ * Mode defaults fill in undefined fields; any explicit user value wins.
210
+ */
211
+ export function applyModeDefaults(mode: WorkflowMode, prefs: GSDPreferences): GSDPreferences {
212
+ const defaults = MODE_DEFAULTS[mode];
213
+ if (!defaults) return prefs;
214
+ return mergePreferences(defaults, prefs);
215
+ }
216
+
166
217
  export function loadEffectiveGSDPreferences(): LoadedGSDPreferences | null {
167
218
  const globalPreferences = loadGlobalGSDPreferences();
168
219
  const projectPreferences = loadProjectGSDPreferences();
169
220
 
170
221
  if (!globalPreferences && !projectPreferences) return null;
171
- if (!globalPreferences) return projectPreferences;
172
- if (!projectPreferences) return globalPreferences;
173
222
 
174
- const mergedWarnings = [
175
- ...(globalPreferences.warnings ?? []),
176
- ...(projectPreferences.warnings ?? []),
177
- ];
223
+ let result: LoadedGSDPreferences;
224
+ if (!globalPreferences) {
225
+ result = projectPreferences!;
226
+ } else if (!projectPreferences) {
227
+ result = globalPreferences;
228
+ } else {
229
+ const mergedWarnings = [
230
+ ...(globalPreferences.warnings ?? []),
231
+ ...(projectPreferences.warnings ?? []),
232
+ ];
233
+ result = {
234
+ path: projectPreferences.path,
235
+ scope: "project",
236
+ preferences: mergePreferences(globalPreferences.preferences, projectPreferences.preferences),
237
+ ...(mergedWarnings.length > 0 ? { warnings: mergedWarnings } : {}),
238
+ };
239
+ }
178
240
 
179
- return {
180
- path: projectPreferences.path,
181
- scope: "project",
182
- preferences: mergePreferences(globalPreferences.preferences, projectPreferences.preferences),
183
- ...(mergedWarnings.length > 0 ? { warnings: mergedWarnings } : {}),
184
- };
241
+ // Apply mode defaults as the lowest-priority layer
242
+ if (result.preferences.mode) {
243
+ result = {
244
+ ...result,
245
+ preferences: applyModeDefaults(result.preferences.mode, result.preferences),
246
+ };
247
+ }
248
+
249
+ return result;
185
250
  }
186
251
 
187
252
  // ─── Skill Reference Resolution ───────────────────────────────────────────────
@@ -419,148 +484,27 @@ function loadPreferencesFile(path: string, scope: "global" | "project"): LoadedG
419
484
 
420
485
  /** @internal Exported for testing only */
421
486
  export function parsePreferencesMarkdown(content: string): GSDPreferences | null {
422
- const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
423
- if (!match) return null;
424
- return parseFrontmatterBlock(match[1]);
487
+ // Use indexOf instead of [\s\S]*? regex to avoid backtracking (#468)
488
+ const startMarker = content.startsWith('---\r\n') ? '---\r\n' : '---\n';
489
+ if (!content.startsWith(startMarker)) return null;
490
+ const searchStart = startMarker.length;
491
+ const endIdx = content.indexOf('\n---', searchStart);
492
+ if (endIdx === -1) return null;
493
+ const block = content.slice(searchStart, endIdx);
494
+ return parseFrontmatterBlock(block.replace(/\r/g, ''));
425
495
  }
426
496
 
427
497
  function parseFrontmatterBlock(frontmatter: string): GSDPreferences {
428
- const root: Record<string, unknown> = {};
429
- const stack: Array<{ indent: number; value: Record<string, unknown> }> = [{ indent: -1, value: root }];
430
-
431
- const lines = frontmatter.split(/\r?\n/);
432
- for (let i = 0; i < lines.length; i++) {
433
- const line = lines[i];
434
- if (!line.trim()) continue;
435
-
436
- const indent = line.match(/^\s*/)?.[0].length ?? 0;
437
- const trimmed = line.trim();
438
-
439
- // Skip comment lines (standalone YAML comments)
440
- if (trimmed.startsWith("#")) continue;
441
-
442
- while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
443
- stack.pop();
444
- }
445
-
446
- const current = stack[stack.length - 1].value;
447
- const keyMatch = trimmed.match(/^([A-Za-z0-9_]+):(.*)$/);
448
- if (!keyMatch) continue;
449
-
450
- const [, key, remainder] = keyMatch;
451
- // Strip inline comments from the value portion
452
- const valuePart = remainder.replace(/\s+#.*$/, "").trim();
453
-
454
- if (valuePart === "") {
455
- const nextLine = lines[i + 1] ?? "";
456
- const nextTrimmed = nextLine.trim();
457
- if (nextTrimmed.startsWith("- ")) {
458
- const items: unknown[] = [];
459
- let j = i + 1;
460
- while (j < lines.length) {
461
- const candidate = lines[j];
462
- const candidateIndent = candidate.match(/^\s*/)?.[0].length ?? 0;
463
- const candidateTrimmed = candidate.trim();
464
- if (!candidateTrimmed) {
465
- j++;
466
- continue;
467
- }
468
- if (candidateIndent <= indent || !candidateTrimmed.startsWith("- ")) break;
469
-
470
- const itemText = candidateTrimmed.slice(2).trim();
471
- const nextCandidate = lines[j + 1] ?? "";
472
- const nextCandidateIndent = nextCandidate.match(/^\s*/)?.[0].length ?? 0;
473
- const nextCandidateTrimmed = nextCandidate.trim();
474
-
475
- // Treat an array item as a structured object only when:
476
- // a) It looks like a YAML key-value pair (key starts with [A-Za-z0-9_]+:), OR
477
- // b) The next line is indented deeper (nested block under this item).
478
- // Bare colons (e.g. "qwen/qwen3-coder:free") are NOT key-value pairs.
479
- const looksLikeKeyValue = /^[A-Za-z0-9_]+:/.test(itemText);
480
- if (looksLikeKeyValue || (nextCandidateTrimmed && nextCandidateIndent > candidateIndent)) {
481
- const obj: Record<string, unknown> = {};
482
- const firstMatch = itemText.match(/^([A-Za-z0-9_]+):(.*)$/);
483
- if (firstMatch) {
484
- obj[firstMatch[1]] = parseScalar(firstMatch[2].trim());
485
- }
486
- j++;
487
- while (j < lines.length) {
488
- const nested = lines[j];
489
- const nestedIndent = nested.match(/^\s*/)?.[0].length ?? 0;
490
- const nestedTrimmed = nested.trim();
491
- if (!nestedTrimmed) {
492
- j++;
493
- continue;
494
- }
495
- if (nestedIndent <= candidateIndent) break;
496
- const nestedMatch = nestedTrimmed.match(/^([A-Za-z0-9_]+):(.*)$/);
497
- if (nestedMatch) {
498
- const nestedValue = nestedMatch[2].trim();
499
- if (nestedValue === "") {
500
- const nestedItems: string[] = [];
501
- j++;
502
- while (j < lines.length) {
503
- const nestedArrayLine = lines[j];
504
- const nestedArrayIndent = nestedArrayLine.match(/^\s*/)?.[0].length ?? 0;
505
- const nestedArrayTrimmed = nestedArrayLine.trim();
506
- if (!nestedArrayTrimmed) {
507
- j++;
508
- continue;
509
- }
510
- if (nestedArrayIndent <= nestedIndent || !nestedArrayTrimmed.startsWith("- ")) break;
511
- nestedItems.push(String(parseScalar(nestedArrayTrimmed.slice(2).trim())));
512
- j++;
513
- }
514
- obj[nestedMatch[1]] = nestedItems;
515
- continue;
516
- }
517
- obj[nestedMatch[1]] = parseScalar(nestedValue);
518
- }
519
- j++;
520
- }
521
- items.push(obj);
522
- continue;
523
- }
524
-
525
- items.push(parseScalar(itemText));
526
- j++;
527
- }
528
- current[key] = items;
529
- i = j - 1;
530
- } else {
531
- const obj: Record<string, unknown> = {};
532
- current[key] = obj;
533
- stack.push({ indent, value: obj });
534
- }
535
- continue;
498
+ try {
499
+ const parsed = parseYaml(frontmatter);
500
+ if (typeof parsed !== 'object' || parsed === null) {
501
+ return {} as GSDPreferences;
536
502
  }
537
-
538
- current[key] = parseScalar(valuePart);
503
+ return parsed as GSDPreferences;
504
+ } catch (e) {
505
+ console.error("[parseFrontmatterBlock] YAML parse error:", e);
506
+ return {} as GSDPreferences;
539
507
  }
540
-
541
- return root as GSDPreferences;
542
- }
543
-
544
- function parseScalar(value: string): unknown {
545
- // Strip inline YAML comments: " # comment" (# preceded by whitespace).
546
- // Quoted strings are returned as-is (the comment is inside quotes).
547
- const quoteMatch = value.match(/^(['"])(.*)(\1)$/);
548
- if (quoteMatch) return quoteMatch[2];
549
-
550
- const stripped = value.replace(/\s+#.*$/, "");
551
- if (stripped === "true") return true;
552
- if (stripped === "false") return false;
553
- // Recognize empty array/object literals (with or without surrounding quotes)
554
- const unquoted = stripped.replace(/^['\"]|['\"]$/g, "");
555
- if (unquoted === "[]") return [];
556
- if (unquoted === "{}") return {};
557
- if (/^-?\d+$/.test(stripped)) {
558
- const n = Number(stripped);
559
- // Keep large integers (e.g. Discord channel IDs) as strings to avoid precision loss
560
- if (Number.isSafeInteger(n)) return n;
561
- return stripped;
562
- }
563
- return unquoted;
564
508
  }
565
509
 
566
510
  /**
@@ -572,6 +516,15 @@ export function resolveSkillDiscoveryMode(): SkillDiscoveryMode {
572
516
  return prefs?.preferences.skill_discovery ?? "suggest";
573
517
  }
574
518
 
519
+ /**
520
+ * Resolve the skill staleness threshold in days.
521
+ * Returns 0 if disabled, default 60 if not configured.
522
+ */
523
+ export function resolveSkillStalenessDays(): number {
524
+ const prefs = loadEffectiveGSDPreferences();
525
+ return prefs?.preferences.skill_staleness_days ?? 60;
526
+ }
527
+
575
528
  /**
576
529
  * Resolve which model ID to use for a given auto-mode unit type.
577
530
  * Returns undefined if no model preference is set for this unit type.
@@ -674,6 +627,20 @@ export function resolveModelWithFallbacksForUnit(unitType: string): ResolvedMode
674
627
  };
675
628
  }
676
629
 
630
+ /**
631
+ * Resolve the dynamic routing configuration from effective preferences.
632
+ * Returns the merged config with defaults applied.
633
+ */
634
+ export function resolveDynamicRoutingConfig(): DynamicRoutingConfig {
635
+ const prefs = loadEffectiveGSDPreferences();
636
+ const configured = prefs?.preferences.dynamic_routing;
637
+ if (!configured) return defaultRoutingConfig();
638
+ return {
639
+ ...defaultRoutingConfig(),
640
+ ...configured,
641
+ };
642
+ }
643
+
677
644
  export function resolveAutoSupervisorConfig(): AutoSupervisorConfig {
678
645
  const prefs = loadEffectiveGSDPreferences();
679
646
  const configured = prefs?.preferences.auto_supervisor ?? {};
@@ -756,6 +723,7 @@ export function resolveInlineLevel(): InlineLevel {
756
723
  function mergePreferences(base: GSDPreferences, override: GSDPreferences): GSDPreferences {
757
724
  return {
758
725
  version: override.version ?? base.version,
726
+ mode: override.mode ?? base.mode,
759
727
  always_use_skills: mergeStringLists(base.always_use_skills, override.always_use_skills),
760
728
  prefer_skills: mergeStringLists(base.prefer_skills, override.prefer_skills),
761
729
  avoid_skills: mergeStringLists(base.avoid_skills, override.avoid_skills),
@@ -763,6 +731,7 @@ function mergePreferences(base: GSDPreferences, override: GSDPreferences): GSDPr
763
731
  custom_instructions: mergeStringLists(base.custom_instructions, override.custom_instructions),
764
732
  models: { ...(base.models ?? {}), ...(override.models ?? {}) },
765
733
  skill_discovery: override.skill_discovery ?? base.skill_discovery,
734
+ skill_staleness_days: override.skill_staleness_days ?? base.skill_staleness_days,
766
735
  auto_supervisor: { ...(base.auto_supervisor ?? {}), ...(override.auto_supervisor ?? {}) },
767
736
  uat_dispatch: override.uat_dispatch ?? base.uat_dispatch,
768
737
  unique_milestone_ids: override.unique_milestone_ids ?? base.unique_milestone_ids,
@@ -780,6 +749,9 @@ function mergePreferences(base: GSDPreferences, override: GSDPreferences): GSDPr
780
749
  : undefined,
781
750
  post_unit_hooks: mergePostUnitHooks(base.post_unit_hooks, override.post_unit_hooks),
782
751
  pre_dispatch_hooks: mergePreDispatchHooks(base.pre_dispatch_hooks, override.pre_dispatch_hooks),
752
+ dynamic_routing: (base.dynamic_routing || override.dynamic_routing)
753
+ ? { ...(base.dynamic_routing ?? {}), ...(override.dynamic_routing ?? {}) } as DynamicRoutingConfig
754
+ : undefined,
783
755
  token_profile: override.token_profile ?? base.token_profile,
784
756
  phases: (base.phases || override.phases)
785
757
  ? { ...(base.phases ?? {}), ...(override.phases ?? {}) }
@@ -811,6 +783,16 @@ export function validatePreferences(preferences: GSDPreferences): {
811
783
  }
812
784
  }
813
785
 
786
+ // ─── Workflow Mode ──────────────────────────────────────────────────
787
+ if (preferences.mode !== undefined) {
788
+ const validModes = new Set<string>(["solo", "team"]);
789
+ if (typeof preferences.mode === "string" && validModes.has(preferences.mode)) {
790
+ validated.mode = preferences.mode as WorkflowMode;
791
+ } else {
792
+ errors.push(`invalid mode "${preferences.mode}" — must be one of: solo, team`);
793
+ }
794
+ }
795
+
814
796
  const validDiscoveryModes = new Set(["auto", "suggest", "off"]);
815
797
  if (preferences.skill_discovery) {
816
798
  if (validDiscoveryModes.has(preferences.skill_discovery)) {
@@ -820,6 +802,15 @@ export function validatePreferences(preferences: GSDPreferences): {
820
802
  }
821
803
  }
822
804
 
805
+ if (preferences.skill_staleness_days !== undefined) {
806
+ const days = Number(preferences.skill_staleness_days);
807
+ if (Number.isFinite(days) && days >= 0) {
808
+ validated.skill_staleness_days = Math.floor(days);
809
+ } else {
810
+ errors.push(`invalid skill_staleness_days: must be a non-negative number`);
811
+ }
812
+ }
813
+
823
814
  validated.always_use_skills = normalizeStringList(preferences.always_use_skills);
824
815
  validated.prefer_skills = normalizeStringList(preferences.prefer_skills);
825
816
  validated.avoid_skills = normalizeStringList(preferences.avoid_skills);
@@ -1100,6 +1091,56 @@ export function validatePreferences(preferences: GSDPreferences): {
1100
1091
  }
1101
1092
  }
1102
1093
 
1094
+ // ─── Dynamic Routing ─────────────────────────────────────────────────
1095
+ if (preferences.dynamic_routing !== undefined) {
1096
+ if (typeof preferences.dynamic_routing === "object" && preferences.dynamic_routing !== null) {
1097
+ const dr = preferences.dynamic_routing as unknown as Record<string, unknown>;
1098
+ const validDr: Partial<DynamicRoutingConfig> = {};
1099
+
1100
+ if (dr.enabled !== undefined) {
1101
+ if (typeof dr.enabled === "boolean") validDr.enabled = dr.enabled;
1102
+ else errors.push("dynamic_routing.enabled must be a boolean");
1103
+ }
1104
+ if (dr.escalate_on_failure !== undefined) {
1105
+ if (typeof dr.escalate_on_failure === "boolean") validDr.escalate_on_failure = dr.escalate_on_failure;
1106
+ else errors.push("dynamic_routing.escalate_on_failure must be a boolean");
1107
+ }
1108
+ if (dr.budget_pressure !== undefined) {
1109
+ if (typeof dr.budget_pressure === "boolean") validDr.budget_pressure = dr.budget_pressure;
1110
+ else errors.push("dynamic_routing.budget_pressure must be a boolean");
1111
+ }
1112
+ if (dr.cross_provider !== undefined) {
1113
+ if (typeof dr.cross_provider === "boolean") validDr.cross_provider = dr.cross_provider;
1114
+ else errors.push("dynamic_routing.cross_provider must be a boolean");
1115
+ }
1116
+ if (dr.hooks !== undefined) {
1117
+ if (typeof dr.hooks === "boolean") validDr.hooks = dr.hooks;
1118
+ else errors.push("dynamic_routing.hooks must be a boolean");
1119
+ }
1120
+ if (dr.tier_models !== undefined) {
1121
+ if (typeof dr.tier_models === "object" && dr.tier_models !== null) {
1122
+ const tm = dr.tier_models as Record<string, unknown>;
1123
+ const validTm: Record<string, string> = {};
1124
+ for (const tier of ["light", "standard", "heavy"]) {
1125
+ if (tm[tier] !== undefined) {
1126
+ if (typeof tm[tier] === "string") validTm[tier] = tm[tier] as string;
1127
+ else errors.push(`dynamic_routing.tier_models.${tier} must be a string`);
1128
+ }
1129
+ }
1130
+ if (Object.keys(validTm).length > 0) validDr.tier_models = validTm as DynamicRoutingConfig["tier_models"];
1131
+ } else {
1132
+ errors.push("dynamic_routing.tier_models must be an object");
1133
+ }
1134
+ }
1135
+
1136
+ if (Object.keys(validDr).length > 0) {
1137
+ validated.dynamic_routing = validDr as unknown as DynamicRoutingConfig;
1138
+ }
1139
+ } else {
1140
+ errors.push("dynamic_routing must be an object");
1141
+ }
1142
+ }
1143
+
1103
1144
  // ─── Git Preferences ───────────────────────────────────────────────────
1104
1145
  if (preferences.git && typeof preferences.git === "object") {
1105
1146
  const git: Record<string, unknown> = {};
@@ -1167,6 +1208,13 @@ export function validatePreferences(preferences: GSDPreferences): {
1167
1208
  if (typeof g.commit_docs === "boolean") git.commit_docs = g.commit_docs;
1168
1209
  else errors.push("git.commit_docs must be a boolean");
1169
1210
  }
1211
+ if (g.worktree_post_create !== undefined) {
1212
+ if (typeof g.worktree_post_create === "string" && g.worktree_post_create.trim()) {
1213
+ git.worktree_post_create = g.worktree_post_create.trim();
1214
+ } else {
1215
+ errors.push("git.worktree_post_create must be a non-empty string (path to script)");
1216
+ }
1217
+ }
1170
1218
  // Deprecated: merge_to_main is ignored (branchless architecture).
1171
1219
  if (g.merge_to_main !== undefined) {
1172
1220
  warnings.push("git.merge_to_main is deprecated — milestone-level merge is now always used. Remove this setting.");
@@ -7,15 +7,17 @@
7
7
  * Templates live at prompts/ relative to this module's directory.
8
8
  * They use {{variableName}} syntax for substitution.
9
9
  *
10
- * Templates are cached on first read per session. This prevents a running
11
- * session from being invalidated when another `gsd` launch overwrites
12
- * ~/.gsd/agent/ with newer templates via initResources(). Without caching,
13
- * the in-memory extension code (which knows variable set A) can read a
14
- * newer template from disk (which expects variable set B), causing a
15
- * "template declares {{X}} but no value was provided" crash mid-session.
10
+ * All templates are eagerly loaded into cache at module init via warmCache().
11
+ * This prevents a running session from being invalidated when another `gsd`
12
+ * launch overwrites ~/.gsd/agent/ with newer templates via initResources().
13
+ * Without eager caching, the in-memory extension code (which knows variable
14
+ * set A) can read a newer template from disk (which expects variable set B),
15
+ * causing a "template declares {{X}} but no value was provided" crash
16
+ * mid-session — especially for late-loading templates like complete-milestone
17
+ * that aren't read until the end of a long auto-mode run.
16
18
  */
17
19
 
18
- import { readFileSync } from "node:fs";
20
+ import { readFileSync, readdirSync } from "node:fs";
19
21
  import { join, dirname } from "node:path";
20
22
  import { fileURLToPath } from "node:url";
21
23
 
@@ -23,10 +25,44 @@ const __extensionDir = dirname(fileURLToPath(import.meta.url));
23
25
  const promptsDir = join(__extensionDir, "prompts");
24
26
  const templatesDir = join(__extensionDir, "templates");
25
27
 
26
- // Cache templates on first read — a running session uses the template versions
27
- // that were on disk when it first loaded them, immune to later overwrites.
28
+ // Cache all templates eagerly at module load — a running session uses the
29
+ // template versions that were on disk at startup, immune to later overwrites.
28
30
  const templateCache = new Map<string, string>();
29
31
 
32
+ /**
33
+ * Eagerly read all .md files from prompts/ and templates/ into cache.
34
+ * Called once at module init so that every template is snapshot before
35
+ * a concurrent initResources() can overwrite files on disk.
36
+ */
37
+ function warmCache(): void {
38
+ try {
39
+ for (const file of readdirSync(promptsDir)) {
40
+ if (!file.endsWith(".md")) continue;
41
+ const name = file.slice(0, -3);
42
+ if (!templateCache.has(name)) {
43
+ templateCache.set(name, readFileSync(join(promptsDir, file), "utf-8"));
44
+ }
45
+ }
46
+ } catch {
47
+ // prompts/ may not exist in test environments — lazy loading still works
48
+ }
49
+
50
+ try {
51
+ for (const file of readdirSync(templatesDir)) {
52
+ if (!file.endsWith(".md")) continue;
53
+ const cacheKey = `tpl:${file.slice(0, -3)}`;
54
+ if (!templateCache.has(cacheKey)) {
55
+ templateCache.set(cacheKey, readFileSync(join(templatesDir, file), "utf-8"));
56
+ }
57
+ }
58
+ } catch {
59
+ // templates/ may not exist in test environments — lazy loading still works
60
+ }
61
+ }
62
+
63
+ // Snapshot all templates at module load time
64
+ warmCache();
65
+
30
66
  /**
31
67
  * Load a prompt template and substitute variables.
32
68
  *
@@ -43,7 +43,7 @@ Then:
43
43
  9. If the task plan includes an Observability Impact section, verify those signals directly. Skip this step if the task plan omits the section.
44
44
  10. **If execution is running long or verification fails:**
45
45
 
46
- **Context budget:** If you've used most of your context and haven't finished all steps, stop implementing and prioritize writing the task summary with clear notes on what's done and what remains. A partial summary that enables clean resumption is more valuable than one more half-finished step with no documentation. Never sacrifice summary quality for one more implementation step.
46
+ **Context budget:** You have approximately **{{verificationBudget}}** reserved for verification context. If you've used most of your context and haven't finished all steps, stop implementing and prioritize writing the task summary with clear notes on what's done and what remains. A partial summary that enables clean resumption is more valuable than one more half-finished step with no documentation. Never sacrifice summary quality for one more implementation step.
47
47
 
48
48
  **Debugging discipline:** If a verification check fails or implementation hits unexpected behavior:
49
49
  - Form a hypothesis first. State what you think is wrong and why, then test that specific theory. Don't shotgun-fix.
@@ -53,9 +53,9 @@ Then:
53
53
  - Know when to stop. If you've tried 3+ fixes without progress, your mental model is probably wrong. Stop. List what you know for certain. List what you've ruled out. Form fresh hypotheses from there.
54
54
  - Don't fix symptoms. Understand *why* something fails before changing code. A test that passes after a change you don't understand is luck, not a fix.
55
55
  11. **Blocker discovery:** If execution reveals that the remaining slice plan is fundamentally invalid — not just a bug or minor deviation, but a plan-invalidating finding like a wrong API, missing capability, or architectural mismatch — set `blocker_discovered: true` in the task summary frontmatter and describe the blocker clearly in the summary narrative. Do NOT set `blocker_discovered: true` for ordinary debugging, minor deviations, or issues that can be fixed within the current task or the remaining plan. This flag triggers an automatic replan of the slice.
56
- 12. If you made an architectural, pattern, library, or observability decision during this task that downstream work should know about, append it to `.gsd/DECISIONS.md` (use the **Decisions** output template from the inlined templates below if the file doesn't exist yet). Not every task produces decisions — only append when a meaningful choice was made.
56
+ 12. If you made an architectural, pattern, library, or observability decision during this task that downstream work should know about, append it to `.gsd/DECISIONS.md` (read the template at `~/.gsd/agent/extensions/gsd/templates/decisions.md` if the file doesn't exist yet). Not every task produces decisions — only append when a meaningful choice was made.
57
57
  13. If you discover a non-obvious rule, recurring gotcha, or useful pattern during execution, append it to `.gsd/KNOWLEDGE.md`. Only add entries that would save future agents from repeating your investigation. Don't add obvious things.
58
- 14. Use the **Task Summary** output template from the inlined templates below
58
+ 14. Read the template at `~/.gsd/agent/extensions/gsd/templates/task-summary.md`
59
59
  15. Write `{{taskSummaryPath}}`
60
60
  16. Mark {{taskId}} done in `{{planPath}}` (change `[ ]` to `[x]`)
61
61
  17. Do not commit manually — the system auto-commits your changes after this unit completes.
@@ -65,6 +65,4 @@ All work stays in your working directory: `{{workingDirectory}}`.
65
65
 
66
66
  **You MUST mark {{taskId}} as `[x]` in `{{planPath}}` AND write `{{taskSummaryPath}}` before finishing.**
67
67
 
68
- {{inlinedTemplates}}
69
-
70
68
  When done, say: "Task {{taskId}} complete."
@@ -0,0 +1,45 @@
1
+ ## Skill Heal Analysis
2
+
3
+ Analyze the just-completed unit ({{unitId}}) for skill drift.
4
+
5
+ ### Steps
6
+
7
+ 1. **Identify loaded skill**: Check which SKILL.md file was read during this unit by examining recent tool calls. If no skill was explicitly loaded (no `read` call to a SKILL.md path), write "No skill loaded — skipping heal analysis" to {{healArtifact}} and stop.
8
+
9
+ 2. **Read the skill**: Load the SKILL.md that was used during this unit.
10
+
11
+ 3. **Compare execution to skill guidance**: Review what the agent actually did vs what the skill recommended. Look for:
12
+ - API patterns the skill recommended that the agent did differently
13
+ - Error handling approaches the skill specified but the agent bypassed
14
+ - Conventions the skill documented that the agent ignored
15
+ - Outdated instructions in the skill that caused errors, retries, or workarounds
16
+ - Commands or tools the skill referenced that no longer exist or have changed
17
+
18
+ 4. **Assess drift severity**:
19
+ - **None**: Agent followed skill correctly → write "No drift detected" to {{healArtifact}} and stop
20
+ - **Minor**: Agent found a better approach but skill isn't wrong → append a note to `.gsd/KNOWLEDGE.md` and stop
21
+ - **Significant**: Skill has outdated or incorrect guidance → continue to step 5
22
+
23
+ 5. **If significant drift found**, append a heal suggestion to `.gsd/skill-review-queue.md`:
24
+
25
+ ```markdown
26
+ ### {{skillName}} (flagged {{date}})
27
+ - **Unit:** {{unitId}}
28
+ - **Issue:** {1-2 sentence description of what was wrong}
29
+ - **Root cause:** {outdated API / incorrect pattern / missing context / etc.}
30
+ - **Discovery method:** {how the agent discovered the skill was wrong — error message, trial and error, docs lookup, etc.}
31
+ - **Proposed fix:**
32
+ - File: {relative path to the file in the skill directory}
33
+ - Section: {section heading or line range}
34
+ - Current: {quote the incorrect/outdated text}
35
+ - Suggested: {the corrected text}
36
+ - **Action:** [ ] Reviewed [ ] Updated [ ] Dismissed
37
+ ```
38
+
39
+ Then write a brief summary of the finding to {{healArtifact}}.
40
+
41
+ **Critical rules:**
42
+ - Do NOT modify any skill files directly. Only write to the review queue.
43
+ - The SkillsBench research (Feb 2026) shows curated skills beat auto-generated ones by +16.2pp. Human review is what makes this valuable.
44
+ - Keep the analysis focused — don't flag stylistic preferences, only genuine errors or outdated content.
45
+ - If multiple issues found, write one entry per issue.
@@ -26,9 +26,13 @@ Narrate your decomposition reasoning — why you're grouping work this way, what
26
26
 
27
27
  **Right-size the plan.** If the slice is simple enough to be 1 task, plan 1 task. Don't split into multiple tasks just because you can identify sub-steps. Don't fill in sections with "None" when the section doesn't apply — omit them entirely. The plan's job is to guide execution, not to fill a template.
28
28
 
29
+ {{executorContextConstraints}}
30
+
29
31
  Then:
30
32
  0. If `REQUIREMENTS.md` was preloaded above, identify which Active requirements the roadmap says this slice owns or supports. These are the requirements this plan must deliver — every owned requirement needs at least one task that directly advances it, and verification must prove the requirement is met.
31
- 1. Use the **Slice Plan** and **Task Plan** output templates from the inlined context above
33
+ 1. Read the templates:
34
+ - `~/.gsd/agent/extensions/gsd/templates/plan.md`
35
+ - `~/.gsd/agent/extensions/gsd/templates/task-plan.md`
32
36
  2. If a `GSD Skill Preferences` block is present in system context, use it to decide which skills to load and follow during planning, without overriding required plan formatting
33
37
  3. Define slice-level verification — the objective stopping condition for this slice:
34
38
  - For non-trivial slices: plan actual test files with real assertions. Name the files.