gsd-pi 2.80.0-dev.c5f2443b3 → 2.80.0-dev.cf9433f56

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 (443) hide show
  1. package/README.md +4 -2
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/GSD-WORKFLOW.md +2 -2
  4. package/dist/resources/extensions/github-sync/templates.js +39 -8
  5. package/dist/resources/extensions/gsd/auto/loop.js +48 -10
  6. package/dist/resources/extensions/gsd/auto/phases.js +66 -45
  7. package/dist/resources/extensions/gsd/auto/resolve.js +17 -0
  8. package/dist/resources/extensions/gsd/auto/run-unit.js +32 -16
  9. package/dist/resources/extensions/gsd/auto-dashboard.js +51 -15
  10. package/dist/resources/extensions/gsd/auto-dispatch.js +10 -0
  11. package/dist/resources/extensions/gsd/auto-post-unit.js +10 -10
  12. package/dist/resources/extensions/gsd/auto-prompts.js +124 -2
  13. package/dist/resources/extensions/gsd/auto-recovery.js +197 -9
  14. package/dist/resources/extensions/gsd/auto-start.js +2 -3
  15. package/dist/resources/extensions/gsd/auto-supervisor.js +8 -1
  16. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +2 -2
  17. package/dist/resources/extensions/gsd/auto.js +77 -5
  18. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +36 -3
  19. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +27 -20
  20. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +32 -1
  21. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +129 -1
  22. package/dist/resources/extensions/gsd/clean-root-preflight.js +42 -4
  23. package/dist/resources/extensions/gsd/commands/dispatcher.js +5 -0
  24. package/dist/resources/extensions/gsd/commands-extract-learnings.js +17 -12
  25. package/dist/resources/extensions/gsd/context-budget.js +37 -2
  26. package/dist/resources/extensions/gsd/crash-recovery.js +56 -10
  27. package/dist/resources/extensions/gsd/custom-workflow-engine.js +22 -2
  28. package/dist/resources/extensions/gsd/db/unit-dispatches.js +39 -0
  29. package/dist/resources/extensions/gsd/db-base-schema.js +18 -2
  30. package/dist/resources/extensions/gsd/db-migration-steps.js +22 -0
  31. package/dist/resources/extensions/gsd/detection.js +106 -0
  32. package/dist/resources/extensions/gsd/git-service.js +36 -4
  33. package/dist/resources/extensions/gsd/graph.js +9 -3
  34. package/dist/resources/extensions/gsd/gsd-db.js +146 -13
  35. package/dist/resources/extensions/gsd/guided-flow.js +82 -16
  36. package/dist/resources/extensions/gsd/memory-store.js +69 -12
  37. package/dist/resources/extensions/gsd/migrate/command.js +40 -1
  38. package/dist/resources/extensions/gsd/migration-auto-check.js +87 -0
  39. package/dist/resources/extensions/gsd/planning-path-scope.js +26 -0
  40. package/dist/resources/extensions/gsd/pr-evidence.js +57 -16
  41. package/dist/resources/extensions/gsd/pre-execution-checks.js +7 -0
  42. package/dist/resources/extensions/gsd/prompt-loader.js +28 -2
  43. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +19 -19
  44. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
  45. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
  46. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  47. package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -5
  48. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
  49. package/dist/resources/extensions/gsd/quick.js +34 -2
  50. package/dist/resources/extensions/gsd/safety/evidence-collector.js +10 -2
  51. package/dist/resources/extensions/gsd/tools/context-mode-tool-result.js +15 -0
  52. package/dist/resources/extensions/gsd/tools/exec-search-tool.js +5 -0
  53. package/dist/resources/extensions/gsd/tools/exec-tool.js +3 -15
  54. package/dist/resources/extensions/gsd/tools/memory-tools.js +1 -0
  55. package/dist/resources/extensions/gsd/tools/plan-slice.js +9 -0
  56. package/dist/resources/extensions/gsd/tools/plan-task.js +9 -0
  57. package/dist/resources/extensions/gsd/tools/resume-tool.js +5 -0
  58. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +1 -1
  59. package/dist/resources/extensions/gsd/unit-context-composer.js +12 -3
  60. package/dist/resources/extensions/gsd/unit-runtime.js +22 -0
  61. package/dist/resources/extensions/gsd/working-output-messages.js +64 -0
  62. package/dist/resources/extensions/gsd/worktree-manager.js +16 -14
  63. package/dist/resources/extensions/gsd/worktree-resolver.js +33 -17
  64. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  65. package/dist/web/standalone/.next/BUILD_ID +1 -1
  66. package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
  67. package/dist/web/standalone/.next/build-manifest.json +3 -3
  68. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  69. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  87. package/dist/web/standalone/.next/server/app/index.html +1 -1
  88. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
  95. package/dist/web/standalone/.next/server/chunks/6897.js +3 -3
  96. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  97. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  99. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  100. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  101. package/dist/web/standalone/.next/static/chunks/{8336.6f6f30e410419aff.js → 8336.631939fb583761fa.js} +1 -1
  102. package/dist/web/standalone/.next/static/chunks/{webpack-d82dbee6356c1733.js → webpack-0481f1221120a7c6.js} +1 -1
  103. package/package.json +12 -8
  104. package/packages/contracts/package.json +1 -1
  105. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  106. package/packages/mcp-server/dist/workflow-tools.js +22 -17
  107. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  108. package/packages/mcp-server/src/workflow-tools.test.ts +75 -2
  109. package/packages/mcp-server/src/workflow-tools.ts +30 -16
  110. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  111. package/packages/native/tsconfig.tsbuildinfo +1 -1
  112. package/packages/pi-ai/dist/models/fake-model.d.ts +12 -0
  113. package/packages/pi-ai/dist/models/fake-model.d.ts.map +1 -0
  114. package/packages/pi-ai/dist/models/fake-model.js +27 -0
  115. package/packages/pi-ai/dist/models/fake-model.js.map +1 -0
  116. package/packages/pi-ai/dist/models/index.d.ts.map +1 -1
  117. package/packages/pi-ai/dist/models/index.js +8 -0
  118. package/packages/pi-ai/dist/models/index.js.map +1 -1
  119. package/packages/pi-ai/dist/providers/fake.d.ts +42 -0
  120. package/packages/pi-ai/dist/providers/fake.d.ts.map +1 -0
  121. package/packages/pi-ai/dist/providers/fake.js +319 -0
  122. package/packages/pi-ai/dist/providers/fake.js.map +1 -0
  123. package/packages/pi-ai/dist/providers/register-builtins.d.ts.map +1 -1
  124. package/packages/pi-ai/dist/providers/register-builtins.js +24 -0
  125. package/packages/pi-ai/dist/providers/register-builtins.js.map +1 -1
  126. package/packages/pi-ai/src/models/fake-model.ts +30 -0
  127. package/packages/pi-ai/src/models/index.ts +9 -0
  128. package/packages/pi-ai/src/providers/fake.ts +376 -0
  129. package/packages/pi-ai/src/providers/register-builtins.ts +23 -0
  130. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  131. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +32 -0
  132. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
  133. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  134. package/packages/pi-coding-agent/dist/core/agent-session.js +8 -0
  135. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  136. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +76 -0
  137. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
  138. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +11 -0
  139. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
  140. package/packages/pi-coding-agent/dist/core/compaction/compaction.js +9 -0
  141. package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
  142. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts +2 -0
  143. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts.map +1 -0
  144. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js +103 -0
  145. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js.map +1 -0
  146. package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts +15 -0
  147. package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts.map +1 -0
  148. package/packages/pi-coding-agent/dist/core/db-snapshot.js +66 -0
  149. package/packages/pi-coding-agent/dist/core/db-snapshot.js.map +1 -0
  150. package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts +2 -0
  151. package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts.map +1 -0
  152. package/packages/pi-coding-agent/dist/core/db-snapshot.test.js +24 -0
  153. package/packages/pi-coding-agent/dist/core/db-snapshot.test.js.map +1 -0
  154. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +3 -0
  155. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  156. package/packages/pi-coding-agent/dist/core/extensions/runner.js +17 -1
  157. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  158. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +99 -0
  159. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
  160. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +7 -0
  161. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  162. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  163. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  164. package/packages/pi-coding-agent/dist/core/model-registry.js +5 -0
  165. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  166. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +24 -0
  167. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  168. package/packages/pi-coding-agent/dist/core/settings-manager.js +33 -0
  169. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  170. package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
  171. package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
  172. package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
  173. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js +6 -4
  174. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js.map +1 -1
  175. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +54 -15
  176. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  177. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.d.ts +26 -0
  178. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.d.ts.map +1 -0
  179. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js +112 -0
  180. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js.map +1 -0
  181. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.d.ts +2 -0
  182. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.d.ts.map +1 -0
  183. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.js +51 -0
  184. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.js.map +1 -0
  185. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  186. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  187. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
  188. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +10 -9
  189. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
  190. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +3 -0
  191. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  192. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +11 -0
  193. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  194. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js +7 -6
  195. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js.map +1 -1
  196. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +17 -0
  197. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  198. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +109 -17
  199. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  200. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  201. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +69 -2
  202. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  203. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +93 -1
  204. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
  205. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +1 -0
  206. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
  207. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +1 -0
  208. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  209. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  210. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +3 -0
  211. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  212. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +26 -0
  213. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  214. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
  215. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +20 -0
  216. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  217. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.test.d.ts +2 -0
  218. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.test.d.ts.map +1 -0
  219. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.test.js +79 -0
  220. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.test.js.map +1 -0
  221. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.d.ts +12 -0
  222. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.d.ts.map +1 -1
  223. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.js +13 -0
  224. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.js.map +1 -1
  225. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts +1 -1
  226. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  227. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +18 -1
  228. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
  229. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  230. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +36 -27
  231. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  232. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.d.ts +11 -0
  233. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.d.ts.map +1 -0
  234. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.js +18 -0
  235. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.js.map +1 -0
  236. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.d.ts +2 -0
  237. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.d.ts.map +1 -0
  238. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js +48 -0
  239. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js.map +1 -0
  240. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts +2 -0
  241. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts.map +1 -0
  242. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js +10 -0
  243. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js.map +1 -0
  244. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.d.ts.map +1 -1
  245. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js +3 -2
  246. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js.map +1 -1
  247. package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +36 -0
  248. package/packages/pi-coding-agent/src/core/agent-session.ts +8 -0
  249. package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +89 -0
  250. package/packages/pi-coding-agent/src/core/compaction/compaction.ts +18 -0
  251. package/packages/pi-coding-agent/src/core/compaction-threshold.test.ts +121 -0
  252. package/packages/pi-coding-agent/src/core/db-snapshot.test.ts +32 -0
  253. package/packages/pi-coding-agent/src/core/db-snapshot.ts +66 -0
  254. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +110 -0
  255. package/packages/pi-coding-agent/src/core/extensions/runner.ts +19 -1
  256. package/packages/pi-coding-agent/src/core/extensions/types.ts +7 -0
  257. package/packages/pi-coding-agent/src/core/model-registry.ts +4 -0
  258. package/packages/pi-coding-agent/src/core/settings-manager.ts +51 -1
  259. package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
  260. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.ts +7 -5
  261. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +78 -15
  262. package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.test.ts +59 -0
  263. package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.ts +160 -0
  264. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +1 -0
  265. package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +10 -9
  266. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +15 -0
  267. package/packages/pi-coding-agent/src/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.ts +10 -9
  268. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +122 -17
  269. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +99 -1
  270. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +92 -3
  271. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +1 -0
  272. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +1 -1
  273. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +28 -0
  274. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.test.ts +95 -0
  275. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +24 -1
  276. package/packages/pi-coding-agent/src/modes/interactive/theme/theme-schema.ts +13 -0
  277. package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +32 -2
  278. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +36 -27
  279. package/packages/pi-coding-agent/src/modes/interactive/tui-mode.test.ts +65 -0
  280. package/packages/pi-coding-agent/src/modes/interactive/tui-mode.ts +29 -0
  281. package/packages/pi-coding-agent/src/resources/extensions/memory/storage-safety-guard.test.ts +14 -0
  282. package/packages/pi-coding-agent/src/resources/extensions/memory/storage.ts +3 -2
  283. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  284. package/packages/pi-tui/dist/__tests__/style.test.d.ts +2 -0
  285. package/packages/pi-tui/dist/__tests__/style.test.d.ts.map +1 -0
  286. package/packages/pi-tui/dist/__tests__/style.test.js +63 -0
  287. package/packages/pi-tui/dist/__tests__/style.test.js.map +1 -0
  288. package/packages/pi-tui/dist/__tests__/tui.test.js +24 -3
  289. package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -1
  290. package/packages/pi-tui/dist/index.d.ts +1 -0
  291. package/packages/pi-tui/dist/index.d.ts.map +1 -1
  292. package/packages/pi-tui/dist/index.js +2 -0
  293. package/packages/pi-tui/dist/index.js.map +1 -1
  294. package/packages/pi-tui/dist/style.d.ts +41 -0
  295. package/packages/pi-tui/dist/style.d.ts.map +1 -0
  296. package/packages/pi-tui/dist/style.js +158 -0
  297. package/packages/pi-tui/dist/style.js.map +1 -0
  298. package/packages/pi-tui/dist/tui.d.ts +0 -1
  299. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  300. package/packages/pi-tui/dist/tui.js +21 -16
  301. package/packages/pi-tui/dist/tui.js.map +1 -1
  302. package/packages/pi-tui/src/__tests__/style.test.ts +76 -0
  303. package/packages/pi-tui/src/__tests__/tui.test.ts +29 -3
  304. package/packages/pi-tui/src/index.ts +9 -0
  305. package/packages/pi-tui/src/style.ts +225 -0
  306. package/packages/pi-tui/src/tui.ts +23 -16
  307. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  308. package/pkg/dist/modes/interactive/theme/theme-schema.d.ts +12 -0
  309. package/pkg/dist/modes/interactive/theme/theme-schema.d.ts.map +1 -1
  310. package/pkg/dist/modes/interactive/theme/theme-schema.js +13 -0
  311. package/pkg/dist/modes/interactive/theme/theme-schema.js.map +1 -1
  312. package/pkg/dist/modes/interactive/theme/theme.d.ts +1 -1
  313. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  314. package/pkg/dist/modes/interactive/theme/theme.js +18 -1
  315. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
  316. package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  317. package/pkg/dist/modes/interactive/theme/themes.js +36 -27
  318. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  319. package/src/resources/GSD-WORKFLOW.md +2 -2
  320. package/src/resources/extensions/github-sync/templates.ts +38 -8
  321. package/src/resources/extensions/github-sync/tests/inline-code.test.ts +66 -0
  322. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  323. package/src/resources/extensions/gsd/auto/loop.ts +67 -18
  324. package/src/resources/extensions/gsd/auto/phases.ts +77 -48
  325. package/src/resources/extensions/gsd/auto/resolve.ts +23 -1
  326. package/src/resources/extensions/gsd/auto/run-unit.ts +42 -15
  327. package/src/resources/extensions/gsd/auto-dashboard.ts +57 -8
  328. package/src/resources/extensions/gsd/auto-dispatch.ts +17 -0
  329. package/src/resources/extensions/gsd/auto-post-unit.ts +10 -10
  330. package/src/resources/extensions/gsd/auto-prompts.ts +133 -2
  331. package/src/resources/extensions/gsd/auto-recovery.ts +207 -7
  332. package/src/resources/extensions/gsd/auto-start.ts +7 -6
  333. package/src/resources/extensions/gsd/auto-supervisor.ts +7 -0
  334. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +2 -2
  335. package/src/resources/extensions/gsd/auto.ts +92 -4
  336. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +37 -2
  337. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +27 -19
  338. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +39 -1
  339. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +135 -1
  340. package/src/resources/extensions/gsd/clean-root-preflight.ts +41 -3
  341. package/src/resources/extensions/gsd/commands/dispatcher.ts +6 -0
  342. package/src/resources/extensions/gsd/commands-extract-learnings.ts +17 -12
  343. package/src/resources/extensions/gsd/context-budget.ts +44 -2
  344. package/src/resources/extensions/gsd/crash-recovery.ts +67 -10
  345. package/src/resources/extensions/gsd/custom-workflow-engine.ts +24 -1
  346. package/src/resources/extensions/gsd/db/unit-dispatches.ts +41 -0
  347. package/src/resources/extensions/gsd/db-base-schema.ts +19 -2
  348. package/src/resources/extensions/gsd/db-migration-steps.ts +25 -0
  349. package/src/resources/extensions/gsd/detection.ts +128 -0
  350. package/src/resources/extensions/gsd/git-service.ts +46 -8
  351. package/src/resources/extensions/gsd/graph.ts +12 -5
  352. package/src/resources/extensions/gsd/gsd-db.ts +168 -13
  353. package/src/resources/extensions/gsd/guided-flow.ts +98 -16
  354. package/src/resources/extensions/gsd/memory-store.ts +77 -12
  355. package/src/resources/extensions/gsd/migrate/command.ts +47 -1
  356. package/src/resources/extensions/gsd/migration-auto-check.ts +129 -0
  357. package/src/resources/extensions/gsd/planning-path-scope.ts +35 -0
  358. package/src/resources/extensions/gsd/pr-evidence.ts +63 -5
  359. package/src/resources/extensions/gsd/pre-execution-checks.ts +7 -0
  360. package/src/resources/extensions/gsd/preferences-types.ts +1 -1
  361. package/src/resources/extensions/gsd/prompt-loader.ts +27 -2
  362. package/src/resources/extensions/gsd/prompts/complete-milestone.md +19 -19
  363. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
  364. package/src/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
  365. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  366. package/src/resources/extensions/gsd/prompts/quick-task.md +1 -5
  367. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
  368. package/src/resources/extensions/gsd/quick.ts +37 -2
  369. package/src/resources/extensions/gsd/safety/evidence-collector.ts +11 -2
  370. package/src/resources/extensions/gsd/tests/auto-abort-pause-regression.test.ts +7 -1
  371. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +33 -0
  372. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +155 -5
  373. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +56 -13
  374. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +184 -2
  375. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +88 -2
  376. package/src/resources/extensions/gsd/tests/commands-extract-learnings.test.ts +9 -0
  377. package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +14 -1
  378. package/src/resources/extensions/gsd/tests/context-budget.test.ts +10 -1
  379. package/src/resources/extensions/gsd/tests/crash-handler-secondary.test.ts +55 -0
  380. package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +22 -0
  381. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +112 -6
  382. package/src/resources/extensions/gsd/tests/custom-workflow-engine.test.ts +40 -2
  383. package/src/resources/extensions/gsd/tests/db-migration-steps.integration.test.ts +428 -0
  384. package/src/resources/extensions/gsd/tests/db-schema-metadata.test.ts +2 -2
  385. package/src/resources/extensions/gsd/tests/detection.test.ts +140 -0
  386. package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +313 -0
  387. package/src/resources/extensions/gsd/tests/exec-history.test.ts +15 -0
  388. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +65 -0
  389. package/src/resources/extensions/gsd/tests/fixtures/pr-body/commands-ship-basic.md +52 -0
  390. package/src/resources/extensions/gsd/tests/fixtures/pr-body/commands-ship-empty-optionals.md +42 -0
  391. package/src/resources/extensions/gsd/tests/fixtures/pr-body/swarm-lane-no-blockers.md +55 -0
  392. package/src/resources/extensions/gsd/tests/fixtures/pr-body/swarm-lane-with-blockers.md +60 -0
  393. package/src/resources/extensions/gsd/tests/graph-operations.test.ts +10 -0
  394. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +44 -0
  395. package/src/resources/extensions/gsd/tests/has-pending-deep-stage.test.ts +33 -1
  396. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +54 -0
  397. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +234 -0
  398. package/src/resources/extensions/gsd/tests/memory-decay-factor.test.ts +90 -0
  399. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +48 -0
  400. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +127 -0
  401. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +50 -0
  402. package/src/resources/extensions/gsd/tests/plan-task.test.ts +21 -0
  403. package/src/resources/extensions/gsd/tests/pr-evidence-equivalence.test.ts +102 -0
  404. package/src/resources/extensions/gsd/tests/pr-evidence-hardening.test.ts +165 -0
  405. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +38 -0
  406. package/src/resources/extensions/gsd/tests/prompt-path-audit.test.ts +40 -0
  407. package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +19 -0
  408. package/src/resources/extensions/gsd/tests/quick-external-gsd.test.ts +40 -0
  409. package/src/resources/extensions/gsd/tests/right-sized-workflow-prompts.test.ts +192 -0
  410. package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +29 -0
  411. package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +156 -0
  412. package/src/resources/extensions/gsd/tests/signal-handlers.test.ts +27 -0
  413. package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +38 -0
  414. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -1
  415. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +101 -2
  416. package/src/resources/extensions/gsd/tests/status-db-open.test.ts +9 -0
  417. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +136 -4
  418. package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +30 -0
  419. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +37 -0
  420. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +1 -1
  421. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +3 -0
  422. package/src/resources/extensions/gsd/tests/working-output-messages.test.ts +93 -0
  423. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +37 -6
  424. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +7 -0
  425. package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +9 -2
  426. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +63 -1
  427. package/src/resources/extensions/gsd/tests/worktree-write-gate.test.ts +179 -0
  428. package/src/resources/extensions/gsd/tools/context-mode-tool-result.ts +25 -0
  429. package/src/resources/extensions/gsd/tools/exec-search-tool.ts +7 -7
  430. package/src/resources/extensions/gsd/tools/exec-tool.ts +4 -23
  431. package/src/resources/extensions/gsd/tools/memory-tools.ts +1 -0
  432. package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -0
  433. package/src/resources/extensions/gsd/tools/plan-task.ts +10 -0
  434. package/src/resources/extensions/gsd/tools/resume-tool.ts +7 -7
  435. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +1 -1
  436. package/src/resources/extensions/gsd/unit-context-composer.ts +19 -4
  437. package/src/resources/extensions/gsd/unit-runtime.ts +25 -0
  438. package/src/resources/extensions/gsd/working-output-messages.ts +120 -0
  439. package/src/resources/extensions/gsd/worktree-manager.ts +15 -4
  440. package/src/resources/extensions/gsd/worktree-resolver.ts +36 -15
  441. package/packages/contracts/tsconfig.tsbuildinfo +0 -1
  442. /package/dist/web/standalone/.next/static/{bQDK5_LtkGVS64AirQgQG → -5nHJWzSdG-WkPMul_khA}/_buildManifest.js +0 -0
  443. /package/dist/web/standalone/.next/static/{bQDK5_LtkGVS64AirQgQG → -5nHJWzSdG-WkPMul_khA}/_ssgManifest.js +0 -0
