gsd-pi 2.65.0 → 2.66.0-dev.e159299

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 (462) hide show
  1. package/dist/mcp-server.js +6 -2
  2. package/dist/resources/extensions/browser-tools/capture.js +20 -1
  3. package/dist/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +93 -0
  4. package/dist/resources/extensions/gsd/auto/finalize-timeout.js +2 -0
  5. package/dist/resources/extensions/gsd/auto/loop.js +2 -2
  6. package/dist/resources/extensions/gsd/auto/phases.js +48 -5
  7. package/dist/resources/extensions/gsd/auto/run-unit.js +13 -2
  8. package/dist/resources/extensions/gsd/auto/session.js +4 -0
  9. package/dist/resources/extensions/gsd/auto/types.js +2 -0
  10. package/dist/resources/extensions/gsd/auto-dashboard.js +2 -1
  11. package/dist/resources/extensions/gsd/auto-dispatch.js +99 -9
  12. package/dist/resources/extensions/gsd/auto-model-selection.js +7 -5
  13. package/dist/resources/extensions/gsd/auto-post-unit.js +17 -6
  14. package/dist/resources/extensions/gsd/auto-prompts.js +24 -0
  15. package/dist/resources/extensions/gsd/auto-recovery.js +40 -22
  16. package/dist/resources/extensions/gsd/auto-start.js +175 -12
  17. package/dist/resources/extensions/gsd/auto-tool-tracking.js +10 -0
  18. package/dist/resources/extensions/gsd/auto-worktree.js +29 -7
  19. package/dist/resources/extensions/gsd/auto.js +21 -15
  20. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +17 -4
  21. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +10 -0
  22. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +6 -4
  23. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +5 -1
  24. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +11 -3
  25. package/dist/resources/extensions/gsd/bootstrap/system-context.js +3 -1
  26. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +31 -1
  27. package/dist/resources/extensions/gsd/commands/context.js +8 -1
  28. package/dist/resources/extensions/gsd/commands/handlers/core.js +23 -2
  29. package/dist/resources/extensions/gsd/commands-extensions.js +1 -1
  30. package/dist/resources/extensions/gsd/config-overlay.js +312 -0
  31. package/dist/resources/extensions/gsd/db-writer.js +13 -3
  32. package/dist/resources/extensions/gsd/detection.js +1 -1
  33. package/dist/resources/extensions/gsd/dispatch-guard.js +2 -1
  34. package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -0
  35. package/dist/resources/extensions/gsd/doctor.js +2 -1
  36. package/dist/resources/extensions/gsd/files.js +17 -0
  37. package/dist/resources/extensions/gsd/gitignore.js +1 -0
  38. package/dist/resources/extensions/gsd/gsd-db.js +47 -4
  39. package/dist/resources/extensions/gsd/guided-flow.js +220 -29
  40. package/dist/resources/extensions/gsd/index.js +1 -1
  41. package/dist/resources/extensions/gsd/json-persistence.js +5 -2
  42. package/dist/resources/extensions/gsd/md-importer.js +14 -7
  43. package/dist/resources/extensions/gsd/notification-overlay.js +1 -1
  44. package/dist/resources/extensions/gsd/notification-widget.js +2 -1
  45. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +1 -1
  46. package/dist/resources/extensions/gsd/parallel-orchestrator.js +17 -11
  47. package/dist/resources/extensions/gsd/pre-execution-checks.js +26 -5
  48. package/dist/resources/extensions/gsd/preferences-types.js +3 -0
  49. package/dist/resources/extensions/gsd/preferences-validation.js +45 -1
  50. package/dist/resources/extensions/gsd/preferences.js +9 -2
  51. package/dist/resources/extensions/gsd/preparation.js +1092 -0
  52. package/dist/resources/extensions/gsd/prompt-validation.js +67 -0
  53. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +3 -3
  54. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  55. package/dist/resources/extensions/gsd/prompts/discuss-prepared.md +424 -0
  56. package/dist/resources/extensions/gsd/prompts/discuss.md +2 -0
  57. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +6 -1
  58. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +5 -4
  59. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +23 -0
  60. package/dist/resources/extensions/gsd/prompts/queue.md +2 -0
  61. package/dist/resources/extensions/gsd/prompts/rethink.md +2 -1
  62. package/dist/resources/extensions/gsd/prompts/system.md +2 -2
  63. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +56 -23
  64. package/dist/resources/extensions/gsd/quick.js +19 -15
  65. package/dist/resources/extensions/gsd/reactive-graph.js +12 -0
  66. package/dist/resources/extensions/gsd/roadmap-slices.js +24 -5
  67. package/dist/resources/extensions/gsd/safety/content-validator.js +3 -3
  68. package/dist/resources/extensions/gsd/session-lock.js +23 -1
  69. package/dist/resources/extensions/gsd/state.js +115 -28
  70. package/dist/resources/extensions/gsd/templates/context-enhanced.md +138 -0
  71. package/dist/resources/extensions/gsd/tools/complete-milestone.js +15 -3
  72. package/dist/resources/extensions/gsd/tools/complete-slice.js +27 -6
  73. package/dist/resources/extensions/gsd/tools/complete-task.js +31 -7
  74. package/dist/resources/extensions/gsd/tools/plan-milestone.js +7 -5
  75. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +5 -2
  76. package/dist/resources/extensions/gsd/tools/reopen-milestone.js +119 -0
  77. package/dist/resources/extensions/gsd/tools/reopen-slice.js +30 -0
  78. package/dist/resources/extensions/gsd/tools/reopen-task.js +18 -0
  79. package/dist/resources/extensions/gsd/triage-resolution.js +33 -16
  80. package/dist/resources/extensions/gsd/undo.js +3 -2
  81. package/dist/resources/extensions/gsd/workflow-events.js +1 -0
  82. package/dist/resources/extensions/gsd/workflow-logger.js +1 -1
  83. package/dist/resources/extensions/gsd/workflow-projections.js +7 -9
  84. package/dist/resources/extensions/gsd/workflow-reconcile.js +100 -9
  85. package/dist/resources/extensions/gsd/workflow-templates.js +11 -2
  86. package/dist/resources/extensions/gsd/worktree-manager.js +5 -2
  87. package/dist/resources/extensions/gsd/worktree.js +9 -0
  88. package/dist/resources/extensions/shared/interview-ui.js +1 -1
  89. package/dist/resources/extensions/subagent/agents.js +19 -5
  90. package/dist/web/standalone/.next/BUILD_ID +1 -1
  91. package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
  92. package/dist/web/standalone/.next/build-manifest.json +4 -4
  93. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  94. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  95. package/dist/web/standalone/.next/required-server-files.json +3 -3
  96. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  97. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  99. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  100. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  102. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  104. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  106. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  107. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  108. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  109. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  110. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  111. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  112. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  113. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  114. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  115. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  116. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  123. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  135. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  137. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  143. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  155. package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  157. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  159. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  161. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  163. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  164. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  165. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  166. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  168. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  169. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  170. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  171. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  172. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  173. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  175. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  176. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  177. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  178. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  179. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  180. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  181. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  182. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  183. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  184. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  185. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  186. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  187. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  188. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  189. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  190. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  191. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  192. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  193. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  194. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  195. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  196. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  197. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  198. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  199. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  200. package/dist/web/standalone/.next/server/app/index.html +1 -1
  201. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  202. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  203. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  204. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  205. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  206. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  207. package/dist/web/standalone/.next/server/app/page.js +2 -2
  208. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  209. package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
  210. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  211. package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
  212. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  213. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  214. package/dist/web/standalone/.next/server/middleware.js +2 -2
  215. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  216. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  217. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  218. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  219. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  220. package/dist/web/standalone/.next/static/chunks/6502.8874bcae249c02e1.js +9 -0
  221. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  222. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  223. package/dist/web/standalone/.next/static/chunks/app/page-0c485498795110d6.js +1 -0
  224. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  225. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  226. package/dist/web/standalone/.next/static/chunks/{webpack-a1c1e452c6b32d04.js → webpack-9fed74684e1c5bb1.js} +1 -1
  227. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  228. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  229. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  230. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  231. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  232. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  233. package/dist/web/standalone/server.js +1 -1
  234. package/package.json +1 -1
  235. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  236. package/packages/pi-coding-agent/dist/core/retry-handler.js +30 -19
  237. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  238. package/packages/pi-coding-agent/dist/core/retry-handler.test.js +51 -0
  239. package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
  240. package/packages/pi-coding-agent/dist/core/sdk.js +9 -9
  241. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  242. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts +2 -1
  243. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
  244. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +10 -1
  245. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
  246. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
  247. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  248. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +20 -5
  249. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  250. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +15 -1
  251. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  252. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +18 -0
  253. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
  254. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  255. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +4 -0
  256. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  257. package/packages/pi-coding-agent/package.json +1 -1
  258. package/packages/pi-coding-agent/src/core/retry-handler.test.ts +80 -0
  259. package/packages/pi-coding-agent/src/core/retry-handler.ts +37 -25
  260. package/packages/pi-coding-agent/src/core/sdk.ts +9 -9
  261. package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +10 -0
  262. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +20 -4
  263. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +27 -0
  264. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +16 -1
  265. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +5 -0
  266. package/packages/pi-tui/dist/components/image.d.ts +2 -0
  267. package/packages/pi-tui/dist/components/image.d.ts.map +1 -1
  268. package/packages/pi-tui/dist/components/image.js +4 -0
  269. package/packages/pi-tui/dist/components/image.js.map +1 -1
  270. package/packages/pi-tui/dist/components/image.test.d.ts +6 -0
  271. package/packages/pi-tui/dist/components/image.test.d.ts.map +1 -0
  272. package/packages/pi-tui/dist/components/image.test.js +32 -0
  273. package/packages/pi-tui/dist/components/image.test.js.map +1 -0
  274. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  275. package/packages/pi-tui/dist/tui.js +3 -1
  276. package/packages/pi-tui/dist/tui.js.map +1 -1
  277. package/packages/pi-tui/src/components/image.test.ts +36 -0
  278. package/packages/pi-tui/src/components/image.ts +5 -0
  279. package/packages/pi-tui/src/tui.ts +3 -1
  280. package/pkg/package.json +1 -1
  281. package/src/resources/extensions/browser-tools/capture.ts +19 -1
  282. package/src/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +93 -0
  283. package/src/resources/extensions/gsd/auto/finalize-timeout.ts +3 -0
  284. package/src/resources/extensions/gsd/auto/loop.ts +2 -2
  285. package/src/resources/extensions/gsd/auto/phases.ts +68 -3
  286. package/src/resources/extensions/gsd/auto/run-unit.ts +12 -2
  287. package/src/resources/extensions/gsd/auto/session.ts +4 -0
  288. package/src/resources/extensions/gsd/auto/types.ts +5 -0
  289. package/src/resources/extensions/gsd/auto-dashboard.ts +2 -1
  290. package/src/resources/extensions/gsd/auto-dispatch.ts +110 -9
  291. package/src/resources/extensions/gsd/auto-model-selection.ts +7 -5
  292. package/src/resources/extensions/gsd/auto-post-unit.ts +16 -6
  293. package/src/resources/extensions/gsd/auto-prompts.ts +31 -0
  294. package/src/resources/extensions/gsd/auto-recovery.ts +29 -23
  295. package/src/resources/extensions/gsd/auto-start.ts +188 -10
  296. package/src/resources/extensions/gsd/auto-tool-tracking.ts +10 -0
  297. package/src/resources/extensions/gsd/auto-worktree.ts +28 -7
  298. package/src/resources/extensions/gsd/auto.ts +19 -8
  299. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +16 -4
  300. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +10 -0
  301. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +5 -4
  302. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +4 -1
  303. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +11 -3
  304. package/src/resources/extensions/gsd/bootstrap/system-context.ts +3 -1
  305. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +36 -1
  306. package/src/resources/extensions/gsd/commands/context.ts +7 -1
  307. package/src/resources/extensions/gsd/commands/handlers/core.ts +26 -2
  308. package/src/resources/extensions/gsd/commands-extensions.ts +1 -1
  309. package/src/resources/extensions/gsd/config-overlay.ts +331 -0
  310. package/src/resources/extensions/gsd/db-writer.ts +11 -3
  311. package/src/resources/extensions/gsd/detection.ts +1 -1
  312. package/src/resources/extensions/gsd/dispatch-guard.ts +2 -1
  313. package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -0
  314. package/src/resources/extensions/gsd/doctor.ts +2 -1
  315. package/src/resources/extensions/gsd/files.ts +19 -0
  316. package/src/resources/extensions/gsd/gitignore.ts +1 -0
  317. package/src/resources/extensions/gsd/gsd-db.ts +46 -4
  318. package/src/resources/extensions/gsd/guided-flow.ts +254 -30
  319. package/src/resources/extensions/gsd/index.ts +1 -0
  320. package/src/resources/extensions/gsd/json-persistence.ts +6 -3
  321. package/src/resources/extensions/gsd/md-importer.ts +13 -6
  322. package/src/resources/extensions/gsd/notification-overlay.ts +1 -1
  323. package/src/resources/extensions/gsd/notification-widget.ts +2 -1
  324. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +1 -1
  325. package/src/resources/extensions/gsd/parallel-orchestrator.ts +19 -11
  326. package/src/resources/extensions/gsd/pre-execution-checks.ts +32 -7
  327. package/src/resources/extensions/gsd/preferences-types.ts +25 -0
  328. package/src/resources/extensions/gsd/preferences-validation.ts +45 -1
  329. package/src/resources/extensions/gsd/preferences.ts +9 -2
  330. package/src/resources/extensions/gsd/preparation.ts +1419 -0
  331. package/src/resources/extensions/gsd/prompt-validation.ts +88 -0
  332. package/src/resources/extensions/gsd/prompts/complete-milestone.md +3 -3
  333. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  334. package/src/resources/extensions/gsd/prompts/discuss-prepared.md +424 -0
  335. package/src/resources/extensions/gsd/prompts/discuss.md +2 -0
  336. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +6 -1
  337. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +5 -4
  338. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +23 -0
  339. package/src/resources/extensions/gsd/prompts/queue.md +2 -0
  340. package/src/resources/extensions/gsd/prompts/rethink.md +2 -1
  341. package/src/resources/extensions/gsd/prompts/system.md +2 -2
  342. package/src/resources/extensions/gsd/prompts/validate-milestone.md +56 -23
  343. package/src/resources/extensions/gsd/quick.ts +20 -15
  344. package/src/resources/extensions/gsd/reactive-graph.ts +18 -0
  345. package/src/resources/extensions/gsd/roadmap-slices.ts +21 -5
  346. package/src/resources/extensions/gsd/safety/content-validator.ts +3 -3
  347. package/src/resources/extensions/gsd/session-lock.ts +17 -1
  348. package/src/resources/extensions/gsd/state.ts +115 -26
  349. package/src/resources/extensions/gsd/templates/context-enhanced.md +138 -0
  350. package/src/resources/extensions/gsd/tests/adversarial-review-fixes.test.ts +223 -0
  351. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +33 -2
  352. package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +56 -0
  353. package/src/resources/extensions/gsd/tests/clear-stale-autostart.test.ts +41 -0
  354. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +72 -0
  355. package/src/resources/extensions/gsd/tests/complete-task-normalize-lists.test.ts +54 -0
  356. package/src/resources/extensions/gsd/tests/defer-milestone-stamp.test.ts +30 -0
  357. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +4 -3
  358. package/src/resources/extensions/gsd/tests/discuss-incremental-persistence.test.ts +36 -0
  359. package/src/resources/extensions/gsd/tests/discuss-slice-structured-questions.test.ts +46 -0
  360. package/src/resources/extensions/gsd/tests/dispatch-guard-closed-status.test.ts +33 -0
  361. package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +37 -0
  362. package/src/resources/extensions/gsd/tests/error-success-mask.test.ts +37 -0
  363. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +125 -0
  364. package/src/resources/extensions/gsd/tests/find-missing-summaries-closed.test.ts +48 -0
  365. package/src/resources/extensions/gsd/tests/format-shortcut.test.ts +69 -0
  366. package/src/resources/extensions/gsd/tests/frontmatter-parse-noise.test.ts +42 -0
  367. package/src/resources/extensions/gsd/tests/gitignore-bg-shell.test.ts +38 -0
  368. package/src/resources/extensions/gsd/tests/guided-flow-state-rebuild.test.ts +103 -0
  369. package/src/resources/extensions/gsd/tests/import-done-milestones.test.ts +42 -0
  370. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +11 -9
  371. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +4 -2
  372. package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +28 -30
  373. package/src/resources/extensions/gsd/tests/integration/test-isolation.ts +53 -0
  374. package/src/resources/extensions/gsd/tests/integration-prepared-discussion.test.ts +525 -0
  375. package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +62 -0
  376. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +11 -10
  377. package/src/resources/extensions/gsd/tests/needs-remediation-revalidation.test.ts +48 -0
  378. package/src/resources/extensions/gsd/tests/note-captures-executed.test.ts +46 -0
  379. package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +189 -0
  380. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +77 -0
  381. package/src/resources/extensions/gsd/tests/phantom-ghost-detection.test.ts +55 -0
  382. package/src/resources/extensions/gsd/tests/phantom-milestone-default-queued.test.ts +39 -0
  383. package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +68 -0
  384. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +284 -20
  385. package/src/resources/extensions/gsd/tests/pre-execution-fail-closed.test.ts +2 -2
  386. package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +2 -2
  387. package/src/resources/extensions/gsd/tests/preparation.test.ts +1211 -0
  388. package/src/resources/extensions/gsd/tests/project-root-cwd-crash.test.ts +53 -0
  389. package/src/resources/extensions/gsd/tests/projection-no-plan-overwrite.test.ts +83 -0
  390. package/src/resources/extensions/gsd/tests/prompt-builder.test.ts +669 -0
  391. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +7 -4
  392. package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +85 -0
  393. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +2 -1
  394. package/src/resources/extensions/gsd/tests/query-tools-db-open.test.ts +47 -0
  395. package/src/resources/extensions/gsd/tests/queued-discuss-fast-path.test.ts +107 -0
  396. package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +45 -0
  397. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +63 -0
  398. package/src/resources/extensions/gsd/tests/rogue-file-detection.test.ts +4 -5
  399. package/src/resources/extensions/gsd/tests/run-uat-replay-cap.test.ts +51 -0
  400. package/src/resources/extensions/gsd/tests/show-config-command.test.ts +56 -0
  401. package/src/resources/extensions/gsd/tests/skip-slice-state-rebuild.test.ts +31 -0
  402. package/src/resources/extensions/gsd/tests/skipped-validation-completion.test.ts +39 -0
  403. package/src/resources/extensions/gsd/tests/slice-sequence-insert.test.ts +51 -0
  404. package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +1 -1
  405. package/src/resources/extensions/gsd/tests/stale-lockfile-recovery.test.ts +36 -0
  406. package/src/resources/extensions/gsd/tests/stale-queued-milestone.test.ts +147 -0
  407. package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +13 -0
  408. package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +21 -0
  409. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +21 -0
  410. package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +6 -7
  411. package/src/resources/extensions/gsd/tests/status-db-open.test.ts +47 -0
  412. package/src/resources/extensions/gsd/tests/stuck-detection-coverage.test.ts +1 -0
  413. package/src/resources/extensions/gsd/tests/subagent-agent-discovery.test.ts +47 -0
  414. package/src/resources/extensions/gsd/tests/symlink-extension-discovery.test.ts +125 -0
  415. package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +65 -0
  416. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +29 -1
  417. package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +2 -1
  418. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +3 -4
  419. package/src/resources/extensions/gsd/tests/verification-operational-gate.test.ts +15 -0
  420. package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +89 -0
  421. package/src/resources/extensions/gsd/tests/wave1-critical-regressions.test.ts +49 -0
  422. package/src/resources/extensions/gsd/tests/wave2-events-regressions.test.ts +48 -0
  423. package/src/resources/extensions/gsd/tests/wave3-session-regressions.test.ts +47 -0
  424. package/src/resources/extensions/gsd/tests/wave4-write-safety-regressions.test.ts +70 -0
  425. package/src/resources/extensions/gsd/tests/wave5-consistency-regressions.test.ts +165 -0
  426. package/src/resources/extensions/gsd/tests/worker-model-override.test.ts +48 -0
  427. package/src/resources/extensions/gsd/tests/workflow-logger-audit.test.ts +6 -3
  428. package/src/resources/extensions/gsd/tests/worktree-expected-warnings.test.ts +38 -0
  429. package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +16 -0
  430. package/src/resources/extensions/gsd/tests/worktree-main-branch.test.ts +20 -0
  431. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +16 -17
  432. package/src/resources/extensions/gsd/tests/worktree-sync-tasks.test.ts +13 -9
  433. package/src/resources/extensions/gsd/tests/worktree.test.ts +26 -9
  434. package/src/resources/extensions/gsd/tests/write-gate.test.ts +127 -2
  435. package/src/resources/extensions/gsd/tests/zero-slice-roadmap-guided.test.ts +19 -0
  436. package/src/resources/extensions/gsd/tools/complete-milestone.ts +13 -3
  437. package/src/resources/extensions/gsd/tools/complete-slice.ts +26 -6
  438. package/src/resources/extensions/gsd/tools/complete-task.ts +29 -7
  439. package/src/resources/extensions/gsd/tools/plan-milestone.ts +11 -9
  440. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +5 -2
  441. package/src/resources/extensions/gsd/tools/reopen-milestone.ts +152 -0
  442. package/src/resources/extensions/gsd/tools/reopen-slice.ts +27 -0
  443. package/src/resources/extensions/gsd/tools/reopen-task.ts +17 -0
  444. package/src/resources/extensions/gsd/triage-resolution.ts +37 -17
  445. package/src/resources/extensions/gsd/types.ts +4 -0
  446. package/src/resources/extensions/gsd/undo.ts +3 -2
  447. package/src/resources/extensions/gsd/workflow-events.ts +5 -3
  448. package/src/resources/extensions/gsd/workflow-logger.ts +1 -1
  449. package/src/resources/extensions/gsd/workflow-projections.ts +7 -8
  450. package/src/resources/extensions/gsd/workflow-reconcile.ts +109 -8
  451. package/src/resources/extensions/gsd/workflow-templates.ts +11 -2
  452. package/src/resources/extensions/gsd/worktree-manager.ts +4 -2
  453. package/src/resources/extensions/gsd/worktree.ts +10 -0
  454. package/src/resources/extensions/shared/interview-ui.ts +1 -1
  455. package/src/resources/extensions/shared/tests/interview-notes-loop.test.ts +8 -10
  456. package/src/resources/extensions/subagent/agents.ts +30 -6
  457. package/dist/web/standalone/.next/static/chunks/6502.7593d7797a4b3999.js +0 -9
  458. package/dist/web/standalone/.next/static/chunks/app/page-62be3b5fa91e4c8f.js +0 -1
  459. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  460. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  461. /package/dist/web/standalone/.next/static/{MRM3OSYIAa4HMDqVGQ9nt → h8aBiLMFjb__ogynY08cm}/_buildManifest.js +0 -0
  462. /package/dist/web/standalone/.next/static/{MRM3OSYIAa4HMDqVGQ9nt → h8aBiLMFjb__ogynY08cm}/_ssgManifest.js +0 -0
