@opengsd/gsd-pi 1.3.0-dev.eed73bea → 1.4.0

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 (505) hide show
  1. package/README.md +10 -10
  2. package/dist/cli.js +17 -4
  3. package/dist/headless-answers.js +1 -1
  4. package/dist/headless-ui.d.ts +1 -1
  5. package/dist/headless-ui.js +1 -1
  6. package/dist/headless.js +1 -1
  7. package/dist/resources/.managed-resources-content-hash +1 -1
  8. package/dist/resources/extensions/claude-code-cli/models.js +9 -0
  9. package/dist/resources/extensions/gsd/artifact-verification.js +427 -0
  10. package/dist/resources/extensions/gsd/auto/session.js +11 -0
  11. package/dist/resources/extensions/gsd/auto/verification-retry-policy.js +1 -1
  12. package/dist/resources/extensions/gsd/auto-artifact-paths.js +28 -1
  13. package/dist/resources/extensions/gsd/auto-budget.js +5 -1
  14. package/dist/resources/extensions/gsd/auto-dispatch.js +17 -6
  15. package/dist/resources/extensions/gsd/auto-post-unit.js +1 -1
  16. package/dist/resources/extensions/gsd/auto-prompts.js +8 -24
  17. package/dist/resources/extensions/gsd/auto-recovery.js +9 -517
  18. package/dist/resources/extensions/gsd/auto-runtime-state.js +4 -5
  19. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +3 -3
  20. package/dist/resources/extensions/gsd/auto-verification.js +5 -2
  21. package/dist/resources/extensions/gsd/auto.js +19 -10
  22. package/dist/resources/extensions/gsd/bootstrap/core-session-tools.js +39 -0
  23. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +5 -4
  24. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
  25. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +33 -18
  26. package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +48 -36
  27. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +15 -4
  28. package/dist/resources/extensions/gsd/closeout-consistency-gate.js +3 -1
  29. package/dist/resources/extensions/gsd/commands/handlers/auto.js +1 -1
  30. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  31. package/dist/resources/extensions/gsd/commands-usage.js +1 -1
  32. package/dist/resources/extensions/gsd/db/engine.js +4 -1
  33. package/dist/resources/extensions/gsd/db-memory-fts-schema.js +34 -0
  34. package/dist/resources/extensions/gsd/db-workspace.js +55 -3
  35. package/dist/resources/extensions/gsd/delegation-policy.js +1 -1
  36. package/dist/resources/extensions/gsd/docs/preferences-reference.md +2 -2
  37. package/dist/resources/extensions/gsd/doctor-engine-checks.js +192 -4
  38. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +3 -0
  39. package/dist/resources/extensions/gsd/exec-sandbox.js +8 -2
  40. package/dist/resources/extensions/gsd/flat-phase-migration.js +45 -0
  41. package/dist/resources/extensions/gsd/git-service.js +57 -0
  42. package/dist/resources/extensions/gsd/gitignore.js +4 -1
  43. package/dist/resources/extensions/gsd/guided-flow-queue.js +20 -13
  44. package/dist/resources/extensions/gsd/json-persistence.js +20 -0
  45. package/dist/resources/extensions/gsd/knowledge-backfill.js +18 -11
  46. package/dist/resources/extensions/gsd/layout-policy.js +3 -1
  47. package/dist/resources/extensions/gsd/markdown-renderer.js +2 -7
  48. package/dist/resources/extensions/gsd/memory-backfill.js +28 -21
  49. package/dist/resources/extensions/gsd/metrics.js +4 -4
  50. package/dist/resources/extensions/gsd/migrate-external.js +11 -2
  51. package/dist/resources/extensions/gsd/migration-auto-check.js +22 -0
  52. package/dist/resources/extensions/gsd/milestone-ids.js +32 -2
  53. package/dist/resources/extensions/gsd/orphan-stash-audit.js +92 -16
  54. package/dist/resources/extensions/gsd/prompts/code-review.md +6 -4
  55. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +2 -2
  56. package/dist/resources/extensions/gsd/prompts/plan-slice.md +3 -3
  57. package/dist/resources/extensions/gsd/root-write-leak-guard.js +7 -1
  58. package/dist/resources/extensions/gsd/safety/destructive-guard.js +8 -1
  59. package/dist/resources/extensions/gsd/safety/evidence-collector.js +16 -1
  60. package/dist/resources/extensions/gsd/safety/file-change-validator.js +5 -2
  61. package/dist/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -1
  62. package/dist/resources/extensions/gsd/state/derive/cache.js +28 -0
  63. package/dist/resources/extensions/gsd/state/derive/db-open.js +39 -0
  64. package/dist/resources/extensions/gsd/state/derive/from-db.js +452 -0
  65. package/dist/resources/extensions/gsd/state/derive/index.js +75 -0
  66. package/dist/resources/extensions/gsd/state/derive/interrupted-work.js +21 -0
  67. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +27 -33
  68. package/dist/resources/extensions/gsd/state-reconciliation/index.js +48 -23
  69. package/dist/resources/extensions/gsd/state-reconciliation/registry.js +32 -28
  70. package/dist/resources/extensions/gsd/state.js +12 -611
  71. package/dist/resources/extensions/gsd/tools/complete-milestone.js +4 -2
  72. package/dist/resources/extensions/gsd/tools/complete-slice.js +3 -34
  73. package/dist/resources/extensions/gsd/tools/complete-task.js +8 -41
  74. package/dist/resources/extensions/gsd/tools/plan-slice.js +59 -44
  75. package/dist/resources/extensions/gsd/tools/plan-task.js +28 -5
  76. package/dist/resources/extensions/gsd/undo.js +43 -6
  77. package/dist/resources/extensions/gsd/unit-registry.js +11 -3
  78. package/dist/resources/extensions/gsd/verification-gate.js +7 -0
  79. package/dist/resources/extensions/gsd/workflow-manifest.js +126 -13
  80. package/dist/resources/extensions/gsd/workflow-projections.js +19 -14
  81. package/dist/resources/extensions/gsd/worktree-manager.js +44 -2
  82. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  83. package/dist/web/standalone/.next/BUILD_ID +1 -1
  84. package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
  85. package/dist/web/standalone/.next/build-manifest.json +4 -4
  86. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  87. package/dist/web/standalone/.next/react-loadable-manifest.json +9 -9
  88. package/dist/web/standalone/.next/required-server-files.json +3 -3
  89. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  90. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  91. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  92. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  100. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  102. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  103. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  104. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  106. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  107. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  108. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  109. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  116. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  128. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  137. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  143. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/mcp-connections/route_client-reference-manifest.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  150. package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  155. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  157. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  159. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  160. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  161. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  163. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  164. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  166. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  168. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  169. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  170. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  171. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  172. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  173. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  175. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  176. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  177. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  178. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  179. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
  180. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  181. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  182. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  183. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  184. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  185. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  186. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  187. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  188. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  189. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  190. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  191. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  192. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  193. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  194. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  195. package/dist/web/standalone/.next/server/app/index.html +1 -1
  196. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  197. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  198. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  199. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  200. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  201. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  202. package/dist/web/standalone/.next/server/app/page.js +2 -2
  203. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  204. package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
  205. package/dist/web/standalone/.next/server/chunks/2842.js +4 -4
  206. package/dist/web/standalone/.next/server/chunks/8357.js +3 -3
  207. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  208. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  209. package/dist/web/standalone/.next/server/middleware.js +3 -3
  210. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  211. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  212. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  213. package/dist/web/standalone/.next/static/chunks/{2659.b7b129ee6a769448.js → 2659.58e950899a9bb82f.js} +1 -1
  214. package/dist/web/standalone/.next/static/chunks/2772.a7c1fcc69a4685ef.js +1 -0
  215. package/dist/web/standalone/.next/static/chunks/{3616.3c60753b8ffcbd2e.js → 3616.61a2af74bb8833c8.js} +1 -1
  216. package/dist/web/standalone/.next/static/chunks/{4283.8e446784528ed9dc.js → 4283.d0d9e0a955e441cb.js} +1 -1
  217. package/dist/web/standalone/.next/static/chunks/{5826.a46ecdd1cfe8dabc.js → 5826.5421d66c72b9f34e.js} +1 -1
  218. package/dist/web/standalone/.next/static/chunks/{8785.481aa5869991b760.js → 8785.e29b3134cab1d153.js} +1 -1
  219. package/dist/web/standalone/.next/static/chunks/8937.640dc9c2aaa1dfad.js +10 -0
  220. package/dist/web/standalone/.next/static/chunks/app/_not-found/page-49f565245e1e4afe.js +1 -0
  221. package/dist/web/standalone/.next/static/chunks/app/{layout-4ae2d68984392bbf.js → layout-b35cbfff38aaf4cf.js} +1 -1
  222. package/dist/web/standalone/.next/static/chunks/app/page-bc4c723c6a19c6f0.js +1 -0
  223. package/dist/web/standalone/.next/static/chunks/{main-app-90d1d8d5e5d2dc6b.js → main-app-590a74400e35f685.js} +1 -1
  224. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ec3eaffc9785ba48.js +1 -0
  225. package/dist/web/standalone/.next/static/chunks/webpack-9c401904f87ded16.js +1 -0
  226. package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
  227. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  228. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  229. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  230. package/dist/web/standalone/server.js +1 -1
  231. package/package.json +6 -6
  232. package/packages/cloud-mcp-gateway/package.json +2 -2
  233. package/packages/contracts/package.json +1 -1
  234. package/packages/daemon/package.json +4 -4
  235. package/packages/gsd-agent-core/dist/agent-session.d.ts +1 -0
  236. package/packages/gsd-agent-core/dist/agent-session.d.ts.map +1 -1
  237. package/packages/gsd-agent-core/dist/agent-session.js +3 -0
  238. package/packages/gsd-agent-core/dist/agent-session.js.map +1 -1
  239. package/packages/gsd-agent-core/dist/extension-ui-snapshot.d.ts +41 -0
  240. package/packages/gsd-agent-core/dist/extension-ui-snapshot.d.ts.map +1 -0
  241. package/packages/gsd-agent-core/dist/extension-ui-snapshot.js +62 -0
  242. package/packages/gsd-agent-core/dist/extension-ui-snapshot.js.map +1 -0
  243. package/packages/gsd-agent-core/dist/index.d.ts +2 -0
  244. package/packages/gsd-agent-core/dist/index.d.ts.map +1 -1
  245. package/packages/gsd-agent-core/dist/index.js +2 -0
  246. package/packages/gsd-agent-core/dist/index.js.map +1 -1
  247. package/packages/gsd-agent-core/dist/session/agent-session-events.js +1 -1
  248. package/packages/gsd-agent-core/dist/session/agent-session-events.js.map +1 -1
  249. package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts +1 -0
  250. package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts.map +1 -1
  251. package/packages/gsd-agent-core/dist/session/agent-session-host.js.map +1 -1
  252. package/packages/gsd-agent-core/dist/session/agent-session-prompt.d.ts +5 -0
  253. package/packages/gsd-agent-core/dist/session/agent-session-prompt.d.ts.map +1 -1
  254. package/packages/gsd-agent-core/dist/session/agent-session-prompt.js +60 -3
  255. package/packages/gsd-agent-core/dist/session/agent-session-prompt.js.map +1 -1
  256. package/packages/gsd-agent-core/dist/transcript-store.d.ts +58 -0
  257. package/packages/gsd-agent-core/dist/transcript-store.d.ts.map +1 -0
  258. package/packages/gsd-agent-core/dist/transcript-store.js +132 -0
  259. package/packages/gsd-agent-core/dist/transcript-store.js.map +1 -0
  260. package/packages/gsd-agent-core/package.json +5 -5
  261. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +2 -0
  262. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  263. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +25 -11
  264. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  265. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.d.ts +4 -0
  266. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.d.ts.map +1 -0
  267. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.js +7 -0
  268. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.js.map +1 -0
  269. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts +3 -24
  270. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  271. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +26 -841
  272. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  273. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.d.ts +58 -0
  274. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.d.ts.map +1 -0
  275. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.js +312 -0
  276. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.js.map +1 -0
  277. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.d.ts +31 -0
  278. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.d.ts.map +1 -0
  279. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.js +130 -0
  280. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.js.map +1 -0
  281. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.d.ts +15 -0
  282. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.d.ts.map +1 -0
  283. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js +258 -0
  284. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js.map +1 -0
  285. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.d.ts +13 -0
  286. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.d.ts.map +1 -0
  287. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.js +118 -0
  288. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.js.map +1 -0
  289. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.d.ts +9 -0
  290. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  291. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.js +1 -1
  292. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  293. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.d.ts +54 -0
  294. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.d.ts.map +1 -0
  295. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.js +20 -0
  296. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.js.map +1 -0
  297. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts +4 -1
  298. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  299. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +9 -0
  300. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
  301. package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.d.ts +56 -0
  302. package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.d.ts.map +1 -0
  303. package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.js +44 -0
  304. package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.js.map +1 -0
  305. package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.d.ts +5 -0
  306. package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.d.ts.map +1 -0
  307. package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.js +77 -0
  308. package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.js.map +1 -0
  309. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.d.ts +2 -0
  310. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  311. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js +23 -5
  312. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js.map +1 -1
  313. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  314. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js +18 -0
  315. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js.map +1 -1
  316. package/packages/gsd-agent-modes/package.json +7 -7
  317. package/packages/mcp-server/dist/workflow-tools.js +1 -1
  318. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  319. package/packages/mcp-server/package.json +4 -4
  320. package/packages/native/package.json +1 -1
  321. package/packages/pi-agent-core/dist/agent-loop.js +3 -2
  322. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  323. package/packages/pi-agent-core/package.json +1 -1
  324. package/packages/pi-ai/dist/models.generated.d.ts +46 -0
  325. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  326. package/packages/pi-ai/dist/models.generated.js +38 -0
  327. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  328. package/packages/pi-ai/package.json +1 -1
  329. package/packages/pi-coding-agent/README.md +3 -2
  330. package/packages/pi-coding-agent/dist/core/session-manager-context.d.ts +9 -0
  331. package/packages/pi-coding-agent/dist/core/session-manager-context.d.ts.map +1 -0
  332. package/packages/pi-coding-agent/dist/core/session-manager-context.js +94 -0
  333. package/packages/pi-coding-agent/dist/core/session-manager-context.js.map +1 -0
  334. package/packages/pi-coding-agent/dist/core/session-manager-list.d.ts +8 -0
  335. package/packages/pi-coding-agent/dist/core/session-manager-list.d.ts.map +1 -0
  336. package/packages/pi-coding-agent/dist/core/session-manager-list.js +244 -0
  337. package/packages/pi-coding-agent/dist/core/session-manager-list.js.map +1 -0
  338. package/packages/pi-coding-agent/dist/core/session-manager-migration.d.ts +12 -0
  339. package/packages/pi-coding-agent/dist/core/session-manager-migration.d.ts.map +1 -0
  340. package/packages/pi-coding-agent/dist/core/session-manager-migration.js +84 -0
  341. package/packages/pi-coding-agent/dist/core/session-manager-migration.js.map +1 -0
  342. package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts +135 -0
  343. package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts.map +1 -0
  344. package/packages/pi-coding-agent/dist/core/session-manager-types.js +2 -0
  345. package/packages/pi-coding-agent/dist/core/session-manager-types.js.map +1 -0
  346. package/packages/pi-coding-agent/dist/core/session-manager.d.ts +6 -154
  347. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  348. package/packages/pi-coding-agent/dist/core/session-manager.js +22 -459
  349. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  350. package/packages/pi-coding-agent/dist/theme/theme-schema.d.ts +75 -75
  351. package/packages/pi-coding-agent/dist/theme/theme-schema.d.ts.map +1 -1
  352. package/packages/pi-coding-agent/dist/theme/theme-schema.js +1 -1
  353. package/packages/pi-coding-agent/dist/theme/theme-schema.js.map +1 -1
  354. package/packages/pi-coding-agent/dist/theme/theme.d.ts.map +1 -1
  355. package/packages/pi-coding-agent/dist/theme/theme.js +11 -7
  356. package/packages/pi-coding-agent/dist/theme/theme.js.map +1 -1
  357. package/packages/pi-coding-agent/package.json +7 -7
  358. package/packages/pi-tui/package.json +2 -2
  359. package/packages/rpc-client/package.json +2 -2
  360. package/pkg/dist/theme/theme-schema.d.ts +75 -75
  361. package/pkg/dist/theme/theme-schema.d.ts.map +1 -1
  362. package/pkg/dist/theme/theme-schema.js +1 -1
  363. package/pkg/dist/theme/theme-schema.js.map +1 -1
  364. package/pkg/dist/theme/theme.d.ts.map +1 -1
  365. package/pkg/dist/theme/theme.js +11 -7
  366. package/pkg/dist/theme/theme.js.map +1 -1
  367. package/pkg/package.json +1 -1
  368. package/src/resources/extensions/claude-code-cli/models.ts +9 -0
  369. package/src/resources/extensions/claude-code-cli/tests/index.test.ts +14 -0
  370. package/src/resources/extensions/gsd/artifact-verification.ts +464 -0
  371. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  372. package/src/resources/extensions/gsd/auto/session.ts +14 -0
  373. package/src/resources/extensions/gsd/auto/verification-retry-policy.ts +3 -1
  374. package/src/resources/extensions/gsd/auto-artifact-paths.ts +47 -1
  375. package/src/resources/extensions/gsd/auto-budget.ts +12 -1
  376. package/src/resources/extensions/gsd/auto-dispatch.ts +17 -7
  377. package/src/resources/extensions/gsd/auto-post-unit.ts +1 -1
  378. package/src/resources/extensions/gsd/auto-prompts.ts +7 -23
  379. package/src/resources/extensions/gsd/auto-recovery.ts +13 -518
  380. package/src/resources/extensions/gsd/auto-runtime-state.ts +4 -5
  381. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +3 -2
  382. package/src/resources/extensions/gsd/auto-verification.ts +5 -1
  383. package/src/resources/extensions/gsd/auto.ts +23 -8
  384. package/src/resources/extensions/gsd/bootstrap/core-session-tools.ts +44 -0
  385. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +5 -4
  386. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
  387. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +43 -18
  388. package/src/resources/extensions/gsd/bootstrap/tests/write-gate-basepath.test.ts +49 -1
  389. package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +50 -37
  390. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +16 -4
  391. package/src/resources/extensions/gsd/closeout-consistency-gate.ts +2 -1
  392. package/src/resources/extensions/gsd/commands/handlers/auto.ts +1 -1
  393. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  394. package/src/resources/extensions/gsd/commands-usage.ts +1 -1
  395. package/src/resources/extensions/gsd/db/engine.ts +8 -1
  396. package/src/resources/extensions/gsd/db-memory-fts-schema.ts +45 -0
  397. package/src/resources/extensions/gsd/db-workspace.ts +61 -3
  398. package/src/resources/extensions/gsd/delegation-policy.ts +1 -1
  399. package/src/resources/extensions/gsd/docs/preferences-reference.md +2 -2
  400. package/src/resources/extensions/gsd/doctor-engine-checks.ts +233 -5
  401. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +3 -0
  402. package/src/resources/extensions/gsd/doctor-types.ts +3 -0
  403. package/src/resources/extensions/gsd/exec-sandbox.ts +7 -2
  404. package/src/resources/extensions/gsd/flat-phase-migration.ts +44 -0
  405. package/src/resources/extensions/gsd/git-service.ts +64 -0
  406. package/src/resources/extensions/gsd/gitignore.ts +4 -1
  407. package/src/resources/extensions/gsd/guided-flow-queue.ts +20 -13
  408. package/src/resources/extensions/gsd/json-persistence.ts +20 -0
  409. package/src/resources/extensions/gsd/knowledge-backfill.ts +16 -10
  410. package/src/resources/extensions/gsd/layout-policy.ts +2 -1
  411. package/src/resources/extensions/gsd/markdown-renderer.ts +2 -10
  412. package/src/resources/extensions/gsd/memory-backfill.ts +26 -20
  413. package/src/resources/extensions/gsd/metrics.ts +4 -4
  414. package/src/resources/extensions/gsd/migrate-external.ts +8 -2
  415. package/src/resources/extensions/gsd/migration-auto-check.ts +23 -0
  416. package/src/resources/extensions/gsd/milestone-ids.ts +31 -2
  417. package/src/resources/extensions/gsd/orphan-stash-audit.ts +108 -20
  418. package/src/resources/extensions/gsd/prompts/code-review.md +6 -4
  419. package/src/resources/extensions/gsd/prompts/plan-milestone.md +2 -2
  420. package/src/resources/extensions/gsd/prompts/plan-slice.md +3 -3
  421. package/src/resources/extensions/gsd/root-write-leak-guard.ts +13 -1
  422. package/src/resources/extensions/gsd/safety/destructive-guard.ts +9 -1
  423. package/src/resources/extensions/gsd/safety/evidence-collector.ts +16 -1
  424. package/src/resources/extensions/gsd/safety/file-change-validator.ts +8 -2
  425. package/src/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -1
  426. package/src/resources/extensions/gsd/state/derive/cache.ts +46 -0
  427. package/src/resources/extensions/gsd/state/derive/db-open.ts +45 -0
  428. package/src/resources/extensions/gsd/state/derive/from-db.ts +561 -0
  429. package/src/resources/extensions/gsd/state/derive/index.ts +104 -0
  430. package/src/resources/extensions/gsd/state/derive/interrupted-work.ts +31 -0
  431. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +36 -30
  432. package/src/resources/extensions/gsd/state-reconciliation/index.ts +50 -24
  433. package/src/resources/extensions/gsd/state-reconciliation/registry.ts +43 -28
  434. package/src/resources/extensions/gsd/state.ts +32 -732
  435. package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +4 -3
  436. package/src/resources/extensions/gsd/tests/auto-artifact-paths.test.ts +98 -1
  437. package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +14 -5
  438. package/src/resources/extensions/gsd/tests/auto-pause-double-entry-guard.test.ts +36 -0
  439. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +182 -4
  440. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +35 -0
  441. package/src/resources/extensions/gsd/tests/auto-retry-mcp-churn-fixes.test.ts +30 -0
  442. package/src/resources/extensions/gsd/tests/commands-gsd-core.test.ts +1 -0
  443. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +28 -0
  444. package/src/resources/extensions/gsd/tests/complete-slice-reopen-handoff.test.ts +69 -0
  445. package/src/resources/extensions/gsd/tests/db-memory-fts-schema.test.ts +58 -3
  446. package/src/resources/extensions/gsd/tests/decisions-projection-from-memories.test.ts +51 -0
  447. package/src/resources/extensions/gsd/tests/destructive-guard.test.ts +44 -0
  448. package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +143 -0
  449. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +239 -2
  450. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +16 -0
  451. package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +40 -0
  452. package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +37 -2
  453. package/src/resources/extensions/gsd/tests/gitignore-bg-shell-runtime.test.ts +4 -0
  454. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +11 -2
  455. package/src/resources/extensions/gsd/tests/json-persistence-atomic.test.ts +47 -0
  456. package/src/resources/extensions/gsd/tests/knowledge-backfill-projection.test.ts +35 -0
  457. package/src/resources/extensions/gsd/tests/memory-store.test.ts +48 -0
  458. package/src/resources/extensions/gsd/tests/merge-closeout-consistency-gate.test.ts +31 -1
  459. package/src/resources/extensions/gsd/tests/metrics-prune-cache-invalidation.test.ts +6 -0
  460. package/src/resources/extensions/gsd/tests/metrics.test.ts +1 -0
  461. package/src/resources/extensions/gsd/tests/migrate-external-worktree.test.ts +57 -0
  462. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +70 -0
  463. package/src/resources/extensions/gsd/tests/orphan-stash-audit.test.ts +91 -7
  464. package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +4 -1
  465. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +10 -0
  466. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +103 -4
  467. package/src/resources/extensions/gsd/tests/plan-task.test.ts +44 -1
  468. package/src/resources/extensions/gsd/tests/post-mutation-hook.test.ts +4 -2
  469. package/src/resources/extensions/gsd/tests/progressive-planning.test.ts +50 -14
  470. package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +58 -0
  471. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +8 -6
  472. package/src/resources/extensions/gsd/tests/register-hooks-loop-guard-auto.test.ts +103 -0
  473. package/src/resources/extensions/gsd/tests/root-write-leak-guard.test.ts +20 -1
  474. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +15 -3
  475. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +56 -0
  476. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +115 -0
  477. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +140 -0
  478. package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +6 -3
  479. package/src/resources/extensions/gsd/tests/undo.test.ts +74 -0
  480. package/src/resources/extensions/gsd/tests/uok-gitops-turn-action.test.ts +38 -0
  481. package/src/resources/extensions/gsd/tests/verification-retry-policy.test.ts +69 -4
  482. package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +96 -15
  483. package/src/resources/extensions/gsd/tests/workflow-mcp-preflight.test.ts +1 -0
  484. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +8 -4
  485. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +6 -5
  486. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +39 -0
  487. package/src/resources/extensions/gsd/tools/complete-milestone.ts +3 -2
  488. package/src/resources/extensions/gsd/tools/complete-slice.ts +3 -30
  489. package/src/resources/extensions/gsd/tools/complete-task.ts +7 -39
  490. package/src/resources/extensions/gsd/tools/plan-slice.ts +60 -47
  491. package/src/resources/extensions/gsd/tools/plan-task.ts +30 -5
  492. package/src/resources/extensions/gsd/undo.ts +47 -6
  493. package/src/resources/extensions/gsd/unit-registry.ts +11 -3
  494. package/src/resources/extensions/gsd/verification-gate.ts +8 -0
  495. package/src/resources/extensions/gsd/workflow-manifest.ts +141 -7
  496. package/src/resources/extensions/gsd/workflow-projections.ts +20 -14
  497. package/src/resources/extensions/gsd/worktree-manager.ts +41 -1
  498. package/dist/web/standalone/.next/static/chunks/2772.bfa657f49f955239.js +0 -1
  499. package/dist/web/standalone/.next/static/chunks/796.3976108148518f7d.js +0 -10
  500. package/dist/web/standalone/.next/static/chunks/app/_not-found/page-a6fb1847f67f167c.js +0 -1
  501. package/dist/web/standalone/.next/static/chunks/app/page-6644fc6ee8ca1247.js +0 -1
  502. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-1115d46ac61b9823.js +0 -1
  503. package/dist/web/standalone/.next/static/chunks/webpack-7c1d97e39be2da11.js +0 -1
  504. /package/dist/web/standalone/.next/static/{SzEuqWX37DR9MEpEuQjP1 → zQLcXm0Za8lexoXtgDpMG}/_buildManifest.js +0 -0
  505. /package/dist/web/standalone/.next/static/{SzEuqWX37DR9MEpEuQjP1 → zQLcXm0Za8lexoXtgDpMG}/_ssgManifest.js +0 -0
