gsd-pi 2.78.1-dev.b0759e59b → 2.78.1-dev.d8826a445

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 (300) hide show
  1. package/README.md +8 -5
  2. package/dist/headless-recover.d.ts +23 -0
  3. package/dist/headless-recover.js +93 -0
  4. package/dist/headless.js +9 -0
  5. package/dist/help-text.js +1 -0
  6. package/dist/resources/.managed-resources-content-hash +1 -1
  7. package/dist/resources/extensions/browser-tools/tools/intent.js +8 -1
  8. package/dist/resources/extensions/gsd/auto/phases.js +7 -2
  9. package/dist/resources/extensions/gsd/auto/session.js +3 -0
  10. package/dist/resources/extensions/gsd/auto-dispatch.js +7 -58
  11. package/dist/resources/extensions/gsd/auto-post-unit.js +14 -28
  12. package/dist/resources/extensions/gsd/auto-start.js +1 -8
  13. package/dist/resources/extensions/gsd/auto-worktree.js +244 -216
  14. package/dist/resources/extensions/gsd/auto.js +86 -7
  15. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +1 -1
  16. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +9 -77
  17. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +17 -16
  18. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +67 -55
  19. package/dist/resources/extensions/gsd/commands-codebase.js +2 -2
  20. package/dist/resources/extensions/gsd/commands-handlers.js +5 -5
  21. package/dist/resources/extensions/gsd/commands-logs.js +2 -2
  22. package/dist/resources/extensions/gsd/commands-scan.js +2 -2
  23. package/dist/resources/extensions/gsd/commands-ship.js +2 -2
  24. package/dist/resources/extensions/gsd/commands-workflow-templates.js +5 -5
  25. package/dist/resources/extensions/gsd/db-writer.js +106 -95
  26. package/dist/resources/extensions/gsd/delegation-policy.js +155 -0
  27. package/dist/resources/extensions/gsd/dispatch-guard.js +6 -10
  28. package/dist/resources/extensions/gsd/doctor-engine-checks.js +2 -2
  29. package/dist/resources/extensions/gsd/gsd-db.js +268 -8
  30. package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
  31. package/dist/resources/extensions/gsd/guided-flow.js +141 -32
  32. package/dist/resources/extensions/gsd/markdown-renderer.js +14 -51
  33. package/dist/resources/extensions/gsd/metrics.js +287 -1
  34. package/dist/resources/extensions/gsd/parallel-merge.js +14 -13
  35. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +5 -2
  36. package/dist/resources/extensions/gsd/paths.js +114 -9
  37. package/dist/resources/extensions/gsd/prompts/complete-slice.md +4 -4
  38. package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -3
  39. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
  40. package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
  41. package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
  42. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
  43. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +6 -0
  44. package/dist/resources/extensions/gsd/queue-order.js +6 -1
  45. package/dist/resources/extensions/gsd/rethink.js +2 -2
  46. package/dist/resources/extensions/gsd/state.js +91 -372
  47. package/dist/resources/extensions/gsd/templates/project.md +10 -0
  48. package/dist/resources/extensions/gsd/tools/complete-milestone.js +6 -5
  49. package/dist/resources/extensions/gsd/tools/complete-slice.js +7 -12
  50. package/dist/resources/extensions/gsd/tools/complete-task.js +19 -31
  51. package/dist/resources/extensions/gsd/tools/validate-milestone.js +7 -5
  52. package/dist/resources/extensions/gsd/workflow-manifest.js +2 -1
  53. package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +3 -21
  54. package/dist/resources/extensions/gsd/workflow-mcp.js +2 -2
  55. package/dist/resources/extensions/gsd/workflow-reconcile.js +3 -3
  56. package/dist/resources/extensions/gsd/workspace.js +59 -0
  57. package/dist/resources/extensions/gsd/worktree-command.js +4 -3
  58. package/dist/resources/extensions/gsd/worktree-resolver.js +15 -2
  59. package/dist/resources/extensions/gsd/write-intercept.js +3 -3
  60. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  61. package/dist/web/standalone/.next/BUILD_ID +1 -1
  62. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  63. package/dist/web/standalone/.next/build-manifest.json +2 -2
  64. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  65. package/dist/web/standalone/.next/required-server-files.json +1 -1
  66. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  67. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  75. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  83. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  84. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  85. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  86. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  87. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  88. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  89. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  90. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  91. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  92. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  93. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  94. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  95. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  96. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  97. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  98. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  99. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  100. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  101. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  102. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  103. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  104. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  105. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  106. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  107. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  108. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  109. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  110. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  111. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  112. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  113. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  114. package/dist/web/standalone/.next/server/app/index.html +1 -1
  115. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  116. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  117. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  118. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  119. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  120. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  121. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  122. package/dist/web/standalone/.next/server/chunks/6336.js +1 -0
  123. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  124. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  125. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  126. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  127. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  128. package/dist/web/standalone/server.js +1 -1
  129. package/package.json +1 -1
  130. package/packages/mcp-server/README.md +2 -11
  131. package/packages/mcp-server/dist/remote-questions.d.ts +27 -0
  132. package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
  133. package/packages/mcp-server/dist/remote-questions.js +28 -0
  134. package/packages/mcp-server/dist/remote-questions.js.map +1 -1
  135. package/packages/mcp-server/dist/server.d.ts +28 -0
  136. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  137. package/packages/mcp-server/dist/server.js +94 -4
  138. package/packages/mcp-server/dist/server.js.map +1 -1
  139. package/packages/mcp-server/dist/workflow-tools.d.ts +6 -0
  140. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  141. package/packages/mcp-server/dist/workflow-tools.js +56 -2
  142. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  143. package/packages/mcp-server/src/mcp-server.test.ts +226 -0
  144. package/packages/mcp-server/src/parse-workflow-args.test.ts +80 -0
  145. package/packages/mcp-server/src/remote-questions.test.ts +103 -0
  146. package/packages/mcp-server/src/remote-questions.ts +35 -0
  147. package/packages/mcp-server/src/server.ts +129 -6
  148. package/packages/mcp-server/src/workflow-tools.ts +62 -3
  149. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  150. package/src/resources/extensions/browser-tools/tools/intent.ts +13 -2
  151. package/src/resources/extensions/gsd/auto/phases.ts +8 -2
  152. package/src/resources/extensions/gsd/auto/session.ts +4 -0
  153. package/src/resources/extensions/gsd/auto-dispatch.ts +14 -62
  154. package/src/resources/extensions/gsd/auto-post-unit.ts +15 -27
  155. package/src/resources/extensions/gsd/auto-start.ts +1 -8
  156. package/src/resources/extensions/gsd/auto-worktree.ts +286 -251
  157. package/src/resources/extensions/gsd/auto.ts +102 -7
  158. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +1 -1
  159. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +9 -84
  160. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +17 -17
  161. package/src/resources/extensions/gsd/bootstrap/tests/write-gate-basepath.test.ts +103 -0
  162. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +80 -55
  163. package/src/resources/extensions/gsd/commands-codebase.ts +2 -2
  164. package/src/resources/extensions/gsd/commands-handlers.ts +5 -5
  165. package/src/resources/extensions/gsd/commands-logs.ts +2 -2
  166. package/src/resources/extensions/gsd/commands-scan.ts +2 -2
  167. package/src/resources/extensions/gsd/commands-ship.ts +2 -2
  168. package/src/resources/extensions/gsd/commands-workflow-templates.ts +5 -5
  169. package/src/resources/extensions/gsd/db-writer.ts +123 -94
  170. package/src/resources/extensions/gsd/delegation-policy.ts +197 -0
  171. package/src/resources/extensions/gsd/dispatch-guard.ts +6 -11
  172. package/src/resources/extensions/gsd/doctor-engine-checks.ts +2 -2
  173. package/src/resources/extensions/gsd/gsd-db.ts +269 -8
  174. package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
  175. package/src/resources/extensions/gsd/guided-flow.ts +181 -32
  176. package/src/resources/extensions/gsd/markdown-renderer.ts +13 -64
  177. package/src/resources/extensions/gsd/metrics.ts +321 -1
  178. package/src/resources/extensions/gsd/parallel-merge.ts +14 -13
  179. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +5 -2
  180. package/src/resources/extensions/gsd/paths.ts +122 -9
  181. package/src/resources/extensions/gsd/prompts/complete-slice.md +4 -4
  182. package/src/resources/extensions/gsd/prompts/execute-task.md +3 -3
  183. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
  184. package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
  185. package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
  186. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
  187. package/src/resources/extensions/gsd/prompts/plan-milestone.md +6 -0
  188. package/src/resources/extensions/gsd/queue-order.ts +6 -1
  189. package/src/resources/extensions/gsd/rethink.ts +2 -2
  190. package/src/resources/extensions/gsd/state.ts +91 -389
  191. package/src/resources/extensions/gsd/templates/project.md +10 -0
  192. package/src/resources/extensions/gsd/tests/artifact-corruption-2630.test.ts +1 -0
  193. package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +14 -14
  194. package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +6 -0
  195. package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +21 -34
  196. package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +331 -0
  197. package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +176 -0
  198. package/src/resources/extensions/gsd/tests/complete-task-rollback-evidence.test.ts +6 -7
  199. package/src/resources/extensions/gsd/tests/complete-task.test.ts +8 -6
  200. package/src/resources/extensions/gsd/tests/completed-at-reconcile.test.ts +12 -27
  201. package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +18 -5
  202. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +4 -4
  203. package/src/resources/extensions/gsd/tests/db-writer-path-containment.test.ts +152 -0
  204. package/src/resources/extensions/gsd/tests/db-writer-root-artifact.test.ts +221 -0
  205. package/src/resources/extensions/gsd/tests/db-writer-scope.test.ts +230 -0
  206. package/src/resources/extensions/gsd/tests/db-writer.test.ts +14 -16
  207. package/src/resources/extensions/gsd/tests/delegation-policy.test.ts +151 -0
  208. package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +6 -5
  209. package/src/resources/extensions/gsd/tests/derive-state-db-disk-reconcile.test.ts +10 -38
  210. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +136 -56
  211. package/src/resources/extensions/gsd/tests/derive-state-draft.test.ts +3 -0
  212. package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +119 -61
  213. package/src/resources/extensions/gsd/tests/derive-state.test.ts +4 -0
  214. package/src/resources/extensions/gsd/tests/dispatch-backgroundable-annotation.test.ts +55 -0
  215. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +6 -20
  216. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +4 -5
  217. package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +14 -15
  218. package/src/resources/extensions/gsd/tests/draft-promotion.test.ts +3 -23
  219. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +11 -16
  220. package/src/resources/extensions/gsd/tests/escalation.test.ts +2 -1
  221. package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +193 -0
  222. package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +246 -0
  223. package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +218 -0
  224. package/src/resources/extensions/gsd/tests/gsd-db-failed-open-restore.test.ts +117 -0
  225. package/src/resources/extensions/gsd/tests/gsd-db-workspace-scope.test.ts +226 -0
  226. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +2 -1
  227. package/src/resources/extensions/gsd/tests/gsd-root-canonical.test.ts +66 -0
  228. package/src/resources/extensions/gsd/tests/gsd-root-home-guard.test.ts +68 -5
  229. package/src/resources/extensions/gsd/tests/gsdroot-worktree-detection.test.ts +15 -36
  230. package/src/resources/extensions/gsd/tests/guided-flow-prompt-consolidation.test.ts +4 -4
  231. package/src/resources/extensions/gsd/tests/handler-worktree-write-isolation.test.ts +57 -0
  232. package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +15 -15
  233. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +15 -5
  234. package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +371 -0
  235. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +14 -8
  236. package/src/resources/extensions/gsd/tests/md-importer.test.ts +2 -1
  237. package/src/resources/extensions/gsd/tests/memory-store.test.ts +3 -2
  238. package/src/resources/extensions/gsd/tests/metrics-atomic-merge.test.ts +222 -0
  239. package/src/resources/extensions/gsd/tests/metrics-lock-hardening.test.ts +400 -0
  240. package/src/resources/extensions/gsd/tests/metrics-lock-not-acquired.test.ts +141 -0
  241. package/src/resources/extensions/gsd/tests/metrics-lock-retry-sleep.test.ts +287 -0
  242. package/src/resources/extensions/gsd/tests/metrics-prune-cache-invalidation.test.ts +149 -0
  243. package/src/resources/extensions/gsd/tests/metrics-scope.test.ts +378 -0
  244. package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +329 -0
  245. package/src/resources/extensions/gsd/tests/park-milestone.test.ts +2 -0
  246. package/src/resources/extensions/gsd/tests/path-cache-decoupled.test.ts +209 -0
  247. package/src/resources/extensions/gsd/tests/path-normalization-unified.test.ts +175 -0
  248. package/src/resources/extensions/gsd/tests/paths-cache.test.ts +170 -0
  249. package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +120 -0
  250. package/src/resources/extensions/gsd/tests/progressive-planning.test.ts +25 -16
  251. package/src/resources/extensions/gsd/tests/projection-regression.test.ts +1 -0
  252. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +150 -7
  253. package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +184 -0
  254. package/src/resources/extensions/gsd/tests/register-hooks-compaction-checkpoint.test.ts +6 -1
  255. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +28 -16
  256. package/src/resources/extensions/gsd/tests/replan-slice.test.ts +3 -0
  257. package/src/resources/extensions/gsd/tests/resolve-ts.mjs +4 -0
  258. package/src/resources/extensions/gsd/tests/resume-missing-worktree-warning.test.ts +209 -0
  259. package/src/resources/extensions/gsd/tests/rogue-file-detection.test.ts +3 -4
  260. package/src/resources/extensions/gsd/tests/slice-disk-reconcile.test.ts +10 -56
  261. package/src/resources/extensions/gsd/tests/stale-slice-rows.test.ts +15 -16
  262. package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -0
  263. package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +23 -27
  264. package/src/resources/extensions/gsd/tests/steer-worktree-path.test.ts +13 -14
  265. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +4 -3
  266. package/src/resources/extensions/gsd/tests/sync-layer-scope.test.ts +453 -0
  267. package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +10 -33
  268. package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +162 -0
  269. package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +102 -0
  270. package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +186 -0
  271. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +1 -1
  272. package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +7 -8
  273. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +9 -15
  274. package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +239 -0
  275. package/src/resources/extensions/gsd/tests/workflow-logger-wiring.test.ts +12 -7
  276. package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +4 -4
  277. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +26 -3
  278. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +9 -15
  279. package/src/resources/extensions/gsd/tests/workspace.test.ts +190 -0
  280. package/src/resources/extensions/gsd/tests/worktree-db-same-file.test.ts +13 -0
  281. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +65 -71
  282. package/src/resources/extensions/gsd/tests/worktree-sync-tasks.test.ts +26 -151
  283. package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +35 -35
  284. package/src/resources/extensions/gsd/tests/write-gate.test.ts +67 -52
  285. package/src/resources/extensions/gsd/tests/write-intercept.test.ts +1 -1
  286. package/src/resources/extensions/gsd/tools/complete-milestone.ts +7 -5
  287. package/src/resources/extensions/gsd/tools/complete-slice.ts +7 -14
  288. package/src/resources/extensions/gsd/tools/complete-task.ts +19 -34
  289. package/src/resources/extensions/gsd/tools/validate-milestone.ts +7 -5
  290. package/src/resources/extensions/gsd/workflow-manifest.ts +4 -1
  291. package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +2 -18
  292. package/src/resources/extensions/gsd/workflow-mcp.ts +2 -2
  293. package/src/resources/extensions/gsd/workflow-reconcile.ts +3 -3
  294. package/src/resources/extensions/gsd/workspace.ts +95 -0
  295. package/src/resources/extensions/gsd/worktree-command.ts +4 -3
  296. package/src/resources/extensions/gsd/worktree-resolver.ts +16 -2
  297. package/src/resources/extensions/gsd/write-intercept.ts +3 -3
  298. package/dist/web/standalone/.next/server/chunks/8527.js +0 -1
  299. /package/dist/web/standalone/.next/static/{rk1EN3FQTE6Z1yalkW_GE → AT5qi39nKXkdmQIOIoh0f}/_buildManifest.js +0 -0
  300. /package/dist/web/standalone/.next/static/{rk1EN3FQTE6Z1yalkW_GE → AT5qi39nKXkdmQIOIoh0f}/_ssgManifest.js +0 -0