@@ -0,0 +1,1419 @@
1
+ /**
2
+ * GSD Preparation — Structured brief generation for discussion LLM sessions.
3
+ *
4
+ * Produces structured briefs (codebase, prior context, ecosystem) before
5
+ * the discussion LLM session starts.
6
+ *
7
+ * Pure functions, zero UI dependencies (except for runPreparation orchestrator).
8
+ */
9
+
10
+ import { readdirSync, readFileSync, statSync, openSync, readSync, closeSync } from "node:fs";
11
+ import { join, relative } from "node:path";
12
+ import { readdirSync as readdirSyncNode } from "node:fs";
13
+ import {
14
+ detectProjectSignals,
15
+ scanProjectFiles,
16
+ PROJECT_FILES,
17
+ type ProjectSignals,
18
+ } from "./detection.js";
19
+ import { loadFile } from "./files.js";
20
+
21
+ // ─── Types ──────────────────────────────────────────────────────────────────────
22
+
23
+ /** Detected patterns in the codebase. */
24
+ export interface CodePatterns {
25
+ /** Primary async style: "async/await" | "callbacks" | "promises" | "mixed" */
26
+ asyncStyle: "async/await" | "callbacks" | "promises" | "mixed" | "unknown";
27
+ /** Primary error handling: "try/catch" | "error-callbacks" | "result-types" | "mixed" */
28
+ errorHandling: "try/catch" | "error-callbacks" | "result-types" | "mixed" | "unknown";
29
+ /** Primary naming convention: "camelCase" | "snake_case" | "PascalCase" | "mixed" */
30
+ namingConvention: "camelCase" | "snake_case" | "PascalCase" | "mixed" | "unknown";
31
+ /** Sample evidence strings for each pattern (for debugging/transparency) */
32
+ evidence: {
33
+ asyncStyle: string[];
34
+ errorHandling: string[];
35
+ namingConvention: string[];
36
+ };
37
+ /** File counts for each pattern type (for formatted output) */
38
+ fileCounts: {
39
+ asyncAwait: number;
40
+ promises: number;
41
+ callbacks: number;
42
+ tryCatch: number;
43
+ errorCallbacks: number;
44
+ resultTypes: number;
45
+ };
46
+ }
47
+
48
+ /** Language-specific pattern detection configuration. */
49
+ export interface LanguagePatternEntry {
50
+ /** Display name for the language (e.g., "JavaScript/TypeScript") */
51
+ displayName: string;
52
+ /** File extensions to sample for this language */
53
+ extensions: string[];
54
+ /** Async style detection patterns */
55
+ asyncStyle: {
56
+ modern: RegExp;
57
+ modernLabel: string;
58
+ legacy: RegExp;
59
+ legacyLabel: string;
60
+ };
61
+ /** Error handling detection patterns */
62
+ errorHandling: {
63
+ structured: RegExp;
64
+ structuredLabel: string;
65
+ inline: RegExp;
66
+ inlineLabel: string;
67
+ };
68
+ }
69
+
70
+ /** Module structure detected in the codebase. */
71
+ export interface ModuleStructure {
72
+ /** Top-level directories found (e.g., ["src", "lib", "test"]) */
73
+ topLevelDirs: string[];
74
+ /** Subdirectories within src/ or lib/ (e.g., ["components", "utils", "hooks"]) */
75
+ srcSubdirs: string[];
76
+ /** Total file count sampled */
77
+ totalFilesSampled: number;
78
+ }
79
+
80
+ /** A single decision entry parsed from DECISIONS.md. */
81
+ export interface DecisionEntry {
82
+ id: string;
83
+ scope: string;
84
+ decision: string;
85
+ choice: string;
86
+ rationale: string;
87
+ }
88
+
89
+ /** A single requirement entry parsed from REQUIREMENTS.md. */
90
+ export interface RequirementEntry {
91
+ id: string;
92
+ description: string;
93
+ status: "active" | "validated" | "deferred" | "out-of-scope";
94
+ }
95
+
96
+ /** Prior context brief aggregated from GSD artifacts. */
97
+ export interface PriorContextBrief {
98
+ /** Decisions grouped by scope. */
99
+ decisions: {
100
+ byScope: Map<string, DecisionEntry[]>;
101
+ totalCount: number;
102
+ };
103
+ /** Requirements grouped by status. */
104
+ requirements: {
105
+ active: RequirementEntry[];
106
+ validated: RequirementEntry[];
107
+ deferred: RequirementEntry[];
108
+ totalCount: number;
109
+ };
110
+ /** Knowledge entries (raw content, truncated). */
111
+ knowledge: string;
112
+ /** Prior milestone summaries (combined, truncated). */
113
+ summaries: string;
114
+ }
115
+
116
+ /** Codebase analysis brief. */
117
+ export interface CodebaseBrief {
118
+ /** Tech stack and language from detectProjectSignals */
119
+ techStack: {
120
+ primaryLanguage?: string;
121
+ detectedFiles: string[];
122
+ packageManager?: string;
123
+ isMonorepo: boolean;
124
+ hasTests: boolean;
125
+ hasCI: boolean;
126
+ };
127
+ /** Module structure */
128
+ moduleStructure: ModuleStructure;
129
+ /** Detected code patterns */
130
+ patterns: CodePatterns;
131
+ /** Source files that were sampled for pattern extraction */
132
+ sampledFiles: string[];
133
+ }
134
+
135
+ /** A single ecosystem research finding. */
136
+ export interface EcosystemFinding {
137
+ /** Query that produced this finding */
138
+ query: string;
139
+ /** Title or snippet from search result */
140
+ title: string;
141
+ /** URL source */
142
+ url?: string;
143
+ /** Brief content snippet */
144
+ snippet: string;
145
+ }
146
+
147
+ /** Ecosystem research brief from web search. */
148
+ export interface EcosystemBrief {
149
+ /** Whether ecosystem research was performed */
150
+ available: boolean;
151
+ /** Search queries that were executed */
152
+ queries: string[];
153
+ /** Aggregated findings from search results */
154
+ findings: EcosystemFinding[];
155
+ /** Reason why research was skipped (if available === false) */
156
+ skippedReason?: string;
157
+ /** Which search provider was used */
158
+ provider?: string;
159
+ }
160
+
161
+ // ─── Constants ──────────────────────────────────────────────────────────────────
162
+
163
+ /** Maximum characters for the codebase section. */
164
+ const MAX_CODEBASE_BRIEF_CHARS = 3000;
165
+
166
+ /** Number of files to sample for pattern extraction. */
167
+ const SAMPLE_FILE_COUNT = 5;
168
+
169
+ /** Maximum bytes to read from each sampled file. */
170
+ const MAX_FILE_SAMPLE_BYTES = 8192;
171
+
172
+ /** Directories to skip when sampling. */
173
+ const SKIP_DIRS = new Set([
174
+ "node_modules",
175
+ "dist",
176
+ "build",
177
+ ".git",
178
+ "coverage",
179
+ ".next",
180
+ ".nuxt",
181
+ "target",
182
+ ".turbo",
183
+ "vendor",
184
+ "__pycache__",
185
+ ".venv",
186
+ "venv",
187
+ ]);
188
+
189
+ /** File patterns to exclude when sampling. */
190
+ const EXCLUDE_PATTERNS = [
191
+ /\.test\.(ts|tsx|js|jsx|mjs|cjs)$/,
192
+ /\.spec\.(ts|tsx|js|jsx|mjs|cjs)$/,
193
+ /\.d\.ts$/,
194
+ /test-.*\.(ts|tsx|js|jsx)$/,
195
+ /.*\.min\.(js|css)$/,
196
+ ];
197
+
198
+ /** File extensions to sample for pattern extraction (JS/TS default). */
199
+ const SAMPLE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
200
+
201
+ /** Common source file extensions for universal pattern detection (naming convention).
202
+ * Used when the language is not in LANGUAGE_PATTERNS but we still want to detect camelCase/snake_case. */
203
+ const UNIVERSAL_SOURCE_EXTENSIONS = [
204
+ // JavaScript/TypeScript
205
+ ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs",
206
+ // Python
207
+ ".py", ".pyw", ".pyi",
208
+ // Ruby
209
+ ".rb", ".rake", ".gemspec",
210
+ // Go
211
+ ".go",
212
+ // Rust
213
+ ".rs",
214
+ // Java/Kotlin
215
+ ".java", ".kt", ".kts",
216
+ // C/C++
217
+ ".c", ".cpp", ".cc", ".cxx", ".h", ".hpp",
218
+ // C#
219
+ ".cs",
220
+ // Swift
221
+ ".swift",
222
+ // PHP
223
+ ".php",
224
+ // Scala
225
+ ".scala",
226
+ // Elixir/Erlang
227
+ ".ex", ".exs", ".erl",
228
+ // Haskell
229
+ ".hs", ".lhs",
230
+ // Shell
231
+ ".sh", ".bash", ".zsh",
232
+ // Lua
233
+ ".lua",
234
+ // Dart
235
+ ".dart",
236
+ ];
237
+
238
+ // ─── Pattern Detection Regexes ──────────────────────────────────────────────────
239
+
240
+ /** Async/await usage patterns. */
241
+ const ASYNC_AWAIT_RE = /\basync\s+function\b|\basync\s*\(|\bawait\s+/g;
242
+
243
+ /** Callback-style patterns (common patterns like done, callback, cb). */
244
+ const CALLBACK_RE = /\b(callback|cb|done)\s*\(|\bfunction\s*\([^)]*\bfunction\b/g;
245
+
246
+ /** Promise patterns (.then, .catch, new Promise). */
247
+ const PROMISE_RE = /\.then\s*\(|\.catch\s*\(|\bnew\s+Promise\s*\(/g;
248
+
249
+ /** Try/catch patterns. */
250
+ const TRY_CATCH_RE = /\btry\s*\{[\s\S]*?\bcatch\s*\(/g;
251
+
252
+ /** Error-first callback patterns. */
253
+ const ERROR_CALLBACK_RE = /\bif\s*\(\s*(err|error)\s*\)|\(err(or)?\s*,/g;
254
+
255
+ /** Result type patterns (Rust-style, fp-ts, etc.). */
256
+ const RESULT_TYPE_RE = /\bResult<|\bEither<|\bisOk\(|\bisErr\(|\b(Ok|Err)\(/g;
257
+
258
+ /** camelCase identifier patterns. */
259
+ const CAMEL_CASE_RE = /\b[a-z][a-zA-Z0-9]*[A-Z][a-zA-Z0-9]*\b/g;
260
+
261
+ /** snake_case identifier patterns. */
262
+ const SNAKE_CASE_RE = /\b[a-z][a-z0-9]*_[a-z0-9_]+\b/g;
263
+
264
+ /** PascalCase identifier patterns (for types/classes). */
265
+ const PASCAL_CASE_RE = /\bclass\s+[A-Z][a-zA-Z0-9]*|\binterface\s+[A-Z][a-zA-Z0-9]*|\btype\s+[A-Z][a-zA-Z0-9]*/g;
266
+
267
+ // ─── Language Pattern Registry ──────────────────────────────────────────────────
268
+
269
+ /**
270
+ * Registry of language-specific patterns for code analysis.
271
+ * Keys MUST match detection.ts LANGUAGE_MAP values exactly.
272
+ */
273
+ export const LANGUAGE_PATTERNS: Record<string, LanguagePatternEntry> = {
274
+ "javascript/typescript": {
275
+ displayName: "JavaScript/TypeScript",
276
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
277
+ asyncStyle: {
278
+ modern: /\basync\s+function\b|\basync\s*\(|\bawait\s+/g,
279
+ modernLabel: "async/await",
280
+ legacy: /\.then\s*\(|\.catch\s*\(|\bnew\s+Promise\s*\(/g,
281
+ legacyLabel: "promises",
282
+ },
283
+ errorHandling: {
284
+ structured: /\btry\s*\{[\s\S]*?\bcatch\s*\(/g,
285
+ structuredLabel: "try/catch",
286
+ inline: /\bif\s*\(\s*(err|error)\s*\)|\(err(or)?\s*,/g,
287
+ inlineLabel: "error-callbacks",
288
+ },
289
+ },
290
+ python: {
291
+ displayName: "Python",
292
+ extensions: [".py", ".pyw", ".pyi"],
293
+ asyncStyle: {
294
+ modern: /\basync\s+def\b|\bawait\s+/g,
295
+ modernLabel: "async/await",
296
+ legacy: /\.add_done_callback\(|ThreadPoolExecutor|ProcessPoolExecutor/g,
297
+ legacyLabel: "futures/executors",
298
+ },
299
+ errorHandling: {
300
+ structured: /\btry\s*:[\s\S]*?\bexcept\b/g,
301
+ structuredLabel: "try/except",
302
+ inline: /\braise\s+\w+Error|\bassert\s+/g,
303
+ inlineLabel: "raise/assert",
304
+ },
305
+ },
306
+ rust: {
307
+ displayName: "Rust",
308
+ extensions: [".rs"],
309
+ asyncStyle: {
310
+ modern: /\basync\s+fn\b|\.await\b/g,
311
+ modernLabel: "async/await",
312
+ legacy: /\bthread::spawn\(|\bmpsc::/g,
313
+ legacyLabel: "threads/channels",
314
+ },
315
+ errorHandling: {
316
+ structured: /\bResult<|\bOption<|\?\s*;/g,
317
+ structuredLabel: "Result/Option",
318
+ inline: /\bunwrap\(\)|\bexpect\(/g,
319
+ inlineLabel: "unwrap/expect",
320
+ },
321
+ },
322
+ go: {
323
+ displayName: "Go",
324
+ extensions: [".go"],
325
+ asyncStyle: {
326
+ modern: /\bgo\s+func\b|\bgo\s+\w+\(/g,
327
+ modernLabel: "goroutines",
328
+ legacy: /\bchan\s+\w+|<-\s*\w+|\w+\s*<-/g,
329
+ legacyLabel: "channels",
330
+ },
331
+ errorHandling: {
332
+ structured: /\bif\s+err\s*!=\s*nil\b/g,
333
+ structuredLabel: "if err != nil",
334
+ inline: /\bpanic\(|\brecover\(\)/g,
335
+ inlineLabel: "panic/recover",
336
+ },
337
+ },
338
+ java: {
339
+ displayName: "Java",
340
+ extensions: [".java"],
341
+ asyncStyle: {
342
+ modern: /\bCompletableFuture<|\bCompletionStage<|\bthenApply\(/g,
343
+ modernLabel: "CompletableFuture",
344
+ legacy: /\bThread\s+\w+\s*=|\bnew\s+Thread\(|\bExecutorService\b/g,
345
+ legacyLabel: "threads/executors",
346
+ },
347
+ errorHandling: {
348
+ structured: /\btry\s*\{[\s\S]*?\bcatch\s*\(/g,
349
+ structuredLabel: "try/catch",
350
+ inline: /\bthrows\s+\w+Exception|\bthrow\s+new\s+\w+Exception/g,
351
+ inlineLabel: "throws/throw",
352
+ },
353
+ },
354
+ "java/kotlin": {
355
+ displayName: "Java/Kotlin",
356
+ extensions: [".java", ".kt", ".kts"],
357
+ asyncStyle: {
358
+ modern: /\bsuspend\s+fun\b|\blaunch\s*\{|\basync\s*\{|\bwithContext\(/g,
359
+ modernLabel: "coroutines",
360
+ legacy: /\bThread\s+\w+\s*=|\bnew\s+Thread\(|\bExecutorService\b|\bCompletableFuture</g,
361
+ legacyLabel: "threads/futures",
362
+ },
363
+ errorHandling: {
364
+ structured: /\btry\s*\{[\s\S]*?\bcatch\s*\(/g,
365
+ structuredLabel: "try/catch",
366
+ inline: /\bthrows\s+\w+Exception|\bthrow\s+\w+Exception|\brunCatching\s*\{/g,
367
+ inlineLabel: "throws/runCatching",
368
+ },
369
+ },
370
+ };
371
+
372
+ // ─── Core Functions ─────────────────────────────────────────────────────────────
373
+
374
+ /**
375
+ * Analyze the codebase and produce a structured brief.
376
+ *
377
+ * @param basePath - Root directory of the project
378
+ * @returns CodebaseBrief with tech stack, module structure, and patterns
379
+ */
380
+ export async function analyzeCodebase(basePath: string): Promise<CodebaseBrief> {
381
+ // Get project signals from detection.ts
382
+ const signals = detectProjectSignals(basePath);
383
+
384
+ // Detect module structure
385
+ const moduleStructure = detectModuleStructure(basePath);
386
+
387
+ // Sample files and extract patterns, passing primary language for language-aware detection
388
+ const sampledFiles = sampleSourceFiles(basePath, signals.primaryLanguage);
389
+ const patterns = extractPatterns(basePath, sampledFiles, signals.primaryLanguage);
390
+
391
+ return {
392
+ techStack: {
393
+ primaryLanguage: signals.primaryLanguage,
394
+ detectedFiles: signals.detectedFiles,
395
+ packageManager: signals.packageManager,
396
+ isMonorepo: signals.isMonorepo,
397
+ hasTests: signals.hasTests,
398
+ hasCI: signals.hasCI,
399
+ },
400
+ moduleStructure,
401
+ patterns,
402
+ sampledFiles,
403
+ };
404
+ }
405
+
406
+ /**
407
+ * Detect the module structure of the codebase.
408
+ *
409
+ * @param basePath - Root directory of the project
410
+ * @returns ModuleStructure with top-level and src subdirs
411
+ */
412
+ function detectModuleStructure(basePath: string): ModuleStructure {
413
+ const topLevelDirs: string[] = [];
414
+ const srcSubdirs: string[] = [];
415
+
416
+ try {
417
+ const entries = readdirSync(basePath, { withFileTypes: true });
418
+ for (const entry of entries) {
419
+ if (entry.isDirectory() && !entry.name.startsWith(".") && !SKIP_DIRS.has(entry.name)) {
420
+ topLevelDirs.push(entry.name);
421
+ }
422
+ }
423
+ } catch {
424
+ // Directory not readable
425
+ }
426
+
427
+ // Scan for subdirs in src/ or lib/
428
+ for (const srcDir of ["src", "lib", "app"]) {
429
+ const srcPath = join(basePath, srcDir);
430
+ try {
431
+ const entries = readdirSync(srcPath, { withFileTypes: true });
432
+ for (const entry of entries) {
433
+ if (entry.isDirectory() && !entry.name.startsWith(".") && !SKIP_DIRS.has(entry.name)) {
434
+ srcSubdirs.push(entry.name);
435
+ }
436
+ }
437
+ } catch {
438
+ // Directory doesn't exist or not readable
439
+ }
440
+ }
441
+
442
+ return {
443
+ topLevelDirs,
444
+ srcSubdirs: [...new Set(srcSubdirs)], // Dedupe
445
+ totalFilesSampled: 0, // Will be set after sampling
446
+ };
447
+ }
448
+
449
+ /**
450
+ * Sample source files from the codebase for pattern extraction.
451
+ *
452
+ * Prefers files in src/ directory, excludes test files and node_modules.
453
+ * Extension selection:
454
+ * - If language is in LANGUAGE_PATTERNS: use language-specific extensions
455
+ * - If language is undefined (no manifest): use JS/TS defaults (common case)
456
+ * - If language is set but not in LANGUAGE_PATTERNS: use UNIVERSAL_SOURCE_EXTENSIONS
457
+ * so we can still detect naming conventions even for unrecognized languages
458
+ *
459
+ * @param basePath - Root directory of the project
460
+ * @param primaryLanguage - Optional primary language identifier from detection.ts LANGUAGE_MAP
461
+ * @returns Array of relative file paths to sampled files
462
+ */
463
+ function sampleSourceFiles(basePath: string, primaryLanguage?: string): string[] {
464
+ // Use scanProjectFiles from detection.ts for bounded recursion
465
+ const allFiles = scanProjectFiles(basePath);
466
+
467
+ // Get extensions to sample based on language detection status
468
+ const languageEntry = primaryLanguage ? LANGUAGE_PATTERNS[primaryLanguage] : undefined;
469
+ let extensionsToSample: string[];
470
+
471
+ if (languageEntry) {
472
+ // Language is in registry — use its specific extensions
473
+ extensionsToSample = languageEntry.extensions;
474
+ } else if (primaryLanguage === undefined) {
475
+ // No language detected (no manifest) — use JS/TS defaults
476
+ extensionsToSample = SAMPLE_EXTENSIONS;
477
+ } else {
478
+ // Language detected but not in registry (e.g., Ruby, Haskell)
479
+ // Use universal extensions so we can still detect naming conventions
480
+ extensionsToSample = UNIVERSAL_SOURCE_EXTENSIONS;
481
+ }
482
+
483
+ // Filter to target language files, excluding tests and dist
484
+ const candidates = allFiles.filter((file) => {
485
+ // Check extension
486
+ const hasValidExtension = extensionsToSample.some((ext) => file.endsWith(ext));
487
+ if (!hasValidExtension) return false;
488
+
489
+ // Check exclusion patterns
490
+ for (const pattern of EXCLUDE_PATTERNS) {
491
+ if (pattern.test(file)) return false;
492
+ }
493
+
494
+ // Check for excluded directories in path
495
+ const parts = file.split(/[/\\]/);
496
+ for (const part of parts) {
497
+ if (SKIP_DIRS.has(part)) return false;
498
+ }
499
+
500
+ return true;
501
+ });
502
+
503
+ // Prioritize files in src/ directory
504
+ const srcFiles = candidates.filter((f) => f.startsWith("src/") || f.startsWith("src\\"));
505
+ const otherFiles = candidates.filter((f) => !f.startsWith("src/") && !f.startsWith("src\\"));
506
+
507
+ // Take SAMPLE_FILE_COUNT files, preferring src/
508
+ const sampled: string[] = [];
509
+
510
+ // First, add src files
511
+ for (const file of srcFiles) {
512
+ if (sampled.length >= SAMPLE_FILE_COUNT) break;
513
+ sampled.push(file);
514
+ }
515
+
516
+ // Then add other files if needed
517
+ for (const file of otherFiles) {
518
+ if (sampled.length >= SAMPLE_FILE_COUNT) break;
519
+ sampled.push(file);
520
+ }
521
+
522
+ return sampled;
523
+ }
524
+
525
+ /**
526
+ * Extract code patterns from sampled files.
527
+ *
528
+ * Pattern detection behavior:
529
+ * 1. When primaryLanguage exists in LANGUAGE_PATTERNS → uses language-specific patterns
530
+ * 2. When primaryLanguage is undefined (no manifest) → falls back to JS/TS patterns
531
+ * since the sampled files are filtered by JS/TS extensions anyway
532
+ * 3. When primaryLanguage is a known value NOT in LANGUAGE_PATTERNS (e.g., "haskell",
533
+ * "elixir") → returns "unknown" for language-specific patterns instead of running
534
+ * JS/TS patterns which would produce misleading results
535
+ *
536
+ * Universal patterns (naming convention) always run regardless of language.
537
+ *
538
+ * @param basePath - Root directory of the project
539
+ * @param sampledFiles - Array of relative file paths
540
+ * @param primaryLanguage - Optional primary language identifier from detection.ts LANGUAGE_MAP
541
+ * @returns CodePatterns with detected patterns and evidence
542
+ */
543
+ function extractPatterns(basePath: string, sampledFiles: string[], primaryLanguage?: string): CodePatterns {
544
+ const evidence = {
545
+ asyncStyle: [] as string[],
546
+ errorHandling: [] as string[],
547
+ namingConvention: [] as string[],
548
+ };
549
+
550
+ const counts = {
551
+ asyncAwait: 0,
552
+ callbacks: 0,
553
+ promises: 0,
554
+ tryCatch: 0,
555
+ errorCallbacks: 0,
556
+ resultTypes: 0,
557
+ camelCase: 0,
558
+ snakeCase: 0,
559
+ pascalCase: 0,
560
+ };
561
+
562
+ // Track how many files contain each pattern type (for formatted output)
563
+ const fileCounts = {
564
+ asyncAwait: 0,
565
+ promises: 0,
566
+ callbacks: 0,
567
+ tryCatch: 0,
568
+ errorCallbacks: 0,
569
+ resultTypes: 0,
570
+ };
571
+
572
+ // Get language-specific patterns if available
573
+ // When primaryLanguage is undefined, fall back to JS/TS (sampled files are JS/TS extensions)
574
+ // When primaryLanguage is set but not in registry, skip language-specific patterns entirely
575
+ const languageEntry = primaryLanguage
576
+ ? LANGUAGE_PATTERNS[primaryLanguage]
577
+ : LANGUAGE_PATTERNS["javascript/typescript"]; // Fallback for undefined only
578
+
579
+ // Language is "unsupported" only when it's explicitly set but not in our registry
580
+ // undefined → use JS/TS fallback (the sampled files are .ts/.js anyway)
581
+ // "haskell" → unsupported, don't run JS patterns against Haskell code
582
+ const languageUnsupported = primaryLanguage !== undefined && !LANGUAGE_PATTERNS[primaryLanguage];
583
+
584
+ // If language is explicitly set but not in registry, add evidence explaining why patterns aren't available
585
+ if (languageUnsupported) {
586
+ evidence.asyncStyle.push(`Language "${primaryLanguage}" not in pattern registry — async style detection not available`);
587
+ evidence.errorHandling.push(`Language "${primaryLanguage}" not in pattern registry — error handling detection not available`);
588
+ }
589
+
590
+ for (const file of sampledFiles) {
591
+ let content: string;
592
+ try {
593
+ const fullPath = join(basePath, file);
594
+ const buffer = Buffer.alloc(MAX_FILE_SAMPLE_BYTES);
595
+ const fd = openSync(fullPath, "r");
596
+ try {
597
+ const bytesRead = readSync(fd, buffer, 0, MAX_FILE_SAMPLE_BYTES, 0);
598
+ content = buffer.toString("utf-8", 0, bytesRead);
599
+ } finally {
600
+ closeSync(fd);
601
+ }
602
+ } catch {
603
+ continue; // Skip unreadable files
604
+ }
605
+
606
+ // Only run language-specific patterns if we have a valid language entry
607
+ // This prevents misleading results from running JS/TS patterns against Haskell, etc.
608
+ if (!languageUnsupported && languageEntry) {
609
+ // Count async patterns using language-appropriate patterns
610
+ // Use String.match() to avoid mutating lastIndex on regex with /g flag
611
+ const asyncModernMatches = content.match(languageEntry.asyncStyle.modern) || [];
612
+ counts.asyncAwait += asyncModernMatches.length;
613
+ if (asyncModernMatches.length > 0) {
614
+ fileCounts.asyncAwait++;
615
+ if (evidence.asyncStyle.length < 3) {
616
+ evidence.asyncStyle.push(`${file}: ${languageEntry.asyncStyle.modernLabel} (${asyncModernMatches.length} occurrences)`);
617
+ }
618
+ }
619
+
620
+ // For JS/TS, also check callbacks (universal pattern)
621
+ if (primaryLanguage === "javascript/typescript") {
622
+ const callbackMatches = content.match(CALLBACK_RE) || [];
623
+ counts.callbacks += callbackMatches.length;
624
+ if (callbackMatches.length > 0) {
625
+ fileCounts.callbacks++;
626
+ if (evidence.asyncStyle.length < 3) {
627
+ evidence.asyncStyle.push(`${file}: callbacks (${callbackMatches.length} occurrences)`);
628
+ }
629
+ }
630
+ }
631
+
632
+ const asyncLegacyMatches = content.match(languageEntry.asyncStyle.legacy) || [];
633
+ counts.promises += asyncLegacyMatches.length;
634
+ if (asyncLegacyMatches.length > 0) {
635
+ fileCounts.promises++;
636
+ if (evidence.asyncStyle.length < 3) {
637
+ evidence.asyncStyle.push(`${file}: ${languageEntry.asyncStyle.legacyLabel} (${asyncLegacyMatches.length} occurrences)`);
638
+ }
639
+ }
640
+
641
+ // Count error handling patterns using language-appropriate patterns
642
+ const errorStructuredMatches = content.match(languageEntry.errorHandling.structured) || [];
643
+ counts.tryCatch += errorStructuredMatches.length;
644
+ if (errorStructuredMatches.length > 0) {
645
+ fileCounts.tryCatch++;
646
+ if (evidence.errorHandling.length < 3) {
647
+ evidence.errorHandling.push(`${file}: ${languageEntry.errorHandling.structuredLabel} (${errorStructuredMatches.length} occurrences)`);
648
+ }
649
+ }
650
+
651
+ const errorInlineMatches = content.match(languageEntry.errorHandling.inline) || [];
652
+ counts.errorCallbacks += errorInlineMatches.length;
653
+ if (errorInlineMatches.length > 0) {
654
+ fileCounts.errorCallbacks++;
655
+ if (evidence.errorHandling.length < 3) {
656
+ evidence.errorHandling.push(`${file}: ${languageEntry.errorHandling.inlineLabel} (${errorInlineMatches.length} occurrences)`);
657
+ }
658
+ }
659
+
660
+ // Result types are still useful for some languages (Rust, fp-ts)
661
+ const resultTypeMatches = content.match(RESULT_TYPE_RE) || [];
662
+ counts.resultTypes += resultTypeMatches.length;
663
+ if (resultTypeMatches.length > 0) {
664
+ fileCounts.resultTypes++;
665
+ if (evidence.errorHandling.length < 3) {
666
+ evidence.errorHandling.push(`${file}: result-types (${resultTypeMatches.length} occurrences)`);
667
+ }
668
+ }
669
+ }
670
+
671
+ // Count naming convention patterns (universal across all languages)
672
+ // These patterns work regardless of whether the language is in the registry
673
+ const camelMatches = content.match(CAMEL_CASE_RE) || [];
674
+ counts.camelCase += camelMatches.length;
675
+
676
+ const snakeMatches = content.match(SNAKE_CASE_RE) || [];
677
+ counts.snakeCase += snakeMatches.length;
678
+
679
+ const pascalMatches = content.match(PASCAL_CASE_RE) || [];
680
+ counts.pascalCase += pascalMatches.length;
681
+ }
682
+
683
+ // Add naming evidence
684
+ if (counts.camelCase > 0) {
685
+ evidence.namingConvention.push(`camelCase: ${counts.camelCase} occurrences`);
686
+ }
687
+ if (counts.snakeCase > 0) {
688
+ evidence.namingConvention.push(`snake_case: ${counts.snakeCase} occurrences`);
689
+ }
690
+ if (counts.pascalCase > 0) {
691
+ evidence.namingConvention.push(`PascalCase: ${counts.pascalCase} occurrences`);
692
+ }
693
+
694
+ // For explicitly set but unrecognized languages, return "unknown" for language-specific patterns
695
+ // but still provide naming convention detection (which is universal)
696
+ if (languageUnsupported) {
697
+ return {
698
+ asyncStyle: "unknown",
699
+ errorHandling: "unknown",
700
+ namingConvention: determineNamingConvention(counts),
701
+ evidence,
702
+ fileCounts,
703
+ };
704
+ }
705
+
706
+ return {
707
+ asyncStyle: determineAsyncStyle(counts),
708
+ errorHandling: determineErrorHandling(counts),
709
+ namingConvention: determineNamingConvention(counts),
710
+ evidence,
711
+ fileCounts,
712
+ };
713
+ }
714
+
715
+ /**
716
+ * Determine the primary async style based on pattern counts.
717
+ */
718
+ function determineAsyncStyle(counts: {
719
+ asyncAwait: number;
720
+ callbacks: number;
721
+ promises: number;
722
+ }): CodePatterns["asyncStyle"] {
723
+ const total = counts.asyncAwait + counts.callbacks + counts.promises;
724
+ if (total === 0) return "unknown";
725
+
726
+ const asyncAwaitRatio = counts.asyncAwait / total;
727
+ const callbackRatio = counts.callbacks / total;
728
+ const promiseRatio = counts.promises / total;
729
+
730
+ // If one style dominates (>60%), report it
731
+ if (asyncAwaitRatio > 0.6) return "async/await";
732
+ if (callbackRatio > 0.6) return "callbacks";
733
+ if (promiseRatio > 0.6) return "promises";
734
+
735
+ return "mixed";
736
+ }
737
+
738
+ /**
739
+ * Determine the primary error handling style based on pattern counts.
740
+ */
741
+ function determineErrorHandling(counts: {
742
+ tryCatch: number;
743
+ errorCallbacks: number;
744
+ resultTypes: number;
745
+ }): CodePatterns["errorHandling"] {
746
+ const total = counts.tryCatch + counts.errorCallbacks + counts.resultTypes;
747
+ if (total === 0) return "unknown";
748
+
749
+ const tryCatchRatio = counts.tryCatch / total;
750
+ const errorCallbackRatio = counts.errorCallbacks / total;
751
+ const resultTypeRatio = counts.resultTypes / total;
752
+
753
+ if (tryCatchRatio > 0.6) return "try/catch";
754
+ if (errorCallbackRatio > 0.6) return "error-callbacks";
755
+ if (resultTypeRatio > 0.6) return "result-types";
756
+
757
+ return "mixed";
758
+ }
759
+
760
+ /**
761
+ * Determine the primary naming convention based on pattern counts.
762
+ */
763
+ function determineNamingConvention(counts: {
764
+ camelCase: number;
765
+ snakeCase: number;
766
+ pascalCase: number;
767
+ }): CodePatterns["namingConvention"] {
768
+ const total = counts.camelCase + counts.snakeCase + counts.pascalCase;
769
+ if (total === 0) return "unknown";
770
+
771
+ // PascalCase is usually for types/classes, so we compare camelCase vs snake_case
772
+ const camelRatio = counts.camelCase / total;
773
+ const snakeRatio = counts.snakeCase / total;
774
+
775
+ if (camelRatio > 0.6) return "camelCase";
776
+ if (snakeRatio > 0.6) return "snake_case";
777
+ if (counts.pascalCase > counts.camelCase && counts.pascalCase > counts.snakeCase) return "PascalCase";
778
+
779
+ return "mixed";
780
+ }
781
+
782
+ // ─── Formatting ─────────────────────────────────────────────────────────────────
783
+
784
+ /**
785
+ * Format a CodebaseBrief as LLM-readable markdown.
786
+ *
787
+ * @param brief - The codebase brief to format
788
+ * @returns Markdown string capped at MAX_CODEBASE_BRIEF_CHARS
789
+ */
790
+ export function formatCodebaseBrief(brief: CodebaseBrief): string {
791
+ const sections: string[] = [];
792
+
793
+ // Tech Stack section
794
+ sections.push("## Tech Stack");
795
+ if (brief.techStack.primaryLanguage) {
796
+ sections.push(`- **Language:** ${brief.techStack.primaryLanguage}`);
797
+ }
798
+ if (brief.techStack.packageManager) {
799
+ sections.push(`- **Package Manager:** ${brief.techStack.packageManager}`);
800
+ }
801
+ if (brief.techStack.detectedFiles.length > 0) {
802
+ const files = brief.techStack.detectedFiles.slice(0, 10).join(", ");
803
+ sections.push(`- **Project Files:** ${files}`);
804
+ }
805
+ sections.push(`- **Monorepo:** ${brief.techStack.isMonorepo ? "Yes" : "No"}`);
806
+ sections.push(`- **Has Tests:** ${brief.techStack.hasTests ? "Yes" : "No"}`);
807
+ sections.push(`- **Has CI:** ${brief.techStack.hasCI ? "Yes" : "No"}`);
808
+
809
+ // Module Structure section
810
+ sections.push("");
811
+ sections.push("## Module Structure");
812
+ if (brief.moduleStructure.topLevelDirs.length > 0) {
813
+ sections.push(`- **Top-level dirs:** ${brief.moduleStructure.topLevelDirs.join(", ")}`);
814
+ }
815
+ if (brief.moduleStructure.srcSubdirs.length > 0) {
816
+ sections.push(`- **Source subdirs:** ${brief.moduleStructure.srcSubdirs.join(", ")}`);
817
+ }
818
+
819
+ // Code Patterns section
820
+ sections.push("");
821
+ sections.push("## Code Patterns");
822
+
823
+ // Format async style with file counts
824
+ const fc = brief.patterns.fileCounts;
825
+ if (brief.patterns.asyncStyle === "unknown") {
826
+ sections.push(`- **Async Style:** ${brief.patterns.asyncStyle}`);
827
+ } else {
828
+ const asyncParts: string[] = [];
829
+ if (fc.asyncAwait > 0) asyncParts.push(`${fc.asyncAwait} async/await`);
830
+ if (fc.promises > 0) asyncParts.push(`${fc.promises} .then()`);
831
+ if (fc.callbacks > 0) asyncParts.push(`${fc.callbacks} callback`);
832
+ const asyncDetail = asyncParts.length > 0 ? ` (${asyncParts.map(p => p + " files").join(" vs ")})` : "";
833
+ sections.push(`- **Async Style:** ${brief.patterns.asyncStyle}${asyncDetail}`);
834
+ }
835
+
836
+ // Format error handling with file counts
837
+ if (brief.patterns.errorHandling === "unknown") {
838
+ sections.push(`- **Error Handling:** ${brief.patterns.errorHandling}`);
839
+ } else {
840
+ const errorParts: string[] = [];
841
+ if (fc.tryCatch > 0) errorParts.push(`${fc.tryCatch} try/catch`);
842
+ if (fc.errorCallbacks > 0) errorParts.push(`${fc.errorCallbacks} error-callback`);
843
+ if (fc.resultTypes > 0) errorParts.push(`${fc.resultTypes} result-type`);
844
+ const errorDetail = errorParts.length > 0 ? ` (${errorParts.map(p => p + " files").join(" vs ")})` : "";
845
+ sections.push(`- **Error Handling:** ${brief.patterns.errorHandling}${errorDetail}`);
846
+ }
847
+
848
+ sections.push(`- **Naming Convention:** ${brief.patterns.namingConvention}`);
849
+
850
+ let result = sections.join("\n");
851
+
852
+ // Truncate if necessary
853
+ if (result.length > MAX_CODEBASE_BRIEF_CHARS) {
854
+ result = result.slice(0, MAX_CODEBASE_BRIEF_CHARS - 3) + "...";
855
+ }
856
+
857
+ return result;
858
+ }
859
+
860
+ // ─── Prior Context Aggregation ──────────────────────────────────────────────────
861
+
862
+ /** Maximum characters per section in the prior context brief. */
863
+ const MAX_SECTION_CHARS = 2000;
864
+
865
+ /** Maximum total characters for the prior context brief. */
866
+ const MAX_PRIOR_CONTEXT_CHARS = 6000;
867
+
868
+ /**
869
+ * Aggregate prior context from GSD artifacts.
870
+ *
871
+ * Reads DECISIONS.md, REQUIREMENTS.md, KNOWLEDGE.md from the .gsd directory
872
+ * and milestone summaries from each milestone's MILESTONE-SUMMARY.md file.
873
+ *
874
+ * @param basePath - Root directory of the project (contains .gsd/)
875
+ * @returns PriorContextBrief with aggregated context
876
+ */
877
+ export async function aggregatePriorContext(basePath: string): Promise<PriorContextBrief> {
878
+ const gsdPath = join(basePath, ".gsd");
879
+
880
+ // Load decisions
881
+ const decisionsContent = await loadFile(join(gsdPath, "DECISIONS.md"));
882
+ const decisions = parseDecisions(decisionsContent);
883
+
884
+ // Load requirements
885
+ const requirementsContent = await loadFile(join(gsdPath, "REQUIREMENTS.md"));
886
+ const requirements = parseRequirements(requirementsContent);
887
+
888
+ // Load knowledge
889
+ const knowledgeContent = await loadFile(join(gsdPath, "KNOWLEDGE.md"));
890
+ const knowledge = truncateSection(knowledgeContent || "", MAX_SECTION_CHARS);
891
+
892
+ // Load milestone summaries
893
+ const summaries = await loadMilestoneSummaries(gsdPath);
894
+
895
+ return {
896
+ decisions,
897
+ requirements,
898
+ knowledge: knowledge || "No prior knowledge recorded.",
899
+ summaries: summaries || "No prior milestone summaries.",
900
+ };
901
+ }
902
+
903
+ /**
904
+ * Parse decisions from DECISIONS.md content.
905
+ *
906
+ * Groups decisions by scope (e.g., "pattern", "architecture").
907
+ */
908
+ function parseDecisions(content: string | null): PriorContextBrief["decisions"] {
909
+ const byScope = new Map<string, DecisionEntry[]>();
910
+
911
+ if (!content) {
912
+ return { byScope, totalCount: 0 };
913
+ }
914
+
915
+ // Parse table rows: | D001 | M001/S01 | pattern | ... |
916
+ // Skip header rows (start with | # or |---)
917
+ const lines = content.split("\n");
918
+ let totalCount = 0;
919
+
920
+ for (const line of lines) {
921
+ const trimmed = line.trim();
922
+
923
+ // Skip non-table lines, header, and separator rows
924
+ if (!trimmed.startsWith("|")) continue;
925
+ if (trimmed.startsWith("| #") || trimmed.startsWith("|---") || trimmed.startsWith("| -")) continue;
926
+
927
+ // Parse: | D001 | M001/S01 | pattern | Decision | Choice | Rationale | Revisable? | Made By |
928
+ const cells = trimmed
929
+ .split("|")
930
+ .map((c) => c.trim())
931
+ .filter((c) => c.length > 0);
932
+
933
+ if (cells.length < 6) continue;
934
+
935
+ const id = cells[0]; // D001
936
+ if (!id.match(/^D\d+$/)) continue; // Must be a decision ID
937
+
938
+ const scope = cells[2]; // pattern, architecture, etc.
939
+ const decision = cells[3];
940
+ const choice = cells[4];
941
+ const rationale = cells[5];
942
+
943
+ const entry: DecisionEntry = { id, scope, decision, choice, rationale };
944
+
945
+ if (!byScope.has(scope)) {
946
+ byScope.set(scope, []);
947
+ }
948
+ byScope.get(scope)!.push(entry);
949
+ totalCount++;
950
+ }
951
+
952
+ return { byScope, totalCount };
953
+ }
954
+
955
+ /**
956
+ * Parse requirements from REQUIREMENTS.md content.
957
+ *
958
+ * Groups requirements by status (active, validated, deferred).
959
+ */
960
+ function parseRequirements(content: string | null): PriorContextBrief["requirements"] {
961
+ const result: PriorContextBrief["requirements"] = {
962
+ active: [],
963
+ validated: [],
964
+ deferred: [],
965
+ totalCount: 0,
966
+ };
967
+
968
+ if (!content) {
969
+ return result;
970
+ }
971
+
972
+ // Parse requirement entries: ### R101 — Description
973
+ // Look for Status: line to determine status
974
+ const reqBlocks = content.split(/(?=^### R\d+)/m);
975
+
976
+ for (const block of reqBlocks) {
977
+ const idMatch = block.match(/^### (R\d+)\s*—\s*(.+)/m);
978
+ if (!idMatch) continue;
979
+
980
+ const id = idMatch[1];
981
+ const description = idMatch[2].trim();
982
+
983
+ // Extract status from "- Status: active" line
984
+ const statusMatch = block.match(/^-\s*Status:\s*(\w+)/m);
985
+ const statusRaw = statusMatch ? statusMatch[1].toLowerCase() : "active";
986
+
987
+ let status: RequirementEntry["status"] = "active";
988
+ if (statusRaw === "validated") status = "validated";
989
+ else if (statusRaw === "deferred") status = "deferred";
990
+ else if (statusRaw === "out-of-scope" || statusRaw === "outofscope") status = "out-of-scope";
991
+
992
+ const entry: RequirementEntry = { id, description, status };
993
+
994
+ if (status === "active") result.active.push(entry);
995
+ else if (status === "validated") result.validated.push(entry);
996
+ else if (status === "deferred") result.deferred.push(entry);
997
+
998
+ result.totalCount++;
999
+ }
1000
+
1001
+ return result;
1002
+ }
1003
+
1004
+ /**
1005
+ * Load and combine milestone summaries from each milestone directory.
1006
+ *
1007
+ * Returns combined content, truncated to MAX_SECTION_CHARS.
1008
+ */
1009
+ async function loadMilestoneSummaries(gsdPath: string): Promise<string> {
1010
+ const milestonesPath = join(gsdPath, "milestones");
1011
+ const summaries: string[] = [];
1012
+
1013
+ try {
1014
+ const entries = readdirSyncNode(milestonesPath, { withFileTypes: true });
1015
+ const milestoneIds = entries
1016
+ .filter((e) => e.isDirectory() && e.name.match(/^M\d+/))
1017
+ .map((e) => e.name)
1018
+ .sort(); // Sort by milestone ID
1019
+
1020
+ for (const mid of milestoneIds) {
1021
+ const summaryPath = join(milestonesPath, mid, "MILESTONE-SUMMARY.md");
1022
+ const content = await loadFile(summaryPath);
1023
+ if (content) {
1024
+ // Extract the one-liner and first section for brevity
1025
+ const oneLiner = extractOneLiner(content);
1026
+ summaries.push(`### ${mid}\n${oneLiner}`);
1027
+ }
1028
+ }
1029
+ } catch {
1030
+ // Milestones directory doesn't exist or not readable
1031
+ }
1032
+
1033
+ if (summaries.length === 0) {
1034
+ return "";
1035
+ }
1036
+
1037
+ return truncateSection(summaries.join("\n\n"), MAX_SECTION_CHARS);
1038
+ }
1039
+
1040
+ /**
1041
+ * Extract the one-liner summary from a MILESTONE-SUMMARY.md.
1042
+ *
1043
+ * Looks for bold text on a line by itself (e.g., "**Completed X and Y**").
1044
+ */
1045
+ function extractOneLiner(content: string): string {
1046
+ const lines = content.split("\n");
1047
+ for (const line of lines) {
1048
+ const trimmed = line.trim();
1049
+ // Look for **bold text** that's the whole line
1050
+ if (trimmed.startsWith("**") && trimmed.endsWith("**") && trimmed.length > 4) {
1051
+ return trimmed.slice(2, -2);
1052
+ }
1053
+ }
1054
+ // Fallback: return first non-empty, non-heading line
1055
+ for (const line of lines) {
1056
+ const trimmed = line.trim();
1057
+ if (trimmed && !trimmed.startsWith("#") && !trimmed.startsWith("---")) {
1058
+ return trimmed.slice(0, 200);
1059
+ }
1060
+ }
1061
+ return "Summary available";
1062
+ }
1063
+
1064
+ /**
1065
+ * Truncate content to maxChars without cutting mid-section.
1066
+ *
1067
+ * Prefers to cut at section boundaries (## headings) or paragraph breaks.
1068
+ */
1069
+ function truncateSection(content: string, maxChars: number): string {
1070
+ if (content.length <= maxChars) {
1071
+ return content;
1072
+ }
1073
+
1074
+ const SECTION_SUFFIX = "\n\n[truncated]"; // 14 chars
1075
+ const WORD_SUFFIX = "... [truncated]"; // 15 chars
1076
+
1077
+ // Reserve space for suffix in all slicing operations
1078
+ const sectionMaxSlice = maxChars - SECTION_SUFFIX.length;
1079
+ const wordMaxSlice = maxChars - WORD_SUFFIX.length;
1080
+
1081
+ // Try to cut at a section boundary
1082
+ const truncated = content.slice(0, sectionMaxSlice);
1083
+ const lastSection = truncated.lastIndexOf("\n## ");
1084
+ if (lastSection > sectionMaxSlice * 0.5) {
1085
+ return truncated.slice(0, lastSection).trim() + SECTION_SUFFIX;
1086
+ }
1087
+
1088
+ // Try to cut at a paragraph break
1089
+ const lastPara = truncated.lastIndexOf("\n\n");
1090
+ if (lastPara > sectionMaxSlice * 0.5) {
1091
+ return truncated.slice(0, lastPara).trim() + SECTION_SUFFIX;
1092
+ }
1093
+
1094
+ // Last resort: cut at word boundary
1095
+ const wordTruncated = content.slice(0, wordMaxSlice);
1096
+ const lastSpace = wordTruncated.lastIndexOf(" ");
1097
+ if (lastSpace > wordMaxSlice * 0.8) {
1098
+ return wordTruncated.slice(0, lastSpace).trim() + WORD_SUFFIX;
1099
+ }
1100
+
1101
+ return content.slice(0, wordMaxSlice) + WORD_SUFFIX;
1102
+ }
1103
+
1104
+ /**
1105
+ * Format a PriorContextBrief as LLM-readable markdown.
1106
+ *
1107
+ * @param brief - The prior context brief to format
1108
+ * @returns Markdown string capped at MAX_PRIOR_CONTEXT_CHARS
1109
+ */
1110
+ export function formatPriorContextBrief(brief: PriorContextBrief): string {
1111
+ const sections: string[] = [];
1112
+
1113
+ // Decisions section
1114
+ sections.push("## Prior Decisions");
1115
+ if (brief.decisions.totalCount === 0) {
1116
+ sections.push("No prior decisions recorded.");
1117
+ } else {
1118
+ sections.push(`${brief.decisions.totalCount} decisions recorded.`);
1119
+ sections.push("");
1120
+
1121
+ // Group by scope
1122
+ for (const [scope, entries] of brief.decisions.byScope) {
1123
+ sections.push(`### ${scope}`);
1124
+ for (const entry of entries.slice(0, 5)) { // Limit per scope
1125
+ sections.push(`- **${entry.id}:** ${entry.decision} → ${entry.choice}`);
1126
+ }
1127
+ if (entries.length > 5) {
1128
+ sections.push(`- _(${entries.length - 5} more in this scope)_`);
1129
+ }
1130
+ sections.push("");
1131
+ }
1132
+ }
1133
+
1134
+ // Requirements section
1135
+ sections.push("## Prior Requirements");
1136
+ const reqTotal = brief.requirements.totalCount;
1137
+ if (reqTotal === 0) {
1138
+ sections.push("No prior requirements recorded.");
1139
+ } else {
1140
+ sections.push(
1141
+ `${reqTotal} requirements: ${brief.requirements.active.length} active, ` +
1142
+ `${brief.requirements.validated.length} validated, ` +
1143
+ `${brief.requirements.deferred.length} deferred.`,
1144
+ );
1145
+ sections.push("");
1146
+
1147
+ // Show active requirements (most relevant)
1148
+ if (brief.requirements.active.length > 0) {
1149
+ sections.push("### Active");
1150
+ for (const req of brief.requirements.active.slice(0, 10)) {
1151
+ sections.push(`- **${req.id}:** ${req.description}`);
1152
+ }
1153
+ if (brief.requirements.active.length > 10) {
1154
+ sections.push(`- _(${brief.requirements.active.length - 10} more active)_`);
1155
+ }
1156
+ sections.push("");
1157
+ }
1158
+
1159
+ // Show validated (recently completed)
1160
+ if (brief.requirements.validated.length > 0) {
1161
+ sections.push("### Validated");
1162
+ for (const req of brief.requirements.validated.slice(0, 5)) {
1163
+ sections.push(`- **${req.id}:** ${req.description}`);
1164
+ }
1165
+ if (brief.requirements.validated.length > 5) {
1166
+ sections.push(`- _(${brief.requirements.validated.length - 5} more validated)_`);
1167
+ }
1168
+ sections.push("");
1169
+ }
1170
+ }
1171
+
1172
+ // Knowledge section
1173
+ sections.push("## Prior Knowledge");
1174
+ if (brief.knowledge === "No prior knowledge recorded.") {
1175
+ sections.push(brief.knowledge);
1176
+ } else {
1177
+ sections.push(truncateSection(brief.knowledge, MAX_SECTION_CHARS));
1178
+ }
1179
+ sections.push("");
1180
+
1181
+ // Summaries section
1182
+ sections.push("## Prior Milestone Summaries");
1183
+ if (brief.summaries === "No prior milestone summaries.") {
1184
+ sections.push(brief.summaries);
1185
+ } else {
1186
+ sections.push(truncateSection(brief.summaries, MAX_SECTION_CHARS));
1187
+ }
1188
+
1189
+ let result = sections.join("\n");
1190
+
1191
+ // Final truncation if total exceeds max
1192
+ if (result.length > MAX_PRIOR_CONTEXT_CHARS) {
1193
+ result = truncateSection(result, MAX_PRIOR_CONTEXT_CHARS);
1194
+ }
1195
+
1196
+ return result;
1197
+ }
1198
+
1199
+ // ─── Ecosystem Research ─────────────────────────────────────────────────────────
1200
+
1201
+ /** Maximum characters for the ecosystem brief. */
1202
+ const MAX_ECOSYSTEM_BRIEF_CHARS = 4000;
1203
+
1204
+ /**
1205
+ * Research the ecosystem for best practices and known issues.
1206
+ *
1207
+ * Ecosystem research is now performed during the discussion session (between
1208
+ * Layer 1 and Layer 2) using whatever web search tools are available to the
1209
+ * LLM — native Anthropic web search for Claude, search-the-web for other
1210
+ * providers. The preparation phase focuses on mechanical work only.
1211
+ *
1212
+ * @param _techStack - Array of technology names from codebase analysis (unused)
1213
+ * @param _basePath - Root directory of the project (unused)
1214
+ * @returns EcosystemBrief indicating research happens during discussion
1215
+ */
1216
+ export async function researchEcosystem(
1217
+ _techStack: string[],
1218
+ _basePath: string,
1219
+ ): Promise<EcosystemBrief> {
1220
+ return {
1221
+ available: false,
1222
+ queries: [],
1223
+ findings: [],
1224
+ skippedReason: "Ecosystem research is performed during the discussion using web search tools, not during preparation.",
1225
+ };
1226
+ }
1227
+
1228
+ /**
1229
+ * Format an EcosystemBrief as LLM-readable markdown.
1230
+ *
1231
+ * @param brief - The ecosystem brief to format
1232
+ * @returns Markdown string capped at MAX_ECOSYSTEM_BRIEF_CHARS
1233
+ */
1234
+ // ─── Preparation Result ─────────────────────────────────────────────────────────
1235
+
1236
+ /**
1237
+ * Combined result from the preparation phase.
1238
+ * Includes briefs from all three analyzers, plus metadata about the run.
1239
+ */
1240
+ export interface PreparationResult {
1241
+ /** Codebase analysis brief. */
1242
+ codebase: CodebaseBrief;
1243
+ /** Formatted codebase brief as markdown. */
1244
+ codebaseBrief: string;
1245
+ /** Prior context brief. */
1246
+ priorContext: PriorContextBrief;
1247
+ /** Formatted prior context brief as markdown. */
1248
+ priorContextBrief: string;
1249
+ /** Ecosystem research brief. */
1250
+ ecosystem: EcosystemBrief;
1251
+ /** Formatted ecosystem brief as markdown. */
1252
+ ecosystemBrief: string;
1253
+ /** Whether preparation was enabled. */
1254
+ enabled: boolean;
1255
+ /** Whether ecosystem research was performed. */
1256
+ ecosystemResearchPerformed: boolean;
1257
+ /** Total duration of preparation in milliseconds. */
1258
+ durationMs: number;
1259
+ }
1260
+
1261
+ /**
1262
+ * Minimal UI context interface for preparation phase.
1263
+ * Mirrors the notify method from ExtensionUIContext.
1264
+ */
1265
+ export interface PreparationUIContext {
1266
+ notify(message: string, type?: "info" | "warning" | "error" | "success"): void;
1267
+ }
1268
+
1269
+ /**
1270
+ * Minimal preferences interface for preparation phase.
1271
+ * Only includes the preferences needed by runPreparation.
1272
+ */
1273
+ export interface PreparationPreferences {
1274
+ /** Enable the preparation phase. Default: true. */
1275
+ discuss_preparation?: boolean;
1276
+ /** Enable web research during preparation. Default: true. */
1277
+ discuss_web_research?: boolean;
1278
+ /** Depth of analysis. Default: "standard". */
1279
+ discuss_depth?: "quick" | "standard" | "thorough";
1280
+ }
1281
+
1282
+ /**
1283
+ * Run the preparation phase before a discussion session.
1284
+ *
1285
+ * Orchestrates all three analyzers (codebase, prior context, ecosystem)
1286
+ * with TUI progress updates. Returns early if preparation is disabled.
1287
+ *
1288
+ * @param basePath - Root directory of the project
1289
+ * @param ui - UI context for progress notifications (null = silent mode)
1290
+ * @param prefs - Preferences controlling preparation behavior
1291
+ * @returns PreparationResult with all briefs and metadata
1292
+ */
1293
+ export async function runPreparation(
1294
+ basePath: string,
1295
+ ui: PreparationUIContext | null,
1296
+ prefs: PreparationPreferences,
1297
+ ): Promise<PreparationResult> {
1298
+ const startTime = performance.now();
1299
+
1300
+ // Check if preparation is disabled
1301
+ const preparationEnabled = prefs.discuss_preparation !== false; // Default: true
1302
+
1303
+ if (!preparationEnabled) {
1304
+ // Return minimal result with empty briefs
1305
+ const emptyCodebase: CodebaseBrief = {
1306
+ techStack: {
1307
+ primaryLanguage: undefined,
1308
+ detectedFiles: [],
1309
+ packageManager: undefined,
1310
+ isMonorepo: false,
1311
+ hasTests: false,
1312
+ hasCI: false,
1313
+ },
1314
+ moduleStructure: {
1315
+ topLevelDirs: [],
1316
+ srcSubdirs: [],
1317
+ totalFilesSampled: 0,
1318
+ },
1319
+ patterns: {
1320
+ asyncStyle: "unknown",
1321
+ errorHandling: "unknown",
1322
+ namingConvention: "unknown",
1323
+ evidence: {
1324
+ asyncStyle: [],
1325
+ errorHandling: [],
1326
+ namingConvention: [],
1327
+ },
1328
+ fileCounts: {
1329
+ asyncAwait: 0,
1330
+ promises: 0,
1331
+ callbacks: 0,
1332
+ tryCatch: 0,
1333
+ errorCallbacks: 0,
1334
+ resultTypes: 0,
1335
+ },
1336
+ },
1337
+ sampledFiles: [],
1338
+ };
1339
+
1340
+ const emptyPriorContext: PriorContextBrief = {
1341
+ decisions: {
1342
+ byScope: new Map(),
1343
+ totalCount: 0,
1344
+ },
1345
+ requirements: {
1346
+ active: [],
1347
+ validated: [],
1348
+ deferred: [],
1349
+ totalCount: 0,
1350
+ },
1351
+ knowledge: "No prior knowledge recorded.",
1352
+ summaries: "No prior milestone summaries.",
1353
+ };
1354
+
1355
+ const emptyEcosystem: EcosystemBrief = {
1356
+ available: false,
1357
+ queries: [],
1358
+ findings: [],
1359
+ skippedReason: "Preparation phase disabled.",
1360
+ };
1361
+
1362
+ return {
1363
+ codebase: emptyCodebase,
1364
+ codebaseBrief: "",
1365
+ priorContext: emptyPriorContext,
1366
+ priorContextBrief: "",
1367
+ ecosystem: emptyEcosystem,
1368
+ ecosystemBrief: "",
1369
+ enabled: false,
1370
+ ecosystemResearchPerformed: false,
1371
+ durationMs: performance.now() - startTime,
1372
+ };
1373
+ }
1374
+
1375
+ // --- Phase 1: Analyze codebase ---
1376
+ ui?.notify("Analyzing codebase...", "info");
1377
+ const codebase = await analyzeCodebase(basePath);
1378
+ const codebaseBrief = formatCodebaseBrief(codebase);
1379
+ ui?.notify("✓ Analyzed codebase", "success");
1380
+
1381
+ // --- Phase 2: Review prior context ---
1382
+ ui?.notify("Reviewing prior context...", "info");
1383
+ const priorContext = await aggregatePriorContext(basePath);
1384
+ const priorContextBrief = formatPriorContextBrief(priorContext);
1385
+ ui?.notify("✓ Reviewed prior context", "success");
1386
+
1387
+ // --- Ecosystem research ---
1388
+ // Ecosystem research is now performed during the discussion session (between
1389
+ // Layer 1 and Layer 2) using available web search tools. The preparation
1390
+ // phase focuses on mechanical work only.
1391
+ const ecosystem: EcosystemBrief = await researchEcosystem([], basePath);
1392
+ const ecosystemBrief = formatEcosystemBrief(ecosystem);
1393
+
1394
+ return {
1395
+ codebase,
1396
+ codebaseBrief,
1397
+ priorContext,
1398
+ priorContextBrief,
1399
+ ecosystem,
1400
+ ecosystemBrief,
1401
+ enabled: true,
1402
+ ecosystemResearchPerformed: false,
1403
+ durationMs: performance.now() - startTime,
1404
+ };
1405
+ }
1406
+
1407
+ /**
1408
+ * Format an EcosystemBrief as LLM-readable markdown.
1409
+ *
1410
+ * Since ecosystem research now always returns unavailable from the preparation
1411
+ * phase (research happens during discussion using web search tools), this
1412
+ * function returns a simple fixed message.
1413
+ *
1414
+ * @param _brief - The ecosystem brief (unused, always unavailable from preparation)
1415
+ * @returns Markdown string directing the LLM to perform research during discussion
1416
+ */
1417
+ export function formatEcosystemBrief(_brief: EcosystemBrief): string {
1418
+ return "## Ecosystem Research\n\nEcosystem research is performed during the discussion using web search tools.";
1419
+ }