@@ -0,0 +1,103 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+
4
+ import { autoSession } from "../auto-runtime-state.ts";
5
+ import { registerHooks } from "../bootstrap/register-hooks.ts";
6
+ import { resetToolCallLoopGuard } from "../bootstrap/tool-call-loop-guard.ts";
7
+
8
+ type Handler = (event: any, ctx?: any) => Promise<any> | any;
9
+
10
+ function makeHookHarness(): {
11
+ emitToolCall: (toolName: string, input: Record<string, unknown>) => Promise<any>;
12
+ } {
13
+ const handlers = new Map<string, Handler[]>();
14
+ const pi = {
15
+ on(event: string, handler: Handler) {
16
+ const existing = handlers.get(event) ?? [];
17
+ existing.push(handler);
18
+ handlers.set(event, existing);
19
+ },
20
+ };
21
+ const ctx = {
22
+ cwd: process.cwd(),
23
+ ui: { notify: () => undefined },
24
+ };
25
+ let callId = 0;
26
+
27
+ registerHooks(pi as any, []);
28
+
29
+ return {
30
+ async emitToolCall(toolName: string, input: Record<string, unknown>): Promise<any> {
31
+ callId += 1;
32
+ const loopGuardHandler = handlers.get("tool_call")?.[0];
33
+ assert.ok(loopGuardHandler, "loop-guard tool_call handler should be registered");
34
+ return loopGuardHandler({ toolCallId: `loop-${callId}`, toolName, input }, ctx);
35
+ },
36
+ };
37
+ }
38
+
39
+ test("register-hooks keeps loop-guard block reason interactive outside auto-mode", async (t) => {
40
+ autoSession.reset();
41
+ resetToolCallLoopGuard();
42
+ t.after(() => {
43
+ autoSession.reset();
44
+ resetToolCallLoopGuard();
45
+ });
46
+
47
+ const { emitToolCall } = makeHookHarness();
48
+
49
+ for (let i = 0; i < 4; i += 1) {
50
+ await emitToolCall("web_search", { query: "same query" });
51
+ }
52
+ const block = await emitToolCall("web_search", { query: "same query" });
53
+
54
+ assert.equal(block?.block, true);
55
+ assert.match(block.reason, /respond to the user in text/);
56
+ assert.doesNotMatch(block.reason, /auto-mode recovery\/replan path/);
57
+ });
58
+
59
+ test("register-hooks rewrites loop-guard block reason for auto-mode identical-args blocks", async (t) => {
60
+ autoSession.reset();
61
+ resetToolCallLoopGuard();
62
+ autoSession.active = true;
63
+ t.after(() => {
64
+ autoSession.reset();
65
+ resetToolCallLoopGuard();
66
+ });
67
+
68
+ const { emitToolCall } = makeHookHarness();
69
+
70
+ for (let i = 0; i < 4; i += 1) {
71
+ await emitToolCall("web_search", { query: "same query" });
72
+ }
73
+ const block = await emitToolCall("web_search", { query: "same query" });
74
+
75
+ assert.equal(block?.block, true);
76
+ assert.match(block.reason, /Tool loop detected \(identical args\): web_search/);
77
+ assert.match(block.reason, /Do not re-issue this blocked tool/);
78
+ assert.match(block.reason, /auto-mode recovery\/replan path/);
79
+ assert.doesNotMatch(block.reason, /respond to the user in text/);
80
+ });
81
+
82
+ test("register-hooks rewrites loop-guard block reason for auto-mode per-tool blocks", async (t) => {
83
+ autoSession.reset();
84
+ resetToolCallLoopGuard();
85
+ autoSession.active = true;
86
+ t.after(() => {
87
+ autoSession.reset();
88
+ resetToolCallLoopGuard();
89
+ });
90
+
91
+ const { emitToolCall } = makeHookHarness();
92
+
93
+ for (let i = 0; i < 6; i += 1) {
94
+ await emitToolCall("gsd_complete_milestone", { milestone: `M${i}` });
95
+ }
96
+ const block = await emitToolCall("gsd_complete_milestone", { milestone: "M7" });
97
+
98
+ assert.equal(block?.block, true);
99
+ assert.match(block.reason, /Tool loop detected \(repeated tool\): gsd_complete_milestone/);
100
+ assert.match(block.reason, /Do not re-issue this blocked tool/);
101
+ assert.match(block.reason, /auto-mode recovery\/replan path/);
102
+ assert.doesNotMatch(block.reason, /respond to the user in text/);
103
+ });
@@ -7,7 +7,26 @@ import { closeSync, ftruncateSync, openSync, statSync, writeFileSync } from "nod
7
7
  import { join } from "node:path";
