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
@@ -9,6 +9,7 @@
9
9
  // parseRoadmap(), parsePlan(), parseSummary() in files.ts.
10
10
 
11
11
  import { readFileSync, existsSync, mkdirSync } from "node:fs";
12
+ import { logWarning } from "./workflow-logger.js";
12
13
  import { isClosedStatus } from "./status-guards.js";
13
14
  import { join, relative } from "node:path";
14
15
  import { createRequire } from "node:module";
@@ -93,9 +94,7 @@ function loadArtifactContent(
93
94
  try {
94
95
  content = readFileSync(absPath, "utf-8");
95
96
  } catch {
96
- process.stderr.write(
97
- `markdown-renderer: cannot read file from disk: ${absPath}\n`,
98
- );
97
+ logWarning("renderer", `cannot read file from disk: ${absPath}`);
99
98
  return null;
100
99
  }
101
100
 
@@ -111,9 +110,7 @@ function loadArtifactContent(
111
110
  });
112
111
  } catch {
113
112
  // Non-fatal: we have the content, DB storage is best-effort
114
- process.stderr.write(
115
- `markdown-renderer: warning — failed to store disk fallback in DB: ${artifactPath}\n`,
116
- );
113
+ logWarning("renderer", `failed to store disk fallback in DB: ${artifactPath}`);
117
114
  }
118
115
 
119
116
  return content;
@@ -146,9 +143,7 @@ async function writeAndStore(
146
143
  });
147
144
  } catch {
148
145
  // Non-fatal: file is on disk, DB is best-effort
149
- process.stderr.write(
150
- `markdown-renderer: warning — failed to update artifact in DB: ${artifactPath}\n`,
151
- );
146
+ logWarning("renderer", `failed to update artifact in DB: ${artifactPath}`);
152
147
  }
153
148
 
154
149
  invalidateCaches();
@@ -806,7 +801,8 @@ export function detectStaleRenders(basePath: string): StaleEntry[] {
806
801
  try {
807
802
  const m = _require("./parsers-legacy.ts");
808
803
  parseRoadmap = m.parseRoadmap; parsePlan = m.parsePlan;
809
- } catch {
804
+ } catch (e) {
805
+ logWarning("renderer", `parsers-legacy.ts require failed, falling back to .js: ${(e as Error).message}`);
810
806
  const m = _require("./parsers-legacy.js");
811
807
  parseRoadmap = m.parseRoadmap; parsePlan = m.parsePlan;
812
808
  }
@@ -841,8 +837,8 @@ export function detectStaleRenders(basePath: string): StaleEntry[] {
841
837
  });
842
838
  }
843
839
  }
844
- } catch {
845
- // Can't parse roadmap skip silently
840
+ } catch (e) {
841
+ logWarning("renderer", `roadmap parse failed: ${(e as Error).message}`);
846
842
  }
847
843
  }
848
844
 
@@ -874,8 +870,8 @@ export function detectStaleRenders(basePath: string): StaleEntry[] {
874
870
  });
875
871
  }
876
872
  }
877
- } catch {
878
- // Can't parse plan skip silently
873
+ } catch (e) {
874
+ logWarning("renderer", `plan parse failed: ${(e as Error).message}`);
879
875
  }
880
876
  }
881
877
 
@@ -1025,9 +1021,7 @@ export async function repairStaleRenders(basePath: string): Promise<number> {
1025
1021
  }
1026
1022
  }
1027
1023
  } catch (err) {
1028
- process.stderr.write(
1029
- `markdown-renderer: repair failed for ${entry.path}: ${(err as Error).message}\n`,
1030
- );
1024
+ logWarning("renderer", `repair failed for ${entry.path}: ${(err as Error).message}`);
1031
1025
  }
1032
1026
  }
1033
1027
 
