gsd-pi 2.73.1 → 2.74.0-dev.b741afb

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 (378) hide show
  1. package/dist/cli-web-branch.d.ts +4 -3
  2. package/dist/cli-web-branch.js +10 -7
  3. package/dist/cli.js +184 -206
  4. package/dist/headless-query.js +4 -1
  5. package/dist/help-text.js +23 -0
  6. package/dist/logo.d.ts +1 -1
  7. package/dist/logo.js +1 -1
  8. package/dist/onboarding.js +59 -53
  9. package/dist/resource-loader.js +2 -2
  10. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +68 -4
  11. package/dist/resources/extensions/gsd/auto/detect-stuck.js +11 -4
  12. package/dist/resources/extensions/gsd/auto/phases.js +60 -10
  13. package/dist/resources/extensions/gsd/auto-dispatch.js +11 -3
  14. package/dist/resources/extensions/gsd/auto-model-selection.js +54 -11
  15. package/dist/resources/extensions/gsd/auto-post-unit.js +93 -57
  16. package/dist/resources/extensions/gsd/auto-prompts.js +12 -0
  17. package/dist/resources/extensions/gsd/auto-start.js +23 -6
  18. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +13 -0
  19. package/dist/resources/extensions/gsd/auto-verification.js +88 -3
  20. package/dist/resources/extensions/gsd/auto.js +37 -10
  21. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +21 -8
  22. package/dist/resources/extensions/gsd/commands/catalog.js +26 -1
  23. package/dist/resources/extensions/gsd/commands/handlers/ops.js +20 -0
  24. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +68 -9
  25. package/dist/resources/extensions/gsd/commands-add-tests.js +111 -0
  26. package/dist/resources/extensions/gsd/commands-backlog.js +140 -0
  27. package/dist/resources/extensions/gsd/commands-do.js +79 -0
  28. package/dist/resources/extensions/gsd/commands-handlers.js +8 -2
  29. package/dist/resources/extensions/gsd/commands-maintenance.js +6 -6
  30. package/dist/resources/extensions/gsd/commands-pr-branch.js +180 -0
  31. package/dist/resources/extensions/gsd/commands-session-report.js +82 -0
  32. package/dist/resources/extensions/gsd/commands-ship.js +187 -0
  33. package/dist/resources/extensions/gsd/db-writer.js +3 -5
  34. package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  35. package/dist/resources/extensions/gsd/graph-context.js +66 -0
  36. package/dist/resources/extensions/gsd/gsd-db.js +321 -0
  37. package/dist/resources/extensions/gsd/index.js +15 -2
  38. package/dist/resources/extensions/gsd/md-importer.js +3 -4
  39. package/dist/resources/extensions/gsd/memory-store.js +19 -51
  40. package/dist/resources/extensions/gsd/milestone-validation-gates.js +13 -12
  41. package/dist/resources/extensions/gsd/native-git-bridge.js +7 -4
  42. package/dist/resources/extensions/gsd/notification-widget.js +2 -2
  43. package/dist/resources/extensions/gsd/preferences-models.js +43 -0
  44. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  45. package/dist/resources/extensions/gsd/preferences-validation.js +22 -0
  46. package/dist/resources/extensions/gsd/prompts/add-tests.md +35 -0
  47. package/dist/resources/extensions/gsd/state.js +66 -15
  48. package/dist/resources/extensions/gsd/tools/complete-slice.js +15 -0
  49. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +3 -14
  50. package/dist/resources/extensions/gsd/triage-resolution.js +2 -5
  51. package/dist/resources/extensions/gsd/workflow-manifest.js +8 -69
  52. package/dist/resources/extensions/gsd/workflow-migration.js +21 -22
  53. package/dist/resources/extensions/gsd/workflow-projections.js +4 -1
  54. package/dist/resources/extensions/gsd/workflow-reconcile.js +14 -11
  55. package/dist/tsconfig.extensions.tsbuildinfo +1 -0
  56. package/dist/update-check.d.ts +1 -0
  57. package/dist/update-check.js +13 -5
  58. package/dist/update-cmd.js +4 -3
  59. package/dist/web/standalone/.next/BUILD_ID +1 -1
  60. package/dist/web/standalone/.next/app-path-routes-manifest.json +9 -9
  61. package/dist/web/standalone/.next/build-manifest.json +3 -3
  62. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  63. package/dist/web/standalone/.next/required-server-files.json +3 -3
  64. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  65. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  66. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  67. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  75. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  78. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  79. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  91. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  103. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  123. package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  133. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  137. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  139. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  143. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  153. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  155. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  157. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  159. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  161. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  163. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  164. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  166. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  168. package/dist/web/standalone/.next/server/app/index.html +1 -1
  169. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  170. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  171. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  172. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  173. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  174. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  175. package/dist/web/standalone/.next/server/app/page.js +2 -2
  176. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  177. package/dist/web/standalone/.next/server/app-paths-manifest.json +9 -9
  178. package/dist/web/standalone/.next/server/chunks/63.js +3 -3
  179. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  180. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  181. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  182. package/dist/web/standalone/.next/server/middleware.js +2 -2
  183. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  184. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  185. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  186. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  187. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  188. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  189. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  190. package/dist/web/standalone/.next/static/chunks/app/page-f1e30ab6bb269149.js +1 -0
  191. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  192. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  193. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  194. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  195. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  196. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  197. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  198. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  199. package/dist/web/standalone/server.js +1 -1
  200. package/package.json +3 -3
  201. package/packages/daemon/package.json +2 -2
  202. package/packages/mcp-server/dist/index.d.ts +3 -0
  203. package/packages/mcp-server/dist/index.d.ts.map +1 -1
  204. package/packages/mcp-server/dist/index.js +3 -0
  205. package/packages/mcp-server/dist/index.js.map +1 -1
  206. package/packages/mcp-server/dist/readers/graph.d.ts +87 -0
  207. package/packages/mcp-server/dist/readers/graph.d.ts.map +1 -0
  208. package/packages/mcp-server/dist/readers/graph.js +548 -0
  209. package/packages/mcp-server/dist/readers/graph.js.map +1 -0
  210. package/packages/mcp-server/dist/readers/index.d.ts +2 -0
  211. package/packages/mcp-server/dist/readers/index.d.ts.map +1 -1
  212. package/packages/mcp-server/dist/readers/index.js +1 -0
  213. package/packages/mcp-server/dist/readers/index.js.map +1 -1
  214. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  215. package/packages/mcp-server/dist/server.js +65 -0
  216. package/packages/mcp-server/dist/server.js.map +1 -1
  217. package/packages/mcp-server/package.json +2 -2
  218. package/packages/mcp-server/src/index.ts +15 -0
  219. package/packages/mcp-server/src/readers/graph.test.ts +426 -0
  220. package/packages/mcp-server/src/readers/graph.ts +708 -0
  221. package/packages/mcp-server/src/readers/index.ts +12 -0
  222. package/packages/mcp-server/src/server.ts +83 -0
  223. package/packages/mcp-server/tsconfig.json +1 -0
  224. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -0
  225. package/packages/native/package.json +2 -2
  226. package/packages/native/tsconfig.tsbuildinfo +1 -0
  227. package/packages/pi-agent-core/package.json +1 -1
  228. package/packages/pi-agent-core/tsconfig.json +1 -0
  229. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -0
  230. package/packages/pi-ai/dist/index.d.ts +1 -0
  231. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  232. package/packages/pi-ai/dist/index.js +1 -0
  233. package/packages/pi-ai/dist/index.js.map +1 -1
  234. package/packages/pi-ai/dist/utils/overflow.d.ts.map +1 -1
  235. package/packages/pi-ai/dist/utils/overflow.js +12 -0
  236. package/packages/pi-ai/dist/utils/overflow.js.map +1 -1
  237. package/packages/pi-ai/dist/utils/tests/overflow.test.d.ts +2 -0
  238. package/packages/pi-ai/dist/utils/tests/overflow.test.d.ts.map +1 -0
  239. package/packages/pi-ai/dist/utils/tests/overflow.test.js +50 -0
  240. package/packages/pi-ai/dist/utils/tests/overflow.test.js.map +1 -0
  241. package/packages/pi-ai/package.json +1 -1
  242. package/packages/pi-ai/src/index.ts +4 -0
  243. package/packages/pi-ai/src/utils/overflow.ts +14 -1
  244. package/packages/pi-ai/src/utils/tests/overflow.test.ts +58 -0
  245. package/packages/pi-ai/tsconfig.json +1 -0
  246. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -0
  247. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +313 -8
  248. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
  249. package/packages/pi-coding-agent/dist/core/compaction/utils.js +5 -5
  250. package/packages/pi-coding-agent/dist/core/compaction/utils.js.map +1 -1
  251. package/packages/pi-coding-agent/dist/core/compaction-utils.test.d.ts +2 -0
  252. package/packages/pi-coding-agent/dist/core/compaction-utils.test.d.ts.map +1 -0
  253. package/packages/pi-coding-agent/dist/core/compaction-utils.test.js +45 -0
  254. package/packages/pi-coding-agent/dist/core/compaction-utils.test.js.map +1 -0
  255. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts +12 -2
  256. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  257. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +61 -28
  258. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  259. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.d.ts +2 -1
  260. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
  261. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.js +9 -3
  262. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.js.map +1 -1
  263. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.d.ts +2 -0
  264. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.d.ts.map +1 -0
  265. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js +52 -0
  266. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js.map +1 -0
  267. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  268. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +94 -16
  269. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  270. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  271. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +11 -3
  272. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  273. package/packages/pi-coding-agent/package.json +1 -1
  274. package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +355 -8
  275. package/packages/pi-coding-agent/src/core/compaction/utils.ts +5 -5
  276. package/packages/pi-coding-agent/src/core/compaction-utils.test.ts +50 -0
  277. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +74 -32
  278. package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.test.ts +73 -0
  279. package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.ts +9 -3
  280. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +113 -21
  281. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +11 -3
  282. package/packages/pi-coding-agent/tsconfig.json +1 -0
  283. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -0
  284. package/packages/pi-tui/dist/__tests__/tui.test.js +60 -1
  285. package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -1
  286. package/packages/pi-tui/dist/tui.d.ts +8 -0
  287. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  288. package/packages/pi-tui/dist/tui.js +32 -3
  289. package/packages/pi-tui/dist/tui.js.map +1 -1
  290. package/packages/pi-tui/package.json +1 -1
  291. package/packages/pi-tui/src/__tests__/tui.test.ts +76 -1
  292. package/packages/pi-tui/src/tui.ts +31 -3
  293. package/packages/pi-tui/tsconfig.json +1 -0
  294. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -0
  295. package/packages/rpc-client/package.json +1 -1
  296. package/packages/rpc-client/tsconfig.json +1 -0
  297. package/packages/rpc-client/tsconfig.tsbuildinfo +1 -0
  298. package/pkg/package.json +1 -1
  299. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +107 -5
  300. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +111 -2
  301. package/src/resources/extensions/gsd/auto/detect-stuck.ts +12 -4
  302. package/src/resources/extensions/gsd/auto/loop-deps.ts +6 -0
  303. package/src/resources/extensions/gsd/auto/phases.ts +90 -10
  304. package/src/resources/extensions/gsd/auto-dispatch.ts +10 -4
  305. package/src/resources/extensions/gsd/auto-model-selection.ts +85 -11
  306. package/src/resources/extensions/gsd/auto-post-unit.ts +107 -58
  307. package/src/resources/extensions/gsd/auto-prompts.ts +13 -0
  308. package/src/resources/extensions/gsd/auto-start.ts +30 -6
  309. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +17 -0
  310. package/src/resources/extensions/gsd/auto-verification.ts +98 -3
  311. package/src/resources/extensions/gsd/auto.ts +38 -14
  312. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +24 -8
  313. package/src/resources/extensions/gsd/commands/catalog.ts +26 -1
  314. package/src/resources/extensions/gsd/commands/handlers/ops.ts +20 -0
  315. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +74 -9
  316. package/src/resources/extensions/gsd/commands-add-tests.ts +137 -0
  317. package/src/resources/extensions/gsd/commands-backlog.ts +182 -0
  318. package/src/resources/extensions/gsd/commands-do.ts +109 -0
  319. package/src/resources/extensions/gsd/commands-handlers.ts +8 -2
  320. package/src/resources/extensions/gsd/commands-maintenance.ts +6 -6
  321. package/src/resources/extensions/gsd/commands-pr-branch.ts +234 -0
  322. package/src/resources/extensions/gsd/commands-session-report.ts +101 -0
  323. package/src/resources/extensions/gsd/commands-ship.ts +219 -0
  324. package/src/resources/extensions/gsd/db-writer.ts +3 -5
  325. package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  326. package/src/resources/extensions/gsd/graph-context.ts +85 -0
  327. package/src/resources/extensions/gsd/gsd-db.ts +467 -0
  328. package/src/resources/extensions/gsd/index.ts +18 -2
  329. package/src/resources/extensions/gsd/md-importer.ts +3 -5
  330. package/src/resources/extensions/gsd/memory-store.ts +31 -62
  331. package/src/resources/extensions/gsd/milestone-validation-gates.ts +13 -14
  332. package/src/resources/extensions/gsd/native-git-bridge.ts +11 -12
  333. package/src/resources/extensions/gsd/notification-widget.ts +2 -2
  334. package/src/resources/extensions/gsd/preferences-models.ts +41 -0
  335. package/src/resources/extensions/gsd/preferences-types.ts +12 -0
  336. package/src/resources/extensions/gsd/preferences-validation.ts +23 -0
  337. package/src/resources/extensions/gsd/prompts/add-tests.md +35 -0
  338. package/src/resources/extensions/gsd/state.ts +80 -17
  339. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +2 -2
  340. package/src/resources/extensions/gsd/tests/auto-post-unit-step-message.test.ts +53 -0
  341. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +51 -2
  342. package/src/resources/extensions/gsd/tests/commands-backlog.test.ts +158 -0
  343. package/src/resources/extensions/gsd/tests/commands-do.test.ts +127 -0
  344. package/src/resources/extensions/gsd/tests/commands-pr-branch.test.ts +68 -0
  345. package/src/resources/extensions/gsd/tests/commands-session-report.test.ts +82 -0
  346. package/src/resources/extensions/gsd/tests/commands-ship.test.ts +71 -0
  347. package/src/resources/extensions/gsd/tests/commands-workflow-custom.test.ts +14 -0
  348. package/src/resources/extensions/gsd/tests/complete-milestone-false-merge.test.ts +142 -0
  349. package/src/resources/extensions/gsd/tests/completed-at-reconcile.test.ts +42 -0
  350. package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +3 -2
  351. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +3 -2
  352. package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +68 -8
  353. package/src/resources/extensions/gsd/tests/derive-state.test.ts +3 -3
  354. package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +154 -0
  355. package/src/resources/extensions/gsd/tests/flat-rate-routing-guard.test.ts +137 -1
  356. package/src/resources/extensions/gsd/tests/graph-context.test.ts +337 -0
  357. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +4 -2
  358. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +68 -1
  359. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +91 -2
  360. package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +140 -0
  361. package/src/resources/extensions/gsd/tests/preferences.test.ts +47 -0
  362. package/src/resources/extensions/gsd/tests/single-writer-invariant.test.ts +180 -0
  363. package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +5 -7
  364. package/src/resources/extensions/gsd/tests/token-profile.test.ts +1 -1
  365. package/src/resources/extensions/gsd/tests/validate-milestone-stuck-guard.test.ts +179 -0
  366. package/src/resources/extensions/gsd/tests/workflow-logger-wiring.test.ts +223 -0
  367. package/src/resources/extensions/gsd/tools/complete-slice.ts +19 -0
  368. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +3 -11
  369. package/src/resources/extensions/gsd/triage-resolution.ts +2 -7
  370. package/src/resources/extensions/gsd/workflow-manifest.ts +9 -104
  371. package/src/resources/extensions/gsd/workflow-migration.ts +21 -29
  372. package/src/resources/extensions/gsd/workflow-projections.ts +8 -1
  373. package/src/resources/extensions/gsd/workflow-reconcile.ts +15 -15
  374. package/dist/web/standalone/.next/static/chunks/app/page-7115e62689b5fd84.js +0 -1
  375. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  376. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  377. /package/dist/web/standalone/.next/static/{Qr27MOHx0lxRGnJvlhxxu → XnHY5eXUsTCFmNodWHetD}/_buildManifest.js +0 -0
  378. /package/dist/web/standalone/.next/static/{Qr27MOHx0lxRGnJvlhxxu → XnHY5eXUsTCFmNodWHetD}/_ssgManifest.js +0 -0