8
8
 
9
9
  import { captureRootDirtySnapshot } from "../root-write-leak-guard.ts";
10
- import { cleanup, git, makeTempRepo } from "./test-utils.ts";
10
+ import { cleanup, createFile, git, makeTempRepo } from "./test-utils.ts";
11
+
12
+ test("captureRootDirtySnapshot ignores unignored GSD root runtime directories", () => {
13
+ const base = makeTempRepo("gsd-root-dirty-runtime-");
14
+ try {
15
+ createFile(base, ".gsd/STATE.md", "state\n");
16
+ createFile(base, ".gsd-backups/snap-001", "backup\n");
17
+ createFile(base, ".gsd-worktrees/M001/file.txt", "worktree\n");
18
+ createFile(base, "src/foo.ts", "export {};\n");
19
+
20
+ const snapshot = captureRootDirtySnapshot(base);
21
+
22
+ assert.equal(snapshot.has(".gsd/STATE.md"), false);
23
+ assert.equal(snapshot.has(".gsd-backups/snap-001"), false);
24
+ assert.equal(snapshot.has(".gsd-worktrees/M001/file.txt"), false);
25
+ assert.equal(snapshot.has("src/foo.ts"), true);
26
+ } finally {
27
+ cleanup(base);
28
+ }
29
+ });
11
30
 