@@ -31,6 +31,7 @@ import {
31
31
  import { findMilestoneIds } from './guided-flow.js';
32
32
  import { parseRoadmap, parsePlan } from './parsers-legacy.js';
33
33
  import { parseContextDependsOn } from './files.js';
34
+ import { logWarning } from './workflow-logger.js';
34
35
 
35
36
  // ─── DECISIONS.md Parser ───────────────────────────────────────────────────
36
37
 
@@ -712,25 +713,25 @@ export function migrateFromMarkdown(gsdDir: string): {
712
713
  try {
713
714
  decisions = importDecisions(gsdDir);
714
715
  } catch (err) {
715
- process.stderr.write(`gsd-migrate: skipping decisions import: ${(err as Error).message}\n`);
716
+ logWarning("migration", `skipping decisions import: ${(err as Error).message}`);
716
717
  }
717
718
 
718
719
  try {
719
720
  requirements = importRequirements(gsdDir);
720
721
  } catch (err) {
721
- process.stderr.write(`gsd-migrate: skipping requirements import: ${(err as Error).message}\n`);
722
+ logWarning("migration", `skipping requirements import: ${(err as Error).message}`);
722
723
  }
723
724
 
724
725
  try {
725
726
  artifacts = importHierarchyArtifacts(gsdDir);
726
727
  } catch (err) {
727
- process.stderr.write(`gsd-migrate: skipping artifacts import: ${(err as Error).message}\n`);
728
+ logWarning("migration", `skipping artifacts import: ${(err as Error).message}`);
728
729
  }
729
730
 
730
731
  try {
731
732
  hierarchy = migrateHierarchyToDb(gsdDir);
732
733
  } catch (err) {
733
- process.stderr.write(`gsd-migrate: skipping hierarchy migration: ${(err as Error).message}\n`);
734
+ logWarning("migration", `skipping hierarchy migration: ${(err as Error).message}`);
734
735
  }
735
736
  });
736
737
 
@@ -21,6 +21,7 @@ import {
21
21
  import { invalidateAllCaches } from "./cache.js";
22
22
  import { loadQueueOrder, saveQueueOrder } from "./queue-order.js";
23
23
  import { isDbAvailable, updateMilestoneStatus } from "./gsd-db.js";
24
+ import { logWarning } from "./workflow-logger.js";
24
25
 
25
26
  // ─── Park ──────────────────────────────────────────────────────────────────
26
27
 
@@ -58,7 +59,7 @@ export function parkMilestone(basePath: string, milestoneId: string, reason: str
58
59
  try {
59
60
  updateMilestoneStatus(milestoneId, "parked");
60
61
  } catch (err) {
61
- process.stderr.write(`gsd: parkMilestone DB sync failed for ${milestoneId}: ${(err as Error).message}\n`);
62
+ logWarning("engine", `parkMilestone DB sync failed for ${milestoneId}: ${(err as Error).message}`);
62
63
  }
63
64
  }
64
65
  invalidateAllCaches();
@@ -84,7 +85,7 @@ export function unparkMilestone(basePath: string, milestoneId: string): boolean
84
85
  try {
85
86
  updateMilestoneStatus(milestoneId, "active");
86
87
  } catch (err) {
87
- process.stderr.write(`gsd: unparkMilestone DB sync failed for ${milestoneId}: ${(err as Error).message}\n`);
88
+ logWarning("engine", `unparkMilestone DB sync failed for ${milestoneId}: ${(err as Error).message}`);
88
89
  }
89
90
  }
90
91
  invalidateAllCaches();
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import { randomInt } from "node:crypto";
9
+ import { logWarning } from "./workflow-logger.js";
9
10
  import { readdirSync, existsSync } from "node:fs";
10
11
  import { milestonesDir } from "./paths.js";
11
12
  import { loadQueueOrder, sortByQueueOrder } from "./queue-order.js";
@@ -128,7 +129,7 @@ export function findMilestoneIds(basePath: string): string[] {
128
129
  } catch (err) {
129
130
  // Log why milestone scanning failed — silent [] here causes infinite loops (#456)
130
131
  if (existsSync(dir)) {
131
- console.error(`[gsd] findMilestoneIds: .gsd/milestones/ exists but readdirSync failed — ${getErrorMessage(err)}`);
132
+ logWarning("engine", `findMilestoneIds: .gsd/milestones/ exists but readdirSync failed — ${getErrorMessage(err)}`);
132
133
  }
133
134
  return [];
134
135
  }
@@ -2,7 +2,7 @@
2
2
  // Maps complexity tiers to models, enforcing downgrade-only semantics.
3
3
  // The user's configured model is always the ceiling.
4
4
 
5
- import type { ComplexityTier, ClassificationResult } from "./complexity-classifier.js";
5
+ import type { ComplexityTier, ClassificationResult, TaskMetadata } from "./complexity-classifier.js";
6
6
  import { tierOrdinal } from "./complexity-classifier.js";