@@ -0,0 +1,337 @@
1
+ /**
2
+ * graph-context.test.ts — Unit tests for inlineGraphSubgraph().
3
+ *
4
+ * Covers:
5
+ * Group 1: Null-return paths (empty term, zero nodes, missing graph.json)
6
+ * Group 2: Correct output formatting (nodes, edges, stale annotation)
7
+ * Group 3: Node formatting (description, confidence, no-description)
8
+ *
9
+ * Testing strategy:
10
+ * @gsd-build/mcp-server is dynamically imported inside inlineGraphSubgraph().
11
+ * Because node:test (v22) does not support mock.module() without the
12
+ * --experimental-test-module-mocks flag (not enabled in test:unit), we
13
+ * exercise the real graphQuery/graphStatus functions by controlling the
14
+ * on-disk graph.json that those functions read. This is a clean, deterministic
15
+ * approach that avoids all module-level mocking.
16
+ *
17
+ * Fixture layout per test:
18
+ * <tmpDir>/.gsd/graphs/graph.json
19
+ *
20
+ * builtAt controls staleness: old timestamp → stale, recent → fresh.
21
+ */
22
+
23
+ import { describe, it } from "node:test";
24
+ import assert from "node:assert/strict";
25
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
26
+ import { join } from "node:path";
27
+ import { tmpdir } from "node:os";
28
+
29
+ import { inlineGraphSubgraph } from "../graph-context.ts";
30
+
31
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
32
+
33
+ interface TestNode {
34
+ id: string;
35
+ label: string;
36
+ type: string;
37
+ confidence: string;
38
+ description?: string;
39
+ sourceFile?: string;
40
+ }
41
+
42
+ interface TestEdge {
43
+ from: string;
44
+ to: string;
45
+ type: string;
46
+ confidence: string;
47
+ }
48
+
49
+ interface GraphFixture {
50
+ nodes: TestNode[];
51
+ edges: TestEdge[];
52
+ /** ISO timestamp for graph.builtAt. Controls staleness. Default: recent (not stale). */
53
+ builtAt?: string;
54
+ }
55
+
56
+ /** Returns an ISO timestamp that is stale (> 24h ago). */
57
+ function staleTimestamp(hoursAgo = 26): string {
58
+ return new Date(Date.now() - hoursAgo * 60 * 60 * 1000).toISOString();
59
+ }
60
+
61
+ /** Returns an ISO timestamp that is fresh (< 24h ago). */
62
+ function freshTimestamp(): string {
63
+ return new Date(Date.now() - 30 * 60 * 1000).toISOString(); // 30 minutes ago
64
+ }
65
+
66
+ /**
67
+ * Creates a temp project directory with a .gsd/graphs/graph.json file.
68
+ * Returns the projectDir path. Caller is responsible for cleanup.
69
+ */
70
+ function makeProjectDir(fixture: GraphFixture): string {
71
+ const projectDir = mkdtempSync(join(tmpdir(), "graph-ctx-test-"));
72
+ const gsdDir = join(projectDir, ".gsd");
73
+ const graphsDir = join(gsdDir, "graphs");
74
+ mkdirSync(graphsDir, { recursive: true });
75
+
76
+ const graph = {
77
+ nodes: fixture.nodes,
78
+ edges: fixture.edges,
79
+ builtAt: fixture.builtAt ?? freshTimestamp(),
80
+ };
81
+
82
+ writeFileSync(join(graphsDir, "graph.json"), JSON.stringify(graph), "utf-8");
83
+ return projectDir;
84
+ }
85
+
86
+ /** Removes a temp directory, suppressing errors on Windows. */
87
+ function cleanup(dir: string): void {
88
+ try { rmSync(dir, { recursive: true, force: true }); } catch { /* ignore */ }
89
+ }
90
+
91
+ /** Minimal node factory. */
92
+ function makeNode(overrides: Partial<TestNode> & { id: string; label: string }): TestNode {
93
+ return {
94
+ type: "CLASS",
95
+ confidence: "INFERRED",
96
+ ...overrides,
97
+ };
98
+ }
99
+
100
+ /** Minimal edge factory. */
101
+ function makeEdge(overrides: Partial<TestEdge> & { from: string; to: string }): TestEdge {
102
+ return {
103
+ type: "CALLS",
104
+ confidence: "INFERRED",
105
+ ...overrides,
106
+ };
107
+ }
108
+
109
+ // ─── Group 1: Null returns ────────────────────────────────────────────────────
110
+
111
+ describe("inlineGraphSubgraph — null returns", () => {
112
+ it("returns null immediately for empty string term", async () => {
113
+ // No graph.json needed — exits before any file I/O
114
+ const result = await inlineGraphSubgraph("/tmp/nonexistent", "", { budget: 3000 });
115
+ assert.strictEqual(result, null);
116
+ });
117
+
118
+ it("returns null for whitespace-only term", async () => {
119
+ const result = await inlineGraphSubgraph("/tmp/nonexistent", " ", { budget: 3000 });
120
+ assert.strictEqual(result, null);
121
+ });
122
+
123
+ it("returns null when graphQuery returns zero nodes (no matching term in graph)", async () => {
124
+ const projectDir = makeProjectDir({
125
+ nodes: [makeNode({ id: "n1", label: "AuthService" })],
126
+ edges: [],
127
+ });
128
+ try {
129
+ // "zzznomatch999" is intentionally absent from the fixture
130
+ const result = await inlineGraphSubgraph(projectDir, "zzznomatch999", { budget: 3000 });
131
+ assert.strictEqual(result, null);
132
+ } finally {
133
+ cleanup(projectDir);
134
+ }
135
+ });
136
+
137
+ it("returns null (no throw) when graph.json is missing", async () => {
138
+ // A project dir with no .gsd directory at all — graphQuery returns zero nodes
139
+ const projectDir = mkdtempSync(join(tmpdir(), "graph-ctx-nofile-"));
140
+ try {
141
+ const result = await inlineGraphSubgraph(projectDir, "auth", { budget: 3000 });
142
+ assert.strictEqual(result, null);
143
+ } finally {
144
+ cleanup(projectDir);
145
+ }
146
+ });
147
+ });
148
+
149
+ // ─── Group 2: Correct output formatting ──────────────────────────────────────
150
+
151
+ describe("inlineGraphSubgraph — correct output", () => {
152
+ it("returns block with section header and node labels when term matches", async () => {
153
+ const projectDir = makeProjectDir({
154
+ nodes: [
155
+ makeNode({ id: "n1", label: "UserService" }),
156
+ makeNode({ id: "n2", label: "UserRepository" }),
157
+ ],
158
+ edges: [],
159
+ });
160
+ try {
161
+ const result = await inlineGraphSubgraph(projectDir, "User", { budget: 3000 });
162
+ assert.ok(result !== null, "result should not be null");
163
+ assert.ok(result!.includes("### Knowledge Graph Context"), "should include section header");
164
+ assert.ok(result!.includes("UserService"), "should include first node label");
165
+ assert.ok(result!.includes("UserRepository"), "should include second node label");
166
+ assert.ok(result!.includes("Nodes (2)"), "should show node count");
167
+ } finally {
168
+ cleanup(projectDir);
169
+ }
170
+ });
171
+
172
+ it("does not include Relations section when edges array is empty", async () => {
173
+ const projectDir = makeProjectDir({
174
+ nodes: [makeNode({ id: "n1", label: "AuthController" })],
175
+ edges: [],
176
+ });
177
+ try {
178
+ const result = await inlineGraphSubgraph(projectDir, "Auth", { budget: 3000 });
179
+ assert.ok(result !== null, "result should not be null");
180
+ assert.ok(!result!.includes("Relations"), "should not include Relations section for zero edges");
181
+ assert.ok(!result!.includes("⚠"), "should not include stale warning for fresh graph");
182
+ } finally {
183
+ cleanup(projectDir);
184
+ }
185
+ });
186
+
187
+ it("includes Relations section when edges are present", async () => {
188
+ const projectDir = makeProjectDir({
189
+ nodes: [
190
+ makeNode({ id: "n1", label: "AuthService" }),
191
+ makeNode({ id: "n2", label: "UserRepo" }),
192
+ ],
193
+ edges: [makeEdge({ from: "n1", to: "n2", type: "CALLS" })],
194
+ });
195
+ try {
196
+ const result = await inlineGraphSubgraph(projectDir, "Auth", { budget: 3000 });
197
+ assert.ok(result !== null, "result should not be null");
198
+ assert.ok(result!.includes("Relations (1)"), "should show edge count");
199
+ assert.ok(result!.includes("→[CALLS]→"), "should include edge type in arrow notation");
200
+ } finally {
201
+ cleanup(projectDir);
202
+ }
203
+ });
204
+
205
+ it("includes stale annotation when graph was built more than 24h ago", async () => {
206
+ const projectDir = makeProjectDir({
207
+ nodes: [makeNode({ id: "n1", label: "AuthService" })],
208
+ edges: [],
209
+ builtAt: staleTimestamp(26), // 26 hours ago → stale
210
+ });
211
+ try {
212
+ const result = await inlineGraphSubgraph(projectDir, "Auth", { budget: 3000 });
213
+ assert.ok(result !== null, "result should not be null");
214
+ assert.ok(result!.includes("⚠ Graph last built"), "should include stale annotation");
215
+ assert.ok(result!.includes("h ago"), "should include hours-ago text");
216
+ } finally {
217
+ cleanup(projectDir);
218
+ }
219
+ });
220
+
221
+ it("does not include stale annotation for a fresh graph", async () => {
222
+ const projectDir = makeProjectDir({
223
+ nodes: [makeNode({ id: "n1", label: "AuthService" })],
224
+ edges: [],
225
+ builtAt: freshTimestamp(), // 30 minutes ago → not stale
226
+ });
227
+ try {
228
+ const result = await inlineGraphSubgraph(projectDir, "Auth", { budget: 3000 });
229
+ assert.ok(result !== null, "result should not be null");
230
+ assert.ok(!result!.includes("⚠"), "should not include stale annotation for fresh graph");
231
+ } finally {
232
+ cleanup(projectDir);
233
+ }
234
+ });
235
+
236
+ it("returns valid block even when graph.json has corrupted builtAt (graphStatus throws internally)", async () => {
237
+ // Write a graph.json with an invalid builtAt — graphStatus will catch and return {exists: false}
238
+ // inlineGraphSubgraph should still return the node block without stale annotation
239
+ const projectDir = mkdtempSync(join(tmpdir(), "graph-ctx-corrupt-"));
240
+ const gsdDir = join(projectDir, ".gsd");
241
+ const graphsDir = join(gsdDir, "graphs");
242
+ mkdirSync(graphsDir, { recursive: true });
243
+
244
+ const graph = {
245
+ nodes: [{ id: "n1", label: "AuthController", type: "CLASS", confidence: "INFERRED" }],
246
+ edges: [],
247
+ builtAt: "NOT-A-DATE", // invalid ISO — will cause Date.now() - NaN to produce NaN
248
+ };
249
+ writeFileSync(join(graphsDir, "graph.json"), JSON.stringify(graph), "utf-8");
250
+
251
+ try {
252
+ const result = await inlineGraphSubgraph(projectDir, "Auth", { budget: 3000 });
253
+ // graphQuery reads the file and finds the node; graphStatus may return {exists: true, stale: false/true}
254
+ // Either way, function must not throw and must return a string with node content
255
+ assert.ok(result !== null, "result should not be null");
256
+ assert.ok(result!.includes("AuthController"), "should include node label");
257
+ } finally {
258
+ cleanup(projectDir);
259
+ }
260
+ });
261
+
262
+ it("passes the budget option to graphQuery (enforces node count limit)", async () => {
263
+ // Each node uses ~20 tokens. With budget=20, only ~1 node should be returned.
264
+ // Build a graph with many nodes all matching the same term.
265
+ const nodes: TestNode[] = Array.from({ length: 10 }, (_, i) =>
266
+ makeNode({ id: `n${i}`, label: `AuthModule${i}` })
267
+ );
268
+ const projectDir = makeProjectDir({ nodes, edges: [] });
269
+ try {
270
+ const resultSmall = await inlineGraphSubgraph(projectDir, "Auth", { budget: 20 });
271
+ const resultLarge = await inlineGraphSubgraph(projectDir, "Auth", { budget: 10000 });
272
+
273
+ // Both should return something (at least 1 node matches)
274
+ assert.ok(resultSmall !== null, "small-budget result should not be null");
275
+ assert.ok(resultLarge !== null, "large-budget result should not be null");
276
+
277
+ // With a very small budget (20 tokens ≈ 1 node), fewer nodes should appear
278
+ const smallNodeCount = (resultSmall!.match(/- \*\*/g) || []).length;
279
+ const largeNodeCount = (resultLarge!.match(/- \*\*/g) || []).length;
280
+ assert.ok(
281
+ smallNodeCount <= largeNodeCount,
282
+ `small-budget should return <= nodes than large-budget (got ${smallNodeCount} vs ${largeNodeCount})`,
283
+ );
284
+ } finally {
285
+ cleanup(projectDir);
286
+ }
287
+ });
288
+ });
289
+
290
+ // ─── Group 3: Node formatting ─────────────────────────────────────────────────
291
+
292
+ describe("inlineGraphSubgraph — node formatting", () => {
293
+ it("includes description after em-dash when node has description", async () => {
294
+ const projectDir = makeProjectDir({
295
+ nodes: [makeNode({ id: "n1", label: "JwtValidator", description: "JWT validation" })],
296
+ edges: [],
297
+ });
298
+ try {
299
+ const result = await inlineGraphSubgraph(projectDir, "Jwt", { budget: 3000 });
300
+ assert.ok(result !== null, "result should not be null");
301
+ assert.ok(result!.includes("— JWT validation"), "should include description after em-dash");
302
+ } finally {
303
+ cleanup(projectDir);
304
+ }
305
+ });
306
+
307
+ it("omits em-dash suffix when node has no description", async () => {
308
+ const projectDir = makeProjectDir({
309
+ nodes: [makeNode({ id: "n1", label: "TokenStore" })], // no description
310
+ edges: [],
311
+ });
312
+ try {
313
+ const result = await inlineGraphSubgraph(projectDir, "Token", { budget: 3000 });
314
+ assert.ok(result !== null, "result should not be null");
315
+ const lines = result!.split("\n");
316
+ const nodeLine = lines.find((l) => l.includes("TokenStore"));
317
+ assert.ok(nodeLine !== undefined, "node line should be present");
318
+ assert.ok(!nodeLine.includes("—"), "node line should not include em-dash when no description");
319
+ } finally {
320
+ cleanup(projectDir);
321
+ }
322
+ });
323
+
324
+ it("includes confidence tier in the node output line", async () => {
325
+ const projectDir = makeProjectDir({
326
+ nodes: [makeNode({ id: "n1", label: "AuthService", confidence: "EXTRACTED" })],
327
+ edges: [],
328
+ });
329
+ try {
330
+ const result = await inlineGraphSubgraph(projectDir, "Auth", { budget: 3000 });
331
+ assert.ok(result !== null, "result should not be null");
332
+ assert.ok(result!.includes("EXTRACTED"), "should include the confidence tier in node line");
333
+ } finally {
334
+ cleanup(projectDir);
335
+ }
336
+ });
337
+ });
@@ -691,7 +691,7 @@ describe("transition boundary failures", () => {
691
691
  );
692
692
  });