12
31
  test("captureRootDirtySnapshot does not read dirty files larger than Node's Buffer limit", () => {
13
32
  const base = makeTempRepo("gsd-root-dirty-large-");
@@ -177,10 +177,22 @@ test("auto Unit tool scope allows status/read helpers named by closeout prompts"
177
177
  }
178
178
  });
179
179
 
180
- test("auto Unit tool scope blocks stale per-task planner in slice planning phases", () => {
181
- for (const unitType of ["plan-slice", "refine-slice", "replan-slice"]) {
180
+ test("auto Unit tool scope allows reactive-execute status and diagnostic tools", () => {
181
+ for (const toolName of ["gsd_milestone_status", "gsd_exec"]) {
182
+ const result = shouldBlockAutoUnitToolCall("reactive-execute", toolName);
183
+ assert.equal(result.block, false, `reactive-execute should be able to call ${toolName}`);
184
+ }
185
+ });
186
+
187
+ test("auto Unit tool scope allows incremental task planning during plan-slice", () => {
188
+ const result = shouldBlockAutoUnitToolCall("plan-slice", "gsd_plan_task");
189
+ assert.equal(result.block, false, "plan-slice should be able to call gsd_plan_task incrementally");
190
+ });
191
+
192
+ test("auto Unit tool scope still blocks per-task planner outside plan-slice", () => {
193
+ for (const unitType of ["refine-slice", "replan-slice"]) {
182
194
  const result = shouldBlockAutoUnitToolCall(unitType, "gsd_plan_task");
183
- assert.equal(result.block, true, `${unitType} should not call stale gsd_plan_task`);
195
+ assert.equal(result.block, true, `${unitType} should not call gsd_plan_task`);
184
196
  }
185
197
  });
186
198
 
@@ -48,6 +48,16 @@ function makeRecordingPi() {
48
48
  } as any;
49
49
  }
50
50
 
51
+ function makeRecordingCtx() {
52
+ const notifications: Array<{ message: string; level: string }> = [];
53
+ return {
54
+ notifications,
55
+ ui: {
56
+ notify: (message: string, level: string) => { notifications.push({ message, level }); },
57
+ },
58
+ } as any;
59
+ }
60
+
51
61
  // ═══ #1855: empty RecoveryContext (basePath undefined) crashes ════════════════
52
62
 
53
63
  {
@@ -111,6 +121,52 @@ function makeRecordingPi() {
111
121
  }
112
122
  }
113
123
 
124
+ // ═══ plan-slice timeout recovery verifies stale PLAN before advancing ═══════
125
+
126
+ {
127
+ console.log("\n=== plan-slice timeout recovery rejects stale placeholder PLAN ===");
128
+ const base = mkdtempSync(join(tmpdir(), "gsd-timeout-stale-plan-"));
129
+ const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
130
+ mkdirSync(sliceDir, { recursive: true });
131
+
132
+ try {
133
+ openDatabase(join(base, ".gsd", "gsd.db"));
134
+ insertMilestone({ id: "M001", title: "Milestone", status: "active" });
135
+ insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "pending" });
136
+ writeFileSync(
137
+ join(sliceDir, "S01-PLAN.md"),
138
+ "# S01: Slice\n\n## Tasks\n\nPlanning was interrupted before task rows were persisted.\n",
139
+ "utf-8",
140
+ );
141
+
142
+ const ctx = makeRecordingCtx();
143
+ const pi = makeRecordingPi();
144
+ const result = await recoverTimedOutUnit(ctx, pi, "plan-slice", "M001/S01", "idle", {
145
+ basePath: base,
146
+ verbose: false,
147
+ currentUnitStartedAt: Date.now(),
148
+ unitRecoveryCount: new Map(),
149
+ });
150
+
151
+ assert.equal(result, "recovered", "invalid existing plan should enter steering recovery");
152
+ assert.equal(pi.messages.length, 1, "invalid existing plan should trigger steering instead of advancing");
153
+ assert.ok(
154
+ ctx.notifications.some((entry: { message: string }) => entry.message.includes("steering plan-slice M001/S01")),
155
+ "recovery notification should describe steering, not artifact advance",
156
+ );
157
+ assert.ok(
158
+ !ctx.notifications.some((entry: { message: string }) => entry.message.includes("artifact already exists on disk. Advancing.")),
159
+ "stale placeholder plan should not use the advance-on-existence path",
160
+ );
161
+ const runtime = JSON.parse(readFileSync(join(base, ".gsd", "runtime", "units", "plan-slice-M001-S01.json"), "utf-8"));
162
+ assert.equal(runtime.phase, "recovered", "stale placeholder plan must not be finalized");
163
+ assert.equal(runtime.recoveryAttempts, 1, "steering recovery attempt should be recorded");
164
+ } finally {
165
+ closeDatabase();
166
+ rmSync(base, { recursive: true, force: true });
167
+ }
168
+ }
169
+
114
170
  // ═══ #1855: valid RecoveryContext does not crash ═════════════════════════════