@@ -1,7 +1,7 @@
1
1
  import { describe, test } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
- // ensureDbOpen — Tests that the lazy DB opener creates + migrates the database
4
- // when .gsd/ exists with Markdown content but no gsd.db file.
3
+ // ensureDbOpen — Tests that the lazy DB opener creates/opens the authoritative
4
+ // database without implicitly importing markdown projections.
5
5
  //
6
6
  // This covers the bug where interactive (non-auto) sessions got
7
7
  // "GSD database is not available" because ensureDbOpen only opened
@@ -11,7 +11,7 @@ import * as path from 'node:path';
11
11
  import * as os from 'node:os';
12
12
  import * as fs from 'node:fs';
13
13
  import { createRequire } from 'node:module';
14
- import { closeDatabase, isDbAvailable, getDecisionById, _getAdapter } from '../gsd-db.ts';
14
+ import { closeDatabase, isDbAvailable, getDecisionById, SCHEMA_VERSION, _getAdapter } from '../gsd-db.ts';
15
15
 
16
16
  const _require = createRequire(import.meta.url);
17
17
 
@@ -284,11 +284,11 @@ function createLegacyV15Db(dbPath: string): void {
284
284
  }
285
285
 
286
286
  // ═══════════════════════════════════════════════════════════════════════════
