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
@@ -0,0 +1,347 @@
1
+ // GSD Extension — Capability-Aware Router Tests
2
+ // Tests for new capability scoring functions and data tables (Plan 01-01)
3
+
4
+ import { describe, test } from "node:test";
5
+ import assert from "node:assert/strict";
6
+
7
+ import {
8
+ scoreModel,
9
+ computeTaskRequirements,
10
+ scoreEligibleModels,
11
+ getEligibleModels,
12
+ resolveModelForComplexity,
13
+ MODEL_CAPABILITY_PROFILES,
14
+ BASE_REQUIREMENTS,
15
+ defaultRoutingConfig,
16
+ } from "../model-router.js";
17
+ import type { ModelCapabilities, DynamicRoutingConfig, RoutingDecision } from "../model-router.js";
18
+
19
+ // ─── scoreModel ──────────────────────────────────────────────────────────────
20
+
21
+ describe("scoreModel", () => {
22
+ const sonnetProfile: ModelCapabilities = {
23
+ coding: 85, debugging: 80, research: 75, reasoning: 80,
24
+ speed: 60, longContext: 75, instruction: 85,
25
+ };
26
+
27
+ test("produces correct weighted average for single dimension", () => {
28
+ // Only coding weight 1.0 → result should be the coding score
29
+ const score = scoreModel(sonnetProfile, { coding: 1.0 });
30
+ assert.equal(score, 85);
31
+ });
32
+
33
+ test("produces correct weighted average for two dimensions (coding 0.9, instruction 0.7)", () => {
34
+ // (0.9*85 + 0.7*85) / (0.9+0.7) = (76.5+59.5)/1.6 = 136/1.6 = 85.0
35
+ const score = scoreModel(sonnetProfile, { coding: 0.9, instruction: 0.7 });
36
+ assert.ok(Math.abs(score - 85.0) < 0.01, `Expected ~85.0, got ${score}`);
37
+ });
38
+
39
+ test("returns 50 when requirements is empty", () => {
40
+ const score = scoreModel(sonnetProfile, {});
41
+ assert.equal(score, 50);
42
+ });
43
+
44
+ test("uses 50 as fallback for unknown dimension in requirements", () => {
45
+ // 'unknown' dimension not in profile → treated as 50
46
+ const score = scoreModel(sonnetProfile, { coding: 0.5, unknown: 1.0 } as any);
47
+ // (0.5*85 + 1.0*50) / (0.5+1.0) = (42.5+50)/1.5 = 92.5/1.5 = 61.67
48
+ assert.ok(score > 61 && score < 62, `Expected ~61.67, got ${score}`);
49
+ });
50
+ });
51
+
52
+ // ─── computeTaskRequirements ─────────────────────────────────────────────────
53
+
54
+ describe("computeTaskRequirements", () => {
55
+ test("execute-task with no metadata returns base requirements", () => {
56
+ const req = computeTaskRequirements("execute-task", undefined);
57
+ assert.deepStrictEqual(req, { coding: 0.9, instruction: 0.7, speed: 0.3 });
58
+ });
59
+
60
+ test("execute-task with docs tag returns docs-adjusted requirements", () => {
61
+ const req = computeTaskRequirements("execute-task", { tags: ["docs"] });
62
+ assert.equal(req.instruction, 0.9);
63
+ assert.equal(req.coding, 0.3);
64
+ assert.equal(req.speed, 0.7);
65
+ });
66
+
67
+ test("execute-task with readme tag returns docs-adjusted requirements", () => {
68
+ const req = computeTaskRequirements("execute-task", { tags: ["readme"] });
69
+ assert.equal(req.instruction, 0.9);
70
+ });
71
+
72
+ test("execute-task with concurrency keyword boosts debugging and reasoning", () => {
73
+ const req = computeTaskRequirements("execute-task", { complexityKeywords: ["concurrency"] });
74
+ assert.equal(req.debugging, 0.9);
75
+ assert.equal(req.reasoning, 0.8);
76
+ });
77
+
78
+ test("execute-task with compatibility keyword boosts debugging and reasoning", () => {
79
+ const req = computeTaskRequirements("execute-task", { complexityKeywords: ["compatibility"] });
80
+ assert.equal(req.debugging, 0.9);
81
+ assert.equal(req.reasoning, 0.8);
82
+ });
83
+
84
+ test("execute-task with migration keyword boosts reasoning and coding", () => {
85
+ const req = computeTaskRequirements("execute-task", { complexityKeywords: ["migration"] });
86
+ assert.equal(req.reasoning, 0.9);
87
+ assert.equal(req.coding, 0.8);
88
+ });
89
+
90
+ test("execute-task with architecture keyword boosts reasoning and coding", () => {
91
+ const req = computeTaskRequirements("execute-task", { complexityKeywords: ["architecture"] });
92
+ assert.equal(req.reasoning, 0.9);
93
+ assert.equal(req.coding, 0.8);
94
+ });
95
+
96
+ test("execute-task with fileCount >= 6 boosts coding and reasoning", () => {
97
+ const req = computeTaskRequirements("execute-task", { fileCount: 8 });
98
+ assert.equal(req.coding, 0.9);
99
+ assert.equal(req.reasoning, 0.7);
100
+ });
101
+
102
+ test("execute-task with fileCount exactly 6 triggers large-file boost", () => {
103
+ const req = computeTaskRequirements("execute-task", { fileCount: 6 });
104
+ assert.equal(req.coding, 0.9);
105
+ assert.equal(req.reasoning, 0.7);
106
+ });
107
+
108
+ test("execute-task with estimatedLines >= 500 boosts coding and reasoning", () => {
109
+ const req = computeTaskRequirements("execute-task", { estimatedLines: 500 });
110
+ assert.equal(req.coding, 0.9);
111
+ assert.equal(req.reasoning, 0.7);
112
+ });
113
+
114
+ test("research-milestone with no metadata returns base requirements", () => {
115
+ const req = computeTaskRequirements("research-milestone", undefined);
116
+ assert.deepStrictEqual(req, { research: 0.9, longContext: 0.7, reasoning: 0.5 });
117
+ });
118
+
119
+ test("unknown unit type returns default reasoning requirement", () => {
120
+ const req = computeTaskRequirements("unknown-type", undefined);
121
+ assert.deepStrictEqual(req, { reasoning: 0.5 });
122
+ });
123
+ });
124
+
125
+ // ─── MODEL_CAPABILITY_PROFILES ───────────────────────────────────────────────
126
+
127
+ describe("MODEL_CAPABILITY_PROFILES", () => {
128
+ test("contains all 9 required models", () => {
129
+ const required = [
130
+ "claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-4-5",
131
+ "gpt-4o", "gpt-4o-mini", "gemini-2.5-pro", "gemini-2.0-flash",
132
+ "deepseek-chat", "o3",
133
+ ];
134
+ for (const model of required) {
135
+ assert.ok(MODEL_CAPABILITY_PROFILES[model], `Missing profile for ${model}`);
136
+ }
137
+ });
138
+
139
+ test("each profile has all 7 capability dimensions", () => {
140
+ const dims: Array<keyof ModelCapabilities> = [
141
+ "coding", "debugging", "research", "reasoning",
142
+ "speed", "longContext", "instruction",
143
+ ];
144
+ for (const [modelId, profile] of Object.entries(MODEL_CAPABILITY_PROFILES)) {
145
+ for (const dim of dims) {
146
+ assert.ok(profile[dim] !== undefined, `${modelId} missing dimension ${dim}`);
147
+ assert.ok(profile[dim] >= 0 && profile[dim] <= 100, `${modelId}.${dim} out of range`);
148
+ }
149
+ }
150
+ });
151
+
152
+ test("claude-opus-4-6 has high reasoning and coding", () => {
153
+ const opus = MODEL_CAPABILITY_PROFILES["claude-opus-4-6"];
154
+ assert.ok(opus.reasoning >= 90, `Expected reasoning >= 90, got ${opus.reasoning}`);
155
+ assert.ok(opus.coding >= 90, `Expected coding >= 90, got ${opus.coding}`);
156
+ });
157
+
158
+ test("claude-haiku-4-5 has high speed but lower reasoning", () => {
159
+ const haiku = MODEL_CAPABILITY_PROFILES["claude-haiku-4-5"];
160
+ assert.ok(haiku.speed >= 90, `Expected speed >= 90, got ${haiku.speed}`);
161
+ assert.ok(haiku.reasoning < 70, `Expected reasoning < 70, got ${haiku.reasoning}`);
162
+ });
163
+ });
164
+
165
+ // ─── BASE_REQUIREMENTS ───────────────────────────────────────────────────────
166
+
167
+ describe("BASE_REQUIREMENTS", () => {
168
+ test("contains all 11 unit types", () => {
169
+ const required = [
170
+ "execute-task", "research-milestone", "research-slice",
171
+ "plan-milestone", "plan-slice", "replan-slice",
172
+ "reassess-roadmap", "complete-slice", "run-uat",
173
+ "discuss-milestone", "complete-milestone",
174
+ ];
175
+ for (const unitType of required) {
176
+ assert.ok(BASE_REQUIREMENTS[unitType], `Missing requirements for ${unitType}`);
177
+ }
178
+ });
179
+ });
180
+
181
+ // ─── scoreEligibleModels ─────────────────────────────────────────────────────
182
+
183
+ describe("scoreEligibleModels", () => {
184
+ test("returns array sorted by score descending", () => {
185
+ const requirements = { research: 0.9, longContext: 0.7, reasoning: 0.5 };
186
+ const results = scoreEligibleModels(["claude-sonnet-4-6", "gpt-4o"], requirements);
187
+ assert.ok(results.length === 2);
188
+ assert.ok(results[0].score >= results[1].score, "Should be sorted descending by score");
189
+ });
190
+
191
+ test("returns single model when only one eligible", () => {
192
+ const requirements = { coding: 0.9 };
193
+ const results = scoreEligibleModels(["claude-sonnet-4-6"], requirements);
194
+ assert.equal(results.length, 1);
195
+ assert.equal(results[0].modelId, "claude-sonnet-4-6");
196
+ });
197
+
198
+ test("models without profiles get uniform 50s score", () => {
199
+ const requirements = { coding: 1.0 };
200
+ const results = scoreEligibleModels(["unknown-model-xyz"], requirements);
201
+ assert.equal(results[0].score, 50);
202
+ });
203
+
204
+ test("when two models score within 2 points, prefers cheaper model", () => {
205
+ // gemini-2.0-flash is cheaper than gpt-4o-mini ($0.0001 vs $0.00015)
206
+ // Use a requirement that causes similar scores for both
207
+ const requirements = { speed: 1.0 };
208
+ const results = scoreEligibleModels(["gpt-4o-mini", "gemini-2.0-flash"], requirements);
209
+ // Both are high-speed: gpt-4o-mini=90, gemini-2.0-flash=95 — scores differ by 5, not within 2
210
+ // So top should be gemini-2.0-flash by score
211
+ assert.equal(results[0].modelId, "gemini-2.0-flash");
212
+ });
213
+
214
+ test("tie-breaks by lexicographic model ID when cost and score are equal", () => {
215
+ // Use models without cost entries — both get Infinity cost
216
+ const requirements = { coding: 1.0 };
217
+ const results = scoreEligibleModels(["model-z", "model-a"], requirements);
218
+ // Both unknown → score=50, cost=Infinity → tiebreak by ID
219
+ assert.equal(results[0].modelId, "model-a");
220
+ });
221
+
222
+ test("scoreEligibleModels respects capabilityOverrides", () => {
223
+ const requirements = { coding: 1.0 };
224
+ // Override claude-sonnet-4-6's coding to 30 (worse)
225
+ const results = scoreEligibleModels(
226
+ ["claude-sonnet-4-6", "gpt-4o"],
227
+ requirements,
228
+ { "claude-sonnet-4-6": { coding: 30 } },
229
+ );
230
+ // gpt-4o coding=80 should beat overridden sonnet coding=30
231
+ assert.equal(results[0].modelId, "gpt-4o");
232
+ });
233
+ });
234
+
235
+ // ─── getEligibleModels ───────────────────────────────────────────────────────
236
+
237
+ describe("getEligibleModels", () => {
238
+ const MODELS = [
239
+ "claude-opus-4-6", // heavy
240
+ "claude-sonnet-4-6", // standard
241
+ "claude-haiku-4-5", // light
242
+ "gpt-4o-mini", // light
243
+ ];
244
+
245
+ test("returns light-tier models sorted by cost when no explicit config", () => {
246
+ const config: DynamicRoutingConfig = defaultRoutingConfig();
247
+ const result = getEligibleModels("light", MODELS, config);
248
+ assert.ok(result.length >= 1);
249
+ // All results should be light-tier
250
+ for (const id of result) {
251
+ assert.ok(
252
+ ["claude-haiku-4-5", "gpt-4o-mini"].includes(id),
253
+ `Expected light-tier model, got ${id}`,
254
+ );
255
+ }
256
+ });
257
+
258
+ test("returns explicit tier_models when configured and available", () => {
259
+ const config: DynamicRoutingConfig = {
260
+ ...defaultRoutingConfig(),
261
+ tier_models: { light: "gpt-4o-mini" },
262
+ };
263
+ const result = getEligibleModels("light", MODELS, config);
264
+ assert.deepStrictEqual(result, ["gpt-4o-mini"]);
265
+ });
266
+
267
+ test("returns empty array when no eligible models for tier", () => {
268
+ const config: DynamicRoutingConfig = defaultRoutingConfig();
269
+ // Only heavy model available, requesting light
270
+ const result = getEligibleModels("light", ["claude-opus-4-6"], config);
271
+ assert.equal(result.length, 0);
272
+ });
273
+ });
274
+
275
+ // ─── DynamicRoutingConfig extension ─────────────────────────────────────────
276
+
277
+ describe("DynamicRoutingConfig.capability_routing", () => {
278
+ test("defaultRoutingConfig includes capability_routing: true", () => {
279
+ const config = defaultRoutingConfig();
280
+ assert.equal(config.capability_routing, true);
281
+ });
282
+ });
283
+
284
+ // ─── RoutingDecision.selectionMethod ─────────────────────────────────────────
285
+
286
+ describe("RoutingDecision.selectionMethod", () => {
287
+ const MODELS = ["claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-4-5", "gpt-4o-mini"];
288
+
289
+ function makeClassification(tier: "light" | "standard" | "heavy") {
290
+ return { tier, reason: "test", downgraded: false };
291
+ }
292
+
293
+ test("returns selectionMethod: tier-only when routing is disabled", () => {
294
+ const config = { ...defaultRoutingConfig(), enabled: false };
295
+ const result: RoutingDecision = resolveModelForComplexity(
296
+ makeClassification("light"),
297
+ { primary: "claude-opus-4-6", fallbacks: [] },
298
+ config,
299
+ MODELS,
300
+ );
301
+ assert.equal(result.selectionMethod, "tier-only");
302
+ });
303
+
304
+ test("returns selectionMethod: tier-only for no phase config passthrough", () => {
305
+ const config = { ...defaultRoutingConfig(), enabled: true };
306
+ const result: RoutingDecision = resolveModelForComplexity(
307
+ makeClassification("light"),
308
+ undefined,
309
+ config,
310
+ MODELS,
311
+ );
312
+ assert.equal(result.selectionMethod, "tier-only");
313
+ });
314
+
315
+ test("returns selectionMethod: tier-only for unknown model passthrough", () => {
316
+ const config = { ...defaultRoutingConfig(), enabled: true };
317
+ const result: RoutingDecision = resolveModelForComplexity(
318
+ makeClassification("light"),
319
+ { primary: "custom-provider/my-model-v3", fallbacks: [] },
320
+ config,
321
+ ["custom-provider/my-model-v3", ...MODELS],
322
+ );
323
+ assert.equal(result.selectionMethod, "tier-only");
324
+ });
325
+
326
+ test("returns selectionMethod: tier-only for no-downgrade passthrough", () => {
327
+ const config = { ...defaultRoutingConfig(), enabled: true };
328
+ const result: RoutingDecision = resolveModelForComplexity(
329
+ makeClassification("heavy"),
330
+ { primary: "claude-opus-4-6", fallbacks: [] },
331
+ config,
332
+ MODELS,
333
+ );
334
+ assert.equal(result.selectionMethod, "tier-only");
335
+ });
336
+
337
+ test("returns selectionMethod: tier-only when downgraded", () => {
338
+ const config = { ...defaultRoutingConfig(), enabled: true };
339
+ const result: RoutingDecision = resolveModelForComplexity(
340
+ makeClassification("light"),
341
+ { primary: "claude-opus-4-6", fallbacks: [] },
342
+ config,
343
+ MODELS,
344
+ );
345
+ assert.equal(result.selectionMethod, "tier-only");
346
+ });
347
+ });
@@ -486,3 +486,66 @@ test("getCodebaseMapStats: reads total file count from header for accuracy with
486
486
  cleanup(base);
487
487
  }