@@ -0,0 +1,192 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { execFileSync } from "node:child_process";
4
+ import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import { tmpdir } from "node:os";
7
+
8
+ import { buildCompleteMilestonePrompt, buildPlanMilestonePrompt } from "../auto-prompts.ts";
9
+
10
+ function git(cwd: string, args: string[]): string {
11
+ return execFileSync("git", args, {
12
+ cwd,
13
+ stdio: ["ignore", "pipe", "pipe"],
14
+ encoding: "utf-8",
15
+ env: { ...process.env, GIT_AUTHOR_NAME: "Test User", GIT_AUTHOR_EMAIL: "test@example.com", GIT_COMMITTER_NAME: "Test User", GIT_COMMITTER_EMAIL: "test@example.com" },
16
+ }).trim();
17
+ }
18
+
19
+ function makeRepo(files: Record<string, string>): string {
20
+ const base = mkdtempSync(join(tmpdir(), "gsd-right-size-"));
21
+ git(base, ["init", "-b", "main"]);
22
+ mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
23
+ writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-CONTEXT.md"), "# Context\n\nTest milestone.");
24
+ for (const [path, content] of Object.entries(files)) {
25
+ const abs = join(base, path);
26
+ mkdirSync(join(abs, ".."), { recursive: true });
27
+ writeFileSync(abs, content);
28
+ }
29
+ git(base, ["add", "."]);
30
+ git(base, ["commit", "-m", "init"]);
31
+ return base;
32
+ }
33
+
34
+ function writeCompleteMilestoneFiles(base: string, validation: string): void {
35
+ const dir = join(base, ".gsd", "milestones", "M001");
36
+ mkdirSync(join(dir, "slices", "S01"), { recursive: true });
37
+ writeFileSync(join(dir, "M001-ROADMAP.md"), "# M001\n\n## Slices\n- [x] **S01: One** `risk:low` `depends:[]`\n > Done\n");
38
+ writeFileSync(join(dir, "M001-VALIDATION.md"), validation);
39
+ writeFileSync(join(dir, "slices", "S01", "S01-SUMMARY.md"), "# S01 Summary\n\n**Verification:** passed\n");
40
+ }
41
+
42
+ function validationMetadata(): string {
43
+ return [
44
+ "validation_metadata:",
45
+ " covered_artifacts:",
46
+ " - `.gsd/milestones/M001/M001-VALIDATION.md`",
47
+ " - `.gsd/milestones/M001/M001-ROADMAP.md`",
48
+ " - `.gsd/milestones/M001/slices/S01/S01-SUMMARY.md`",
49
+ ].join("\n");
50
+ }
51
+
52
+ test("plan-milestone prompt includes tiny untyped project classification and one-slice guidance", async () => {
53
+ const base = makeRepo({ "index.html": "<!doctype html>\n<title>Test</title>\n" });
54
+ try {
55
+ const prompt = await buildPlanMilestonePrompt("M001", "Polish static page", base, "minimal");
56
+ assert.match(prompt, /\*\*Kind:\*\* untyped-existing/);
57
+ assert.match(prompt, /\*\*Content files:\*\* 1/);
58
+ assert.match(prompt, /`index\.html`/);
59
+ assert.match(prompt, /Prefer exactly one slice/);
60
+ } finally {
61
+ rmSync(base, { recursive: true, force: true });
62
+ }
63
+ });
64
+
65
+ test("plan-milestone prompt includes small untyped project 1-2 slice guidance", async () => {
66
+ const base = makeRepo({
67
+ "index.html": "html",
68
+ "README.md": "readme",
69
+ "styles.css": "body {}",
70
+ });
71
+ try {
72
+ const prompt = await buildPlanMilestonePrompt("M001", "Polish static files", base, "minimal");
73
+ assert.match(prompt, /\*\*Kind:\*\* untyped-existing/);
74
+ assert.match(prompt, /\*\*Content files:\*\* 3/);
75
+ assert.match(prompt, /Prefer 1-2 slices/);
76
+ } finally {
77
+ rmSync(base, { recursive: true, force: true });
78
+ }
79
+ });
80
+
81
+ test("plan-milestone prompt keeps normal guidance for typed projects", async () => {
82
+ const base = makeRepo({
83
+ "package.json": "{\"scripts\":{\"test\":\"node --test\"}}\n",
84
+ "src/index.js": "console.log('ok');\n",
85
+ });
86
+ try {
87
+ const prompt = await buildPlanMilestonePrompt("M001", "Update app", base, "minimal");
88
+ assert.match(prompt, /\*\*Kind:\*\* typed-existing/);
89
+ assert.match(prompt, /Use normal ecosystem-aware planning guidance/);
90
+ assert.doesNotMatch(prompt, /Prefer exactly one slice/);
91
+ } finally {
92
+ rmSync(base, { recursive: true, force: true });
93
+ }
94
+ });
95
+
96
+ test("workflow docs no longer contain blanket 4-10 slice guidance", () => {
97
+ const docs = readFileSync(join(process.cwd(), "src", "resources", "GSD-WORKFLOW.md"), "utf-8");
98
+ assert.doesNotMatch(docs, /4-10 slices/);
99
+ assert.match(docs, /1-10 slices/);
100
+ assert.match(docs, /single-file/);
101
+ });
102
+
103
+ test("prompt templates carry right-sized planning and closeout mode guidance", () => {
104
+ const planTemplate = readFileSync(join(process.cwd(), "src", "resources", "extensions", "gsd", "prompts", "plan-milestone.md"), "utf-8");
105
+ const completeTemplate = readFileSync(join(process.cwd(), "src", "resources", "extensions", "gsd", "prompts", "complete-milestone.md"), "utf-8");
106
+
107
+ assert.match(planTemplate, /Use 1-10 slices, sized to the work/);
108
+ assert.match(planTemplate, /tiny\/single-file\/static work should usually be one slice/);
109
+ assert.match(planTemplate, /untyped-existing/);
110
+ assert.match(completeTemplate, /Closeout Review Mode/);
111
+ assert.match(completeTemplate, /passing validation artifact is present/);
112
+ assert.doesNotMatch(completeTemplate, /^### Delegate Review Work/m);
113
+ });
114
+
115
+ test("complete-milestone prompt trusts passing validation artifact", async () => {
116
+ const base = makeRepo({ "index.html": "<!doctype html>\n<title>Test</title>\n" });
117
+ try {
118
+ writeCompleteMilestoneFiles(base, `---\nverdict: pass\nremediation_round: 0\n---\n\n# Validation\n${validationMetadata()}\n\nAll checks passed.`);
119
+ const prompt = await buildCompleteMilestonePrompt("M001", "Polish static page", base, "minimal");
120
+ assert.match(prompt, /Passing Validation Artifact/);
121
+ assert.match(prompt, /Treat it as authoritative/);
122
+ assert.match(prompt, /Do not delegate fresh reviewer\/security\/tester audits/);
123
+ assert.match(prompt, /All checks passed/);
124
+ } finally {
125
+ rmSync(base, { recursive: true, force: true });
126
+ }
127
+ });
128
+
129
+ test("complete-milestone prompt trusts centralized markdown body pass verdict", async () => {
130
+ const base = makeRepo({ "index.html": "<!doctype html>\n<title>Test</title>\n" });
131
+ try {
132
+ writeCompleteMilestoneFiles(base, `# Validation\n\n**Verdict:** PASS\n\n${validationMetadata()}\n\nAll checks passed.`);
133
+ const prompt = await buildCompleteMilestonePrompt("M001", "Polish static page", base, "minimal");
134
+ assert.match(prompt, /Passing Validation Artifact/);
135
+ assert.match(prompt, /Treat it as authoritative/);
136
+ assert.match(prompt, /Do not delegate fresh reviewer\/security\/tester audits/);
137
+ } finally {
138
+ rmSync(base, { recursive: true, force: true });
139
+ }
140
+ });
141
+
142
+ test("complete-milestone prompt does not trust stale pass validation without metadata", async () => {
143
+ const base = makeRepo({ "index.html": "<!doctype html>\n<title>Test</title>\n" });
144
+ try {
145
+ writeCompleteMilestoneFiles(base, "---\nverdict: pass\nremediation_round: 0\n---\n\n# Validation\nAll checks passed.");
146
+ const prompt = await buildCompleteMilestonePrompt("M001", "Polish static page", base, "minimal");
147
+ assert.match(prompt, /Validation Requires Attention/);
148
+ assert.match(prompt, /missing freshness metadata/);
149
+ assert.doesNotMatch(prompt, /Passing Validation Artifact/);
150
+ } finally {
151
+ rmSync(base, { recursive: true, force: true });
152
+ }
153
+ });
154
+
155
+ test("complete-milestone prompt does not trust pass validation missing current summary coverage", async () => {
156
+ const base = makeRepo({ "index.html": "<!doctype html>\n<title>Test</title>\n" });
157
+ try {
158
+ writeCompleteMilestoneFiles(base, [
159
+ "---",
160
+ "verdict: pass",
161
+ "remediation_round: 0",
162
+ "---",
163
+ "",
164
+ "# Validation",
165
+ "validation_metadata:",
166
+ " covered_artifacts:",
167
+ " - `.gsd/milestones/M001/M001-VALIDATION.md`",
168
+ " - `.gsd/milestones/M001/M001-ROADMAP.md`",
169
+ "",
170
+ "All checks passed.",
171
+ ].join("\n"));
172
+ const prompt = await buildCompleteMilestonePrompt("M001", "Polish static page", base, "minimal");
173
+ assert.match(prompt, /Validation Requires Attention/);
174
+ assert.match(prompt, /does not cover current milestone artifacts/);
175
+ assert.doesNotMatch(prompt, /Passing Validation Artifact/);
176
+ } finally {
177
+ rmSync(base, { recursive: true, force: true });
178
+ }
179
+ });
180
+
181
+ test("complete-milestone prompt keeps deeper review path without passing validation", async () => {
182
+ const base = makeRepo({ "index.html": "<!doctype html>\n<title>Test</title>\n" });
183
+ try {
184
+ writeCompleteMilestoneFiles(base, "---\nverdict: needs-attention\nremediation_round: 0\n---\n\n# Validation\nFix gaps.");
185
+ const prompt = await buildCompleteMilestonePrompt("M001", "Polish static page", base, "minimal");
186
+ assert.match(prompt, /Validation Requires Attention/);
187
+ assert.match(prompt, /verdict `needs-attention`/);
188
+ assert.match(prompt, /Use `subagent` for review work needing fresh context/i);
189
+ } finally {
190
+ rmSync(base, { recursive: true, force: true });
191
+ }
192
+ });
@@ -144,6 +144,18 @@ test("safety-harness-bug2-race: bash evidence survives mid-unit reset between to
144
144
  assert.ok(bash[0].outputSnippet.includes("found"), "output snippet captured");
145
145
  });
