gsd-pi 2.59.0 → 2.60.0-dev.2580e65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (354) hide show
  1. package/dist/resources/extensions/ask-user-questions.js +7 -4
  2. package/dist/resources/extensions/gsd/auto/phases.js +62 -1
  3. package/dist/resources/extensions/gsd/auto-dashboard.js +21 -8
  4. package/dist/resources/extensions/gsd/auto-dispatch.js +6 -3
  5. package/dist/resources/extensions/gsd/auto-model-selection.js +57 -3
  6. package/dist/resources/extensions/gsd/auto-post-unit.js +43 -3
  7. package/dist/resources/extensions/gsd/auto-prompts.js +49 -20
  8. package/dist/resources/extensions/gsd/auto-recovery.js +37 -18
  9. package/dist/resources/extensions/gsd/auto-start.js +9 -5
  10. package/dist/resources/extensions/gsd/auto-timers.js +11 -5
  11. package/dist/resources/extensions/gsd/auto-unit-closeout.js +5 -3
  12. package/dist/resources/extensions/gsd/auto-verification.js +3 -2
  13. package/dist/resources/extensions/gsd/auto-worktree.js +120 -55
  14. package/dist/resources/extensions/gsd/auto.js +39 -17
  15. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +6 -3
  16. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +72 -2
  17. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +4 -10
  18. package/dist/resources/extensions/gsd/bootstrap/journal-tools.js +2 -1
  19. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +58 -5
  20. package/dist/resources/extensions/gsd/bootstrap/system-context.js +11 -10
  21. package/dist/resources/extensions/gsd/captures.js +54 -1
  22. package/dist/resources/extensions/gsd/commands/catalog.js +2 -0
  23. package/dist/resources/extensions/gsd/commands-codebase.js +48 -21
  24. package/dist/resources/extensions/gsd/commands-inspect.js +2 -1
  25. package/dist/resources/extensions/gsd/commands-maintenance.js +32 -19
  26. package/dist/resources/extensions/gsd/complexity-classifier.js +9 -5
  27. package/dist/resources/extensions/gsd/context-masker.js +68 -0
  28. package/dist/resources/extensions/gsd/custom-verification.js +3 -2
  29. package/dist/resources/extensions/gsd/docs/preferences-reference.md +7 -0
  30. package/dist/resources/extensions/gsd/gsd-db.js +35 -15
  31. package/dist/resources/extensions/gsd/guided-flow.js +19 -9
  32. package/dist/resources/extensions/gsd/init-wizard.js +12 -0
  33. package/dist/resources/extensions/gsd/markdown-renderer.js +11 -9
  34. package/dist/resources/extensions/gsd/md-importer.js +5 -4
  35. package/dist/resources/extensions/gsd/milestone-actions.js +3 -2
  36. package/dist/resources/extensions/gsd/milestone-ids.js +2 -1
  37. package/dist/resources/extensions/gsd/model-router.js +199 -45
  38. package/dist/resources/extensions/gsd/parallel-merge.js +5 -3
  39. package/dist/resources/extensions/gsd/parallel-orchestrator.js +26 -14
  40. package/dist/resources/extensions/gsd/phase-anchor.js +56 -0
  41. package/dist/resources/extensions/gsd/preferences-types.js +2 -0
  42. package/dist/resources/extensions/gsd/preferences-validation.js +91 -0
  43. package/dist/resources/extensions/gsd/preferences.js +15 -3
  44. package/dist/resources/extensions/gsd/prompt-loader.js +3 -2
  45. package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -0
  46. package/dist/resources/extensions/gsd/prompts/rethink.md +7 -0
  47. package/dist/resources/extensions/gsd/prompts/triage-captures.md +6 -1
  48. package/dist/resources/extensions/gsd/rethink.js +5 -2
  49. package/dist/resources/extensions/gsd/rule-registry.js +7 -6
  50. package/dist/resources/extensions/gsd/safe-fs.js +6 -8
  51. package/dist/resources/extensions/gsd/state.js +1 -1
  52. package/dist/resources/extensions/gsd/status-guards.js +4 -3
  53. package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -2
  54. package/dist/resources/extensions/gsd/tools/complete-slice.js +3 -2
  55. package/dist/resources/extensions/gsd/tools/complete-task.js +3 -2
  56. package/dist/resources/extensions/gsd/tools/plan-milestone.js +3 -2
  57. package/dist/resources/extensions/gsd/tools/plan-slice.js +3 -2
  58. package/dist/resources/extensions/gsd/tools/plan-task.js +2 -1
  59. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +4 -4
  60. package/dist/resources/extensions/gsd/tools/reopen-slice.js +2 -1
  61. package/dist/resources/extensions/gsd/tools/reopen-task.js +2 -1
  62. package/dist/resources/extensions/gsd/tools/replan-slice.js +2 -1
  63. package/dist/resources/extensions/gsd/tools/validate-milestone.js +2 -1
  64. package/dist/resources/extensions/gsd/triage-resolution.js +135 -1
  65. package/dist/resources/extensions/gsd/triage-ui.js +12 -3
  66. package/dist/resources/extensions/gsd/workflow-events.js +2 -1
  67. package/dist/resources/extensions/gsd/workflow-logger.js +37 -4
  68. package/dist/resources/extensions/gsd/workflow-migration.js +14 -12
  69. package/dist/resources/extensions/gsd/workflow-projections.js +2 -2
  70. package/dist/resources/extensions/gsd/workflow-reconcile.js +2 -2
  71. package/dist/resources/extensions/gsd/worktree-manager.js +26 -14
  72. package/dist/resources/extensions/shared/interview-ui.js +3 -1
  73. package/dist/resources/skills/btw/SKILL.md +42 -0
  74. package/dist/web/standalone/.next/BUILD_ID +1 -1
  75. package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
  76. package/dist/web/standalone/.next/build-manifest.json +3 -3
  77. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  78. package/dist/web/standalone/.next/required-server-files.json +3 -3
  79. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  80. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  81. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  82. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  90. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  91. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  92. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  93. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  94. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  96. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  106. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  118. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  137. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  143. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  146. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  152. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  155. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  157. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  159. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  161. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  163. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  164. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  166. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  168. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  169. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  170. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  171. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  172. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  173. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  175. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  176. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  177. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  178. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  179. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  180. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  181. package/dist/web/standalone/.next/server/app/index.html +1 -1
  182. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  183. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  184. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  185. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  186. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  187. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  188. package/dist/web/standalone/.next/server/app/page.js +2 -2
  189. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  190. package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
  191. package/dist/web/standalone/.next/server/chunks/2229.js +1 -1
  192. package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
  193. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  194. package/dist/web/standalone/.next/server/middleware.js +2 -2
  195. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  196. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  197. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  198. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  199. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  200. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  201. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  202. package/dist/web/standalone/.next/static/chunks/app/page-0c485498795110d6.js +1 -0
  203. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  204. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  205. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  206. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  207. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  208. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  209. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  210. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  211. package/dist/web/standalone/server.js +1 -1
  212. package/package.json +1 -1
  213. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  214. package/packages/pi-coding-agent/dist/core/extensions/loader.js +5 -0
  215. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  216. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +2 -1
  217. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  218. package/packages/pi-coding-agent/dist/core/extensions/runner.js +16 -0
  219. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  220. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +26 -0
  221. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  222. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  223. package/packages/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -1
  224. package/packages/pi-coding-agent/dist/core/lsp/config.js +6 -1
  225. package/packages/pi-coding-agent/dist/core/lsp/config.js.map +1 -1
  226. package/packages/pi-coding-agent/dist/core/lsp/defaults.json +2 -2
  227. package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.d.ts +2 -0
  228. package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.d.ts.map +1 -0
  229. package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.js +47 -0
  230. package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.js.map +1 -0
  231. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts +1 -0
  232. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  233. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +6 -0
  234. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  235. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.d.ts +2 -0
  236. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.d.ts.map +1 -0
  237. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +122 -0
  238. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -0
  239. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +1 -0
  240. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  241. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +30 -0
  242. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  243. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
  244. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +1 -7
  245. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  246. package/packages/pi-coding-agent/package.json +1 -1
  247. package/packages/pi-coding-agent/src/core/extensions/loader.ts +6 -0
  248. package/packages/pi-coding-agent/src/core/extensions/runner.ts +19 -0
  249. package/packages/pi-coding-agent/src/core/extensions/types.ts +26 -0
  250. package/packages/pi-coding-agent/src/core/lsp/config.ts +7 -1
  251. package/packages/pi-coding-agent/src/core/lsp/defaults.json +2 -2
  252. package/packages/pi-coding-agent/src/core/lsp/lsp-legacy-alias.test.ts +70 -0
  253. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +156 -0
  254. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +7 -0
  255. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +38 -0
  256. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +1 -8
  257. package/pkg/package.json +1 -1
  258. package/src/resources/extensions/ask-user-questions.ts +7 -3
  259. package/src/resources/extensions/gsd/auto/phases.ts +70 -1
  260. package/src/resources/extensions/gsd/auto-dashboard.ts +22 -8
  261. package/src/resources/extensions/gsd/auto-dispatch.ts +7 -3
  262. package/src/resources/extensions/gsd/auto-model-selection.ts +77 -6
  263. package/src/resources/extensions/gsd/auto-post-unit.ts +52 -5
  264. package/src/resources/extensions/gsd/auto-prompts.ts +54 -20
  265. package/src/resources/extensions/gsd/auto-recovery.ts +38 -18
  266. package/src/resources/extensions/gsd/auto-start.ts +10 -9
  267. package/src/resources/extensions/gsd/auto-timers.ts +12 -5
  268. package/src/resources/extensions/gsd/auto-unit-closeout.ts +6 -2
  269. package/src/resources/extensions/gsd/auto-verification.ts +3 -6
  270. package/src/resources/extensions/gsd/auto-worktree.ts +121 -55
  271. package/src/resources/extensions/gsd/auto.ts +40 -17
  272. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +4 -3
  273. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +80 -2
  274. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +4 -16
  275. package/src/resources/extensions/gsd/bootstrap/journal-tools.ts +2 -1
  276. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +61 -4
  277. package/src/resources/extensions/gsd/bootstrap/system-context.ts +11 -10
  278. package/src/resources/extensions/gsd/captures.ts +71 -2
  279. package/src/resources/extensions/gsd/commands/catalog.ts +2 -0
  280. package/src/resources/extensions/gsd/commands-codebase.ts +52 -20
  281. package/src/resources/extensions/gsd/commands-inspect.ts +2 -1
  282. package/src/resources/extensions/gsd/commands-maintenance.ts +28 -19
  283. package/src/resources/extensions/gsd/complexity-classifier.ts +10 -5
  284. package/src/resources/extensions/gsd/context-masker.ts +74 -0
  285. package/src/resources/extensions/gsd/custom-verification.ts +3 -2
  286. package/src/resources/extensions/gsd/docs/preferences-reference.md +7 -0
  287. package/src/resources/extensions/gsd/gsd-db.ts +14 -16
  288. package/src/resources/extensions/gsd/guided-flow.ts +9 -8
  289. package/src/resources/extensions/gsd/init-wizard.ts +12 -0
  290. package/src/resources/extensions/gsd/markdown-renderer.ts +11 -17
  291. package/src/resources/extensions/gsd/md-importer.ts +5 -4
  292. package/src/resources/extensions/gsd/milestone-actions.ts +3 -2
  293. package/src/resources/extensions/gsd/milestone-ids.ts +2 -1
  294. package/src/resources/extensions/gsd/model-router.ts +245 -56
  295. package/src/resources/extensions/gsd/parallel-merge.ts +5 -3
  296. package/src/resources/extensions/gsd/parallel-orchestrator.ts +18 -14
  297. package/src/resources/extensions/gsd/phase-anchor.ts +71 -0
  298. package/src/resources/extensions/gsd/preferences-types.ts +22 -0
  299. package/src/resources/extensions/gsd/preferences-validation.ts +83 -0
  300. package/src/resources/extensions/gsd/preferences.ts +16 -3
  301. package/src/resources/extensions/gsd/prompt-loader.ts +3 -2
  302. package/src/resources/extensions/gsd/prompts/execute-task.md +2 -0
  303. package/src/resources/extensions/gsd/prompts/rethink.md +7 -0
  304. package/src/resources/extensions/gsd/prompts/triage-captures.md +6 -1
  305. package/src/resources/extensions/gsd/rethink.ts +5 -2
  306. package/src/resources/extensions/gsd/rule-registry.ts +7 -6
  307. package/src/resources/extensions/gsd/safe-fs.ts +6 -5
  308. package/src/resources/extensions/gsd/state.ts +1 -1
  309. package/src/resources/extensions/gsd/status-guards.ts +4 -3
  310. package/src/resources/extensions/gsd/tests/capability-router.test.ts +347 -0
  311. package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +63 -0
  312. package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +27 -2
  313. package/src/resources/extensions/gsd/tests/context-masker.test.ts +122 -0
  314. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +4 -4
  315. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +1188 -0
  316. package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +841 -0
  317. package/src/resources/extensions/gsd/tests/model-router.test.ts +488 -2
  318. package/src/resources/extensions/gsd/tests/phase-anchor.test.ts +83 -0
  319. package/src/resources/extensions/gsd/tests/preferences.test.ts +62 -0
  320. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +21 -0
  321. package/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts +284 -0
  322. package/src/resources/extensions/gsd/tests/status-guards.test.ts +4 -0
  323. package/src/resources/extensions/gsd/tests/stop-backtrack.test.ts +216 -0
  324. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +1 -1
  325. package/src/resources/extensions/gsd/tests/workflow-logger-audit.test.ts +120 -0
  326. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +6 -6
  327. package/src/resources/extensions/gsd/tools/complete-milestone.ts +3 -6
  328. package/src/resources/extensions/gsd/tools/complete-slice.ts +3 -6
  329. package/src/resources/extensions/gsd/tools/complete-task.ts +3 -6
  330. package/src/resources/extensions/gsd/tools/plan-milestone.ts +3 -6
  331. package/src/resources/extensions/gsd/tools/plan-slice.ts +3 -6
  332. package/src/resources/extensions/gsd/tools/plan-task.ts +2 -3
  333. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +4 -6
  334. package/src/resources/extensions/gsd/tools/reopen-slice.ts +2 -3
  335. package/src/resources/extensions/gsd/tools/reopen-task.ts +2 -3
  336. package/src/resources/extensions/gsd/tools/replan-slice.ts +2 -3
  337. package/src/resources/extensions/gsd/tools/validate-milestone.ts +2 -3
  338. package/src/resources/extensions/gsd/triage-resolution.ts +151 -1
  339. package/src/resources/extensions/gsd/triage-ui.ts +12 -3
  340. package/src/resources/extensions/gsd/types.ts +1 -0
  341. package/src/resources/extensions/gsd/workflow-events.ts +2 -1
  342. package/src/resources/extensions/gsd/workflow-logger.ts +52 -5
  343. package/src/resources/extensions/gsd/workflow-migration.ts +14 -12
  344. package/src/resources/extensions/gsd/workflow-projections.ts +2 -2
  345. package/src/resources/extensions/gsd/workflow-reconcile.ts +2 -2
  346. package/src/resources/extensions/gsd/worktree-manager.ts +16 -14
  347. package/src/resources/extensions/shared/interview-ui.ts +3 -1
  348. package/src/resources/extensions/shared/tests/interview-notes-loop.test.ts +144 -0
  349. package/src/resources/skills/btw/SKILL.md +42 -0
  350. package/dist/web/standalone/.next/static/chunks/app/page-62be3b5fa91e4c8f.js +0 -1
  351. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  352. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  353. /package/dist/web/standalone/.next/static/{DGvT_c5Vb7Wu3X-fEOVUU → ogyMN7M-3bGGuRY08L5HR}/_buildManifest.js +0 -0
  354. /package/dist/web/standalone/.next/static/{DGvT_c5Vb7Wu3X-fEOVUU → ogyMN7M-3bGGuRY08L5HR}/_ssgManifest.js +0 -0