488
488
  });
489
+
490
+ // ─── excludePatterns from options ────────────────────────────────────────
491
+
492
+ test("generateCodebaseMap: custom excludePatterns filters additional directories", () => {
493
+ const base = makeTmpRepo();
494
+ try {
495
+ addFile(base, "src/main.ts");
496
+ addFile(base, "src/utils.ts");
497
+ addFile(base, ".cache-data/data/index.lance");
498
+ addFile(base, "docs/guide.md");
499
+
500
+ const result = generateCodebaseMap(base, {
501
+ excludePatterns: [".cache-data/", "docs/"],
502
+ });
503
+ assert.ok(result.content.includes("`src/main.ts`"));
504
+ assert.ok(result.content.includes("`src/utils.ts`"));
505
+ assert.ok(!result.content.includes(".cache-data"));
506
+ assert.ok(!result.content.includes("guide.md"));
507
+ assert.equal(result.fileCount, 2);
508
+ } finally {
509
+ cleanup(base);
510
+ }
511
+ });
512
+
513
+ test("generateCodebaseMap: collapseThreshold option overrides default", () => {
514
+ const base = makeTmpRepo();
515
+ try {
516
+ // Create 10 files in one directory — below default threshold (20)
517
+ // but above a custom threshold of 5
518
+ for (let i = 0; i < 10; i++) {
519
+ addFile(base, `src/comp${i}.ts`);
520
+ }
521
+
522
+ // With default threshold (20), files should NOT collapse
523
+ const expanded = generateCodebaseMap(base);
524
+ assert.ok(expanded.content.includes("`src/comp0.ts`"));
525
+
526
+ // With custom threshold (5), files SHOULD collapse
527
+ const collapsed = generateCodebaseMap(base, { collapseThreshold: 5 });
528
+ assert.ok(collapsed.content.includes("10 files"));
529
+ assert.ok(!collapsed.content.includes("`src/comp0.ts`\n"));
530
+ } finally {
531
+ cleanup(base);
532
+ }
533
+ });
534
+
535
+ test("updateCodebaseMap: respects excludePatterns option", () => {
536
+ const base = makeTmpRepo();
537
+ try {
538
+ addFile(base, "src/main.ts");
539
+ addFile(base, "vendor-extra/lib.js");
540
+
541
+ const initial = generateCodebaseMap(base);
542
+ writeCodebaseMap(base, initial.content);
543
+
544
+ // Update with exclusion should remove vendor-extra files
545
+ const result = updateCodebaseMap(base, { excludePatterns: ["vendor-extra/"] });
546
+ assert.ok(result.content.includes("`src/main.ts`"));
547
+ assert.ok(!result.content.includes("vendor-extra"));
548
+ } finally {
549
+ cleanup(base);
550
+ }
551
+ });
@@ -1,7 +1,7 @@
1
- import test from "node:test";
1
+ import test, { describe } from "node:test";
2
2
  import assert from "node:assert/strict";