146
146
 
147
+ test("safety-harness: gsd_exec counts as execution evidence", () => {
148
+ resetEvidence();
149
+
150
+ recordToolCall("tc-exec-1", "gsd_exec", { command: "grep -n render index.html" });
151
+ recordToolResult("tc-exec-1", "gsd_exec", "Command exited with code 0\n1:render\n", false);
152
+
153
+ const bash = getEvidence().filter((e): e is BashEvidence => e.kind === "bash");
154
+ assert.equal(bash.length, 1, "gsd_exec must be tracked as execution evidence");
155
+ assert.equal(bash[0].command, "grep -n render index.html");
156
+ assert.equal(bash[0].exitCode, 0);
157
+ });
158
+
147
159
  // ─── Bug 3: git diff HEAD~1 scope check ─────────────────────────────────────
148
160
 
149
161
  test("safety-harness-bug3: validateFileChanges works on initial commit (no HEAD~1)", (t) => {
@@ -237,3 +249,20 @@ test("safety-harness-bug3: validateFileChanges works on merge commit", (t) => {
237
249
  // Must produce a valid result without throwing
238
250
  assert.ok(audit !== null, "audit must be produced for merge commit repo");
239
251
  });
252
+
253
+ test("safety-harness: planned changed file avoids unexpected-file warning", (t) => {
254
+ const base = mkdtempSync(join(tmpdir(), "gsd-planned-file-"));
255
+ t.after(() => rmSync(base, { recursive: true, force: true }));
256
+
257
+ execFileSync("git", ["init"], { cwd: base });
258
+ execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: base });
259
+ execFileSync("git", ["config", "user.name", "Test User"], { cwd: base });
260
+ writeFileSync(join(base, "index.html"), "<main></main>\n");
261
+ execFileSync("git", ["add", "index.html"], { cwd: base });
262
+ execFileSync("git", ["commit", "-m", "add static app"], { cwd: base });
263
+
264
+ const audit = validateFileChanges(base, [], ["index.html"]);
265
+ assert.ok(audit !== null, "audit must be produced");
266
+ assert.deepEqual(audit!.unexpectedFiles, [], "planned index.html must not be unexpected");
267
+ assert.deepEqual(audit!.missingFiles, [], "planned index.html must not be missing");
268
+ });
@@ -0,0 +1,156 @@
1
+ // gsd-2 / V27 + V28 schema migration regression tests
2
+ //
3
+ // Same bug class as #4591 (schema-v21-sequence): a migration block can be
4
+ // added but the SCHEMA_VERSION constant left unchanged, causing fresh-install
5
+ // + upgrade paths to silently skip the column add. This file pins both V27
6
+ // (artifacts.content_hash) and V28 (memories.last_hit_at) at the schema and
7
+ // write-path level on fresh-install DBs.
8
+
9
+ import test from "node:test";
10
+ import assert from "node:assert/strict";
11
+ import * as fs from "node:fs";
12
+ import * as path from "node:path";
13
+ import * as os from "node:os";
14
+
15
+ import {
16
+ openDatabase,
17
+ closeDatabase,
18
+ _getAdapter,
19
+ insertArtifact,
20
+ insertMemoryRow,
21
+ incrementMemoryHitCount,
22
+ SCHEMA_VERSION,
23
+ } from "../gsd-db.ts";
24
+
25
+ function makeTmp(): string {
26
+ return fs.mkdtempSync(path.join(os.tmpdir(), "gsd-v27v28-"));
27
+ }
28
+
29
+ function cleanup(base: string): void {
30
+ try { closeDatabase(); } catch { /* noop */ }
31
+ try { fs.rmSync(base, { recursive: true, force: true }); } catch { /* noop */ }
32
+ }
33
+
34
+ test("SCHEMA_VERSION constant is at least 28 (V28 migration committed)", () => {
35
+ assert.ok(
36
+ SCHEMA_VERSION >= 28,
37
+ `SCHEMA_VERSION must be ≥ 28 after V28 migration; got ${SCHEMA_VERSION}`,
38
+ );
39
+ });
40
+
41
+ test("fresh-install DB has artifacts.content_hash column (V27)", () => {
42
+ const base = makeTmp();
43
+ const dbPath = path.join(base, "gsd.db");
44
+ try {
45
+ openDatabase(dbPath);
46
+ const db = _getAdapter()!;
47
+ const cols = db.prepare("PRAGMA table_info(artifacts)").all() as Array<Record<string, unknown>>;
48
+ const colNames = new Set(cols.map((c) => c["name"] as string));
49
+ assert.ok(
50
+ colNames.has("content_hash"),
51
+ "V27 must add content_hash column to artifacts on fresh install",
52
+ );
53
+ } finally {
54
+ cleanup(base);
55
+ }
56
+ });
57
+
58
+ test("fresh-install DB has memories.last_hit_at column (V28)", () => {
59
+ const base = makeTmp();
60
+ const dbPath = path.join(base, "gsd.db");
61
+ try {
62
+ openDatabase(dbPath);
63
+ const db = _getAdapter()!;
64
+ const cols = db.prepare("PRAGMA table_info(memories)").all() as Array<Record<string, unknown>>;
65
+ const colNames = new Set(cols.map((c) => c["name"] as string));
66
+ assert.ok(
67
+ colNames.has("last_hit_at"),
68
+ "V28 must add last_hit_at column to memories on fresh install",
69
+ );
70
+ } finally {
71
+ cleanup(base);
72
+ }
73
+ });
74
+
75
+ test("fresh-install DB stamps SCHEMA_VERSION (≥28) in schema_version table", () => {
76
+ const base = makeTmp();
77
+ const dbPath = path.join(base, "gsd.db");
78
+ try {
79
+ openDatabase(dbPath);
80
+ const db = _getAdapter()!;
81
+ const row = db.prepare("SELECT MAX(version) as v FROM schema_version").get() as Record<string, unknown> | undefined;
82
+ const max = (row?.["v"] as number) ?? 0;
83
+ assert.ok(max >= 28, `fresh install must record schema_version ≥ 28; got ${max}`);
84
+ } finally {
85
+ cleanup(base);
86
+ }
87
+ });
88
+
89
+ test("insertArtifact populates content_hash with SHA-256 of full_content (V27 write-path)", () => {
90
+ const base = makeTmp();
91
+ const dbPath = path.join(base, "gsd.db");
92
+ try {
93
+ openDatabase(dbPath);
94
+ insertArtifact({
95
+ path: "M001/PROJECT.md",
96
+ artifact_type: "PROJECT",
97
+ milestone_id: "M001",
98
+ slice_id: null,
99
+ task_id: null,
100
+ full_content: "hello world",
101
+ });
102
+
103
+ const db = _getAdapter()!;
104
+ const row = db
105
+ .prepare("SELECT content_hash FROM artifacts WHERE path = :p")
106
+ .get({ ":p": "M001/PROJECT.md" }) as Record<string, unknown> | undefined;
107
+ const hash = row?.["content_hash"] as string | null | undefined;
108
+
109
+ // SHA-256 of "hello world" hex-encoded:
110
+ const expected = "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9";
111
+ assert.equal(hash, expected, "content_hash must be SHA-256 hex of full_content");
112
+ } finally {
113
+ cleanup(base);
114
+ }
115
+ });
116
+
117
+ test("incrementMemoryHitCount sets last_hit_at alongside hit_count (V28 write-path)", () => {
118
+ const base = makeTmp();
119
+ const dbPath = path.join(base, "gsd.db");
120
+ try {
121
+ openDatabase(dbPath);
122
+
123
+ const created = "2026-01-01T00:00:00.000Z";
124
+ insertMemoryRow({
125
+ id: "MEM001",
126
+ category: "gotcha",
127
+ content: "test memory",
128
+ confidence: 0.9,
129
+ sourceUnitType: null,
130
+ sourceUnitId: null,
131
+ createdAt: created,
132
+ updatedAt: created,
133
+ scope: "project",
134
+ tags: [],
135
+ structuredFields: null,
136
+ });
137
+
138
+ // Before increment: last_hit_at should be NULL
139
+ const db = _getAdapter()!;
140
+ const before = db
141
+ .prepare("SELECT last_hit_at FROM memories WHERE id = :id")
142
+ .get({ ":id": "MEM001" }) as Record<string, unknown>;
143
+ assert.equal(before["last_hit_at"], null, "last_hit_at starts NULL on fresh insert");
144
+
145
+ const hitTime = "2026-02-01T00:00:00.000Z";
146
+ incrementMemoryHitCount("MEM001", hitTime);
147
+
148
+ const after = db
149
+ .prepare("SELECT hit_count, last_hit_at FROM memories WHERE id = :id")
150
+ .get({ ":id": "MEM001" }) as Record<string, unknown>;
151
+ assert.equal(after["hit_count"], 1, "hit_count increments");
152
+ assert.equal(after["last_hit_at"], hitTime, "last_hit_at set to provided timestamp");
153
+ } finally {
154
+ cleanup(base);
155
+ }
156
+ });
@@ -101,3 +101,30 @@ test("registerSigtermHandler deregisters previous handler from all signals", ()
101
101
  // Clean up
102
102
  deregisterSigtermHandler(handler2);
103
103
  });
