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,140 @@
1
+ // native-git-bridge-exec-fallback.test.ts — regression for #4180
2
+ //
3
+ // nativeCommit, nativeIsRepo, and nativeResetHard used execSync() (string
4
+ // command) in their fallback paths. On Windows, execSync spawns cmd.exe which
5
+ // cannot resolve git when Git for Windows is installed via MSYS2/bash but not
6
+ // in cmd.exe's PATH. All other fallback paths in this file use execFileSync()
7
+ // which invokes the binary directly — these three must do the same.
8
+ //
9
+ // Static-analysis tests fail before the fix (source still has execSync calls)
10
+ // and pass after (replaced with execFileSync). Integration tests verify the
11
+ // fallback functions behave correctly on all platforms.
12
+
13
+ import { describe, test, beforeEach, afterEach } from "node:test";
14
+ import assert from "node:assert/strict";
15
+ import { mkdtempSync, writeFileSync, readFileSync, rmSync } from "node:fs";
16
+ import { join } from "node:path";
17
+ import { tmpdir } from "node:os";
18
+ import { execFileSync } from "node:child_process";
19
+ import { nativeIsRepo, nativeCommit, nativeResetHard } from "../native-git-bridge.js";
20
+
21
+ // ─── Static analysis ──────────────────────────────────────────────────────
22
+ // Verify the fallback paths of the three affected functions do not call the
23
+ // raw execSync() string-command variant. Replacing all execFileSync( tokens
24
+ // first ensures we match only the bare execSync( form.
25
+
26
+ const SRC_PATH = join(import.meta.dirname, "..", "native-git-bridge.ts");
27
+
28
+ function extractFunctionBody(src: string, fnName: string): string {
29
+ const idx = src.indexOf(`export function ${fnName}`);
30
+ if (idx === -1) throw new Error(`${fnName} not found in source`);
31
+ return src.slice(idx, idx + 1500);
32
+ }
33
+
34
+ function hasRawExecSync(body: string): boolean {
35
+ const withoutFileSync = body.replace(/execFileSync\(/g, "__FILESYNC__");
36
+ return withoutFileSync.includes("execSync(");
37
+ }
38
+
39
+ describe("native-git-bridge #4180: fallback paths use execFileSync not execSync", () => {
40
+ const src = readFileSync(SRC_PATH, "utf-8");
41
+
42
+ test("nativeIsRepo fallback does not use raw execSync", () => {
43
+ const body = extractFunctionBody(src, "nativeIsRepo");
44
+ assert.equal(
45
+ hasRawExecSync(body),
46
+ false,
47
+ "nativeIsRepo fallback must use execFileSync to avoid cmd.exe PATH failures on Windows",
48
+ );
49
+ });
50
+
51
+ test("nativeCommit fallback does not use raw execSync", () => {
52
+ const body = extractFunctionBody(src, "nativeCommit");
53
+ assert.equal(
54
+ hasRawExecSync(body),
55
+ false,
56
+ "nativeCommit fallback must use execFileSync to avoid cmd.exe PATH failures on Windows",
57
+ );
58
+ });
59
+
60
+ test("nativeResetHard fallback does not use raw execSync", () => {
61
+ const body = extractFunctionBody(src, "nativeResetHard");
62
+ assert.equal(
63
+ hasRawExecSync(body),
64
+ false,
65
+ "nativeResetHard fallback must use execFileSync to avoid cmd.exe PATH failures on Windows",
66
+ );
67
+ });
68
+ });
69
+
70
+ // ─── Integration tests ────────────────────────────────────────────────────
71
+ // Verify correct runtime behaviour through the fallback path (native module
72
+ // is disabled by default in tests — GSD_ENABLE_NATIVE_GSD_GIT is not set).
73
+
74
+ function git(args: string[], cwd: string): string {
75
+ return execFileSync("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
76
+ }
77
+
78
+ describe("native-git-bridge #4180: fallback runtime behaviour", () => {
79
+ let repo: string;
80
+
81
+ beforeEach(() => {
82
+ repo = mkdtempSync(join(tmpdir(), "ngb4180-"));
83
+ git(["init"], repo);
84
+ git(["config", "user.email", "test@test.com"], repo);
85
+ git(["config", "user.name", "Test"], repo);
86
+ writeFileSync(join(repo, "file.txt"), "initial\n");
87
+ git(["add", "."], repo);
88
+ git(["commit", "-m", "init"], repo);
89
+ });
90
+
91
+ afterEach(() => {
92
+ rmSync(repo, { recursive: true, force: true });
93
+ });
94
+
95
+ test("nativeIsRepo returns true for a valid git repository", () => {
96
+ assert.equal(nativeIsRepo(repo), true);
97
+ });
98
+
99
+ test("nativeIsRepo returns false for a plain directory", (t) => {
100
+ const dir = mkdtempSync(join(tmpdir(), "ngb4180-notrepo-"));
101
+ t.after(() => rmSync(dir, { recursive: true, force: true }));
102
+ assert.equal(nativeIsRepo(dir), false);
103
+ });
104
+
105
+ test("nativeCommit commits staged changes and returns non-null output", () => {
106
+ writeFileSync(join(repo, "file.txt"), "modified\n");
107
+ git(["add", "."], repo);
108
+
109
+ const result = nativeCommit(repo, "test: regression commit #4180");
110
+ assert.ok(result !== null, "should return output string for a successful commit");
111
+
112
+ const subject = git(["log", "-1", "--format=%s"], repo);
113
+ assert.equal(subject, "test: regression commit #4180");
114
+ });
115
+
116
+ test("nativeCommit returns null when nothing is staged", () => {
117
+ const result = nativeCommit(repo, "test: nothing staged");
118
+ assert.equal(result, null);
119
+ });
120
+
121
+ test("nativeCommit respects the allowEmpty option", () => {
122
+ const result = nativeCommit(repo, "test: empty commit #4180", { allowEmpty: true });
123
+ assert.ok(result !== null, "allow-empty commit should return output");
124
+
125
+ const subject = git(["log", "-1", "--format=%s"], repo);
126
+ assert.equal(subject, "test: empty commit #4180");
127
+ });
128
+
129
+ test("nativeResetHard discards unstaged working tree changes", () => {
130
+ writeFileSync(join(repo, "file.txt"), "dirty content\n");
131
+
132
+ const statusBefore = git(["status", "--short"], repo);
133
+ assert.ok(statusBefore.length > 0, "repo should be dirty before reset");
134
+
135
+ nativeResetHard(repo);
136
+
137
+ const content = readFileSync(join(repo, "file.txt"), "utf-8");
138
+ assert.equal(content, "initial\n", "file should be restored to HEAD content after hard reset");
139
+ });
140
+ });
@@ -134,6 +134,53 @@ test("invalid value types produce errors and fall back to undefined", () => {
134
134
  }
135
135
  });
136
136
 
137
+ test("flat_rate_providers: accepts string array", () => {
138
+ const { errors, preferences } = validatePreferences({
139
+ flat_rate_providers: ["my-proxy", "private-cli"],
140
+ });
141
+ assert.equal(errors.length, 0);
142
+ assert.deepEqual(preferences.flat_rate_providers, ["my-proxy", "private-cli"]);
143
+ });
144
+
145
+ test("flat_rate_providers: trims whitespace and drops empty entries", () => {
146
+ const { errors, preferences } = validatePreferences({
147
+ flat_rate_providers: [" my-proxy ", "", " ", "private-cli"],
148
+ });
149
+ assert.equal(errors.length, 0);
150
+ assert.deepEqual(preferences.flat_rate_providers, ["my-proxy", "private-cli"]);
151
+ });
152
+
153
+ test("flat_rate_providers: non-array rejected", () => {
154
+ const { errors } = validatePreferences({
155
+ flat_rate_providers: "my-proxy" as any,
156
+ });
157
+ assert.ok(
158
+ errors.some(e => e.includes("flat_rate_providers")),
159
+ "should error on non-array value",
160
+ );
161
+ });
162
+
163
+ test("flat_rate_providers: non-string elements rejected", () => {
164
+ const { errors } = validatePreferences({
165
+ flat_rate_providers: ["ok", 123 as any, "also-ok"],
166
+ });
167
+ assert.ok(
168
+ errors.some(e => e.includes("flat_rate_providers")),
169
+ "should error when array contains non-strings",
170
+ );
171
+ });
172
+
173
+ test("flat_rate_providers is a recognized preference key (no warning)", () => {
174
+ const { warnings } = validatePreferences({
175
+ flat_rate_providers: ["my-proxy"],
176
+ });
177
+ assert.equal(
178
+ warnings.filter(w => w.includes("flat_rate_providers")).length,
179
+ 0,
180
+ "flat_rate_providers must be in KNOWN_PREFERENCE_KEYS",
181
+ );
182
+ });
183
+
137
184
  test("valid values pass through correctly", () => {
138
185
  const { preferences: p1 } = validatePreferences({ budget_enforcement: "halt" });
139
186
  assert.equal(p1.budget_enforcement, "halt");
@@ -0,0 +1,180 @@
1
+ // Structural invariant: gsd-db.ts is the single writer for .gsd/gsd.db.
2
+ //
3
+ // No file under src/resources/extensions/gsd/ may issue raw write SQL
4
+ // (INSERT/UPDATE/DELETE/REPLACE) or raw transaction control (BEGIN/COMMIT/
5
+ // ROLLBACK via `.exec(...)`) against the engine database. Every bypass must
6
+ // route through a typed wrapper exported from gsd-db.ts.
7
+ //
8
+ // Allowlist:
9
+ // - gsd-db.ts itself — the single writer
10
+ // - unit-ownership.ts — manages a separate .gsd/unit-claims.db for
11
+ // cross-worktree claim races; intentionally outside this invariant
12
+ // - tests/** — fixtures and direct DB inspection are fair game
13
+ //
14
+ // When this test fails, do not add a new suppression. Instead:
15
+ // 1. Add a typed wrapper to gsd-db.ts that captures the SQL
16
+ // 2. Switch the flagged site to call the wrapper
17
+ //
18
+ // See `.claude/plans/joyful-doodling-pony.md` for the full rationale.
19
+
20
+ import test from "node:test";
21
+ import assert from "node:assert/strict";
22
+ import { readFileSync, readdirSync } from "node:fs";
23
+ import { join, relative } from "node:path";
24
+
25
+ const gsdDir = join(process.cwd(), "src/resources/extensions/gsd");
26
+
27
+ const ALLOWLIST = new Set([
28
+ "gsd-db.ts",
29
+ "unit-ownership.ts",
30
+ ]);
31
+
32
+ /** Walk the gsd extension dir and return all .ts files outside tests/. */
33
+ function walkTsFiles(root: string): string[] {
34
+ const out: string[] = [];
35
+ const stack: string[] = [root];
36
+
37
+ while (stack.length > 0) {
38
+ const dir = stack.pop()!;
39
+ let entries;
40
+ try {
41
+ entries = readdirSync(dir, { withFileTypes: true });
42
+ } catch {
43
+ continue;
44
+ }
45
+
46
+ for (const ent of entries) {
47
+ const full = join(dir, ent.name);
48
+ if (ent.isDirectory()) {
49
+ // Skip tests/ — fixtures and direct DB inspection are expected there
50
+ if (ent.name === "tests") continue;
51
+ stack.push(full);
52
+ continue;
53
+ }
54
+ if (!ent.isFile()) continue;
55
+ if (!ent.name.endsWith(".ts")) continue;
56
+ // Skip dotfiles and backup/generated files
57
+ if (ent.name.startsWith(".")) continue;
58
+ out.push(full);
59
+ }
60
+ }
61
+
62
+ return out;
63
+ }
64
+
65
+ interface Violation {
66
+ file: string;
67
+ line: number;
68
+ snippet: string;
69
+ kind: string;
70
+ }
71
+
72
+ // Match .prepare("... INSERT|UPDATE|DELETE|REPLACE ...") in any quoting style.
73
+ const PREPARE_WRITE_RE = /\.prepare\s*\(\s*[`'"][^`'"]*\b(INSERT|UPDATE|DELETE|REPLACE)\b/i;
74
+
75
+ // Match .exec("... INSERT|UPDATE|DELETE|REPLACE ...") or raw BEGIN/COMMIT/ROLLBACK.
76
+ const EXEC_WRITE_RE = /\.exec\s*\(\s*[`'"][^`'"]*\b(INSERT|UPDATE|DELETE|REPLACE|BEGIN|COMMIT|ROLLBACK)\b/i;
77
+
78
+ test("no module outside gsd-db.ts issues raw write SQL against the engine DB", () => {
79
+ const files = walkTsFiles(gsdDir);
80
+ assert.ok(files.length >= 20, `Expected at least 20 .ts files under gsd/, found ${files.length}`);
81
+
82
+ const violations: Violation[] = [];
83
+
84
+ for (const abs of files) {
85
+ const rel = relative(gsdDir, abs);
86
+ const base = rel.split("/").pop()!;
87
+ if (ALLOWLIST.has(base)) continue;
88
+
89
+ let content: string;
90
+ try {
91
+ content = readFileSync(abs, "utf-8");
92
+ } catch {
93
+ continue;
94
+ }
95
+
96
+ const lines = content.split("\n");
97
+ for (let i = 0; i < lines.length; i++) {
98
+ const line = lines[i];
99
+
100
+ const prepareMatch = PREPARE_WRITE_RE.exec(line);
101
+ if (prepareMatch) {
102
+ violations.push({
103
+ file: rel,
104
+ line: i + 1,
105
+ snippet: line.trim(),
106
+ kind: `prepare(${prepareMatch[1].toUpperCase()})`,
107
+ });
108
+ }
109
+
110
+ const execMatch = EXEC_WRITE_RE.exec(line);
111
+ if (execMatch) {
112
+ violations.push({
113
+ file: rel,
114
+ line: i + 1,
115
+ snippet: line.trim(),
116
+ kind: `exec(${execMatch[1].toUpperCase()})`,
117
+ });
118
+ }
119
+ }
120
+ }
121
+
122
+ if (violations.length > 0) {
123
+ const lines = violations.map(
124
+ (v) => ` ${v.file}:${v.line} [${v.kind}] — ${v.snippet}`,
125
+ );
126
+ assert.fail(
127
+ `Found ${violations.length} raw write SQL bypass(es) outside gsd-db.ts:\n` +
128
+ lines.join("\n") +
129
+ "\n\nEach of these must be replaced with a typed wrapper exported from gsd-db.ts.",
130
+ );
131
+ }
132
+ });
133
+
134
+ test("gsd-db.ts exports the expected single-writer wrappers", async () => {
135
+ // Positive assertion — fail loudly if the module layout changes so this
136
+ // structural test can't silently become a no-op.
137
+ const db = await import("../gsd-db.js");
138
+
139
+ const expected = [
140
+ "deleteDecisionById",
141
+ "deleteRequirementById",
142
+ "deleteArtifactByPath",
143
+ "clearEngineHierarchy",
144
+ "insertOrIgnoreSlice",
145
+ "insertOrIgnoreTask",
146
+ "setSliceReplanTriggeredAt",
147
+ "upsertQualityGate",
148
+ "restoreManifest",
149
+ "bulkInsertLegacyHierarchy",
150
+ "readTransaction",
151
+ "insertMemoryRow",
152
+ "rewriteMemoryId",
153
+ "updateMemoryContentRow",
154
+ "incrementMemoryHitCount",
155
+ "supersedeMemoryRow",
156
+ "markMemoryUnitProcessed",
157
+ "decayMemoriesBefore",
158
+ "supersedeLowestRankedMemories",
159
+ ];
160
+
161
+ for (const name of expected) {
162
+ assert.ok(
163
+ typeof (db as Record<string, unknown>)[name] === "function",
164
+ `gsd-db.ts must export ${name} as a function`,
165
+ );
166
+ }
167
+ });
168
+
169
+ test("the invariant test touches every .ts module under gsd/ (sanity check)", () => {
170
+ const files = walkTsFiles(gsdDir);
171
+ // Rough sanity: ensure we're not accidentally walking an empty tree
172
+ assert.ok(files.length >= 30, `Expected to scan at least 30 .ts files, scanned ${files.length}`);
173
+
174
+ // Spot-check a couple of known files that must be included
175
+ const rels = files.map((f) => relative(gsdDir, f));
176
+ assert.ok(rels.includes("gsd-db.ts"), "walker must include gsd-db.ts");
177
+ assert.ok(rels.includes("memory-store.ts"), "walker must include memory-store.ts");
178
+ assert.ok(rels.includes("workflow-manifest.ts"), "walker must include workflow-manifest.ts");
179
+ });
180
+
@@ -811,9 +811,9 @@ describe("state-machine-full-walkthrough", () => {
811
811
  assert.ok(state.blockers.length > 0, "should have blockers");
812
812
  });
813
813
 
814
- test("no eligible slice (all deps unmet) → blocked at slice level", async () => {
814
+ test("no eligible slice (all deps unmet) → fallback picks slice with most deps satisfied", async () => {
815
815
  const base = createFixtureBase();
816
- // S01 depends on S00 which doesn't exist
816
+ // S01 depends on S00 which doesn't exist — fallback picks S01 anyway
817
817
  writeRoadmap(base, "M001", [
818
818
  "# M001: Test Milestone",
819
819
  "",
@@ -827,11 +827,9 @@ describe("state-machine-full-walkthrough", () => {
827
827
  invalidateStateCache();
828
828
  const state = await deriveState(base);
829
829
 
830
- assert.equal(state.phase, "blocked");
831
- assert.ok(
832
- state.blockers.some(b => b.includes("dependency") || b.includes("eligible")),
833
- "blockers should mention dependency or eligibility",
834
- );
830
+ // With partial-dep fallback, S01 is picked despite unmet dep on S00
831
+ assert.equal(state.phase, "planning");
832
+ assert.equal(state.activeSlice?.id, "S01");
835
833
  });
836
834
  });
837
835
 
@@ -263,6 +263,6 @@ test("dispatch: phase skip guards return null (not stop)", () => {
263
263
  const researchGuard = dispatchSrc.match(/skip_research\).*?return null/s);
264
264
  assert.ok(researchGuard, "skip_research guard should return null (fall-through)");
265
265
 
266
- const reassessGuard = dispatchSrc.match(/reassess_after_slice\).*?return null/s);
266
+ const reassessGuard = dispatchSrc.match(/reassess_after_slice.*?return null/s);
267
267
  assert.ok(reassessGuard, "reassess_after_slice guard should return null (fall-through)");
268
268
  });
@@ -0,0 +1,179 @@
1
+ // gsd-pi — Regression tests for the validate-milestone stuck-loop guard (#4094)
2
+
3
+ import { describe, test, mock, beforeEach, afterEach } from "node:test";
4
+ import assert from "node:assert/strict";
5
+ import { tmpdir } from "node:os";
6
+ import { mkdirSync, writeFileSync, rmSync } from "node:fs";
7
+ import { join } from "node:path";
8
+
9
+ import { runPostUnitVerification, type VerificationContext } from "../auto-verification.ts";
10
+ import { AutoSession } from "../auto/session.ts";
11
+ import {
12
+ openDatabase,
13
+ closeDatabase,
14
+ insertMilestone,
15
+ insertSlice,
16
+ } from "../gsd-db.ts";
17
+ import { invalidateAllCaches } from "../cache.ts";
18
+ import { _clearGsdRootCache } from "../paths.ts";
19
+
20
+ let tempDir: string;
21
+ let dbPath: string;
22
+ let originalCwd: string;
23
+
24
+ function makeMockCtx() {
25
+ return {
26
+ ui: {
27
+ notify: mock.fn(),
28
+ setStatus: () => {},
29
+ setWidget: () => {},
30
+ setFooter: () => {},
31
+ },
32
+ model: { id: "test-model" },
33
+ } as any;
34
+ }
35
+
36
+ function makeMockPi() {
37
+ return {
38
+ sendMessage: mock.fn(),
39
+ setModel: mock.fn(async () => true),
40
+ } as any;
41
+ }
42
+
43
+ function makeMockSession(basePath: string, unitType: string, unitId: string): AutoSession {
44
+ const s = new AutoSession();
45
+ s.basePath = basePath;
46
+ s.active = true;
47
+ s.pendingVerificationRetry = null;
48
+ s.currentUnit = { type: unitType, id: unitId, startedAt: Date.now() };
49
+ return s;
50
+ }
51
+
52
+ function setupTestEnvironment(): void {
53
+ originalCwd = process.cwd();
54
+ tempDir = join(tmpdir(), `validate-milestone-guard-${Date.now()}-${Math.random().toString(36).slice(2)}`);
55
+ mkdirSync(tempDir, { recursive: true });
56
+
57
+ const milestoneDir = join(tempDir, ".gsd", "milestones", "M001");
58
+ mkdirSync(milestoneDir, { recursive: true });
59
+
60
+ process.chdir(tempDir);
61
+ _clearGsdRootCache();
62
+
63
+ dbPath = join(tempDir, ".gsd", "gsd.db");
64
+ openDatabase(dbPath);
65
+ invalidateAllCaches();
66
+ }
67
+
68
+ function cleanupTestEnvironment(): void {
69
+ try { process.chdir(originalCwd); } catch { /* ignore */ }
70
+ try { closeDatabase(); } catch { /* ignore */ }
71
+ try { rmSync(tempDir, { recursive: true, force: true }); } catch { /* ignore */ }
72
+ }
73
+
74
+ function writeValidationFile(verdict: string): void {
75
+ const path = join(tempDir, ".gsd", "milestones", "M001", "M001-VALIDATION.md");
76
+ const content = `---
77
+ verdict: ${verdict}
78
+ remediation_round: 1
79
+ ---
80
+
81
+ # Milestone Validation: M001
82
+
83
+ ## Verdict Rationale
84
+ Test fixture
85
+ `;
86
+ writeFileSync(path, content, "utf-8");
87
+ invalidateAllCaches();
88
+ }
89
+
90
+ describe("validate-milestone stuck-loop guard (#4094)", () => {
91
+ beforeEach(() => setupTestEnvironment());
92
+ afterEach(() => cleanupTestEnvironment());
93
+
94
+ test("pauses when verdict=needs-remediation and all slices are closed", async () => {
95
+ insertMilestone({ id: "M001" });
96
+ insertSlice({ id: "S01", milestoneId: "M001", title: "Slice 1", status: "complete" });
97
+ insertSlice({ id: "S02", milestoneId: "M001", title: "Slice 2", status: "done" });
98
+ writeValidationFile("needs-remediation");
99
+
100
+ const ctx = makeMockCtx();
101
+ const pi = makeMockPi();
102
+ const pauseAutoMock = mock.fn(async () => {});
103
+ const s = makeMockSession(tempDir, "validate-milestone", "M001");
104
+
105
+ const result = await runPostUnitVerification({ s, ctx, pi } as VerificationContext, pauseAutoMock);
106
+
107
+ assert.equal(result, "pause");
108
+ assert.equal(pauseAutoMock.mock.callCount(), 1);
109
+ assert.equal(ctx.ui.notify.mock.callCount(), 1);
110
+ const notifyArgs = ctx.ui.notify.mock.calls[0].arguments;
111
+ assert.match(notifyArgs[0], /needs-remediation/);
112
+ assert.equal(notifyArgs[1], "error");
113
+ });
114
+
115
+ test("treats skipped slices as closed", async () => {
116
+ insertMilestone({ id: "M001" });
117
+ insertSlice({ id: "S01", milestoneId: "M001", title: "Slice 1", status: "complete" });
118
+ insertSlice({ id: "S02", milestoneId: "M001", title: "Slice 2", status: "skipped" });
119
+ writeValidationFile("needs-remediation");
120
+
121
+ const ctx = makeMockCtx();
122
+ const pi = makeMockPi();
123
+ const pauseAutoMock = mock.fn(async () => {});
124
+ const s = makeMockSession(tempDir, "validate-milestone", "M001");
125
+
126
+ const result = await runPostUnitVerification({ s, ctx, pi } as VerificationContext, pauseAutoMock);
127
+
128
+ assert.equal(result, "pause");
129
+ assert.equal(pauseAutoMock.mock.callCount(), 1);
130
+ });
131
+
132
+ test("continues when verdict=needs-remediation but a queued remediation slice exists", async () => {
133
+ insertMilestone({ id: "M001" });
134
+ insertSlice({ id: "S01", milestoneId: "M001", title: "Slice 1", status: "complete" });
135
+ insertSlice({ id: "S02", milestoneId: "M001", title: "Remediation", status: "queued" });
136
+ writeValidationFile("needs-remediation");
137
+
138
+ const ctx = makeMockCtx();
139
+ const pi = makeMockPi();
140
+ const pauseAutoMock = mock.fn(async () => {});
141
+ const s = makeMockSession(tempDir, "validate-milestone", "M001");
142
+
143
+ const result = await runPostUnitVerification({ s, ctx, pi } as VerificationContext, pauseAutoMock);
144
+
145
+ assert.equal(result, "continue");
146
+ assert.equal(pauseAutoMock.mock.callCount(), 0);
147
+ });
148
+
149
+ test("continues when verdict is pass", async () => {
150
+ insertMilestone({ id: "M001" });
151
+ insertSlice({ id: "S01", milestoneId: "M001", title: "Slice 1", status: "complete" });
152
+ writeValidationFile("pass");
153
+
154
+ const ctx = makeMockCtx();
155
+ const pi = makeMockPi();
156
+ const pauseAutoMock = mock.fn(async () => {});
157
+ const s = makeMockSession(tempDir, "validate-milestone", "M001");
158
+
159
+ const result = await runPostUnitVerification({ s, ctx, pi } as VerificationContext, pauseAutoMock);
160
+
161
+ assert.equal(result, "continue");
162
+ assert.equal(pauseAutoMock.mock.callCount(), 0);
163
+ });
164
+
165
+ test("continues when no VALIDATION file exists yet", async () => {
166
+ insertMilestone({ id: "M001" });
167
+ insertSlice({ id: "S01", milestoneId: "M001", title: "Slice 1", status: "complete" });
168
+
169
+ const ctx = makeMockCtx();
170
+ const pi = makeMockPi();
171
+ const pauseAutoMock = mock.fn(async () => {});
172
+ const s = makeMockSession(tempDir, "validate-milestone", "M001");
173
+
174
+ const result = await runPostUnitVerification({ s, ctx, pi } as VerificationContext, pauseAutoMock);
175
+
176
+ assert.equal(result, "continue");
177
+ assert.equal(pauseAutoMock.mock.callCount(), 0);
178
+ });
179
+ });