@@ -8,6 +8,7 @@
8
8
  // Critical invariant: rendered markdown must round-trip through
9
9
  // parseRoadmap(), parsePlan(), parseSummary() in files.ts.
10
10
  import { readFileSync, existsSync, mkdirSync } from "node:fs";
11
+ import { logWarning } from "./workflow-logger.js";
11
12
  import { isClosedStatus } from "./status-guards.js";
12
13
  import { join, relative } from "node:path";
13
14
  import { createRequire } from "node:module";
@@ -56,7 +57,7 @@ function loadArtifactContent(artifactPath, absPath, opts) {
56
57
  content = readFileSync(absPath, "utf-8");
57
58
  }
58
59
  catch {
59
- process.stderr.write(`markdown-renderer: cannot read file from disk: ${absPath}\n`);
60
+ logWarning("renderer", `cannot read file from disk: ${absPath}`);
60
61
  return null;
61
62
  }
62
63
  // Store in DB for future use (graceful degradation path)
@@ -72,7 +73,7 @@ function loadArtifactContent(artifactPath, absPath, opts) {
72
73
  }
73
74
  catch {
74
75
  // Non-fatal: we have the content, DB storage is best-effort
75
- process.stderr.write(`markdown-renderer: warning — failed to store disk fallback in DB: ${artifactPath}\n`);
76
+ logWarning("renderer", `failed to store disk fallback in DB: ${artifactPath}`);
76
77
  }