104
+
105
+ test("registered signal handler runs best-effort cleanup before exiting", () => {
106
+ let cleanupCalled = false;
107
+ let exitCode: number | string | null | undefined;
108
+ const originalExit = process.exit;
109
+ const handler = registerSigtermHandler(
110
+ "/tmp/test-signal-cleanup",
111
+ null,
112
+ () => {
113
+ cleanupCalled = true;
114
+ },
115
+ );
116
+
117
+ (process as any).exit = ((code?: number | string | null) => {
118
+ exitCode = code;
119
+ throw new Error("process.exit intercepted");
120
+ }) as never;
121
+
122
+ try {
123
+ assert.throws(() => handler(), /process\.exit intercepted/);
124
+ assert.equal(cleanupCalled, true);
125
+ assert.equal(exitCode, 0);
126
+ } finally {
127
+ (process as any).exit = originalExit;
128
+ deregisterSigtermHandler(handler);
129
+ }
130
+ });
@@ -47,6 +47,9 @@ test("guided-flow complete branch offers a chooser for next milestone or status"
47
47
  const branchChunk = guidedFlowSource.slice(branchIdx, nextBranchIdx === -1 ? branchIdx + 1600 : nextBranchIdx);
48
48
 
49
49
  assert.match(branchChunk, /showNextAction\(/, "complete branch should present a chooser");
50
+ assert.match(branchChunk, /id:\s*"quick_task"/, "complete branch should offer quick task before milestone planning");
51
+ assert.match(branchChunk, /Do a small bounded task without opening a milestone/, "quick task action should explain that it avoids milestones");
52
+ assert.match(branchChunk, /recommended:\s*true/, "quick task action should be the recommended complete-state action");
50
53
  assert.match(branchChunk, /findMilestoneIds\(basePath\)/, "complete branch should compute the next milestone id");
51
54
  assert.match(
52
55
  branchChunk,
@@ -56,6 +59,41 @@ test("guided-flow complete branch offers a chooser for next milestone or status"
56
59
  assert.match(branchChunk, /dispatchWorkflow\(pi, await prepareAndBuildDiscussPrompt\(/, "complete branch should dispatch the prepared discuss prompt");
57
60
  });
58
61
 
62
+ test("guided-flow quick task choices prompt for text and route through /gsd quick", () => {
63
+ const guidedFlowSource = readFileSync(join(import.meta.dirname, "..", "guided-flow.ts"), "utf-8");
64
+
65
+ assert.match(
66
+ guidedFlowSource,
67
+ /async function runQuickTaskChoice\([\s\S]*ctx\.ui\.input\("Quick task"[\s\S]*await import\("\.\/quick\.js"\)[\s\S]*await handleQuick\(task,\s*ctx,\s*pi\)/,
68
+ "quick task chooser should prompt for task text and route through handleQuick",
69
+ );
70
+
71
+ const notifyOnlyPattern = /if \(choice === "quick_task"\) \{\s*ctx\.ui\.notify\("Run \/gsd quick <task>/;
72
+ assert.doesNotMatch(
73
+ guidedFlowSource,
74
+ notifyOnlyPattern,
75
+ "quick task chooser must not merely print instructions after selection",
76
+ );
77
+
78
+ const quickChoiceCalls = guidedFlowSource.match(/if \(choice === "quick_task"\) \{\s*await runQuickTaskChoice\(ctx,\s*pi\);/g) ?? [];
79
+ assert.equal(quickChoiceCalls.length, 3, "all guided-flow quick task choices should dispatch the helper");
80
+ });
81
+
82
+ test("dispatcher routes multi-word freeform /gsd input through /gsd do", () => {
83
+ const dispatcherSource = readFileSync(join(import.meta.dirname, "..", "commands", "dispatcher.ts"), "utf-8");
84
+
85
+ assert.match(
86
+ dispatcherSource,
87
+ /if\s*\(trimmed\.includes\(" "\)\)\s*\{[\s\S]*handleDo\(trimmed,\s*ctx,\s*pi\)/,
88
+ "dispatcher should treat multi-word unknown input as natural-language /gsd do work",
89
+ );
90
+ assert.match(
91
+ dispatcherSource,
92
+ /Unknown: \/gsd/,
93
+ "single-token unknown commands should still report the normal unknown-command warning",
94
+ );
95
+ });
96
+
59
97
  test("guided-flow needs-discussion skip branch opens the project DB before reserving a new milestone", () => {
60
98
  const guidedFlowSource = readFileSync(join(import.meta.dirname, "..", "guided-flow.ts"), "utf-8");
61
99
  const laterDbOpenIdx = guidedFlowSource.indexOf("// Ensure DB is open before querying slices (#2560).");
@@ -15,10 +15,11 @@
15
15
  * (the fix) and verifies it does not crash.
16
16
  */
17
17
 
18
- import { mkdtempSync, mkdirSync, rmSync } from "node:fs";
18
+ import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
19
19
  import { join } from "node:path";
20
20
  import { tmpdir } from "node:os";
21
21
  import { recoverTimedOutUnit, type RecoveryContext } from "../auto-timeout-recovery.ts";
22
+ import { closeDatabase, insertMilestone, insertSlice, insertTask, openDatabase } from "../gsd-db.ts";
22
23
  import { test } from 'node:test';
23
24
  import assert from 'node:assert/strict';
24
25
 
@@ -39,6 +40,14 @@ function makeMockPi() {
39
40
  } as any;
40
41
  }
41
42
 
43
+ function makeRecordingPi() {
44
+ const messages: unknown[] = [];
45
+ return {
46
+ messages,
47
+ sendMessage: (message: unknown) => { messages.push(message); },
48
+ } as any;
49
+ }
50
+
42
51
  // ═══ #1855: empty RecoveryContext (basePath undefined) crashes ════════════════
43
52
 
44
53
  {
@@ -63,6 +72,45 @@ function makeMockPi() {
63
72
  assert.ok(crashed, "should crash when basePath is undefined (reproduces #1855)");
64
73
  }
65
74
 
75
+ // ═══ DB-complete execute-task recovery advances without steering ═════════════
76
+
77
+ {
78
+ console.log("\n=== execute-task timeout recovery trusts closed DB status ===");
79
+ const base = mkdtempSync(join(tmpdir(), "gsd-timeout-db-complete-"));
80
+ mkdirSync(join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks"), { recursive: true });
81
+
82
+ try {
83
+ openDatabase(join(base, ".gsd", "gsd.db"));
84
+ insertMilestone({ id: "M001", title: "Milestone", status: "active" });
85
+ insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "in_progress" });
86
+ insertTask({ id: "T01", milestoneId: "M001", sliceId: "S01", title: "Task", status: "complete" });
87
+ writeFileSync(
88
+ join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-PLAN.md"),
89
+ "# S01\n\n## Tasks\n\n- [ ] **T01: Task** `est:10m`\n",
90
+ "utf-8",
91
+ );
92
+ writeFileSync(join(base, ".gsd", "STATE.md"), "## Next Action\nExecute T01 for S01: Task\n", "utf-8");
93
+
94
+ const ctx = makeMockCtx();
95
+ const pi = makeRecordingPi();
96
+ const result = await recoverTimedOutUnit(ctx, pi, "execute-task", "M001/S01/T01", "idle", {
97
+ basePath: base,
98
+ verbose: false,
99
+ currentUnitStartedAt: Date.now(),
100
+ unitRecoveryCount: new Map(),
101
+ });
102
+
103
+ assert.equal(result, "recovered", "db-complete task should recover immediately");
104
+ assert.equal(pi.messages.length, 0, "db-complete task should not send steering recovery");
105
+ const runtime = JSON.parse(readFileSync(join(base, ".gsd", "runtime", "units", "execute-task-M001-S01-T01.json"), "utf-8"));
106
+ assert.equal(runtime.phase, "finalized", "db-complete task should be finalized");
107
+ assert.equal(runtime.recovery.dbComplete, true, "runtime recovery should record DB completion");
108
+ } finally {
109
+ closeDatabase();
110
+ rmSync(base, { recursive: true, force: true });
111
+ }
112
+ }
113
+
66
114
  // ═══ #1855: valid RecoveryContext does not crash ═════════════════════════════
67
115
 
68
116
  {
@@ -3,6 +3,9 @@ import assert from "node:assert/strict";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { resolve } from "node:path";
5
5
 
6
+ import { _withDetachedAutoKeepaliveForTest } from "../auto.ts";
7
+ import { _scheduleAutoStartAfterIdleForTest } from "../guided-flow.ts";
8
+
6
9
  const gsdDir = resolve(import.meta.dirname, "..");
7
10
 
8
11
  function readGsdFile(relativePath: string): string {
@@ -94,6 +97,23 @@ test("auto bootstrap validates blocked directories before touching .gsd migratio
94
97
  );
95
98
  });
96
99
 
100
+ test("fresh start registers the auto worker before bootstrap enters worktree flow (#5405)", () => {
101
+ const autoSrc = readGsdFile("auto.ts");
102
+ const startAutoIdx = autoSrc.indexOf("export async function startAuto(");
103
+ const startAutoBody = autoSrc.slice(startAutoIdx);
104
+
105
+ const preBootstrapRegisterIdx = startAutoBody.indexOf("registerAutoWorkerForSession(s, base);");
106
+ const bootstrapCallIdx = startAutoBody.indexOf("const ready = await bootstrapAutoSession(");
107
+
108
+ assert.ok(startAutoIdx > -1, "startAuto should exist");
109
+ assert.ok(preBootstrapRegisterIdx > -1, "startAuto should register worker before bootstrap");
110
+ assert.ok(bootstrapCallIdx > -1, "startAuto should call bootstrapAutoSession");
111
+ assert.ok(
112
+ preBootstrapRegisterIdx < bootstrapCallIdx,
113
+ "worker registration must happen before bootstrap so enterMilestone can claim milestone leases on first entry",
114
+ );
115
+ });
116
+
97
117
  test("startAutoDetached reports failures asynchronously (#3733)", () => {
98
118
  const autoSrc = readGsdFile("auto.ts");
99
119
 
@@ -102,8 +122,8 @@ test("startAutoDetached reports failures asynchronously (#3733)", () => {
102
122
  "auto.ts should export startAutoDetached",
103
123
  );
104
124
  assert.ok(
105
- autoSrc.includes("void startAuto(ctx, pi, base, verboseMode, options).catch"),
106
- "startAutoDetached should launch startAuto without awaiting it",
125
+ autoSrc.includes("void withDetachedAutoKeepalive(startAuto(ctx, pi, base, verboseMode, options)).catch"),
126
+ "startAutoDetached should launch startAuto without awaiting it and keep the process alive",
107
127
  );
108
128
  assert.ok(
109
129
  autoSrc.includes("ctx.ui.notify(`Auto-start failed: ${message}`, \"error\")"),
@@ -111,6 +131,48 @@ test("startAutoDetached reports failures asynchronously (#3733)", () => {
111
131
  );
112
132
  });
113
133
 
134
+ test("detached auto-start keeps a ref'ed handle until the run settles", async () => {
135
+ const originalSetInterval = globalThis.setInterval;
136
+ const originalClearInterval = globalThis.clearInterval;
137
+ let intervalCreated = false;
138
+ let intervalCleared = false;
139
+ let createdHandle: NodeJS.Timeout | undefined;
140
+ let resolveRun!: () => void;
141
+ const run = new Promise<void>((resolve) => {
142
+ resolveRun = resolve;
143
+ });
144
+
145
+ globalThis.setInterval = ((handler: TimerHandler, timeout?: number, ...args: unknown[]) => {
146
+ intervalCreated = true;
147
+ assert.equal(timeout, 30_000);
148
+ void handler;
149
+ void args;
150
+ createdHandle = originalSetInterval(() => {}, 1_000_000);
151
+ assert.equal(createdHandle.hasRef(), true, "detached auto keepalive must be ref'ed");
152
+ return createdHandle;
153
+ }) as unknown as typeof setInterval;
154
+
155
+ globalThis.clearInterval = ((handle?: NodeJS.Timeout | number | string) => {
156
+ if (handle === createdHandle) intervalCleared = true;
157
+ return originalClearInterval(handle);
158
+ }) as unknown as typeof clearInterval;
159
+
160
+ try {
161
+ const heldRun = _withDetachedAutoKeepaliveForTest(run);
162
+ assert.equal(intervalCreated, true, "keepalive interval should start immediately");
163
+ assert.equal(intervalCleared, false, "keepalive should remain active while auto-mode is running");
164
+
165
+ resolveRun();
166
+ await heldRun;
167
+
168
+ assert.equal(intervalCleared, true, "keepalive interval should clear when auto-mode settles");
169
+ } finally {
170
+ if (createdHandle) originalClearInterval(createdHandle);
171
+ globalThis.setInterval = originalSetInterval;
172
+ globalThis.clearInterval = originalClearInterval;
173
+ }
174
+ });
175
+
114
176
  test("detached auto-start preserves milestone lock across pause/stop cleanup (#3733)", () => {
115
177
  const autoSrc = readGsdFile("auto.ts");
116
178
  const sessionSrc = readGsdFile("auto/session.ts");
@@ -141,3 +203,40 @@ test("detached auto-start preserves milestone lock across pause/stop cleanup (#3
141
203
  "AutoSession should track the detached milestone lock explicitly",
142
204
  );
143
205
  });
206
+
207
+ test("discussion auto-start waits for the current command context to become idle", async () => {
208
+ let releaseIdle!: () => void;
209
+ const idle = new Promise<void>((resolveIdle) => {
210
+ releaseIdle = resolveIdle;
211
+ });
212
+ const launches: unknown[][] = [];
213
+ const ctx = {
214
+ waitForIdle: () => idle,
215
+ ui: {
216
+ notify: () => {},
217
+ },
218
+ } as any;
219
+
220
+ _scheduleAutoStartAfterIdleForTest(
221
+ ctx,
222
+ {} as any,
223
+ "/tmp/gsd-auto-start-idle-test",
224
+ false,
225
+ { step: true },
226
+ (...args: unknown[]) => {
227
+ launches.push(args);
228
+ },
229
+ );
230
+
231
+ await Promise.resolve();
232
+ assert.equal(launches.length, 0, "auto-start must not launch before waitForIdle resolves");
233
+
234
+ releaseIdle();
235
+ await Promise.resolve();
236
+ assert.equal(launches.length, 0, "auto-start should defer launch to the next timer turn");
237
+
238
+ await new Promise((resolveTimer) => setTimeout(resolveTimer, 0));
239
+ assert.equal(launches.length, 1);
240
+ assert.equal(launches[0][2], "/tmp/gsd-auto-start-idle-test");
241
+ assert.deepEqual(launches[0][4], { step: true });
242
+ });