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
@@ -3,7 +3,19 @@
3
3
  // Storage layer for auto-learned project memories. Follows context-store.ts patterns.
4
4
  // All functions degrade gracefully: return empty results when DB unavailable, never throw.
5
5
 
6
- import { isDbAvailable, _getAdapter, transaction } from './gsd-db.js';
6
+ import {
7
+ isDbAvailable,
8
+ _getAdapter,
9
+ transaction,
10
+ insertMemoryRow,
11
+ rewriteMemoryId,
12
+ updateMemoryContentRow,
13
+ incrementMemoryHitCount,
14
+ supersedeMemoryRow,
15
+ markMemoryUnitProcessed,
16
+ decayMemoriesBefore,
17
+ supersedeLowestRankedMemories,
18
+ } from './gsd-db.js';
7
19
 
8
20
  // ─── Types ──────────────────────────────────────────────────────────────────
9
21
 
@@ -170,28 +182,22 @@ export function createMemory(fields: {
170
182
  const now = new Date().toISOString();
171
183
  // Insert with a temporary placeholder ID — seq is auto-assigned
172
184
  const placeholder = `_TMP_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
173
- adapter.prepare(
174
- `INSERT INTO memories (id, category, content, confidence, source_unit_type, source_unit_id, created_at, updated_at)
175
- VALUES (:id, :category, :content, :confidence, :source_unit_type, :source_unit_id, :created_at, :updated_at)`,
176
- ).run({
177
- ':id': placeholder,
178
- ':category': fields.category,
179
- ':content': fields.content,
180
- ':confidence': fields.confidence ?? 0.8,
181
- ':source_unit_type': fields.source_unit_type ?? null,
182
- ':source_unit_id': fields.source_unit_id ?? null,
183
- ':created_at': now,
184
- ':updated_at': now,
185
+ insertMemoryRow({
186
+ id: placeholder,
187
+ category: fields.category,
188
+ content: fields.content,
189
+ confidence: fields.confidence ?? 0.8,
190
+ sourceUnitType: fields.source_unit_type ?? null,
191
+ sourceUnitId: fields.source_unit_id ?? null,
192
+ createdAt: now,
193
+ updatedAt: now,
185
194
  });
186
- // Derive the real ID from the assigned seq
195
+ // Derive the real ID from the assigned seq (SELECT is still fine via adapter)
187
196
  const row = adapter.prepare('SELECT seq FROM memories WHERE id = :id').get({ ':id': placeholder });
188
197
  if (!row) return placeholder; // fallback — should not happen
189
198
  const seq = row['seq'] as number;
190
199
  const realId = `MEM${String(seq).padStart(3, '0')}`;
191
- adapter.prepare('UPDATE memories SET id = :real_id WHERE id = :placeholder').run({
192
- ':real_id': realId,
193
- ':placeholder': placeholder,
194
- });
200
+ rewriteMemoryId(placeholder, realId);
195
201
  return realId;
196
202
  } catch {
197
203
  return null;
@@ -203,20 +209,9 @@ export function createMemory(fields: {
203
209
  */
204
210
  export function updateMemoryContent(id: string, content: string, confidence?: number): boolean {
205
211
  if (!isDbAvailable()) return false;
206
- const adapter = _getAdapter();
207
- if (!adapter) return false;
208
212
 
209
213
  try {
210
- const now = new Date().toISOString();
211
- if (confidence != null) {
212
- adapter.prepare(
213
- 'UPDATE memories SET content = :content, confidence = :confidence, updated_at = :updated_at WHERE id = :id',
214
- ).run({ ':content': content, ':confidence': confidence, ':updated_at': now, ':id': id });
215
- } else {
216
- adapter.prepare(
217
- 'UPDATE memories SET content = :content, updated_at = :updated_at WHERE id = :id',
218
- ).run({ ':content': content, ':updated_at': now, ':id': id });
219
- }
214
+ updateMemoryContentRow(id, content, confidence, new Date().toISOString());
220
215
  return true;
221
216
  } catch {
222
217
  return false;
@@ -228,13 +223,9 @@ export function updateMemoryContent(id: string, content: string, confidence?: nu
228
223
  */
229
224
  export function reinforceMemory(id: string): boolean {
230
225
  if (!isDbAvailable()) return false;
231
- const adapter = _getAdapter();
232
- if (!adapter) return false;
233
226
 
234
227
  try {
235
- adapter.prepare(
236
- 'UPDATE memories SET hit_count = hit_count + 1, updated_at = :updated_at WHERE id = :id',
237
- ).run({ ':updated_at': new Date().toISOString(), ':id': id });
228
+ incrementMemoryHitCount(id, new Date().toISOString());
238
229
  return true;
239
230
  } catch {
240
231
  return false;
@@ -246,13 +237,9 @@ export function reinforceMemory(id: string): boolean {
246
237
  */
247
238
  export function supersedeMemory(oldId: string, newId: string): boolean {
248
239
  if (!isDbAvailable()) return false;
249
- const adapter = _getAdapter();
250
- if (!adapter) return false;
251
240
 
252
241
  try {
253
- adapter.prepare(
254
- 'UPDATE memories SET superseded_by = :new_id, updated_at = :updated_at WHERE id = :old_id',
255
- ).run({ ':new_id': newId, ':updated_at': new Date().toISOString(), ':old_id': oldId });
242
+ supersedeMemoryRow(oldId, newId, new Date().toISOString());
256
243
  return true;
257
244
  } catch {
258
245
  return false;
@@ -284,14 +271,9 @@ export function isUnitProcessed(unitKey: string): boolean {
284
271
  */
285
272
  export function markUnitProcessed(unitKey: string, activityFile: string): boolean {
286
273
  if (!isDbAvailable()) return false;
287
- const adapter = _getAdapter();
288
- if (!adapter) return false;
289
274
 
290
275
  try {
291
- adapter.prepare(
292
- `INSERT OR IGNORE INTO memory_processed_units (unit_key, activity_file, processed_at)
293
- VALUES (:key, :file, :at)`,
294
- ).run({ ':key': unitKey, ':file': activityFile, ':at': new Date().toISOString() });
276
+ markMemoryUnitProcessed(unitKey, activityFile, new Date().toISOString());
295
277
  return true;
296
278
  } catch {
297
279
  return false;
@@ -310,7 +292,7 @@ export function decayStaleMemories(thresholdUnits = 20): void {
310
292
  if (!adapter) return;
311
293
 
312
294
  try {
313
- // Find the timestamp of the Nth most recent processed unit
295
+ // Find the timestamp of the Nth most recent processed unit (read-only SELECT)
314
296
  const row = adapter.prepare(
315
297
  `SELECT processed_at FROM memory_processed_units
316
298
  ORDER BY processed_at DESC
@@ -320,11 +302,7 @@ export function decayStaleMemories(thresholdUnits = 20): void {
320
302
  if (!row) return; // not enough processed units yet
321
303
 
322
304
  const cutoff = row['processed_at'] as string;
323
- adapter.prepare(
324
- `UPDATE memories
325
- SET confidence = MAX(0.1, confidence - 0.1), updated_at = :now
326
- WHERE superseded_by IS NULL AND updated_at < :cutoff AND confidence > 0.1`,
327
- ).run({ ':now': new Date().toISOString(), ':cutoff': cutoff });
305
+ decayMemoriesBefore(cutoff, new Date().toISOString());
328
306
  } catch {
329
307
  // non-fatal
330
308
  }
@@ -346,16 +324,7 @@ export function enforceMemoryCap(max = 50): void {
346
324
  if (count <= max) return;
347
325
 
348
326
  const excess = count - max;
349
- // Batch update: supersede lowest-ranked active memories in a single statement
350
- adapter.prepare(
351
- `UPDATE memories SET superseded_by = 'CAP_EXCEEDED', updated_at = :now
352
- WHERE id IN (
353
- SELECT id FROM memories
354
- WHERE superseded_by IS NULL
355
- ORDER BY (confidence * (1.0 + hit_count * 0.1)) ASC
356
- LIMIT :limit
357
- )`,
358
- ).run({ ':now': new Date().toISOString(), ':limit': excess });
327
+ supersedeLowestRankedMemories(excess, new Date().toISOString());
359
328
  } catch {
360
329
  // non-fatal
361
330
  }
@@ -11,7 +11,7 @@
11
11
  * dispatch rules, and state derivation. See gate-registry.ts.
12
12
  */
13
13
 
14
- import { _getAdapter } from "./gsd-db.js";
14
+ import { isDbAvailable, upsertQualityGate } from "./gsd-db.js";
15
15
  import { getGatesForTurn } from "./gate-registry.js";
16
16
 
17
17
  /**
@@ -31,24 +31,23 @@ export function insertMilestoneValidationGates(
31
31
  verdict: string,
32
32
  evaluatedAt: string,
33
33
  ): void {
34
- const db = _getAdapter();
35
- if (!db) return;
34
+ if (!isDbAvailable()) return;
36
35
 
37
36
  const gateVerdict = verdict === "pass" ? "pass" : "flag";
38
37
  const milestoneGates = getGatesForTurn("validate-milestone");
39
38
 
40
39
  for (const def of milestoneGates) {
41
- db.prepare(
42
- `INSERT OR REPLACE INTO quality_gates
43
- (milestone_id, slice_id, gate_id, scope, task_id, status, verdict, rationale, findings, evaluated_at)
44
- VALUES (:mid, :sid, :gid, 'milestone', '', 'complete', :verdict, :rationale, '', :evaluated_at)`,
45
- ).run({
46
- ":mid": milestoneId,
47
- ":sid": sliceId,
48
- ":gid": def.id,
49
- ":verdict": gateVerdict,
50
- ":rationale": `${def.promptSection} — milestone validation verdict: ${verdict}`,
51
- ":evaluated_at": evaluatedAt,
40
+ upsertQualityGate({
41
+ milestoneId,
42
+ sliceId,
43
+ gateId: def.id,
44
+ scope: "milestone",
45
+ taskId: "",
46
+ status: "complete",
47
+ verdict: gateVerdict,
48
+ rationale: `${def.promptSection} — milestone validation verdict: ${verdict}`,
49
+ findings: "",
50
+ evaluatedAt,
52
51
  });
53
52
  }
54
53
  }
@@ -323,7 +323,7 @@ export function nativeIsRepo(basePath: string): boolean {
323
323
  return native.gitIsRepo(basePath);
324
324
  }
325
325
  try {
326
- execSync("git rev-parse --git-dir", { cwd: basePath, stdio: "pipe" });
326
+ execFileSync("git", ["rev-parse", "--git-dir"], { cwd: basePath, stdio: "pipe" });
327
327
  return true;
328
328
  } catch {
329
329
  return false;
@@ -790,16 +790,15 @@ export function nativeCommit(
790
790
 
791
791
  // Fallback: use git commit with stdin pipe for safe multi-line messages
792
792
  try {
793
- const result = execSync(
794
- `git commit --no-verify -F -${options?.allowEmpty ? " --allow-empty" : ""}`,
795
- {
796
- cwd: basePath,
797
- stdio: ["pipe", "pipe", "pipe"],
798
- encoding: "utf-8",
799
- env: GIT_NO_PROMPT_ENV,
800
- input: message,
801
- },
802
- ).trim();
793
+ const args = ["commit", "--no-verify", "-F", "-"];
794
+ if (options?.allowEmpty) args.push("--allow-empty");
795
+ const result = execFileSync("git", args, {
796
+ cwd: basePath,
797
+ stdio: ["pipe", "pipe", "pipe"],
798
+ encoding: "utf-8",
799
+ env: GIT_NO_PROMPT_ENV,
800
+ input: message,
801
+ }).trim();
803
802
  return result;
804
803
  } catch (err: unknown) {
805
804
  const errObj = err as { stdout?: string; stderr?: string; message?: string };
@@ -940,7 +939,7 @@ export function nativeResetHard(basePath: string): void {
940
939
  native.gitResetHard(basePath);
941
940
  return;
942
941
  }
943
- execSync("git reset --hard HEAD", { cwd: basePath, stdio: "pipe" });
942
+ execFileSync("git", ["reset", "--hard", "HEAD"], { cwd: basePath, stdio: "pipe" });
944
943
  }
945
944
 
946
945
  /**
@@ -1,6 +1,6 @@
1
1
  // GSD Extension — Notification Widget
2
2
  // Always-on ambient widget rendered belowEditor showing unread count and
3
- // the most recent notification message. Refreshes every 5 seconds.
3
+ // the most recent notification message. Refreshes every 30 seconds.
4
4
  // Widget key: "gsd-notifications", placement: "belowEditor"
5
5
 
6
6
  import type { ExtensionContext } from "@gsd/pi-coding-agent";
@@ -19,7 +19,7 @@ export function buildNotificationWidgetLines(): string[] {
19
19
 
20
20
  // ─── Widget init ────────────────────────────────────────────────────────
21
21
 
22
- const REFRESH_INTERVAL_MS = 5_000;
22
+ const REFRESH_INTERVAL_MS = 30_000;
23
23
 
24
24
  /**
25
25
  * Initialize the always-on notification widget (belowEditor).
@@ -7,6 +7,8 @@
7
7
  */
8
8
 
9
9
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
10
+ import { homedir } from "node:os";
11
+ import { join } from "node:path";
10
12
  import type { DynamicRoutingConfig } from "./model-router.js";
11
13
  import { defaultRoutingConfig } from "./model-router.js";
12
14
  import type { TokenProfile, InlineLevel } from "./types.js";
@@ -185,6 +187,45 @@ export function resolveDefaultSessionModel(
185
187
  return undefined;
186
188
  }
187
189
 
190
+ /**
191
+ * Returns true if `provider` is defined as a custom provider in the user's
192
+ * `~/.gsd/agent/models.json` (Ollama, vLLM, LM Studio, OpenAI-compatible
193
+ * proxies, etc.).
194
+ *
195
+ * Used by auto-mode bootstrap to decide whether the session model
196
+ * (set via `/gsd model`) should override `PREFERENCES.md`. Custom providers
197
+ * are never reachable from `PREFERENCES.md` (which only knows built-in
198
+ * providers), so when the user has explicitly selected one, it must take
199
+ * priority — otherwise auto-mode tries to start the built-in provider from
200
+ * PREFERENCES.md and fails with "Not logged in · Please run /login" (#4122).
201
+ *
202
+ * Reads models.json directly with a lightweight JSON parse to avoid
203
+ * pulling in the full model-registry at this call site. Falls back to
204
+ * `~/.pi/agent/models.json` for parity with `resolveModelsJsonPath()`.
205
+ * Any read or parse error yields `false` (treat as not-custom) so a
206
+ * malformed models.json never breaks the session bootstrap.
207
+ */
208
+ export function isCustomProvider(provider: string | undefined): boolean {
209
+ if (!provider) return false;
210
+ const candidates = [
211
+ join(homedir(), ".gsd", "agent", "models.json"),
212
+ join(homedir(), ".pi", "agent", "models.json"),
213
+ ];
214
+ for (const path of candidates) {
215
+ if (!existsSync(path)) continue;
216
+ try {
217
+ const raw = readFileSync(path, "utf-8");
218
+ const parsed = JSON.parse(raw) as { providers?: Record<string, unknown> };
219
+ if (parsed?.providers && Object.prototype.hasOwnProperty.call(parsed.providers, provider)) {
220
+ return true;
221
+ }
222
+ } catch {
223
+ // Ignore — malformed models.json must not break bootstrap.
224
+ }
225
+ }
226
+ return false;
227
+ }
228
+
188
229
  /**
189
230
  * Determines the next fallback model to try when the current model fails.
190
231
  * If the current model is not in the configured list, returns the primary model.
@@ -113,6 +113,7 @@ export const KNOWN_PREFERENCE_KEYS = new Set<string>([
113
113
  "discuss_preparation",
114
114
  "discuss_web_research",
115
115
  "discuss_depth",
116
+ "flat_rate_providers",
116
117
  ]);
117
118
 
118
119
  /** Canonical list of all dispatch unit types. */
@@ -359,6 +360,17 @@ export interface GSDPreferences {
359
360
  * Default: "standard".
360
361
  */
361
362
  discuss_depth?: "quick" | "standard" | "thorough";
363
+ /**
364
+ * Extra provider IDs to treat as flat-rate (no cost benefit from dynamic
365
+ * routing). Dynamic routing is suppressed for any provider listed here,
366
+ * in addition to the built-in list (github-copilot, copilot, claude-code)
367
+ * and any provider auto-detected via `authMode: "externalCli"`.
368
+ *
369
+ * Intended for private subscription-backed proxies, enterprise-gated
370
+ * deployments, and custom CLI wrappers where every request costs the
371
+ * same regardless of model. Case-insensitive.
372
+ */
373
+ flat_rate_providers?: string[];
362
374
  }
363
375
 
364
376
  export interface LoadedGSDPreferences {
@@ -180,6 +180,29 @@ export function validatePreferences(preferences: GSDPreferences): {
180
180
  }
181
181
  }
182
182
 
183
+ // ─── Flat-rate Providers ────────────────────────────────────────────
184
+ // User-declared flat-rate providers for dynamic routing suppression.
185
+ // Built-in providers (github-copilot, copilot, claude-code) and any
186
+ // externalCli provider are already auto-detected; this list layers on
187
+ // top for private subscription proxies and custom CLI wrappers.
188
+ if (preferences.flat_rate_providers !== undefined) {
189
+ if (Array.isArray(preferences.flat_rate_providers)) {
190
+ const allStrings = preferences.flat_rate_providers.every(
191
+ (item: unknown) => typeof item === "string",
192
+ );
193
+ if (allStrings) {
194
+ // Strip empty/whitespace-only entries to avoid false matches.
195
+ validated.flat_rate_providers = preferences.flat_rate_providers
196
+ .map((s: string) => s.trim())
197
+ .filter((s: string) => s.length > 0);
198
+ } else {
199
+ errors.push("flat_rate_providers must be an array of strings");
200
+ }
201
+ } else {
202
+ errors.push("flat_rate_providers must be an array of strings");
203
+ }
204
+ }
205
+
183
206
  // ─── Phase Skip Preferences ─────────────────────────────────────────
184
207
  if (preferences.phases !== undefined) {
185
208
  if (typeof preferences.phases === "object" && preferences.phases !== null) {
@@ -0,0 +1,35 @@
1
+ You are generating tests for recently completed GSD work.
2
+
3
+ ## Slice: {{sliceId}} — {{sliceTitle}}
4
+
5
+ ### Summary
6
+
7
+ {{sliceSummary}}
8
+
9
+ ### Existing Test Patterns
10
+
11
+ {{existingTestPatterns}}
12
+
13
+ ## Working Directory
14
+
15
+ `{{workingDirectory}}`
16
+
17
+ ## Instructions
18
+
19
+ 1. Read the slice summary above to understand what was built
20
+ 2. Identify the source files that were created or modified for this slice
21
+ 3. Read the implementation code to understand behavior, edge cases, and error paths
22
+ 4. Write comprehensive tests following the project's existing test patterns and framework
23
+ 5. Run the tests to verify they pass
24
+ 6. Fix any failures
25
+
26
+ ### Rules
27
+
28
+ - Follow the project's existing test patterns (framework, assertions, file structure)
29
+ - Test behavior, not implementation details
30
+ - Cover: happy path, edge cases, error conditions, boundary values
31
+ - Do NOT modify implementation files — only create or update test files
32
+ - Name test files consistently with the project's conventions
33
+ - Keep tests focused and readable
34
+
35
+ {{skillActivation}}
@@ -345,8 +345,15 @@ function reconcileDiskToDb(basePath: string): MilestoneRow[] {
345
345
  const dbSliceIds = new Set(dbSlices.map(s => s.id));
346
346
 
347
347
  let roadmapContent: string;
348
- try { roadmapContent = readFileSync(roadmapPath, "utf-8"); }
349
- catch { continue; }
348
+ try {
349
+ roadmapContent = readFileSync(roadmapPath, "utf-8");
350
+ } catch (err) {
351
+ logWarning("state", "reconcileDiskToDb: roadmap read failed, skipping milestone", {
352
+ mid,
353
+ error: (err as Error).message,
354
+ });
355
+ continue;
356
+ }
350
357
 
351
358
  const parsed = parseRoadmap(roadmapContent);
352
359
  for (const s of parsed.slices) {
@@ -386,6 +393,10 @@ function buildCompletenessSet(basePath: string, milestones: MilestoneRow[]) {
386
393
  const completeMilestoneIds = new Set<string>();
387
394
  const parkedMilestoneIds = new Set<string>();
388
395
 
396
+ // DB-authoritative: a milestone is only "complete" when its DB row says so.
397
+ // SUMMARY-file presence is NOT a completion signal here — an orphan SUMMARY
398
+ // (crashed complete-milestone turn, partial merge, manual edit) must not
399
+ // flip derived state to complete and cascade into a false auto-merge (#4179).
389
400
  for (const m of milestones) {
390
401
  const parkedFile = resolveMilestoneFile(basePath, m.id, "PARKED");
391
402
  if (parkedFile || m.status === 'parked') {
@@ -396,11 +407,6 @@ function buildCompletenessSet(basePath: string, milestones: MilestoneRow[]) {
396
407
  completeMilestoneIds.add(m.id);
397
408
  continue;
398
409
  }
399
- const summaryFile = resolveMilestoneFile(basePath, m.id, "SUMMARY");
400
- if (summaryFile) {
401
- completeMilestoneIds.add(m.id);
402
- continue;
403
- }
404
410
  }
405
411
  return { completeMilestoneIds, parkedMilestoneIds };
406
412
  }
@@ -429,18 +435,22 @@ async function buildRegistryAndFindActive(
429
435
  if (isGhostMilestone(basePath, m.id)) continue;
430
436
  }
431
437
 
432
- const summaryFile = resolveMilestoneFile(basePath, m.id, "SUMMARY");
433
-
434
- if (completeMilestoneIds.has(m.id) || (summaryFile !== null)) {
438
+ // DB-authoritative completeness (#4179): only trust completeMilestoneIds,
439
+ // which is itself derived from DB status. SUMMARY-file presence alone must
440
+ // not imply completion. The summary file may still be consulted below as a
441
+ // title source for legitimately-complete milestones whose DB row has no title.
442
+ if (completeMilestoneIds.has(m.id)) {
435
443
  let title = stripMilestonePrefix(m.title) || m.id;
436
- if (summaryFile && !m.title) {
437
- const summaryContent = await loadFile(summaryFile);
438
- if (summaryContent) {
439
- title = parseSummary(summaryContent).title || m.id;
444
+ if (!m.title) {
445
+ const summaryFile = resolveMilestoneFile(basePath, m.id, "SUMMARY");
446
+ if (summaryFile) {
447
+ const summaryContent = await loadFile(summaryFile);
448
+ if (summaryContent) {
449
+ title = parseSummary(summaryContent).title || m.id;
450
+ }
440
451
  }
441
452
  }
442
453
  registry.push({ id: m.id, title, status: 'complete' });
443
- completeMilestoneIds.add(m.id);
444
454
  continue;
445
455
  }
446
456
 
@@ -481,7 +491,14 @@ async function buildRegistryAndFindActive(
481
491
  const validationContent = validationFile ? await loadFile(validationFile) : null;
482
492
  const validationTerminal = validationContent ? isValidationTerminal(validationContent) : false;
483
493
 
484
- if (!validationTerminal || (validationTerminal && !summaryFile)) {
494
+ // DB-authoritative (#4179): completeness is already decided by
495
+ // completeMilestoneIds above. If we reached this branch, the DB says
496
+ // the milestone is NOT complete — so any SUMMARY file on disk is an
497
+ // orphan (crashed complete-milestone, partial merge, manual edit) and
498
+ // must not short-circuit this path. When validation is terminal, fall
499
+ // through to the default active-push below so `complete-milestone` can
500
+ // re-run idempotently.
501
+ if (!validationTerminal) {
485
502
  activeMilestone = { id: m.id, title };
486
503
  activeMilestoneSlices = slices;
487
504
  activeMilestoneFound = true;
@@ -630,13 +647,39 @@ function resolveSliceDependencies(activeMilestoneSlices: SliceRow[]): { activeSl
630
647
  }
631
648
  }
632
649
 
650
+ // First pass: find a slice with ALL dependencies satisfied (strict)
651
+ let bestFallback: SliceRow | null = null;
652
+ let bestFallbackSatisfied = -1;
653
+
633
654
  for (const s of activeMilestoneSlices) {
634
655
  if (isStatusDone(s.status)) continue;
635
656
  if (isDeferredStatus(s.status)) continue;
636
657
  if (s.depends.every(dep => doneSliceIds.has(dep))) {
637
658
  return { activeSlice: { id: s.id, title: s.title }, activeSliceRow: s };
638
659
  }
660
+ // Track the slice with the most satisfied dependencies as fallback
661
+ const satisfied = s.depends.filter(dep => doneSliceIds.has(dep)).length;
662
+ if (satisfied > bestFallbackSatisfied || (satisfied === bestFallbackSatisfied && !bestFallback)) {
663
+ bestFallback = s;
664
+ bestFallbackSatisfied = satisfied;
665
+ }
666
+ }
667
+
668
+ // Fallback: if no slice has all deps met but there ARE incomplete non-deferred
669
+ // slices, pick the one with the most deps satisfied. This prevents hard-blocking
670
+ // when dependency metadata is stale (e.g. after reassessment added/removed slices)
671
+ // or when deps reference slices from previous milestones.
672
+ if (bestFallback) {
673
+ const unmet = bestFallback.depends.filter(dep => !doneSliceIds.has(dep));
674
+ logWarning("state",
675
+ `No slice has all deps satisfied — falling back to ${bestFallback.id} ` +
676
+ `(${bestFallbackSatisfied}/${bestFallback.depends.length} deps met, ` +
677
+ `unmet: ${unmet.join(", ")})`,
678
+ { mid: activeMilestoneSlices[0]?.milestone_id, sid: bestFallback.id },
679
+ );
680
+ return { activeSlice: { id: bestFallback.id, title: bestFallback.title }, activeSliceRow: bestFallback };
639
681
  }
682
+
640
683
  return { activeSlice: null, activeSliceRow: null };
641
684
  }
642
685
 
@@ -684,7 +727,7 @@ async function reconcileSliceTasks(
684
727
  const summaryPath = resolveTaskFile(basePath, milestoneId, sliceId, t.id, "SUMMARY");
685
728
  if (summaryPath && existsSync(summaryPath)) {
686
729
  try {
687
- updateTaskStatus(milestoneId, sliceId, t.id, "complete");
730
+ updateTaskStatus(milestoneId, sliceId, t.id, "complete", new Date().toISOString());
688
731
  logWarning("reconcile", `task ${milestoneId}/${sliceId}/${t.id} status reconciled from "${t.status}" to "complete" (#2514)`, { mid: milestoneId, sid: sliceId, tid: t.id });
689
732
  reconciled = true;
690
733
  } catch (e) {
@@ -1431,12 +1474,32 @@ export async function _deriveStateImpl(basePath: string): Promise<GSDState> {
1431
1474
  };
1432
1475
  }
1433
1476
  } else {
1477
+ let bestFallbackLegacy: { id: string; title: string; depends: string[] } | null = null;
1478
+ let bestFallbackLegacySatisfied = -1;
1479
+
1434
1480
  for (const s of activeRoadmap.slices) {
1435
1481
  if (s.done) continue;
1436
1482
  if (s.depends.every(dep => doneSliceIds.has(dep))) {
1437
1483
  activeSlice = { id: s.id, title: s.title };
1438
1484
  break;
1439
1485
  }
1486
+ // Track best fallback
1487
+ const satisfied = s.depends.filter(dep => doneSliceIds.has(dep)).length;
1488
+ if (satisfied > bestFallbackLegacySatisfied) {
1489
+ bestFallbackLegacy = s;
1490
+ bestFallbackLegacySatisfied = satisfied;
1491
+ }
1492
+ }
1493
+
1494
+ // Fallback: if no slice has all deps met, pick the one with the most deps satisfied
1495
+ if (!activeSlice && bestFallbackLegacy) {
1496
+ const unmet = bestFallbackLegacy.depends.filter(dep => !doneSliceIds.has(dep));
1497
+ logWarning("state",
1498
+ `No slice has all deps satisfied — falling back to ${bestFallbackLegacy.id} ` +
1499
+ `(${bestFallbackLegacySatisfied}/${bestFallbackLegacy.depends.length} deps met, ` +
1500
+ `unmet: ${unmet.join(", ")})`,
1501
+ );
1502
+ activeSlice = { id: bestFallbackLegacy.id, title: bestFallbackLegacy.title };
1440
1503
  }
1441
1504
  }
1442
1505
 
@@ -688,8 +688,8 @@ test("autoLoop exits on terminal blocked state", async (t) => {
688
688
 
689
689
  assert.ok(deps.callLog.includes("deriveState"), "should have derived state");
690
690
  assert.ok(
691
- deps.callLog.includes("stopAuto"),
692
- "should have called stopAuto for blocked state",
691
+ deps.callLog.includes("pauseAuto"),
692
+ "should have called pauseAuto for blocked state",
693
693
  );
694
694
  assert.ok(
695
695
  !deps.callLog.includes("resolveDispatch"),
@@ -0,0 +1,53 @@
1
+ // GSD-2 — Tests for step-mode completion messages in auto-post-unit
2
+
3
+ import test from "node:test";
4
+ import assert from "node:assert/strict";
5
+
6
+ import { buildStepCompleteMessage, STEP_COMPLETE_FALLBACK_MESSAGE } from "../auto-post-unit.ts";
7
+ import type { GSDState } from "../types.ts";
8
+
9
+ function makeState(overrides: Partial<GSDState>): GSDState {
10
+ return {
11
+ activeMilestone: null,
12
+ activeSlice: null,
13
+ activeTask: null,
14
+ phase: "executing",
15
+ recentDecisions: [],
16
+ blockers: [],
17
+ nextAction: "",
18
+ registry: [],
19
+ ...overrides,
20
+ };
21
+ }
22
+
23
+ test("buildStepCompleteMessage: milestone complete surfaces review guidance", () => {
24
+ const msg = buildStepCompleteMessage(makeState({ phase: "complete" }));
25
+ assert.match(msg, /milestone finished/);
26
+ assert.match(msg, /\/gsd status/);
27
+ assert.doesNotMatch(msg, /Next:/);
28
+ });
29
+
30
+ test("buildStepCompleteMessage: mid-flight step includes next unit label and /clear hint", () => {
31
+ const state = makeState({
32
+ phase: "executing",
33
+ activeSlice: { id: "S01", title: "Core" },
34
+ activeTask: { id: "T03", title: "Wire notify" },
35
+ });
36
+ const msg = buildStepCompleteMessage(state);
37
+ assert.match(msg, /Next: Execute T03: Wire notify/);
38
+ assert.match(msg, /\/clear/);
39
+ assert.match(msg, /\/gsd to continue/);
40
+ });
41
+
42
+ test("buildStepCompleteMessage: unknown phase falls back to generic continue label", () => {
43
+ // Cast to bypass Phase union so we exercise the default branch of describeNextUnit.
44
+ const state = makeState({ phase: "totally-unknown" as unknown as GSDState["phase"] });
45
+ const msg = buildStepCompleteMessage(state);
46
+ assert.match(msg, /Next: Continue/);
47
+ assert.match(msg, /\/clear/);
48
+ });
49
+
50
+ test("STEP_COMPLETE_FALLBACK_MESSAGE: used when deriveState throws, still points users at /clear + /gsd", () => {
51
+ assert.match(STEP_COMPLETE_FALLBACK_MESSAGE, /\/clear/);
52
+ assert.match(STEP_COMPLETE_FALLBACK_MESSAGE, /\/gsd/);
53
+ });