77
78
  return content;
78
79
  }
@@ -93,7 +94,7 @@ async function writeAndStore(absPath, artifactPath, content, opts) {
93
94
  }
94
95
  catch {
95
96
  // Non-fatal: file is on disk, DB is best-effort
96
- process.stderr.write(`markdown-renderer: warning — failed to update artifact in DB: ${artifactPath}\n`);
97
+ logWarning("renderer", `failed to update artifact in DB: ${artifactPath}`);
97
98
  }
98
99
  invalidateCaches();
99
100
  }
@@ -613,7 +614,8 @@ export function detectStaleRenders(basePath) {
613
614
  parseRoadmap = m.parseRoadmap;
614
615
  parsePlan = m.parsePlan;
615
616
  }
616
- catch {
617
+ catch (e) {
618
+ logWarning("renderer", `parsers-legacy.ts require failed, falling back to .js: ${e.message}`);
617
619
  const m = _require("./parsers-legacy.js");
618
620
  parseRoadmap = m.parseRoadmap;
619
621
  parsePlan = m.parsePlan;
@@ -647,8 +649,8 @@ export function detectStaleRenders(basePath) {
647
649
  }
648
650
  }
649
651
  }
650
- catch {
651
- // Can't parse roadmap — skip silently
652
+ catch (e) {
653
+ logWarning("renderer", `roadmap parse failed: ${e.message}`);
652
654
  }
653
655
  }