7
7
  import type { ResolvedModelConfig } from "./preferences.js";
8
8
 
@@ -10,6 +10,7 @@ import type { ResolvedModelConfig } from "./preferences.js";
10
10
 
11
11
  export interface DynamicRoutingConfig {
12
12
  enabled?: boolean;
13
+ capability_routing?: boolean; // default: false — enable capability profile scoring
13
14
  tier_models?: {
14
15
  light?: string;
15
16
  standard?: string;
@@ -32,6 +33,25 @@ export interface RoutingDecision {
32
33
  wasDowngraded: boolean;
33
34
  /** Human-readable reason for this decision */
34
35
  reason: string;
36
+ /** How the model was selected */
37
+ selectionMethod: "tier-only" | "capability-scored";
38
+ /** Capability scores per eligible model (capability-scored path only) */
39
+ capabilityScores?: Record<string, number>;
40
+ /** Task requirement vector used for scoring */
41
+ taskRequirements?: Partial<Record<string, number>>;
42
+ }
43
+
44
+ // ─── Capability Profiles ─────────────────────────────────────────────────────
45
+
46
+ /** Seven-dimension capability profile for a model. All values in 0–100 range. */
47
+ export interface ModelCapabilities {
48
+ coding: number;
49
+ debugging: number;
50
+ research: number;
51
+ reasoning: number;
52
+ speed: number;
53
+ longContext: number;
54
+ instruction: number;
35
55
  }
36
56
 
37
57
  // ─── Known Model Tiers ───────────────────────────────────────────────────────
@@ -114,24 +134,207 @@ const MODEL_COST_PER_1K_INPUT: Record<string, number> = {
114
134
  "deepseek-chat": 0.00014,
115
135
  };
116
136
 
137
+ // ─── Capability Profiles Data Table ──────────────────────────────────────────
138
+ // Per-model capability profiles (0–100 scale). Used for capability-aware
139
+ // model selection within an eligible tier set.
140
+
141
+ export const MODEL_CAPABILITY_PROFILES: Record<string, ModelCapabilities> = {
142
+ "claude-opus-4-6": { coding: 95, debugging: 90, research: 85, reasoning: 95, speed: 30, longContext: 80, instruction: 90 },
143
+ "claude-sonnet-4-6": { coding: 85, debugging: 80, research: 75, reasoning: 80, speed: 60, longContext: 75, instruction: 85 },
144
+ "claude-haiku-4-5": { coding: 60, debugging: 50, research: 45, reasoning: 50, speed: 95, longContext: 50, instruction: 75 },
145
+ "gpt-4o": { coding: 80, debugging: 75, research: 70, reasoning: 75, speed: 65, longContext: 70, instruction: 80 },
146
+ "gpt-4o-mini": { coding: 55, debugging: 45, research: 40, reasoning: 45, speed: 90, longContext: 45, instruction: 70 },
147
+ "gemini-2.5-pro": { coding: 75, debugging: 70, research: 85, reasoning: 75, speed: 55, longContext: 90, instruction: 75 },
148
+ "gemini-2.0-flash": { coding: 50, debugging: 40, research: 50, reasoning: 40, speed: 95, longContext: 60, instruction: 65 },
149
+ "deepseek-chat": { coding: 75, debugging: 65, research: 55, reasoning: 70, speed: 70, longContext: 55, instruction: 65 },
150
+ "o3": { coding: 80, debugging: 85, research: 80, reasoning: 92, speed: 25, longContext: 70, instruction: 85 },
151
+ };
152
+
153
+ // ─── Base Task Requirements Data Table ───────────────────────────────────────
154
+ // Per-unit-type base requirement vectors. Weights indicate how important each
155
+ // capability dimension is for this unit type.
156
+
157
+ export const BASE_REQUIREMENTS: Record<string, Partial<Record<keyof ModelCapabilities, number>>> = {
158
+ "execute-task": { coding: 0.9, instruction: 0.7, speed: 0.3 },
159
+ "research-milestone": { research: 0.9, longContext: 0.7, reasoning: 0.5 },
160
+ "research-slice": { research: 0.9, longContext: 0.7, reasoning: 0.5 },
161
+ "plan-milestone": { reasoning: 0.9, coding: 0.5 },
162
+ "plan-slice": { reasoning: 0.9, coding: 0.5 },
163
+ "replan-slice": { reasoning: 0.9, debugging: 0.6, coding: 0.5 },
164
+ "reassess-roadmap": { reasoning: 0.9, research: 0.5 },
165
+ "complete-slice": { instruction: 0.8, speed: 0.7 },
166
+ "run-uat": { instruction: 0.7, speed: 0.8 },
167
+ "discuss-milestone": { reasoning: 0.6, instruction: 0.7 },
168
+ "complete-milestone": { instruction: 0.8, reasoning: 0.5 },
169
+ };
170
+
117
171
  // ─── Public API ──────────────────────────────────────────────────────────────
118
172
 
173
+ /**
174
+ * Score a model's suitability for a task given a requirement vector.
175
+ * Returns a weighted average of capability dimensions (0–100).
176
+ * Returns 50 if requirements are empty (neutral score).
177
+ */
178
+ export function scoreModel(
179
+ model: ModelCapabilities,
180
+ requirements: Partial<Record<keyof ModelCapabilities, number>>,
181
+ ): number {
182
+ let weightedSum = 0;
183
+ let weightSum = 0;
184
+ for (const [dim, weight] of Object.entries(requirements)) {
185
+ const capability = model[dim as keyof ModelCapabilities] ?? 50;
186
+ weightedSum += weight * capability;
187
+ weightSum += weight;
188
+ }
189
+ return weightSum > 0 ? weightedSum / weightSum : 50;
190
+ }
191
+
192
+ /**
193
+ * Compute dynamic task requirements from unit type and optional task metadata.
194
+ * Returns a requirement vector refined by task-specific signals.
195
+ */
196
+ export function computeTaskRequirements(
197
+ unitType: string,
198
+ metadata?: TaskMetadata,
199
+ ): Partial<Record<keyof ModelCapabilities, number>> {
200
+ const base = BASE_REQUIREMENTS[unitType] ?? { reasoning: 0.5 };
201
+ if (unitType === "execute-task" && metadata) {
202
+ if (metadata.tags?.some(t => /^(docs?|readme|comment|config|typo|rename)$/i.test(t))) {
203
+ return { ...base, instruction: 0.9, coding: 0.3, speed: 0.7 };
204
+ }
205
+ if (metadata.complexityKeywords?.some(k => k === "concurrency" || k === "compatibility")) {
206
+ return { ...base, debugging: 0.9, reasoning: 0.8 };
207
+ }
208
+ if (metadata.complexityKeywords?.some(k => k === "migration" || k === "architecture")) {
209
+ return { ...base, reasoning: 0.9, coding: 0.8 };
210
+ }
211
+ if ((metadata.fileCount ?? 0) >= 6 || (metadata.estimatedLines ?? 0) >= 500) {
212
+ return { ...base, coding: 0.9, reasoning: 0.7 };
213
+ }
214
+ }
215
+ return base;
216
+ }
217
+
218
+ /**
219
+ * Score all eligible models against a requirement vector and return them
220
+ * sorted by score descending. Within 2 points: prefer cheaper; equal cost:
221
+ * lexicographic tie-break by model ID.
222
+ */
223
+ export function scoreEligibleModels(
224
+ eligibleModelIds: string[],
225
+ requirements: Partial<Record<keyof ModelCapabilities, number>>,
226
+ capabilityOverrides?: Record<string, Partial<ModelCapabilities>>,
227
+ ): Array<{ modelId: string; score: number }> {
228
+ const scored = eligibleModelIds.map(modelId => {
229
+ const builtin = MODEL_CAPABILITY_PROFILES[modelId];
230
+ const override = capabilityOverrides?.[modelId];
231
+ const profile: ModelCapabilities = builtin
232
+ ? override ? { ...builtin, ...override } : builtin
233
+ : { coding: 50, debugging: 50, research: 50, reasoning: 50, speed: 50, longContext: 50, instruction: 50 };
234
+ return { modelId, score: scoreModel(profile, requirements) };
235
+ });
236
+ scored.sort((a, b) => {
237
+ const scoreDiff = b.score - a.score;
238
+ if (Math.abs(scoreDiff) > 2) return scoreDiff;
239
+ const costA = MODEL_COST_PER_1K_INPUT[a.modelId] ?? Infinity;
240
+ const costB = MODEL_COST_PER_1K_INPUT[b.modelId] ?? Infinity;
241
+ if (costA !== costB) return costA - costB;
242
+ return a.modelId.localeCompare(b.modelId);
243
+ });
244
+ return scored;
245
+ }
246
+
247
+ /**
248
+ * Return all models eligible for a given tier, sorted cheapest first.
249
+ * If routingConfig.tier_models[tier] is set and available, returns only that
250
+ * model. Otherwise filters availableModelIds by tier from MODEL_CAPABILITY_TIER.
251
+ */
252
+ export function getEligibleModels(
253
+ tier: ComplexityTier,
254
+ availableModelIds: string[],
255
+ routingConfig: DynamicRoutingConfig,
256
+ ): string[] {
257
+ // 1. Check explicit tier_models config
258
+ const explicitModel = routingConfig.tier_models?.[tier];
259
+ if (explicitModel) {
260
+ // Exact match
261
+ if (availableModelIds.includes(explicitModel)) return [explicitModel];
262
+ // Provider-prefix-stripped match
263
+ const match = availableModelIds.find(id => {
264
+ const bareAvail = id.includes("/") ? id.split("/").pop()! : id;
265
+ const bareExplicit = explicitModel.includes("/") ? explicitModel.split("/").pop()! : explicitModel;
266
+ return bareAvail === bareExplicit;
267
+ });
268
+ if (match) return [match];
269
+ }
270
+
271
+ // 2. Auto-detect: filter by tier, sort cheapest first
272
+ return availableModelIds
273
+ .filter(id => getModelTier(id) === tier)
274
+ .sort((a, b) => {
275
+ const costA = getModelCost(a);
276
+ const costB = getModelCost(b);
277
+ return costA - costB;
278
+ });
279
+ }
280
+
281
+ /**
282
+ * Build a fallback chain for a selected model: [selectedModel, ...configuredFallbacks, configuredPrimary]
283
+ * Deduplicates entries while preserving order.
284
+ */
285
+ function buildFallbackChain(selectedModelId: string, phaseConfig: ResolvedModelConfig): string[] {
286
+ return [
287
+ ...phaseConfig.fallbacks.filter(f => f !== selectedModelId),
288
+ phaseConfig.primary,
289
+ ].filter(f => f !== selectedModelId);
290
+ }
291
+
292
+ /**
293
+ * Load capability overrides from user preferences' modelOverrides section.
294
+ * Returns a map of model ID → partial capability overrides to deep-merge with built-in profiles.
295
+ *
296
+ * Per D-17: partial capability overrides via models.json modelOverrides, deep-merged with defaults.
297
+ */
298
+ export function loadCapabilityOverrides(
299
+ prefs: { modelOverrides?: Record<string, { capabilities?: Partial<ModelCapabilities> }> },
300
+ ): Record<string, Partial<ModelCapabilities>> {
301
+ const result: Record<string, Partial<ModelCapabilities>> = {};
302
+ if (!prefs.modelOverrides) return result;
303
+ for (const [modelId, overrideEntry] of Object.entries(prefs.modelOverrides)) {
304
+ if (overrideEntry.capabilities) {
305
+ result[modelId] = overrideEntry.capabilities;
306
+ }
307
+ }
308
+ return result;
309
+ }
310
+
119
311
  /**
120
312
  * Resolve the model to use for a given complexity tier.
121
313
  *
122
314
  * Downgrade-only: the returned model is always equal to or cheaper than
123
315
  * the user's configured primary model. Never upgrades beyond configuration.
124
316
  *
125
- * @param classification The complexity classification result
126
- * @param phaseConfig The user's configured model for this phase (ceiling)
127
- * @param routingConfig Dynamic routing configuration
128
- * @param availableModelIds List of available model IDs (from registry)
317
+ * STEP 1: Filter to eligible models for the requested tier.
318
+ * STEP 2: Capability scoring ranks eligible models by task-capability match
319
+ * when capability_routing is enabled and multiple eligible models exist.
320
+ * STEP 3: Fallback chain assembly.
321
+ *
322
+ * @param classification The complexity classification result
323
+ * @param phaseConfig The user's configured model for this phase (ceiling)
324
+ * @param routingConfig Dynamic routing configuration
325
+ * @param availableModelIds List of available model IDs (from registry)
326
+ * @param unitType The unit type for capability requirement computation (optional)
327
+ * @param taskMetadata Task metadata for refined requirement vectors (optional)
328
+ * @param capabilityOverrides User-provided capability overrides (deep-merged with built-in profiles, optional)
129
329
  */
130
330
  export function resolveModelForComplexity(
131
331
  classification: ClassificationResult,
132
332
  phaseConfig: ResolvedModelConfig | undefined,
133
333
  routingConfig: DynamicRoutingConfig,
134
334
  availableModelIds: string[],
335
+ unitType?: string,
336
+ taskMetadata?: TaskMetadata,
337
+ capabilityOverrides?: Record<string, Partial<ModelCapabilities>>,
135
338
  ): RoutingDecision {
136
339
  // If no phase config or routing disabled, pass through
137
340
  if (!phaseConfig || !routingConfig.enabled) {
@@ -141,6 +344,7 @@ export function resolveModelForComplexity(
141
344
  tier: classification.tier,
142
345
  wasDowngraded: false,
143
346
  reason: "dynamic routing disabled or no phase config",
347
+ selectionMethod: "tier-only",
144
348
  };
145
349
  }
146
350
 
@@ -160,6 +364,7 @@ export function resolveModelForComplexity(
160
364
  tier: requestedTier,
161
365
  wasDowngraded: false,
162
366
  reason: `configured model "${configuredPrimary}" is not in the known tier map — honoring explicit config`,
367
+ selectionMethod: "tier-only",
163
368
  };
164
369
  }
165
370
 
@@ -171,18 +376,14 @@ export function resolveModelForComplexity(
171
376
  tier: requestedTier,
172
377
  wasDowngraded: false,
173
378
  reason: `tier ${requestedTier} >= configured ${configuredTier}`,
379
+ selectionMethod: "tier-only",
174
380
  };
175
381
  }
176
382
 
177
- // Find the best model for the requested tier
178
- const targetModelId = findModelForTier(
179
- requestedTier,
180
- routingConfig,
181
- availableModelIds,
182
- routingConfig.cross_provider !== false,
183
- );
383
+ // STEP 1: Get all eligible models for the requested tier
384
+ const eligible = getEligibleModels(requestedTier, availableModelIds, routingConfig);
184
385
 
185
- if (!targetModelId) {
386
+ if (eligible.length === 0) {
186
387
  // No suitable model found — use configured primary
187
388
  return {
188
389
  modelId: configuredPrimary,
@@ -190,14 +391,37 @@ export function resolveModelForComplexity(
190
391
  tier: requestedTier,
191
392
  wasDowngraded: false,
192
393
  reason: `no ${requestedTier}-tier model available`,
394
+ selectionMethod: "tier-only",
193
395
  };
194
396
  }
195
397
 
398
+ // STEP 2: Capability scoring (when enabled and multiple eligible models exist)
399
+ if (routingConfig.capability_routing !== false && eligible.length > 1 && unitType) {
400
+ const requirements = computeTaskRequirements(unitType, taskMetadata);
401
+ const scored = scoreEligibleModels(eligible, requirements, capabilityOverrides);
402
+ const winner = scored[0];
403
+ if (winner) {
404
+ const capScores: Record<string, number> = {};
405
+ for (const s of scored) capScores[s.modelId] = s.score;
406
+ const fallbacks = buildFallbackChain(winner.modelId, phaseConfig);
407
+ return {
408
+ modelId: winner.modelId,
409
+ fallbacks,
410
+ tier: requestedTier,
411
+ wasDowngraded: true,
412
+ reason: `capability-scored: ${winner.modelId} (${winner.score.toFixed(1)}) for ${unitType}`,
413
+ capabilityScores: capScores,
414
+ taskRequirements: requirements,
415
+ selectionMethod: "capability-scored",
416
+ };
417
+ }
418
+ }
419
+
420
+ // STEP 3: Fallback — use first eligible model (cheapest in tier, or single eligible)
421
+ const targetModelId = eligible[0];
422
+
196
423
  // Build fallback chain: [downgraded_model, ...configured_fallbacks, configured_primary]
197
- const fallbacks = [
198
- ...phaseConfig.fallbacks.filter(f => f !== targetModelId),
199
- configuredPrimary,
200
- ].filter(f => f !== targetModelId);
424
+ const fallbacks = buildFallbackChain(targetModelId, phaseConfig);
201
425
 
202
426
  return {
203
427
  modelId: targetModelId,
@@ -205,6 +429,7 @@ export function resolveModelForComplexity(
205
429
  tier: requestedTier,
206
430
  wasDowngraded: true,
207
431
  reason: classification.reason,
432
+ selectionMethod: "tier-only",
208
433
  };
209
434
  }
210
435
 
@@ -226,6 +451,7 @@ export function escalateTier(currentTier: ComplexityTier): ComplexityTier | null
226
451
  export function defaultRoutingConfig(): DynamicRoutingConfig {
227
452
  return {
228
453
  enabled: true,
454
+ capability_routing: true,
229
455
  escalate_on_failure: true,
230
456
  budget_pressure: true,
231
457
  cross_provider: true,
@@ -247,8 +473,8 @@ function getModelTier(modelId: string): ComplexityTier {
247
473
  if (bareId.includes(knownId) || knownId.includes(bareId)) return tier;
248
474
  }
249
475
 
250
- // Unknown models are assumed heavy (safest assumption)
251
- return "heavy";
476
+ // Unknown models are assumed standard (per D-15: avoids silently ignoring user config)
477
+ return "standard";
252
478
  }
253
479
 
254
480
  /** Check if a model ID has a known capability tier mapping. (#2192) */
@@ -261,43 +487,6 @@ function isKnownModel(modelId: string): boolean {
261
487
  return false;
262
488
  }
263
489
 
264
- function findModelForTier(
265
- tier: ComplexityTier,
266
- config: DynamicRoutingConfig,
267
- availableModelIds: string[],
268
- crossProvider: boolean,
269
- ): string | null {
270
- // 1. Check explicit tier_models config
271
- const explicitModel = config.tier_models?.[tier];
272
- if (explicitModel && availableModelIds.includes(explicitModel)) {
273
- return explicitModel;
274
- }
275
- // Also check with provider prefix stripped
276
- if (explicitModel) {
277
- const match = availableModelIds.find(id => {
278
- const bareAvail = id.includes("/") ? id.split("/").pop()! : id;
279
- const bareExplicit = explicitModel.includes("/") ? explicitModel.split("/").pop()! : explicitModel;
280
- return bareAvail === bareExplicit;
281
- });
282
- if (match) return match;
283
- }
284
-
285
- // 2. Auto-detect: find the cheapest available model in the requested tier
286
- const candidates = availableModelIds
287
- .filter(id => {
288
- const modelTier = getModelTier(id);
289
- return modelTier === tier;
290
- })
291
- .sort((a, b) => {
292
- if (!crossProvider) return 0;
293
- const costA = getModelCost(a);
294
- const costB = getModelCost(b);
295
- return costA - costB;
296
- });
297
-
298
- return candidates[0] ?? null;
299
- }
300
-
301
490
  function getModelCost(modelId: string): number {
302
491
  const bareId = modelId.includes("/") ? modelId.split("/").pop()! : modelId;
303
492
 
@@ -15,6 +15,7 @@ import { MergeConflictError } from "./git-service.js";
15
15
  import { removeSessionStatus } from "./session-status-io.js";
16
16
  import type { WorkerInfo } from "./parallel-orchestrator.js";
17
17
  import { getErrorMessage } from "./error-utils.js";
18
+ import { logWarning } from "./workflow-logger.js";
18
19
 
19
20
  // ─── Types ─────────────────────────────────────────────────────────────────
20
21
 
@@ -47,7 +48,8 @@ export function isMilestoneCompleteInWorktreeDb(basePath: string, mid: string):
47
48
  { timeout: 3000, encoding: "utf-8" },
48
49
  );
49
50
  return (result.stdout || "").trim() === "complete";
50
- } catch {
51
+ } catch (e) {
52
+ logWarning("parallel", `spawnSync milestone completion check failed for ${mid}: ${(e as Error).message}`);
51
53
  return false;
52
54
  }
53
55
  }
@@ -65,8 +67,8 @@ function discoverDbCompletedMilestones(basePath: string): Set<string> {
65
67
  completed.add(entry);
66
68
  }
67
69
  }
68
- } catch {
69
- // worktrees dir may not exist
70
+ } catch (e) {
71
+ logWarning("parallel", `readdirSync for completed set failed: ${(e as Error).message}`);
70
72
  }
71
73
  return completed;
72
74
  }