115
171
 
116
172
  {
@@ -42,6 +42,7 @@ import {
42
42
  type ReconciliationDeps,
43
43
  } from "../state-reconciliation.ts";
44
44
  import { classifyFailure } from "../recovery-classification.ts";
45
+ import { handlerPhaseIndex, RECONCILIATION_REPAIR_PHASES } from "../state-reconciliation/registry.ts";
45
46
  import { staleRenderHandler } from "../state-reconciliation/drift/stale-render.ts";
46
47
  import type { GSDState } from "../types.ts";
47
48
 
@@ -160,6 +161,29 @@ test("ADR-017 (#5700): repair failure throws ReconciliationFailedError with shap
160
161
  );
161
162
  });
162
163
 
164
+ test("ADR-017 (#5700): custom registry handlers outside built-in phases are repaired", async () => {
165
+ const drift = { kind: "custom-drift", id: "D001" } as unknown as DriftRecord;
166
+ let repaired = false;
167
+ const handler = {
168
+ kind: "custom-drift",
169
+ detect: () => (repaired ? [] : [drift]),
170
+ repair: () => {
171
+ repaired = true;
172
+ },
173
+ } as unknown as DriftHandler;
174
+
175
+ const result = await reconcileBeforeDispatch("/project", {
176
+ invalidateStateCache: () => {},
177
+ deriveState: async () => makeState(),
178
+ registry: [handler],
179
+ });
180
+
181
+ assert.equal(result.ok, true);
182
+ assert.equal(repaired, true);
183
+ assert.equal(result.repaired.length, 1);
184
+ assert.equal(result.repaired[0]?.kind, "custom-drift");
185
+ });
186
+
163
187
  test("ADR-017 (#5700): a detector failure degrades to a blocker without aborting other handlers", async () => {
164
188
  // A single detector throwing (e.g. a transient file read error) must NOT
165
189
  // abort the whole cycle and hide every later handler's drift. It is collected
@@ -772,6 +796,51 @@ test("#1003: stale-render plan repair switches back from an open wrong DB", asyn
772
796
  assert.equal(getSliceTasks("M001", "S01").length, 1, "repair should leave the project DB active");
773
797
  });
774
798
 
799
+ test("#1034: validation-blocked milestone summary drift returns blocker instead of exhausting repair passes", async () => {
800
+ const drift: Extract<DriftRecord, { kind: "stale-render" }> = {
801
+ kind: "stale-render",
802
+ renderPath: "/repo/.gsd/milestones/M001/M001-SUMMARY.md",
803
+ reason: "M001 is complete with summary in DB but SUMMARY.md missing on disk",
804
+ };
805
+ let repairCalled = false;
806
+ const handler: DriftHandler<Extract<DriftRecord, { kind: "stale-render" }>> = {
807
+ kind: "stale-render",
808
+ detect: () => [drift],
809
+ blocker: staleRenderHandler.blocker!,
810
+ repair: () => {
811
+ repairCalled = true;
812
+ },
813
+ };
814
+
815
+ const validationBlocker = [
816
+ "Milestone M001 is blocked because milestone validation returned needs-attention.",
817
+ "Fix options:",
818
+ "1. Review the validation details: `/gsd status`",
819
+ ].join("\n");
820
+
821
+ const result = await reconcileBeforeDispatch("/repo", {
822
+ invalidateStateCache: () => {},
823
+ deriveState: async () =>
824
+ makeState({
825
+ phase: "blocked",
826
+ blockers: [validationBlocker],
827
+ nextAction: "Resolve M001 validation attention before proceeding.",
828
+ }),
829
+ registry: [handler],
830
+ });
831
+
832
+ assert.equal(result.ok, true);
833
+ assert.equal(repairCalled, false, "validation-blocked milestone summary drift should not attempt repair");
834
+ assert.ok(
835
+ result.blockers.some((blocker) => blocker.includes("milestone validation returned needs-attention")),
836
+ "validation blocker should be returned to the caller",
837
+ );
838
+ assert.ok(
839
+ result.blockers.some((blocker) => blocker.includes("Stale milestone summary render")),
840
+ "stale-render blocker should explain why repair did not run",
841
+ );
842
+ });
843
+
775
844
  test("ADR-017 (#5702): stale-render detector reason strings match repair contract", (t) => {
776
845
  t.skip("TODO(flat-phase): stale-render detection temporarily disabled during layout transition"); return;
777
846
  const base = mkdtempSync(join(tmpdir(), "gsd-adr017-render-reasons-"));
@@ -1887,3 +1956,49 @@ test("ADR-017 (#5700): cascading drift triggers second pass within cap", async (
1887
1956
  assert.equal(result.repaired.length, 2, "both passes' repairs collected");
1888
1957
  assert.equal(repaired.length, 2);
1889
1958
  });
1959
+
1960
+ test("deriveState is pure: stale sketch healed only via reconcileBeforeDispatch", async (t) => {
1961
+ const base = makeFixtureBase();
1962
+ t.after(() => cleanup(base));
1963
+
1964
+ openDatabase(join(base, ".gsd", "gsd.db"));
1965
+ insertMilestone({ id: "M001", title: "Test", status: "active" });
1966
+ insertSlice({
1967
+ id: "S02",
1968
+ milestoneId: "M001",
1969
+ title: "Feature",
1970
+ status: "pending",
1971
+ risk: "medium",
1972
+ depends: [],
1973
+ demo: "S02 demo.",
1974
+ sequence: 1,
1975
+ isSketch: true,
1976
+ sketchScope: "limited",
1977
+ });
1978
+ writeFileSync(
1979
+ join(base, ".gsd", "phases", "01-test", "01-02-PLAN.md"),
1980
+ "# S02 Plan\n",
1981
+ );
1982
+
1983
+ const { deriveState } = await import("../state.ts");
1984
+ invalidateStateCache();
1985
+
1986
+ const beforeReconcile = await deriveState(base);
1987
+ assert.equal(beforeReconcile.phase, "refining", "derive alone must not heal stale sketch flag");
1988
+ assert.equal(getSlice("M001", "S02")?.is_sketch, 1, "DB flag unchanged before reconcile");
1989
+
1990
+ const result = await reconcileBeforeDispatch(base);
1991
+ assert.equal(result.ok, true);
1992
+ assert.equal(getSlice("M001", "S02")?.is_sketch, 0, "reconcile clears sketch flag");
1993
+
1994
+ invalidateStateCache();
1995
+ const afterReconcile = await deriveState(base);
1996
+ assert.notEqual(afterReconcile.phase, "refining", "derive after reconcile advances past sketch gate");
1997
+ });
1998
+
1999
+ test("reconciliation repair phases: external edits precede re-project handlers", () => {
2000
+ assert.equal(RECONCILIATION_REPAIR_PHASES.length, 3);
2001
+ assert.ok(handlerPhaseIndex("external-markdown-edit") < handlerPhaseIndex("stale-render"));
2002
+ assert.ok(handlerPhaseIndex("external-planning-edit") < handlerPhaseIndex("roadmap-divergence"));
2003
+ assert.ok(handlerPhaseIndex("stale-sketch-flag") < handlerPhaseIndex("stale-render"));
2004
+ });
@@ -7,6 +7,7 @@ import { test } from 'node:test';
7
7
  import assert from 'node:assert/strict';
8
8
  import {
9
9
  checkToolCallLoop,
10
+ recordToolCallLoopMutation,
10
11
  resetToolCallLoopGuard,
11
12
  disableToolCallLoopGuard,
12
13
  getToolCallLoopCount,
@@ -223,6 +224,62 @@ console.log('\n── Loop guard: repeatable tools get the higher cap (#783) ─
223
224
  assert.ok(blocked.reason!.includes('cap 15'), 'reason should mention the repeatable cap');
224
225
  }
225
226
 
227
+ // ═══════════════════════════════════════════════════════════════════════════
228
+ // Mutation progress decays per-tool counts (#1092)
229
+ // ═══════════════════════════════════════════════════════════════════════════
230
+
231
+ console.log('\n── Loop guard: mutation progress decays per-tool counts (#1092) ──');
232
+
233
+ {
234
+ resetToolCallLoopGuard();
235
+
236
+ // bash is called more than the repeatable cap, but each count is separated
237
+ // by a successful file mutation. Guard 2 should treat that as progress and
238
+ // avoid blocking the progressing unit.
239
+ for (let i = 1; i <= 20; i++) {
240
+ const result = checkToolCallLoop('bash', { command: `echo ${i}` });
241
+ assert.ok(result.block === false, `bash call ${i} after mutation progress should be allowed`);
242
+ assert.deepStrictEqual(getToolCallCountForTool('bash'), 1, `bash count should decay before call ${i}`);
243
+
244
+ const mutationTool = i % 2 === 0 ? 'write' : 'edit';
245
+ const mutation = checkToolCallLoop(mutationTool, {
246
+ path: `file-${i}.ts`,
247
+ content: `content ${i}`,
248
+ });
249
+ assert.ok(mutation.block === false, `${mutationTool} mutation ${i} should be allowed`);
250
+ recordToolCallLoopMutation(mutationTool);
251
+ }
252
+ }
253
+
254
+ // ═══════════════════════════════════════════════════════════════════════════
255
+ // Distinct read-only navigation calls are context gathering, not repeated-tool loops.
256
+ // ═══════════════════════════════════════════════════════════════════════════
257
+
258
+ console.log('\n── Loop guard: distinct read-only navigation calls are not capped by per-tool count ──');
259
+
260
+ {
261
+ const exemptTools = ['find', 'glob', 'grep', 'ls', 'read', 'search_and_read'];
262
+
263
+ for (const toolName of exemptTools) {
264
+ resetToolCallLoopGuard();
265
+
266
+ for (let i = 1; i <= 20; i++) {
267
+ const result = checkToolCallLoop(toolName, { path: `file-${i}.ts` });
268
+ assert.ok(result.block === false, `distinct ${toolName} call ${i} should be allowed`);
269
+ }
270
+
271
+ resetToolCallLoopGuard();
272
+ for (let i = 1; i <= 4; i++) {
273
+ const result = checkToolCallLoop(toolName, { path: 'same-file.ts' });
274
+ assert.ok(result.block === false, `identical ${toolName} call ${i} should be allowed`);
275
+ }
276
+
277
+ const blocked = checkToolCallLoop(toolName, { path: 'same-file.ts' });
278
+ assert.ok(blocked.block === true, `5th identical ${toolName} call should still be blocked`);
279
+ assert.ok(blocked.reason?.includes('identical args'), `${toolName} loops should be caught by Guard 1`);
280
+ }
281
+ }
282
+
226
283
  // ═══════════════════════════════════════════════════════════════════════════
227
284
  // Per-tool counts are independent per tool and reset together
228
285
  // ═══════════════════════════════════════════════════════════════════════════
@@ -245,3 +302,86 @@ console.log('\n── Loop guard: per-tool counts are independent and reset toge
245
302
  }
246
303
 
247
304
  // ═══════════════════════════════════════════════════════════════════════════
305
+ // Non-exempt repeatable tools from core-session-tools.ts get the higher cap.
306
+ // Read-only navigation tools are exempted earlier; mutating/execution repeatable
307
+ // tools (e.g. gsd_exec) must still trip the per-tool cap.
308
+ // ═══════════════════════════════════════════════════════════════════════════
309
+
310
+ console.log('\n── Loop guard: non-exempt repeatable tools get high cap ──');
311
+
312
+ {
313
+ for (const toolName of ['bg_shell', 'gsd_exec', 'write']) {
314
+ resetToolCallLoopGuard();
315
+
316
+ // Should allow up to 15 varied calls without blocking.
317
+ for (let i = 1; i <= 15; i++) {
318
+ const result = checkToolCallLoop(toolName, { arg: `v${i}` });
319
+ assert.ok(result.block === false, `${toolName} call ${i} (varied args) should be allowed`);
320
+ }
321
+ // 16th call with varied args must be blocked by the per-tool repeatable cap.
322
+ const blocked = checkToolCallLoop(toolName, { arg: 'v16' });
323
+ assert.ok(blocked.block === true, `${toolName}: 16th call must be blocked by repeatable cap`);
324
+ assert.ok(blocked.reason?.includes('cap 15'), `${toolName}: reason must mention cap 15`);
325
+ }
326
+ }
327
+
328
+ // ═══════════════════════════════════════════════════════════════════════════
329
+ // Non-repeatable tools excluded from INHERENTLY_REPEATABLE_TOOL_SET use the
330
+ // default cap (6), not the repeatable cap (15).
331
+ // ═══════════════════════════════════════════════════════════════════════════
332
+
333
+ console.log('\n── Loop guard: ToolSearch (excluded from repeatable set) hits default cap ──');
334
+
335
+ {
336
+ // ToolSearch is in MINIMAL_AUTO_BASE but explicitly excluded from repeatable.
337
+ // It should hit the default cap of 6, not 15.
338
+ resetToolCallLoopGuard();
339
+ for (let i = 1; i <= 6; i++) {
340
+ const result = checkToolCallLoop('ToolSearch', { query: `q${i}` });
341
+ assert.ok(result.block === false, `ToolSearch call ${i} should be allowed`);
342
+ }
343
+ const blocked = checkToolCallLoop('ToolSearch', { query: 'q7' });
344
+ assert.ok(blocked.block === true, 'ToolSearch: 7th varied call must be blocked by default cap');
345
+ assert.ok(!blocked.reason?.includes('cap 15'), 'ToolSearch must NOT use the repeatable cap');
346
+ }
347
+
348
+ // ═══════════════════════════════════════════════════════════════════════════
349
+ // Block reasons instruct the model to stop tooling this turn
350
+ // ═══════════════════════════════════════════════════════════════════════════
351
+
352
+ console.log('\n── Loop guard: block reasons tell model to respond in text ──');
353
+
354
+ {
355
+ resetToolCallLoopGuard();
356
+
357
+ for (let i = 0; i < 4; i++) {
358
+ checkToolCallLoop('web_search', { query: 'same query' });
359
+ }
360
+ const identicalBlocked = checkToolCallLoop('web_search', { query: 'same query' });
361
+ assert.ok(identicalBlocked.block === true, 'identical-args guard should block');
362
+ assert.ok(
363
+ identicalBlocked.reason!.includes('respond to the user in text'),
364
+ 'identical-args reason should tell the model to stop tooling',
365
+ );
366
+ assert.ok(
367
+ !identicalBlocked.reason!.includes('Try a different approach'),
368
+ 'identical-args reason should not suggest retrying another tool',
369
+ );
370
+
371
+ resetToolCallLoopGuard();
372
+ for (let i = 0; i < 6; i++) {
373
+ checkToolCallLoop('gsd_complete_milestone', { milestone: `M${i}` });
374
+ }
375
+ const perToolBlocked = checkToolCallLoop('gsd_complete_milestone', { milestone: 'M7' });
376
+ assert.ok(perToolBlocked.block === true, 'per-tool guard should block');
377
+ assert.ok(
378
+ perToolBlocked.reason!.includes('respond to the user in text'),
379
+ 'per-tool reason should tell the model to stop tooling',
380
+ );
381
+ assert.ok(
382
+ perToolBlocked.reason!.includes('Do not retry this tool'),
383
+ 'per-tool reason should forbid retrying the blocked tool',
384
+ );
385
+ }
386
+
387
+ // ═══════════════════════════════════════════════════════════════════════════
@@ -385,14 +385,17 @@ test("gsd_plan_slice — enrichment fields are optional", () => {
385
385
 
386
386
  const required = new Set(getRequiredProps(tool));
387
387
 
388
- // Core fields
389
- const coreRequired = ["milestoneId", "sliceId", "goal", "tasks"];
388
+ // Core fields. `tasks` is intentionally NOT here: incremental planning
389
+ // (#1027) lets gsd_plan_slice persist slice metadata only, then add tasks
390
+ // one at a time via gsd_plan_task, so tasks must be optional.
391
+ const coreRequired = ["milestoneId", "sliceId", "goal"];
390
392
  for (const field of coreRequired) {
391
393
  assert.ok(required.has(field), `core field "${field}" must be required`);
392
394
  }
393
395
 
394
- // Enrichment fields
396
+ // Enrichment fields (plus tasks, optional for incremental planning).
395
397
  const enrichmentFields = [
398
+ "tasks",
396
399
  "successCriteria",
397
400
  "proofLevel",
398
401
  "integrationClosure",
@@ -10,6 +10,7 @@ import {
10
10
  handleUndo,
11
11
  handleUndoTask,
12
12
  handleResetSlice,
13
+ parseActivityLogFilename,
13
14
  uncheckTaskInPlan,
14
15
  } from "../undo.ts";
15
16
  import {
@@ -67,6 +68,37 @@ test("handleUndo without --force only warns and leaves completed units intact",
67
68
  }
68
69
  });
69
70
 
71
+ test("handleUndo execute-task with --force reopens the task row and re-renders plan", async () => {
72
+ const base = makeTempDir("gsd-undo-execute-task");
73
+ try {
74
+ setupTaskFixture(base);
75
+ mkdirSync(join(base, ".gsd", "activity"), { recursive: true });
76
+ writeFileSync(
77
+ join(base, ".gsd", "activity", "001-execute-task-M001-S01-T01.jsonl"),
78
+ "",
79
+ "utf-8",
80
+ );
81
+
82
+ const { notifications, ctx } = makeCtx();
83
+ await handleUndo("--force", ctx, {} as any, base);
84
+
85
+ const task = getTask("M001", "S01", "T01");
86
+ assert.equal(task?.status, "pending");
87
+
88
+ const planContent = readFileSync(
89
+ join(base, ".gsd", "phases", "01-test", "01-01-PLAN.md"),
90
+ "utf-8",
91
+ );
92
+ assert.match(planContent, /\[ \] \*\*T01\*\*:/);
93
+
94
+ assert.equal(notifications[0]?.level, "success");
95
+ assert.match(notifications[0]?.message ?? "", /Unchecked task in PLAN/);
96
+ } finally {
97
+ closeDatabase();
98
+ rmSync(base, { recursive: true, force: true });
99
+ }
100
+ });
101
+
70
102
  test("uncheckTaskInPlan flips a checked task back to unchecked", () => {
71
103
  const base = makeTempDir("gsd-undo-plan");
72
104
  try {
@@ -154,6 +186,48 @@ test("extractCommitShas ignores malformed commit tokens", () => {
154
186
  assert.deepEqual(extractCommitShas(content), ["1234567"]);
155
187
  });
156
188
 
189
+ test("parseActivityLogFilename splits hyphenated unit types from their IDs", () => {
190
+ // Task-level: unit type and ID both contain hyphens.
191
+ assert.deepEqual(parseActivityLogFilename("001-execute-task-M001-S01-T01.jsonl"), {
192
+ unitType: "execute-task",
193
+ unitId: "M001-S01-T01",
194
+ });
195
+ // Variant must win over its shorter prefix ("execute-task").
196
+ assert.deepEqual(parseActivityLogFilename("002-execute-task-simple-M001-S02-T03.jsonl"), {
197
+ unitType: "execute-task-simple",
198
+ unitId: "M001-S02-T03",
199
+ });
200
+ // Milestone- and slice-shaped IDs.
201
+ assert.deepEqual(parseActivityLogFilename("003-research-milestone-M001.jsonl"), {
202
+ unitType: "research-milestone",
203
+ unitId: "M001",
204
+ });
205
+ assert.deepEqual(parseActivityLogFilename("004-plan-slice-M001-S01.jsonl"), {
206
+ unitType: "plan-slice",
207
+ unitId: "M001-S01",
208
+ });
209
+ });
210
+
211
+ test("parseActivityLogFilename accepts non-milestone-shaped unit IDs", () => {
212
+ // Regression (#1057): project-level units use IDs that do not start with
213
+ // "M<digit>". A milestone-shaped regex made /gsd undo bail out with
214
+ // "could not parse latest activity log" when one of these was most recent.
215
+ assert.deepEqual(parseActivityLogFilename("005-discuss-project-PROJECT.jsonl"), {
216
+ unitType: "discuss-project",
217
+ unitId: "PROJECT",
218
+ });
219
+ assert.deepEqual(parseActivityLogFilename("006-workflow-preferences-WORKFLOW-PREFS.jsonl"), {
220
+ unitType: "workflow-preferences",
221
+ unitId: "WORKFLOW-PREFS",
222
+ });
223
+ });
224
+
225
+ test("parseActivityLogFilename returns null for unrecognized names", () => {
226
+ assert.equal(parseActivityLogFilename("007-not-a-real-unit-M001.jsonl"), null);
227
+ assert.equal(parseActivityLogFilename("no-sequence-prefix.jsonl"), null);
228
+ assert.equal(parseActivityLogFilename("001-execute-task-M001-S01-T01.txt"), null);
229
+ });
230
+
157
231
  // ─── handleUndoTask tests ────────────────────────────────────────────────────
158
232
 
159
233
  function makeCtx(): { notifications: Array<{ message: string; level: string }>; ctx: any } {