693
693
 
694
- test("blocked state: all slices have unmet deps → blocked phase", async () => {
694
+ test("blocked state: all slices have unmet deps → fallback picks slice", async () => {
695
695
  base = makeTempDir();
696
696
  const mDir = join(base, ".gsd", "milestones", "M001");
697
697
  mkdirSync(join(mDir, "slices", "S01", "tasks"), { recursive: true });
@@ -736,7 +736,9 @@ describe("transition boundary failures", () => {
736
736
 
737
737
  invalidateAllCaches();
738
738
  const state = await deriveStateFromDb(base);
739
- assert.equal(state.phase, "blocked", "circular deps should produce blocked phase");
739
+ // With partial-dep fallback, circular deps no longer block — fallback picks first eligible slice
740
+ assert.equal(state.phase, "planning", "circular deps: fallback picks a slice instead of blocking");
741
+ assert.ok(state.activeSlice !== null, "activeSlice set via fallback");
740
742
  });
741
743
  });
742
744
 
@@ -92,6 +92,7 @@ function makeMockDeps(
92
92
  getPriorSliceCompletionBlocker: () => null,
93
93
  getMainBranch: () => "main",
94
94
  closeoutUnit: async () => {},
95
+ autoCommitUnit: async () => null,
95
96
  recordOutcome: () => {},
96
97
  writeLock: () => {},
97
98
  captureAvailableSkills: () => {},
@@ -567,7 +568,15 @@ test("unit-end event contains errorContext when unit is cancelled with structure
567
568
  const { resolveAgentEndCancelled, _resetPendingResolve } = await import("../auto-loop.js");
568
569
  _resetPendingResolve();
569
570
 
570
- const deps = makeMockDeps(capture);
571
+ let pauseCalls = 0;
572
+ let commitCalls = 0;
573
+ const deps = makeMockDeps(capture, {
574
+ pauseAuto: async () => { pauseCalls++; },
575
+ autoCommitUnit: async () => {
576
+ commitCalls++;
577
+ return "commit";
578
+ },
579
+ });
571
580
  const ic = makeIC(deps);
572
581
  const iterData: IterationData = {
573
582
  unitType: "execute-task",
@@ -593,10 +602,68 @@ test("unit-end event contains errorContext when unit is cancelled with structure
593
602
  // Transient timeout cancellations pause (recoverable) instead of hard-stopping
594
603
  assert.equal(result.action, "break");
595
604
  assert.equal((result as any).reason, "session-timeout");
605
+ assert.equal(pauseCalls, 1, "timeout cancellations should pause auto-mode exactly once");
606
+ assert.equal(commitCalls, 1, "timeout cancellations should flush a unit auto-commit once");
596
607
 
597
608
  // Verify error classification used structured errorContext on the window entry
598
609
  const entry = loopState.recentUnits[loopState.recentUnits.length - 1];
599
610
  assert.ok(entry.error, "window entry must have error set");
600
611
  assert.ok(entry.error!.startsWith("timeout:"), "error must start with category from errorContext");
601
612
  assert.ok(entry.error!.includes("Hard timeout error"), "error must include the errorContext message");
613
+
614
+ const endEvents = capture.events.filter(e => e.eventType === "unit-end");
615
+ assert.equal(endEvents.length, 1, "timeout cancellations should still emit unit-end");
616
+ assert.equal((endEvents[0].data as any).status, "cancelled");
617
+ assert.equal((endEvents[0].data as any).artifactVerified, false);
618
+ assert.equal((endEvents[0].data as any).errorContext.category, "timeout");
619
+ });
620
+
621
+ test("session-failed cancellations close out and emit unit-end before hard stop", async () => {
622
+ const capture = createEventCapture();
623
+ const { resolveAgentEndCancelled, _resetPendingResolve } = await import("../auto-loop.js");
624
+ _resetPendingResolve();
625
+
626
+ let closeoutCalls = 0;
627
+ let commitCalls = 0;
628
+ let stopCalls = 0;
629
+ const deps = makeMockDeps(capture, {
630
+ closeoutUnit: async () => { closeoutCalls++; },
631
+ autoCommitUnit: async () => {
632
+ commitCalls++;
633
+ return "commit";
634
+ },
635
+ stopAuto: async () => { stopCalls++; },
636
+ });
637
+ const ic = makeIC(deps);
638
+ const iterData: IterationData = {
639
+ unitType: "execute-task",
640
+ unitId: "M001/S01/T01",
641
+ prompt: "do stuff",
642
+ finalPrompt: "do stuff",
643
+ pauseAfterUatDispatch: false,
644
+ state: { phase: "executing", activeMilestone: { id: "M001" }, activeSlice: { id: "S01" }, registry: [], blockers: [] } as any,
645
+ mid: "M001",
646
+ midTitle: "Test",
647
+ isRetry: false,
648
+ previousTier: undefined,
649
+ };
650
+ const loopState: LoopState = { recentUnits: [{ key: "execute-task/M001/S01/T01" }], stuckRecoveryAttempts: 0, consecutiveFinalizeTimeouts: 0 };
651
+
652
+ const unitPromise = runUnitPhase(ic, iterData, loopState);
653
+ await new Promise(r => setTimeout(r, 50));
654
+
655
+ resolveAgentEndCancelled({ message: "session bootstrap exploded", category: "session-failed", isTransient: false });
656
+
657
+ const result = await unitPromise;
658
+ assert.equal(result.action, "break");
659
+ assert.equal((result as any).reason, "session-failed");
660
+ assert.equal(closeoutCalls, 1, "session-failed cancellations should close out the unit before stopping");
661
+ assert.equal(commitCalls, 1, "session-failed cancellations should try one auto-commit flush");
662
+ assert.equal(stopCalls, 1, "session-failed cancellations should hard-stop auto-mode");
663
+
664
+ const endEvents = capture.events.filter(e => e.eventType === "unit-end");
665
+ assert.equal(endEvents.length, 1, "session-failed cancellations should emit unit-end");
666
+ assert.equal((endEvents[0].data as any).status, "cancelled");
667
+ assert.equal((endEvents[0].data as any).artifactVerified, false);
668
+ assert.equal((endEvents[0].data as any).errorContext.category, "session-failed");
602
669
  });
@@ -1,6 +1,8 @@
1
1
  /**
2
- * Tests for model config isolation between concurrent instances (#650, #1065)
3
- * and session-scoped model precedence behavior.
2
+ * Tests for model config isolation between concurrent instances (#650, #1065),
3
+ * session-scoped model precedence behavior including manual session override,
4
+ * GSD preferences override of settings.json defaults (#3517), and custom
5
+ * provider precedence over PREFERENCES.md when set via `/gsd model` (#4122).
4
6
  */
5
7
 
6
8
  import { describe, it, beforeEach, afterEach } from "node:test";
@@ -214,3 +216,90 @@ describe("manual session model override precedence", () => {
214
216
  "should be null when no model source is available");
215
217
  });
216
218
  });
219
+
220
+ // ─── Custom provider session model wins over PREFERENCES.md (#4122) ─────────
221
+
222
+ describe("custom provider session model overrides PREFERENCES.md (#4122)", () => {
223
+ // Mirrors the auto-start.ts logic:
224
+ // sessionProviderIsCustom && ctx.model
225
+ // ? ctx.model
226
+ // : (preferredModel ?? ctx.model ?? null)
227
+ function selectStartModel(args: {
228
+ ctxModel: { provider: string; id: string } | null;
229
+ preferredModel: { provider: string; id: string } | undefined;
230
+ sessionProviderIsCustom: boolean;
231
+ }): { provider: string; id: string } | null {
232
+ const { ctxModel, preferredModel, sessionProviderIsCustom } = args;
233
+ if (sessionProviderIsCustom && ctxModel) {
234
+ return { provider: ctxModel.provider, id: ctxModel.id };
235
+ }
236
+ return preferredModel
237
+ ?? (ctxModel ? { provider: ctxModel.provider, id: ctxModel.id } : null);
238
+ }
239
+
240
+ it("custom provider from /gsd model wins over PREFERENCES.md built-in default", () => {
241
+ // User runs `/gsd model ollama/llama3.1:8b`, then `/gsd auto`.
242
+ // PREFERENCES.md still has the project-template claude-code default.
243
+ const ctxModel = { provider: "ollama", id: "llama3.1:8b" };
244
+ const preferredModel = { provider: "claude-code", id: "claude-sonnet-4-6" };
245
+
246
+ const snapshot = selectStartModel({
247
+ ctxModel,
248
+ preferredModel,
249
+ sessionProviderIsCustom: true,
250
+ });
251
+
252
+ assert.equal(snapshot?.provider, "ollama",
253
+ "custom-provider session model must win over PREFERENCES.md");
254
+ assert.equal(snapshot?.id, "llama3.1:8b",
255
+ "custom-provider session model id must be preserved");
256
+ assert.notEqual(snapshot?.provider, "claude-code",
257
+ "claude-code from PREFERENCES.md must NOT be selected when session is custom");
258
+ });
259
+
260
+ it("built-in session provider still defers to PREFERENCES.md (#3517 preserved)", () => {
261
+ // ctx.model is a built-in provider (claude-code) but PREFERENCES.md has
262
+ // an explicit openai-codex preference. PREFERENCES.md should still win.
263
+ const ctxModel = { provider: "claude-code", id: "claude-sonnet-4-6" };
264
+ const preferredModel = { provider: "openai-codex", id: "gpt-5.4" };
265
+
266
+ const snapshot = selectStartModel({
267
+ ctxModel,
268
+ preferredModel,
269
+ sessionProviderIsCustom: false,
270
+ });
271
+
272
+ assert.equal(snapshot?.provider, "openai-codex",
273
+ "PREFERENCES.md must still win when session provider is built-in");
274
+ assert.equal(snapshot?.id, "gpt-5.4");
275
+ });
276
+
277
+ it("custom provider with no PREFERENCES.md still uses ctx.model", () => {
278
+ const ctxModel = { provider: "vllm", id: "qwen2.5-coder:32b" };
279
+
280
+ const snapshot = selectStartModel({
281
+ ctxModel,
282
+ preferredModel: undefined,
283
+ sessionProviderIsCustom: true,
284
+ });
285
+
286
+ assert.equal(snapshot?.provider, "vllm");
287
+ assert.equal(snapshot?.id, "qwen2.5-coder:32b");
288
+ });
289
+
290
+ it("null ctx.model with custom flag falls through to preferredModel", () => {
291
+ // Defensive: sessionProviderIsCustom can only be true if ctx.model exists,
292
+ // but verify the guard works if that invariant is ever broken.
293
+ const preferredModel = { provider: "claude-code", id: "claude-sonnet-4-6" };
294
+
295
+ const snapshot = selectStartModel({
296
+ ctxModel: null,
297
+ preferredModel,
298
+ sessionProviderIsCustom: true,
299
+ });
300
+
301
+ assert.equal(snapshot?.provider, "claude-code",
302
+ "should fall back to preferredModel when ctx.model is null");
303
+ });
304
+ });
305
+