654
656
  // ── Check plan checkbox state and summaries for each slice ────────
@@ -679,8 +681,8 @@ export function detectStaleRenders(basePath) {
679
681
  }
680
682
  }
681
683
  }
682
- catch {
683
- // Can't parse plan — skip silently
684
+ catch (e) {
685
+ logWarning("renderer", `plan parse failed: ${e.message}`);
684
686
  }
685
687
  }
686
688
  // Check missing task summary files
@@ -824,7 +826,7 @@ export async function repairStaleRenders(basePath) {
824
826
  }
825
827
  }
826
828
  catch (err) {
827
- process.stderr.write(`markdown-renderer: repair failed for ${entry.path}: ${err.message}\n`);
829
+ logWarning("renderer", `repair failed for ${entry.path}: ${err.message}`);
828
830
  }
829
831
  }
830
832
  if (repairCount > 0) {
@@ -10,6 +10,7 @@ import { resolveGsdRootFile, resolveMilestoneFile, resolveSliceFile, resolveTask
10
10
  import { findMilestoneIds } from './guided-flow.js';
11
11
  import { parseRoadmap, parsePlan } from './parsers-legacy.js';
12
12
  import { parseContextDependsOn } from './files.js';
13
+ import { logWarning } from './workflow-logger.js';
13
14
  // ─── DECISIONS.md Parser ───────────────────────────────────────────────────
14
15
  const VALID_MADE_BY = new Set(['human', 'agent', 'collaborative']);
15
16
  /**
@@ -597,25 +598,25 @@ export function migrateFromMarkdown(gsdDir) {
597
598
  decisions = importDecisions(gsdDir);
598
599
  }
599
600
  catch (err) {
600
- process.stderr.write(`gsd-migrate: skipping decisions import: ${err.message}\n`);
601
+ logWarning("migration", `skipping decisions import: ${err.message}`);
601
602
  }
602
603
  try {
603
604
  requirements = importRequirements(gsdDir);
604
605
  }
605
606
  catch (err) {
606
- process.stderr.write(`gsd-migrate: skipping requirements import: ${err.message}\n`);
607
+ logWarning("migration", `skipping requirements import: ${err.message}`);
607
608
  }
608
609
  try {
609
610
  artifacts = importHierarchyArtifacts(gsdDir);
610
611
  }
611
612
  catch (err) {
612
- process.stderr.write(`gsd-migrate: skipping artifacts import: ${err.message}\n`);
613
+ logWarning("migration", `skipping artifacts import: ${err.message}`);
613
614
  }
614
615
  try {
615
616
  hierarchy = migrateHierarchyToDb(gsdDir);
616
617
  }
617
618
  catch (err) {
618
- process.stderr.write(`gsd-migrate: skipping hierarchy migration: ${err.message}\n`);
619
+ logWarning("migration", `skipping hierarchy migration: ${err.message}`);
619
620
  }
620
621
  });
621
622
  process.stderr.write(`gsd-migrate: imported ${decisions} decisions, ${requirements} requirements, ${artifacts} artifacts, ${hierarchy.milestones}M/${hierarchy.slices}S/${hierarchy.tasks}T hierarchy\n`);
@@ -16,6 +16,7 @@ import { resolveMilestonePath, resolveMilestoneFile, buildMilestoneFileName, } f
16
16
  import { invalidateAllCaches } from "./cache.js";
17
17
  import { loadQueueOrder, saveQueueOrder } from "./queue-order.js";
18
18
  import { isDbAvailable, updateMilestoneStatus } from "./gsd-db.js";
19
+ import { logWarning } from "./workflow-logger.js";
19
20
  // ─── Park ──────────────────────────────────────────────────────────────────
20
21
  /**
21
22
  * Park a milestone — creates a PARKED.md marker file with reason and timestamp.
@@ -51,7 +52,7 @@ export function parkMilestone(basePath, milestoneId, reason) {
51
52
  updateMilestoneStatus(milestoneId, "parked");
52
53
  }
53
54
  catch (err) {
54
- process.stderr.write(`gsd: parkMilestone DB sync failed for ${milestoneId}: ${err.message}\n`);
55
+ logWarning("engine", `parkMilestone DB sync failed for ${milestoneId}: ${err.message}`);
55
56
  }
56
57
  }
57
58
  invalidateAllCaches();
@@ -76,7 +77,7 @@ export function unparkMilestone(basePath, milestoneId) {
76
77
  updateMilestoneStatus(milestoneId, "active");
77
78
  }
78
79
  catch (err) {
79
- process.stderr.write(`gsd: unparkMilestone DB sync failed for ${milestoneId}: ${err.message}\n`);
80
+ logWarning("engine", `unparkMilestone DB sync failed for ${milestoneId}: ${err.message}`);
80
81
  }
81
82
  }
82
83
  invalidateAllCaches();
@@ -5,6 +5,7 @@
5
5
  * Consumed by 15+ modules across the GSD extension. Zero side-effects.
6
6
  */
7
7
  import { randomInt } from "node:crypto";
8
+ import { logWarning } from "./workflow-logger.js";
8
9
  import { readdirSync, existsSync } from "node:fs";
9
10
  import { milestonesDir } from "./paths.js";
10
11
  import { loadQueueOrder, sortByQueueOrder } from "./queue-order.js";
@@ -109,7 +110,7 @@ export function findMilestoneIds(basePath) {
109
110
  catch (err) {
110
111
  // Log why milestone scanning failed — silent [] here causes infinite loops (#456)
111
112
  if (existsSync(dir)) {
112
- console.error(`[gsd] findMilestoneIds: .gsd/milestones/ exists but readdirSync failed — ${getErrorMessage(err)}`);
113
+ logWarning("engine", `findMilestoneIds: .gsd/milestones/ exists but readdirSync failed — ${getErrorMessage(err)}`);
113
114
  }
114
115
  return [];
115
116
  }
@@ -76,19 +76,177 @@ const MODEL_COST_PER_1K_INPUT = {
76
76
  "gemini-2.5-pro": 0.00125,
77
77
  "deepseek-chat": 0.00014,
78
78
  };
79
+ // ─── Capability Profiles Data Table ──────────────────────────────────────────
80
+ // Per-model capability profiles (0–100 scale). Used for capability-aware
81
+ // model selection within an eligible tier set.
82
+ export const MODEL_CAPABILITY_PROFILES = {
83
+ "claude-opus-4-6": { coding: 95, debugging: 90, research: 85, reasoning: 95, speed: 30, longContext: 80, instruction: 90 },
84
+ "claude-sonnet-4-6": { coding: 85, debugging: 80, research: 75, reasoning: 80, speed: 60, longContext: 75, instruction: 85 },
85
+ "claude-haiku-4-5": { coding: 60, debugging: 50, research: 45, reasoning: 50, speed: 95, longContext: 50, instruction: 75 },
86
+ "gpt-4o": { coding: 80, debugging: 75, research: 70, reasoning: 75, speed: 65, longContext: 70, instruction: 80 },
87
+ "gpt-4o-mini": { coding: 55, debugging: 45, research: 40, reasoning: 45, speed: 90, longContext: 45, instruction: 70 },
88
+ "gemini-2.5-pro": { coding: 75, debugging: 70, research: 85, reasoning: 75, speed: 55, longContext: 90, instruction: 75 },
89
+ "gemini-2.0-flash": { coding: 50, debugging: 40, research: 50, reasoning: 40, speed: 95, longContext: 60, instruction: 65 },
90
+ "deepseek-chat": { coding: 75, debugging: 65, research: 55, reasoning: 70, speed: 70, longContext: 55, instruction: 65 },
91
+ "o3": { coding: 80, debugging: 85, research: 80, reasoning: 92, speed: 25, longContext: 70, instruction: 85 },
92
+ };
93
+ // ─── Base Task Requirements Data Table ───────────────────────────────────────
94
+ // Per-unit-type base requirement vectors. Weights indicate how important each
95
+ // capability dimension is for this unit type.
96
+ export const BASE_REQUIREMENTS = {
97
+ "execute-task": { coding: 0.9, instruction: 0.7, speed: 0.3 },
98
+ "research-milestone": { research: 0.9, longContext: 0.7, reasoning: 0.5 },
99
+ "research-slice": { research: 0.9, longContext: 0.7, reasoning: 0.5 },
100
+ "plan-milestone": { reasoning: 0.9, coding: 0.5 },
101
+ "plan-slice": { reasoning: 0.9, coding: 0.5 },
102
+ "replan-slice": { reasoning: 0.9, debugging: 0.6, coding: 0.5 },
103
+ "reassess-roadmap": { reasoning: 0.9, research: 0.5 },
104
+ "complete-slice": { instruction: 0.8, speed: 0.7 },
105
+ "run-uat": { instruction: 0.7, speed: 0.8 },
106
+ "discuss-milestone": { reasoning: 0.6, instruction: 0.7 },
107
+ "complete-milestone": { instruction: 0.8, reasoning: 0.5 },
108
+ };
79
109
  // ─── Public API ──────────────────────────────────────────────────────────────
110
+ /**
111
+ * Score a model's suitability for a task given a requirement vector.
112
+ * Returns a weighted average of capability dimensions (0–100).
113
+ * Returns 50 if requirements are empty (neutral score).
114
+ */
115
+ export function scoreModel(model, requirements) {
116
+ let weightedSum = 0;
117
+ let weightSum = 0;
118
+ for (const [dim, weight] of Object.entries(requirements)) {
119
+ const capability = model[dim] ?? 50;
120
+ weightedSum += weight * capability;
121
+ weightSum += weight;
122
+ }
123
+ return weightSum > 0 ? weightedSum / weightSum : 50;
124
+ }
125
+ /**
126
+ * Compute dynamic task requirements from unit type and optional task metadata.
127
+ * Returns a requirement vector refined by task-specific signals.
128
+ */
129
+ export function computeTaskRequirements(unitType, metadata) {
130
+ const base = BASE_REQUIREMENTS[unitType] ?? { reasoning: 0.5 };
131
+ if (unitType === "execute-task" && metadata) {
132
+ if (metadata.tags?.some(t => /^(docs?|readme|comment|config|typo|rename)$/i.test(t))) {
133
+ return { ...base, instruction: 0.9, coding: 0.3, speed: 0.7 };
134
+ }
135
+ if (metadata.complexityKeywords?.some(k => k === "concurrency" || k === "compatibility")) {
136
+ return { ...base, debugging: 0.9, reasoning: 0.8 };
137
+ }
138
+ if (metadata.complexityKeywords?.some(k => k === "migration" || k === "architecture")) {
139
+ return { ...base, reasoning: 0.9, coding: 0.8 };
140
+ }
141
+ if ((metadata.fileCount ?? 0) >= 6 || (metadata.estimatedLines ?? 0) >= 500) {
142
+ return { ...base, coding: 0.9, reasoning: 0.7 };
143
+ }
144
+ }
145
+ return base;
146
+ }
147
+ /**
148
+ * Score all eligible models against a requirement vector and return them
149
+ * sorted by score descending. Within 2 points: prefer cheaper; equal cost:
150
+ * lexicographic tie-break by model ID.
151
+ */
152
+ export function scoreEligibleModels(eligibleModelIds, requirements, capabilityOverrides) {
153
+ const scored = eligibleModelIds.map(modelId => {
154
+ const builtin = MODEL_CAPABILITY_PROFILES[modelId];
155
+ const override = capabilityOverrides?.[modelId];
156
+ const profile = builtin
157
+ ? override ? { ...builtin, ...override } : builtin
158
+ : { coding: 50, debugging: 50, research: 50, reasoning: 50, speed: 50, longContext: 50, instruction: 50 };
159
+ return { modelId, score: scoreModel(profile, requirements) };
160
+ });
161
+ scored.sort((a, b) => {
162
+ const scoreDiff = b.score - a.score;
163
+ if (Math.abs(scoreDiff) > 2)
164
+ return scoreDiff;
165
+ const costA = MODEL_COST_PER_1K_INPUT[a.modelId] ?? Infinity;
166
+ const costB = MODEL_COST_PER_1K_INPUT[b.modelId] ?? Infinity;
167
+ if (costA !== costB)
168
+ return costA - costB;
169
+ return a.modelId.localeCompare(b.modelId);
170
+ });
171
+ return scored;
172
+ }
173
+ /**
174
+ * Return all models eligible for a given tier, sorted cheapest first.
175
+ * If routingConfig.tier_models[tier] is set and available, returns only that
176
+ * model. Otherwise filters availableModelIds by tier from MODEL_CAPABILITY_TIER.
177
+ */
178
+ export function getEligibleModels(tier, availableModelIds, routingConfig) {
179
+ // 1. Check explicit tier_models config
180
+ const explicitModel = routingConfig.tier_models?.[tier];
181
+ if (explicitModel) {
182
+ // Exact match
183
+ if (availableModelIds.includes(explicitModel))
184
+ return [explicitModel];
185
+ // Provider-prefix-stripped match
186
+ const match = availableModelIds.find(id => {
187
+ const bareAvail = id.includes("/") ? id.split("/").pop() : id;
188
+ const bareExplicit = explicitModel.includes("/") ? explicitModel.split("/").pop() : explicitModel;
189
+ return bareAvail === bareExplicit;
190
+ });
191
+ if (match)
192
+ return [match];
193
+ }
194
+ // 2. Auto-detect: filter by tier, sort cheapest first
195
+ return availableModelIds
196
+ .filter(id => getModelTier(id) === tier)
197
+ .sort((a, b) => {
198
+ const costA = getModelCost(a);
199
+ const costB = getModelCost(b);
200
+ return costA - costB;
201
+ });
202
+ }
203
+ /**
204
+ * Build a fallback chain for a selected model: [selectedModel, ...configuredFallbacks, configuredPrimary]
205
+ * Deduplicates entries while preserving order.
206
+ */
207
+ function buildFallbackChain(selectedModelId, phaseConfig) {
208
+ return [
209
+ ...phaseConfig.fallbacks.filter(f => f !== selectedModelId),
210
+ phaseConfig.primary,
211
+ ].filter(f => f !== selectedModelId);
212
+ }
213
+ /**
214
+ * Load capability overrides from user preferences' modelOverrides section.
215
+ * Returns a map of model ID → partial capability overrides to deep-merge with built-in profiles.
216
+ *
217
+ * Per D-17: partial capability overrides via models.json modelOverrides, deep-merged with defaults.
218
+ */
219
+ export function loadCapabilityOverrides(prefs) {
220
+ const result = {};
221
+ if (!prefs.modelOverrides)
222
+ return result;
223
+ for (const [modelId, overrideEntry] of Object.entries(prefs.modelOverrides)) {
224
+ if (overrideEntry.capabilities) {
225
+ result[modelId] = overrideEntry.capabilities;
226
+ }
227
+ }
228
+ return result;
229
+ }
80
230
  /**
81
231
  * Resolve the model to use for a given complexity tier.
82
232
  *
83
233
  * Downgrade-only: the returned model is always equal to or cheaper than
84
234
  * the user's configured primary model. Never upgrades beyond configuration.
85
235
  *
86
- * @param classification The complexity classification result
87
- * @param phaseConfig The user's configured model for this phase (ceiling)
88
- * @param routingConfig Dynamic routing configuration
89
- * @param availableModelIds List of available model IDs (from registry)
236
+ * STEP 1: Filter to eligible models for the requested tier.
237
+ * STEP 2: Capability scoring ranks eligible models by task-capability match
238
+ * when capability_routing is enabled and multiple eligible models exist.
239
+ * STEP 3: Fallback chain assembly.
240
+ *
241
+ * @param classification The complexity classification result
242
+ * @param phaseConfig The user's configured model for this phase (ceiling)
243
+ * @param routingConfig Dynamic routing configuration
244
+ * @param availableModelIds List of available model IDs (from registry)
245
+ * @param unitType The unit type for capability requirement computation (optional)
246
+ * @param taskMetadata Task metadata for refined requirement vectors (optional)
247
+ * @param capabilityOverrides User-provided capability overrides (deep-merged with built-in profiles, optional)
90
248
  */
91
- export function resolveModelForComplexity(classification, phaseConfig, routingConfig, availableModelIds) {
249
+ export function resolveModelForComplexity(classification, phaseConfig, routingConfig, availableModelIds, unitType, taskMetadata, capabilityOverrides) {
92
250
  // If no phase config or routing disabled, pass through
93
251
  if (!phaseConfig || !routingConfig.enabled) {
94
252
  return {
@@ -97,6 +255,7 @@ export function resolveModelForComplexity(classification, phaseConfig, routingCo
97
255
  tier: classification.tier,
98
256
  wasDowngraded: false,
99
257
  reason: "dynamic routing disabled or no phase config",
258
+ selectionMethod: "tier-only",
100
259
  };
101
260
  }
102
261
  const configuredPrimary = phaseConfig.primary;
@@ -114,6 +273,7 @@ export function resolveModelForComplexity(classification, phaseConfig, routingCo
114
273
  tier: requestedTier,
115
274
  wasDowngraded: false,
116
275
  reason: `configured model "${configuredPrimary}" is not in the known tier map — honoring explicit config`,
276
+ selectionMethod: "tier-only",
117
277
  };
118
278
  }
119
279
  // Downgrade-only: if requested tier >= configured tier, no change
@@ -124,11 +284,12 @@ export function resolveModelForComplexity(classification, phaseConfig, routingCo
124
284
  tier: requestedTier,
125
285
  wasDowngraded: false,
126
286
  reason: `tier ${requestedTier} >= configured ${configuredTier}`,
287
+ selectionMethod: "tier-only",
127
288
  };
128
289
  }
129
- // Find the best model for the requested tier
130
- const targetModelId = findModelForTier(requestedTier, routingConfig, availableModelIds, routingConfig.cross_provider !== false);
131
- if (!targetModelId) {
290
+ // STEP 1: Get all eligible models for the requested tier
291
+ const eligible = getEligibleModels(requestedTier, availableModelIds, routingConfig);
292
+ if (eligible.length === 0) {
132
293
  // No suitable model found — use configured primary
133
294
  return {
134
295
  modelId: configuredPrimary,
@@ -136,19 +297,42 @@ export function resolveModelForComplexity(classification, phaseConfig, routingCo
136
297
  tier: requestedTier,
137
298
  wasDowngraded: false,
138
299
  reason: `no ${requestedTier}-tier model available`,
300
+ selectionMethod: "tier-only",
139
301
  };
140
302
  }
303
+ // STEP 2: Capability scoring (when enabled and multiple eligible models exist)
304
+ if (routingConfig.capability_routing !== false && eligible.length > 1 && unitType) {
305
+ const requirements = computeTaskRequirements(unitType, taskMetadata);
306
+ const scored = scoreEligibleModels(eligible, requirements, capabilityOverrides);
307
+ const winner = scored[0];
308
+ if (winner) {
309
+ const capScores = {};
310
+ for (const s of scored)
311
+ capScores[s.modelId] = s.score;
312
+ const fallbacks = buildFallbackChain(winner.modelId, phaseConfig);
313
+ return {
314
+ modelId: winner.modelId,
315
+ fallbacks,
316
+ tier: requestedTier,
317
+ wasDowngraded: true,
318
+ reason: `capability-scored: ${winner.modelId} (${winner.score.toFixed(1)}) for ${unitType}`,
319
+ capabilityScores: capScores,
320
+ taskRequirements: requirements,
321
+ selectionMethod: "capability-scored",
322
+ };
323
+ }
324
+ }
325
+ // STEP 3: Fallback — use first eligible model (cheapest in tier, or single eligible)
326
+ const targetModelId = eligible[0];
141
327
  // Build fallback chain: [downgraded_model, ...configured_fallbacks, configured_primary]
142
- const fallbacks = [
143
- ...phaseConfig.fallbacks.filter(f => f !== targetModelId),
144
- configuredPrimary,
145
- ].filter(f => f !== targetModelId);
328
+ const fallbacks = buildFallbackChain(targetModelId, phaseConfig);
146
329
  return {
147
330
  modelId: targetModelId,
148
331
  fallbacks,
149
332
  tier: requestedTier,
150
333
  wasDowngraded: true,
151
334
  reason: classification.reason,
335
+ selectionMethod: "tier-only",
152
336
  };
153
337
  }
154
338
  /**
@@ -168,6 +352,7 @@ export function escalateTier(currentTier) {
168
352
  export function defaultRoutingConfig() {
169
353
  return {
170
354
  enabled: true,
355
+ capability_routing: true,
171
356
  escalate_on_failure: true,
172
357
  budget_pressure: true,
173
358
  cross_provider: true,
@@ -186,8 +371,8 @@ function getModelTier(modelId) {
186
371
  if (bareId.includes(knownId) || knownId.includes(bareId))
187
372
  return tier;
188
373
  }
189
- // Unknown models are assumed heavy (safest assumption)
190
- return "heavy";
374
+ // Unknown models are assumed standard (per D-15: avoids silently ignoring user config)
375
+ return "standard";
191
376
  }
192
377
  /** Check if a model ID has a known capability tier mapping. (#2192) */
193
378
  function isKnownModel(modelId) {
@@ -200,37 +385,6 @@ function isKnownModel(modelId) {
200
385
  }
201
386
  return false;
202
387
  }
203
- function findModelForTier(tier, config, availableModelIds, crossProvider) {
204
- // 1. Check explicit tier_models config
205
- const explicitModel = config.tier_models?.[tier];
206
- if (explicitModel && availableModelIds.includes(explicitModel)) {
207
- return explicitModel;
208
- }
209
- // Also check with provider prefix stripped
210
- if (explicitModel) {
211
- const match = availableModelIds.find(id => {
212
- const bareAvail = id.includes("/") ? id.split("/").pop() : id;
213
- const bareExplicit = explicitModel.includes("/") ? explicitModel.split("/").pop() : explicitModel;
214
- return bareAvail === bareExplicit;
215
- });
216
- if (match)
217
- return match;
218
- }
219
- // 2. Auto-detect: find the cheapest available model in the requested tier
220
- const candidates = availableModelIds
221
- .filter(id => {
222
- const modelTier = getModelTier(id);
223
- return modelTier === tier;
224
- })
225
- .sort((a, b) => {
226
- if (!crossProvider)
227
- return 0;
228
- const costA = getModelCost(a);
229
- const costB = getModelCost(b);
230
- return costA - costB;
231
- });
232
- return candidates[0] ?? null;
233
- }
234
388
  function getModelCost(modelId) {
235
389
  const bareId = modelId.includes("/") ? modelId.split("/").pop() : modelId;
236
390
  if (MODEL_COST_PER_1K_INPUT[bareId] !== undefined) {
@@ -13,6 +13,7 @@ import { mergeMilestoneToMain } from "./auto-worktree.js";
13
13
  import { MergeConflictError } from "./git-service.js";
14
14
  import { removeSessionStatus } from "./session-status-io.js";
15
15
  import { getErrorMessage } from "./error-utils.js";
16
+ import { logWarning } from "./workflow-logger.js";
16
17
  // ─── Merge Queue ───────────────────────────────────────────────────────────
17
18
  /**
18
19
  * Check whether a milestone is complete by querying its worktree SQLite DB.
@@ -27,7 +28,8 @@ export function isMilestoneCompleteInWorktreeDb(basePath, mid) {
27
28
  const result = spawnSync("sqlite3", [dbPath, `SELECT status FROM milestones WHERE id='${mid}' LIMIT 1`], { timeout: 3000, encoding: "utf-8" });
28
29
  return (result.stdout || "").trim() === "complete";
29
30
  }
30
- catch {
31
+ catch (e) {
32
+ logWarning("parallel", `spawnSync milestone completion check failed for ${mid}: ${e.message}`);
31
33
  return false;
32
34
  }
33
35
  }
@@ -45,8 +47,8 @@ function discoverDbCompletedMilestones(basePath) {
45
47
  }
46
48
  }
47
49
  }
48
- catch {
49
- // worktrees dir may not exist
50
+ catch (e) {
51
+ logWarning("parallel", `readdirSync for completed set failed: ${e.message}`);
50
52
  }
51
53
  return completed;
52
54
  }
@@ -19,6 +19,7 @@ import { resolveParallelConfig } from "./preferences.js";
19
19
  import { writeSessionStatus, readAllSessionStatuses, readSessionStatus, removeSessionStatus, sendSignal, cleanupStaleSessions, } from "./session-status-io.js";
20
20
  import { analyzeParallelEligibility, } from "./parallel-eligibility.js";
21
21
  import { getErrorMessage } from "./error-utils.js";
22
+ import { logWarning } from "./workflow-logger.js";
22
23
  // ─── Module State ──────────────────────────────────────────────────────────
23
24
  let state = null;
24
25
  // ─── Persistence ──────────────────────────────────────────────────────────
@@ -61,7 +62,9 @@ export function persistState(basePath) {
61
62
  writeFileSync(tmp, JSON.stringify(persisted, null, 2), "utf-8");
62
63
  renameSync(tmp, dest);
63
64
  }
64
- catch { /* non-fatal */ }
65
+ catch (e) {
66
+ logWarning("parallel", `persist parallel state failed: ${e.message}`);
67
+ }
65
68
  }
66
69
  /**
67
70
  * Remove the persisted state file.
@@ -72,7 +75,9 @@ function removeStateFile(basePath) {
72
75
  if (existsSync(p))
73
76
  unlinkSync(p);
74
77
  }
75
- catch { /* non-fatal */ }
78
+ catch (e) {
79
+ logWarning("parallel", `clear parallel state file failed: ${e.message}`);
80
+ }
76
81
  }
77
82
  function isPidAlive(pid) {
78
83
  if (!Number.isInteger(pid) || pid <= 0)
@@ -81,7 +86,8 @@ function isPidAlive(pid) {
81
86
  process.kill(pid, 0);
82
87
  return true;
83
88
  }
84
- catch {
89
+ catch (e) {
90
+ logWarning("parallel", `pid alive check failed for pid ${pid}: ${e.message}`);
85
91
  return false;
86
92
  }
87
93
  }
@@ -112,7 +118,8 @@ export function restoreState(basePath) {
112
118
  }
113
119
  return persisted;
114
120
  }
115
- catch {
121
+ catch (e) {
122
+ logWarning("parallel", `readParallelState JSON parse failed: ${e.message}`);
116
123
  return null;
117
124
  }
118
125
  }
@@ -126,8 +133,8 @@ function appendWorkerLog(basePath, milestoneId, chunk) {
126
133
  mkdirSync(dir, { recursive: true });
127
134
  appendFileSync(workerLogPath(basePath, milestoneId), chunk, "utf-8");
128
135
  }
129
- catch {
130
- // Non-fatal diagnostics should never break orchestration.
136
+ catch (e) {
137
+ logWarning("parallel", `appendFileSync worker log failed for ${milestoneId}: ${e.message}`);
131
138
  }
132
139
  }
133
140
  function restoreRuntimeState(basePath) {
@@ -331,9 +338,8 @@ export async function startParallel(basePath, milestoneIds, prefs) {
331
338
  try {
332
339
  wtPath = createMilestoneWorktree(basePath, mid);
333
340
  }
334
- catch {
335
- // Worktree creation may fail in test environments or when git
336
- // is not available. Fall back to a placeholder path.
341
+ catch (e) {
342
+ logWarning("parallel", `createMilestoneWorktree fallback for ${mid}: ${e.message}`);
337
343
  wtPath = worktreePath(basePath, mid);
338
344
  }
339
345
  const worker = {
@@ -451,7 +457,8 @@ export function spawnWorker(basePath, milestoneId) {
451
457
  detached: false,
452
458
  });
453
459
  }
454
- catch {
460
+ catch (e) {
461
+ logWarning("parallel", `spawnSync worker failed for ${milestoneId}: ${e.message}`);
455
462
  return false;
456
463
  }
457
464
  // Handle spawn errors (e.g., ENOENT when binary doesn't exist)
@@ -572,7 +579,8 @@ function resolveGsdBin() {
572
579
  try {
573
580
  thisDir = dirname(fileURLToPath(import.meta.url));
574
581
  }
575
- catch {
582
+ catch (e) {
583
+ logWarning("parallel", `dirname(fileURLToPath) failed: ${e.message}`);
576
584
  thisDir = process.cwd();
577
585
  }
578
586
  const candidates = [
@@ -599,7 +607,7 @@ function processWorkerLine(basePath, milestoneId, line) {
599
607
  event = JSON.parse(line);
600
608
  }
601
609
  catch {
602
- return; // Not valid JSON — skip (stderr leakage, debug output, etc.)
610
+ return; // Non-NDJSON lines (progress text, tool output) are expected — silent drop
603
611
  }
604
612
  const type = String(event.type ?? "");
605
613
  // message_end carries usage data with cost
@@ -684,7 +692,9 @@ export async function stopParallel(basePath, milestoneId) {
684
692
  process.kill(worker.pid, "SIGTERM");
685
693
  }
686
694
  }
687
- catch { /* process may already be dead */ }
695
+ catch (e) {
696
+ logWarning("parallel", `process.kill SIGTERM failed for pid ${worker.pid}: ${e.message}`);
697
+ }
688
698
  }
689
699
  // Wait for the headless process to cascade SIGTERM to its RPC child.
690
700
  // The headless signal handler calls client.stop() which sends SIGTERM
@@ -701,7 +711,9 @@ export async function stopParallel(basePath, milestoneId) {
701
711
  process.kill(worker.pid, "SIGKILL");
702
712
  }
703
713
  }
704
- catch { /* process may already be dead */ }
714
+ catch (e) {
715
+ logWarning("parallel", `process.kill SIGKILL failed for pid ${worker.pid}: ${e.message}`);
716
+ }
705
717
  await waitForWorkerExit(worker, 250);
706
718
  }
707
719
  // Remove stream listeners before releasing the process handle