287
- // ensureDbOpen creates DB + migrates when .gsd/ has Markdown
287
+ // ensureDbOpen creates DB without implicit Markdown migration
288
288
  // ═══════════════════════════════════════════════════════════════════════════
289
289
 
290
290
  describe('ensure-db-open', () => {
291
- test('ensureDbOpen: creates DB from Markdown', async () => {
291
+ test('ensureDbOpen: creates empty DB without importing Markdown', async () => {
292
292
  const tmpDir = makeTmpDir();
293
293
  const gsdDir = path.join(tmpDir, '.gsd');
294
294
  fs.mkdirSync(gsdDir, { recursive: true });
@@ -319,17 +319,12 @@ describe('ensure-db-open', () => {
319
319
 
320
320
  const result = await ensureDbOpen();
321
321
 
322
- assert.ok(result === true, 'ensureDbOpen should return true when .gsd/ has Markdown');
322
+ assert.ok(result === true, 'ensureDbOpen should return true when .gsd/ exists');
323
323
  assert.ok(fs.existsSync(dbPath), 'DB file should be created after ensureDbOpen');
324
324
  assert.ok(isDbAvailable(), 'DB should be available after ensureDbOpen');
325
325
 
326
- // Verify that Markdown migration actually ran
327
326
  const decision = getDecisionById('D001');
328
- assert.ok(decision !== null, 'D001 should be migrated from DECISIONS.md');
329
- if (decision) {
330
- assert.deepStrictEqual(decision.scope, 'architecture', 'Migrated decision scope should match');
331
- assert.deepStrictEqual(decision.choice, 'SQLite', 'Migrated decision choice should match');
332
- }
327
+ assert.equal(decision, null, 'D001 should not be imported from DECISIONS.md without explicit migration');
333
328
  } finally {
334
329
  process.cwd = origCwd;
335
330
  closeDatabase();
@@ -360,7 +355,7 @@ describe('ensure-db-open', () => {
360
355
  assert.ok(result === true, 'ensureDbOpen should honor explicit basePath');
361
356
  assert.equal(process.cwd(), originalCwd, 'ensureDbOpen should not mutate process.cwd');
362
357
  assert.ok(isDbAvailable(), 'DB should be available after explicit open');
363
- assert.ok(getDecisionById('D777') !== null, 'explicit basePath DB should be opened');
358
+ assert.equal(getDecisionById('D777'), null, 'explicit basePath should not import DECISIONS.md');
364
359
  } finally {
365
360
  closeDatabase();
366
361
  cleanupDir(tmpDir);
@@ -389,7 +384,7 @@ describe('ensure-db-open', () => {
389
384
  assert.ok(db, 'adapter should be available after ensureDbOpen');
390
385
  assert.equal(
391
386
  db.prepare('SELECT MAX(version) as version FROM schema_version').get()?.version,
392
- 22,
387
+ SCHEMA_VERSION,
393
388
  'legacy DB should migrate to current schema version',
394
389
  );
395
390
 
@@ -519,9 +514,9 @@ describe('ensure-db-open', () => {
519
514
  try {
520
515
  const { ensureDbOpen } = await import('../bootstrap/dynamic-tools.ts');
521
516
  assert.equal(await ensureDbOpen(firstDir), true);
522
- assert.ok(getDecisionById('D101') !== null, 'first DB should be active');
517
+ assert.equal(getDecisionById('D101'), null, 'first DB should not import DECISIONS.md');
523
518
  assert.equal(await ensureDbOpen(secondDir), true);
524
- assert.ok(getDecisionById('D202') !== null, 'second DB should be active after switch');
519
+ assert.equal(getDecisionById('D202'), null, 'second DB should not import DECISIONS.md');
525
520
  assert.equal(getDecisionById('D101'), null, 'first DB should no longer be active after switch');
526
521
  } finally {
527
522
  closeDatabase();
@@ -19,6 +19,7 @@ import {
19
19
  claimEscalationOverride,
20
20
  findUnappliedEscalationOverride,
21
21
  listEscalationArtifacts,
22
+ SCHEMA_VERSION,
22
23
  _getAdapter,
23
24
  } from "../gsd-db.ts";
24
25
  import {
@@ -348,7 +349,7 @@ test("ADR-011 P2: schema v20 fresh DB has all escalation columns on tasks + sour
348
349
  assert.ok(decCols.includes("source"), "decisions table must have source column");
349
350
 
350
351
  const version = adapter.prepare("SELECT MAX(version) as v FROM schema_version").get();
351
- assert.equal(version?.["v"], 22);
352
+ assert.equal(version?.["v"], SCHEMA_VERSION);
352
353
  });
353
354
 
354
355
  test("ADR-011 P2: findUnappliedEscalationOverride returns null when escalation_pending=1 (still pending)", (t) => {
@@ -0,0 +1,193 @@
1
+ /**
2
+ * GSD-2 / guided-flow — regression tests for Gate 1b orphan discrimination
3
+ *
4
+ * Gate 1b in checkAutoStartAfterDiscuss discriminates between two "queued" states:
5
+ * (a) plan-blocked: discuss completed (CONTEXT.md on disk), but gsd_plan_milestone
6
+ * was hard-blocked by the depth-verification gate. DB row stuck at "queued".
7
+ * → emit recovery hint directing the LLM to retry gsd_plan_milestone.
8
+ * (b) discuss-incomplete: discuss did not finish, no CONTEXT.md, DB row "queued".
9
+ * → silent block (no recovery hint).
10
+ */
11
+
12
+ import { describe, test, beforeEach, afterEach } from "node:test";
13
+ import assert from "node:assert/strict";
14
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
15
+ import { join } from "node:path";
16
+ import { tmpdir } from "node:os";
17
+
18
+ import {
19
+ checkAutoStartAfterDiscuss,
20
+ setPendingAutoStart,
21
+ clearPendingAutoStart,
22
+ } from "../guided-flow.ts";
23
+ import { drainLogs } from "../workflow-logger.ts";
24
+ import {
25
+ openDatabase,
26
+ closeDatabase,
27
+ insertMilestone,
28
+ } from "../gsd-db.ts";
29
+
30
+ // ─── Harness ───────────────────────────────────────────────────────────────
31
+
32
+ interface MockCapture {
33
+ notifies: Array<{ msg: string; level: string }>;
34
+ messages: Array<{ payload: any; options: any }>;
35
+ }
36
+
37
+ function mkCapture(): MockCapture {
38
+ return { notifies: [], messages: [] };
39
+ }
40
+
41
+ function mkCtx(cap: MockCapture): any {
42
+ return {
43
+ ui: {
44
+ notify: (msg: string, level: string) => {
45
+ cap.notifies.push({ msg, level });
46
+ },
47
+ },
48
+ };
49
+ }
50
+
51
+ function mkPi(cap: MockCapture): any {
52
+ return {
53
+ sendMessage: (payload: any, options: any) => {
54
+ cap.messages.push({ payload, options });
55
+ },
56
+ setActiveTools: () => undefined,
57
+ getActiveTools: () => [],
58
+ };
59
+ }
60
+
61
+ /**
62
+ * Create a minimal temp tree with a .gsd/milestones/M001 directory.
63
+ */
64
+ function mkBase(): string {
65
+ const base = mkdtempSync(join(tmpdir(), "gsd-gate1b-"));
66
+ mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
67
+ return base;
68
+ }
69
+
70
+ // ─── Tests ─────────────────────────────────────────────────────────────────
71
+
72
+ describe("Gate 1b orphan discrimination in checkAutoStartAfterDiscuss", () => {
73
+ let base: string;
74
+ let cap: MockCapture;
75
+
76
+ beforeEach(() => {
77
+ clearPendingAutoStart();
78
+ drainLogs(); // discard noise from prior tests
79
+ });
80
+
81
+ afterEach(() => {
82
+ closeDatabase();
83
+ clearPendingAutoStart();
84
+ if (base) {
85
+ rmSync(base, { recursive: true, force: true });
86
+ }
87
+ });
88
+
89
+ test("plan-blocked: CONTEXT.md present + DB row queued → returns false + recovery hint emitted", () => {
90
+ base = mkBase();
91
+ openDatabase(":memory:");
92
+
93
+ // DB row exists with status "queued" (plan_milestone was blocked)
94
+ insertMilestone({ id: "M001", title: "Test Milestone", status: "queued" });
95
+
96
+ // CONTEXT.md on disk (discuss phase completed)
97
+ writeFileSync(
98
+ join(base, ".gsd", "milestones", "M001", "M001-CONTEXT.md"),
99
+ "# M001: Test Milestone\n\nContext written by discuss phase.\n",
100
+ );
101
+
102
+ cap = mkCapture();
103
+ setPendingAutoStart(base, {
104
+ basePath: base,
105
+ milestoneId: "M001",
106
+ ctx: mkCtx(cap),
107
+ pi: mkPi(cap),
108
+ });
109
+
110
+ const result = checkAutoStartAfterDiscuss();
111
+
112
+ // Must return false — auto-start should not proceed
113
+ assert.equal(result, false, "checkAutoStartAfterDiscuss must return false (plan still blocked)");
114
+
115
+ // Recovery hint must be sent to the LLM
116
+ assert.equal(
117
+ cap.messages.length,
118
+ 1,
119
+ "exactly one sendMessage call expected for the recovery hint",
120
+ );
121
+ assert.equal(
122
+ cap.messages[0].payload.customType,
123
+ "gsd-plan-milestone-blocked-recovery",
124
+ "recovery message must have customType gsd-plan-milestone-blocked-recovery",
125
+ );
126
+ assert.equal(
127
+ cap.messages[0].options.triggerTurn,
128
+ true,
129
+ "recovery message must set triggerTurn: true",
130
+ );
131
+ assert.match(
132
+ cap.messages[0].payload.content,
133
+ /gsd_plan_milestone/,
134
+ "recovery message content must mention gsd_plan_milestone",
135
+ );
136
+
137
+ // User must be notified via ctx.ui.notify
138
+ assert.ok(
139
+ cap.notifies.some((n) => n.level === "warning" && /queued/.test(n.msg)),
140
+ "user must be notified with a warning about the queued state",
141
+ );
142
+
143
+ // logWarning must have recorded the Gate 1b event
144
+ const logs = drainLogs();
145
+ const gate1bLog = logs.find(
146
+ (e) => e.component === "guided" && /Gate 1b/.test(e.message),
147
+ );
148
+ assert.ok(gate1bLog, "Gate 1b warning must be logged via logWarning");
149
+ });
150
+
151
+ test("discuss-incomplete: no CONTEXT.md + DB row queued → returns false silently (no recovery hint)", () => {
152
+ base = mkBase();
153
+ openDatabase(":memory:");
154
+
155
+ // DB row exists with status "queued", but NO CONTEXT.md on disk
156
+ insertMilestone({ id: "M001", title: "Test Milestone", status: "queued" });
157
+
158
+ // No CONTEXT.md written — discuss phase is incomplete
159
+ cap = mkCapture();
160
+ setPendingAutoStart(base, {
161
+ basePath: base,
162
+ milestoneId: "M001",
163
+ ctx: mkCtx(cap),
164
+ pi: mkPi(cap),
165
+ });
166
+
167
+ drainLogs(); // clear any noise before the call
168
+
169
+ const result = checkAutoStartAfterDiscuss();
170
+
171
+ // Must return false — silent block
172
+ assert.equal(result, false, "checkAutoStartAfterDiscuss must return false when discuss is incomplete");
173
+
174
+ // No recovery hint — Gate 1 blocks before Gate 1b is reached
175
+ assert.equal(
176
+ cap.messages.length,
177
+ 0,
178
+ "no sendMessage calls expected when CONTEXT.md is absent",
179
+ );
180
+ assert.equal(
181
+ cap.notifies.length,
182
+ 0,
183
+ "no user notifications expected for discuss-incomplete case",
184
+ );
185
+
186
+ // No Gate 1b log entry
187
+ const logs = drainLogs();
188
+ const gate1bLog = logs.find(
189
+ (e) => e.component === "guided" && /Gate 1b/.test(e.message),
190
+ );
191
+ assert.equal(gate1bLog, undefined, "Gate 1b must not log when CONTEXT.md is absent");
192
+ });
193
+ });
@@ -0,0 +1,246 @@
1
+ // GSD-2 + Gate 1b recovery bound corrections — regression tests for the two bugs
2
+ // found in peer review of the H1 fix (commit f0e1d42a2):
3
+ // 1. Escalation message must describe /gsd (counter reset) AND /gsd-debug (diagnose).
4
+ // 2. planBlockedRecoveryCount must NOT increment when pi.sendMessage throws.
5
+
6
+ import { describe, test, beforeEach, afterEach } from "node:test";
7
+ import assert from "node:assert/strict";
8
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { tmpdir } from "node:os";
11
+
12
+ import {
13
+ checkAutoStartAfterDiscuss,
14
+ setPendingAutoStart,
15
+ clearPendingAutoStart,
16
+ _getPendingAutoStart,
17
+ } from "../guided-flow.ts";
18
+ import { drainLogs } from "../workflow-logger.ts";
19
+ import {
20
+ openDatabase,
21
+ closeDatabase,
22
+ insertMilestone,
23
+ } from "../gsd-db.ts";
24
+
25
+ // ─── Harness ───────────────────────────────────────────────────────────────
26
+
27
+ interface MockCapture {
28
+ notifies: Array<{ msg: string; level: string }>;
29
+ messages: Array<{ payload: any; options: any }>;
30
+ }
31
+
32
+ function mkCapture(): MockCapture {
33
+ return { notifies: [], messages: [] };
34
+ }
35
+
36
+ function mkCtx(cap: MockCapture): any {
37
+ return {
38
+ ui: {
39
+ notify: (msg: string, level: string) => {
40
+ cap.notifies.push({ msg, level });
41
+ },
42
+ },
43
+ };
44
+ }
45
+
46
+ /** Returns a pi stub whose sendMessage throws on the first call, succeeds after. */
47
+ function mkPiThrowOnce(cap: MockCapture): any {
48
+ let callCount = 0;
49
+ return {
50
+ sendMessage: (payload: any, options: any) => {
51
+ callCount += 1;
52
+ if (callCount === 1) {
53
+ throw new Error("transient network error");
54
+ }
55
+ cap.messages.push({ payload, options });
56
+ },
57
+ setActiveTools: () => undefined,
58
+ getActiveTools: () => [],
59
+ };
60
+ }
61
+
62
+ function mkPi(cap: MockCapture): any {
63
+ return {
64
+ sendMessage: (payload: any, options: any) => {
65
+ cap.messages.push({ payload, options });
66
+ },
67
+ setActiveTools: () => undefined,
68
+ getActiveTools: () => [],
69
+ };
70
+ }
71
+
72
+ function mkBase(): string {
73
+ const base = mkdtempSync(join(tmpdir(), "gsd-gate1b-corrections-"));
74
+ mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
75
+ writeFileSync(
76
+ join(base, ".gsd", "milestones", "M001", "M001-CONTEXT.md"),
77
+ "# M001: Corrections Test\n\nContext written by discuss phase.\n",
78
+ );
79
+ return base;
80
+ }
81
+
82
+ // ─── Tests ─────────────────────────────────────────────────────────────────
83
+
84
+ describe("Gate 1b recovery bound corrections", () => {
85
+ let base: string;
86
+ let cap: MockCapture;
87
+
88
+ beforeEach(() => {
89
+ clearPendingAutoStart();
90
+ drainLogs();
91
+ });
92
+
93
+ afterEach(() => {
94
+ closeDatabase();
95
+ clearPendingAutoStart();
96
+ if (base) {
97
+ rmSync(base, { recursive: true, force: true });
98
+ }
99
+ });
100
+
101
+ // ── Fix 1: escalation message ──────────────────────────────────────────
102
+
103
+ test("escalation message describes /gsd for reset AND /gsd-debug for diagnosis", () => {
104
+ base = mkBase();
105
+ openDatabase(":memory:");
106
+ insertMilestone({ id: "M001", title: "Corrections Test", status: "queued" });
107
+
108
+ cap = mkCapture();
109
+ setPendingAutoStart(base, {
110
+ basePath: base,
111
+ milestoneId: "M001",
112
+ ctx: mkCtx(cap),
113
+ pi: mkPi(cap),
114
+ });
115
+
116
+ // Exhaust the recovery budget (MAX = 3)
117
+ checkAutoStartAfterDiscuss(); // count → 1
118
+ checkAutoStartAfterDiscuss(); // count → 2
119
+ checkAutoStartAfterDiscuss(); // count → 3
120
+
121
+ cap.notifies = [];
122
+ drainLogs();
123
+
124
+ // This call hits the cap and must escalate
125
+ const result = checkAutoStartAfterDiscuss();
126
+ assert.equal(result, false, "escalation call must return false");
127
+
128
+ const errorNotify = cap.notifies.find((n) => n.level === "error");
129
+ assert.ok(errorNotify, "escalation must emit a notify with level 'error'");
130
+
131
+ // Must mention /gsd with reset semantics
132
+ assert.match(
133
+ errorNotify.msg,
134
+ /\/gsd\b/,
135
+ "escalation message must reference /gsd (the command that resets the counter)",
136
+ );
137
+ assert.match(
138
+ errorNotify.msg,
139
+ /reset/i,
140
+ "escalation message must use the word 'reset' so users know /gsd resets the counter",
141
+ );
142
+
143
+ // Must also mention /gsd-debug
144
+ assert.match(
145
+ errorNotify.msg,
146
+ /\/gsd-debug/i,
147
+ "escalation message must also reference /gsd-debug for diagnosis",
148
+ );
149
+
150
+ // Must NOT suggest /gsd-debug alone as the sole remediation
151
+ assert.doesNotMatch(
152
+ errorNotify.msg,
153
+ /^[^/]*\/gsd-debug[^/]*$/,
154
+ "escalation message must not mention /gsd-debug as the only option",
155
+ );
156
+ });
157
+
158
+ // ── Fix 2: counter ordering ────────────────────────────────────────────
159
+
160
+ test("counter stays at 0 when sendMessage throws on the first call", () => {
161
+ base = mkBase();
162
+ openDatabase(":memory:");
163
+ insertMilestone({ id: "M001", title: "Corrections Test", status: "queued" });
164
+
165
+ cap = mkCapture();
166
+ setPendingAutoStart(base, {
167
+ basePath: base,
168
+ milestoneId: "M001",
169
+ ctx: mkCtx(cap),
170
+ pi: mkPiThrowOnce(cap),
171
+ });
172
+
173
+ // First call: sendMessage throws — counter must NOT increment
174
+ const result = checkAutoStartAfterDiscuss();
175
+ assert.equal(result, false, "must return false even when sendMessage throws");
176
+
177
+ const entry = _getPendingAutoStart(base);
178
+ assert.ok(entry, "entry must still exist after a failed sendMessage");
179
+ assert.equal(
180
+ entry.planBlockedRecoveryCount,
181
+ 0,
182
+ "counter must remain 0 when sendMessage throws — no budget burned by transient failure",
183
+ );
184
+ });
185
+
186
+ test("counter increments to 1 on the second call when first sendMessage threw", () => {
187
+ base = mkBase();
188
+ openDatabase(":memory:");
189
+ insertMilestone({ id: "M001", title: "Corrections Test", status: "queued" });
190
+
191
+ cap = mkCapture();
192
+ setPendingAutoStart(base, {
193
+ basePath: base,
194
+ milestoneId: "M001",
195
+ ctx: mkCtx(cap),
196
+ pi: mkPiThrowOnce(cap),
197
+ });
198
+
199
+ checkAutoStartAfterDiscuss(); // sendMessage throws → count stays 0
200
+
201
+ const entryAfterThrow = _getPendingAutoStart(base);
202
+ assert.equal(entryAfterThrow!.planBlockedRecoveryCount, 0, "count is 0 after throw");
203
+
204
+ checkAutoStartAfterDiscuss(); // sendMessage succeeds → count becomes 1
205
+ assert.equal(cap.messages.length, 1, "second call must produce one successful sendMessage");
206
+
207
+ const entryAfterSuccess = _getPendingAutoStart(base);
208
+ assert.equal(
209
+ entryAfterSuccess!.planBlockedRecoveryCount,
210
+ 1,
211
+ "counter must be 1 after first successful dispatch",
212
+ );
213
+ });
214
+
215
+ test("3 successful sendMessage calls exhaust the budget; 4th emits escalation notify", () => {
216
+ base = mkBase();
217
+ openDatabase(":memory:");
218
+ insertMilestone({ id: "M001", title: "Corrections Test", status: "queued" });
219
+
220
+ cap = mkCapture();
221
+ setPendingAutoStart(base, {
222
+ basePath: base,
223
+ milestoneId: "M001",
224
+ ctx: mkCtx(cap),
225
+ pi: mkPi(cap),
226
+ });
227
+
228
+ // Three successful recoveries
229
+ checkAutoStartAfterDiscuss(); // count → 1
230
+ checkAutoStartAfterDiscuss(); // count → 2
231
+ checkAutoStartAfterDiscuss(); // count → 3
232
+
233
+ const entry = _getPendingAutoStart(base);
234
+ assert.equal(entry!.planBlockedRecoveryCount, 3, "counter must be 3 after three successes");
235
+ assert.equal(cap.messages.length, 3, "three sendMessage calls must have occurred");
236
+
237
+ // Fourth call hits the cap
238
+ cap.notifies = [];
239
+ cap.messages = [];
240
+ const resultAtCap = checkAutoStartAfterDiscuss();
241
+ assert.equal(resultAtCap, false, "4th call must return false");
242
+ assert.equal(cap.messages.length, 0, "4th call must NOT call sendMessage");
243
+ const errorNotify = cap.notifies.find((n) => n.level === "error");
244
+ assert.ok(errorNotify, "4th call must emit escalation notify with level 'error'");
245
+ });
246
+ });