3
3
 
4
- import { classifyUnitComplexity, tierLabel, tierOrdinal } from "../complexity-classifier.js";
4
+ import { classifyUnitComplexity, tierLabel, tierOrdinal, extractTaskMetadata } from "../complexity-classifier.js";
5
5
  import type { ComplexityTier, TaskMetadata } from "../complexity-classifier.js";
6
6
 
7
7
  // ─── tierLabel ───────────────────────────────────────────────────────────────
@@ -179,3 +179,28 @@ test("execute-task with few code blocks stays standard", () => {
179
179
  const result = classifyUnitComplexity("execute-task", "M001/S01/T01", "/tmp/fake", undefined, metadata);
180
180
  assert.equal(result.tier, "standard");
181
181
  });
182
+
183
+ // ─── ClassificationResult taskMetadata passthrough ───────────────────────────
184
+
185
+ describe("ClassificationResult taskMetadata", () => {
186
+ test("classifyUnitComplexity for execute-task returns result with taskMetadata populated", () => {
187
+ const metadata: TaskMetadata = { fileCount: 3, tags: ["docs"] };
188
+ const result = classifyUnitComplexity("execute-task", "M001/S01/T01", "/tmp/fake", undefined, metadata);
189
+ assert.ok(result.taskMetadata !== undefined, "taskMetadata should be populated for execute-task");
190
+ assert.equal(result.taskMetadata!.tags?.[0], "docs");
191
+ });
192
+
193
+ test("classifyUnitComplexity for hook/xyz returns result with taskMetadata undefined", () => {
194
+ const result = classifyUnitComplexity("hook/verify", "M001/S01/T01", "/tmp/fake");
195
+ assert.equal(result.taskMetadata, undefined, "taskMetadata should be undefined for hook units");
196
+ });
197
+
198
+ test("classifyUnitComplexity for plan-slice returns result with taskMetadata undefined", () => {
199
+ const result = classifyUnitComplexity("plan-slice", "M001/S01", "/tmp/fake");
200
+ assert.equal(result.taskMetadata, undefined, "taskMetadata should be undefined for plan-slice");
201
+ });
202
+
203
+ test("extractTaskMetadata is importable as a named export and is a function", () => {
204
+ assert.equal(typeof extractTaskMetadata, "function", "extractTaskMetadata should be a callable function");
205
+ });
206
+ });
@@ -0,0 +1,122 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+
4
+ import { createObservationMask } from "../context-masker.js";
5
+
6
+ // These helpers produce messages in the pi-ai LLM payload format
7
+ // (post-convertToLlm, pre-provider), which is what before_provider_request sees.
8
+
9
+ function userMsg(content: string) {
10
+ return { role: "user", content: [{ type: "text", text: content }] };
11
+ }
12
+
13
+ function assistantMsg(content: string) {
14
+ return { role: "assistant", content: [{ type: "text", text: content }] };
15
+ }
16
+
17
+ /** toolResult in pi-ai format: role "toolResult", content as TextContent[] */
18
+ function toolResult(text: string) {
19
+ return { role: "toolResult", content: [{ type: "text", text }], toolCallId: "toolu_test", toolName: "Read", isError: false };
20
+ }
21
+
22
+ /** bashExecution after convertToLlm: becomes a user message with "Ran `cmd`" prefix */
23
+ function bashResult(text: string) {
24
+ return { role: "user", content: [{ type: "text", text: `Ran \`echo test\`\n\`\`\`\n${text}\n\`\`\`` }] };
25
+ }
26
+
27
+ const MASK_TEXT = "[result masked — within summarized history]";
28
+
29
+ test("masks nothing when message count is within keepRecentTurns", () => {
30
+ const mask = createObservationMask(8);
31
+ const messages = [
32
+ userMsg("hello"),
33
+ assistantMsg("hi"),
34
+ toolResult("file contents"),
35
+ ];
36
+ const result = mask(messages as any);
37
+ assert.equal(result.length, 3);
38
+ assert.deepEqual((result[2].content as any)[0].text, "file contents");
39
+ });
40
+
41
+ test("masks tool results older than keepRecentTurns", () => {
42
+ const mask = createObservationMask(2);
43
+ const messages = [
44
+ userMsg("turn 1"),
45
+ toolResult("old tool output"),
46
+ assistantMsg("response 1"),
47
+ userMsg("turn 2"),
48
+ toolResult("newer tool output"),
49
+ assistantMsg("response 2"),
50
+ userMsg("turn 3"),
51
+ toolResult("newest tool output"),
52
+ assistantMsg("response 3"),
53
+ ];
54
+ const result = mask(messages as any);
55
+ // Old tool result (before boundary) should be masked
56
+ assert.equal((result[1].content as any)[0].text, MASK_TEXT);
57
+ // Recent tool results (within keep window) should be preserved
58
+ assert.equal((result[4].content as any)[0].text, "newer tool output");
59
+ assert.equal((result[7].content as any)[0].text, "newest tool output");
60
+ });
61
+
62
+ test("never masks assistant messages", () => {
63
+ const mask = createObservationMask(1);
64
+ const messages = [
65
+ userMsg("turn 1"),
66
+ assistantMsg("old reasoning"),
67
+ userMsg("turn 2"),
68
+ assistantMsg("new reasoning"),
69
+ ];
70
+ const result = mask(messages as any);
71
+ assert.equal((result[1].content as any)[0].text, "old reasoning");
72
+ assert.equal((result[3].content as any)[0].text, "new reasoning");
73
+ });
74
+
75
+ test("never masks user messages", () => {
76
+ const mask = createObservationMask(1);
77
+ const messages = [
78
+ userMsg("old user message"),
79
+ assistantMsg("response"),
80
+ userMsg("new user message"),
81
+ assistantMsg("response"),
82
+ ];
83
+ const result = mask(messages as any);
84
+ assert.equal((result[0].content as any)[0].text, "old user message");
85
+ });
86
+
87
+ test("masks bash result user messages", () => {
88
+ const mask = createObservationMask(1);
89
+ const messages = [
90
+ userMsg("turn 1"),
91
+ bashResult("huge log output"),
92
+ assistantMsg("response 1"),
93
+ userMsg("turn 2"),
94
+ assistantMsg("response 2"),
95
+ ];
96
+ const result = mask(messages as any);
97
+ assert.equal((result[1].content as any)[0].text, MASK_TEXT);
98
+ });
99
+
100
+ test("returns same array length", () => {
101
+ const mask = createObservationMask(1);
102
+ const messages = [
103
+ userMsg("a"), toolResult("b"), assistantMsg("c"),
104
+ userMsg("d"), toolResult("e"), assistantMsg("f"),
105
+ ];
106
+ const result = mask(messages as any);
107
+ assert.equal(result.length, messages.length);
108
+ });
109
+
110
+ test("masks toolResult by role, not by type field", () => {
111
+ const mask = createObservationMask(1);
112
+ const messages = [
113
+ userMsg("turn 1"),
114
+ // This is the actual pi-ai format: role "toolResult", no type field
115
+ { role: "toolResult", content: [{ type: "text", text: "old result" }], toolCallId: "t1", toolName: "Read", isError: false },
116
+ assistantMsg("response 1"),
117
+ userMsg("turn 2"),
118
+ assistantMsg("response 2"),
119
+ ];
120
+ const result = mask(messages as any);
121
+ assert.equal((result[1].content as any)[0].text, MASK_TEXT);
122
+ });
@@ -94,11 +94,11 @@ const dynamicToolsSrc = readFileSync(
94
94
  "utf-8",
95
95
  );
96
96
 
97
- // ensureDbOpen should return a structured result, not just boolean false
98
- // Check that the catch block provides diagnostic information
97
+ // ensureDbOpen should surface diagnostic context, not just boolean false
98
+ // Check that the catch block logs error details via workflow-logger
99
99
  assertTrue(
100
- dynamicToolsSrc.includes("resolvedPath") || dynamicToolsSrc.includes("diagnostic"),
101
- "ensureDbOpen catch block surfaces diagnostic information (resolvedPath or diagnostic) instead of bare false (#2517)",
100
+ dynamicToolsSrc.includes("ensureDbOpen failed") && dynamicToolsSrc.includes("logWarning"),
101
+ "ensureDbOpen catch block surfaces diagnostic information via logWarning instead of bare false (#2517)",
102
102
  );
103
103
 
104
104
  // ── Part 3: post-unit does NOT artifact-retry on db_unavailable ──────────