gsd-pi 2.27.0 → 2.28.0-dev.346ee62

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 (1237) hide show
  1. package/README.md +39 -12
  2. package/dist/cli.js +20 -21
  3. package/dist/extension-discovery.d.ts +16 -0
  4. package/dist/extension-discovery.js +66 -0
  5. package/dist/headless-answers.d.ts +51 -0
  6. package/dist/headless-answers.js +224 -0
  7. package/dist/headless-context.d.ts +18 -0
  8. package/dist/headless-context.js +44 -0
  9. package/dist/headless-events.d.ts +28 -0
  10. package/dist/headless-events.js +59 -0
  11. package/dist/headless-query.d.ts +40 -0
  12. package/dist/headless-query.js +77 -0
  13. package/dist/headless-ui.d.ts +23 -0
  14. package/dist/headless-ui.js +103 -0
  15. package/dist/headless.d.ts +2 -0
  16. package/dist/headless.js +67 -186
  17. package/dist/help-text.js +6 -0
  18. package/dist/loader.js +18 -59
  19. package/dist/onboarding.js +8 -7
  20. package/dist/remote-questions-config.js +8 -3
  21. package/dist/resource-loader.d.ts +1 -6
  22. package/dist/resource-loader.js +52 -95
  23. package/dist/resources/extensions/ask-user-questions.ts +3 -2
  24. package/dist/resources/extensions/bg-shell/bg-shell-command.ts +219 -0
  25. package/dist/resources/extensions/bg-shell/bg-shell-lifecycle.ts +400 -0
  26. package/dist/resources/extensions/bg-shell/bg-shell-tool.ts +985 -0
  27. package/dist/resources/extensions/bg-shell/index.ts +17 -1561
  28. package/dist/resources/extensions/bg-shell/overlay.ts +4 -0
  29. package/dist/resources/extensions/bg-shell/utilities.ts +4 -16
  30. package/dist/resources/extensions/browser-tools/capture.ts +34 -2
  31. package/dist/resources/extensions/browser-tools/lifecycle.ts +5 -5
  32. package/dist/resources/extensions/browser-tools/settle.ts +1 -1
  33. package/dist/resources/extensions/browser-tools/state.ts +5 -5
  34. package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +1 -1
  35. package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +3 -3
  36. package/dist/resources/extensions/browser-tools/tools/assertions.ts +1 -1
  37. package/dist/resources/extensions/browser-tools/tools/device.ts +1 -1
  38. package/dist/resources/extensions/browser-tools/tools/extract.ts +1 -1
  39. package/dist/resources/extensions/browser-tools/tools/navigation.ts +6 -6
  40. package/dist/resources/extensions/browser-tools/tools/network-mock.ts +1 -1
  41. package/dist/resources/extensions/browser-tools/tools/pages.ts +1 -1
  42. package/dist/resources/extensions/browser-tools/tools/screenshot.ts +28 -10
  43. package/dist/resources/extensions/browser-tools/tools/state-persistence.ts +1 -1
  44. package/dist/resources/extensions/browser-tools/tools/visual-diff.ts +1 -1
  45. package/dist/resources/extensions/browser-tools/utils.ts +5 -5
  46. package/dist/resources/extensions/get-secrets-from-user.ts +1 -1
  47. package/dist/resources/extensions/google-search/index.ts +21 -8
  48. package/dist/resources/extensions/gsd/activity-log.ts +2 -1
  49. package/dist/resources/extensions/gsd/atomic-write.ts +35 -0
  50. package/dist/resources/extensions/gsd/auto/session.ts +12 -0
  51. package/dist/resources/extensions/gsd/auto-dashboard.ts +84 -3
  52. package/dist/resources/extensions/gsd/auto-idempotency.ts +150 -0
  53. package/dist/resources/extensions/gsd/auto-post-unit.ts +591 -0
  54. package/dist/resources/extensions/gsd/auto-prompts.ts +116 -22
  55. package/dist/resources/extensions/gsd/auto-recovery.ts +20 -9
  56. package/dist/resources/extensions/gsd/auto-start.ts +500 -0
  57. package/dist/resources/extensions/gsd/auto-stuck-detection.ts +220 -0
  58. package/dist/resources/extensions/gsd/auto-timers.ts +223 -0
  59. package/dist/resources/extensions/gsd/auto-unit-closeout.ts +3 -1
  60. package/dist/resources/extensions/gsd/auto-verification.ts +195 -0
  61. package/dist/resources/extensions/gsd/auto-worktree-sync.ts +8 -31
  62. package/dist/resources/extensions/gsd/auto-worktree.ts +83 -67
  63. package/dist/resources/extensions/gsd/auto.ts +364 -1873
  64. package/dist/resources/extensions/gsd/commands-config.ts +102 -0
  65. package/dist/resources/extensions/gsd/commands-handlers.ts +402 -0
  66. package/dist/resources/extensions/gsd/commands-inspect.ts +90 -0
  67. package/dist/resources/extensions/gsd/commands-logs.ts +537 -0
  68. package/dist/resources/extensions/gsd/commands-maintenance.ts +206 -0
  69. package/dist/resources/extensions/gsd/commands-prefs-wizard.ts +747 -0
  70. package/dist/resources/extensions/gsd/commands.ts +378 -1431
  71. package/dist/resources/extensions/gsd/constants.ts +21 -0
  72. package/dist/resources/extensions/gsd/context-budget.ts +25 -2
  73. package/dist/resources/extensions/gsd/crash-recovery.ts +4 -2
  74. package/dist/resources/extensions/gsd/dashboard-overlay.ts +13 -4
  75. package/dist/resources/extensions/gsd/db-writer.ts +21 -2
  76. package/dist/resources/extensions/gsd/detection.ts +469 -0
  77. package/dist/resources/extensions/gsd/diff-context.ts +2 -1
  78. package/dist/resources/extensions/gsd/dispatch-guard.ts +4 -0
  79. package/dist/resources/extensions/gsd/docs/preferences-reference.md +83 -0
  80. package/dist/resources/extensions/gsd/doctor-checks.ts +564 -0
  81. package/dist/resources/extensions/gsd/doctor-format.ts +78 -0
  82. package/dist/resources/extensions/gsd/doctor-types.ts +72 -0
  83. package/dist/resources/extensions/gsd/doctor.ts +64 -701
  84. package/dist/resources/extensions/gsd/errors.ts +0 -2
  85. package/dist/resources/extensions/gsd/export-html.ts +367 -11
  86. package/dist/resources/extensions/gsd/export.ts +119 -30
  87. package/dist/resources/extensions/gsd/files.ts +8 -126
  88. package/dist/resources/extensions/gsd/forensics.ts +2 -12
  89. package/dist/resources/extensions/gsd/git-constants.ts +11 -0
  90. package/dist/resources/extensions/gsd/git-service.ts +13 -9
  91. package/dist/resources/extensions/gsd/gsd-db.ts +26 -6
  92. package/dist/resources/extensions/gsd/guided-flow-queue.ts +451 -0
  93. package/dist/resources/extensions/gsd/guided-flow.ts +231 -514
  94. package/dist/resources/extensions/gsd/history.ts +2 -20
  95. package/dist/resources/extensions/gsd/index.ts +206 -45
  96. package/dist/resources/extensions/gsd/init-wizard.ts +615 -0
  97. package/dist/resources/extensions/gsd/jsonl-utils.ts +21 -0
  98. package/dist/resources/extensions/gsd/key-manager.ts +995 -0
  99. package/dist/resources/extensions/gsd/metrics.ts +32 -5
  100. package/dist/resources/extensions/gsd/migrate/command.ts +1 -1
  101. package/dist/resources/extensions/gsd/migrate/parsers.ts +10 -95
  102. package/dist/resources/extensions/gsd/milestone-actions.ts +126 -0
  103. package/dist/resources/extensions/gsd/milestone-ids.ts +95 -0
  104. package/dist/resources/extensions/gsd/native-git-bridge.ts +5 -10
  105. package/dist/resources/extensions/gsd/parallel-eligibility.ts +3 -3
  106. package/dist/resources/extensions/gsd/paths.ts +1 -3
  107. package/dist/resources/extensions/gsd/plugin-importer.ts +3 -2
  108. package/dist/resources/extensions/gsd/preferences-hooks.ts +10 -0
  109. package/dist/resources/extensions/gsd/preferences-models.ts +323 -0
  110. package/dist/resources/extensions/gsd/preferences-skills.ts +169 -0
  111. package/dist/resources/extensions/gsd/preferences-types.ts +223 -0
  112. package/dist/resources/extensions/gsd/preferences-validation.ts +597 -0
  113. package/dist/resources/extensions/gsd/preferences.ts +219 -1305
  114. package/dist/resources/extensions/gsd/prompt-cache-optimizer.ts +213 -0
  115. package/dist/resources/extensions/gsd/prompt-compressor.ts +508 -0
  116. package/dist/resources/extensions/gsd/prompt-loader.ts +4 -2
  117. package/dist/resources/extensions/gsd/prompt-ordering.ts +200 -0
  118. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -2
  119. package/dist/resources/extensions/gsd/prompts/complete-slice.md +4 -4
  120. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +2 -4
  121. package/dist/resources/extensions/gsd/prompts/discuss.md +13 -5
  122. package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -2
  123. package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  124. package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +0 -1
  125. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -2
  126. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -2
  127. package/dist/resources/extensions/gsd/prompts/queue.md +30 -0
  128. package/dist/resources/extensions/gsd/prompts/quick-task.md +0 -6
  129. package/dist/resources/extensions/gsd/prompts/replan-slice.md +0 -1
  130. package/dist/resources/extensions/gsd/prompts/rewrite-docs.md +0 -1
  131. package/dist/resources/extensions/gsd/prompts/system.md +5 -2
  132. package/dist/resources/extensions/gsd/provider-error-pause.ts +59 -10
  133. package/dist/resources/extensions/gsd/queue-order.ts +1 -1
  134. package/dist/resources/extensions/gsd/queue-reorder-ui.ts +15 -2
  135. package/dist/resources/extensions/gsd/quick.ts +18 -15
  136. package/dist/resources/extensions/gsd/reports.ts +1 -7
  137. package/dist/resources/extensions/gsd/safe-fs.ts +47 -0
  138. package/dist/resources/extensions/gsd/semantic-chunker.ts +336 -0
  139. package/dist/resources/extensions/gsd/session-forensics.ts +8 -23
  140. package/dist/resources/extensions/gsd/session-lock.ts +284 -0
  141. package/dist/resources/extensions/gsd/skills/gsd-headless/SKILL.md +80 -16
  142. package/dist/resources/extensions/gsd/skills/gsd-headless/references/answer-injection.md +35 -6
  143. package/dist/resources/extensions/gsd/skills/gsd-headless/references/commands.md +7 -2
  144. package/dist/resources/extensions/gsd/skills/gsd-headless/references/multi-session.md +4 -13
  145. package/dist/resources/extensions/gsd/state.ts +54 -2
  146. package/dist/resources/extensions/gsd/structured-data-formatter.ts +144 -0
  147. package/dist/resources/extensions/gsd/summary-distiller.ts +258 -0
  148. package/dist/resources/extensions/gsd/templates/preferences.md +34 -0
  149. package/dist/resources/extensions/gsd/tests/activity-log.test.ts +213 -0
  150. package/dist/resources/extensions/gsd/tests/agent-end-retry.test.ts +107 -0
  151. package/dist/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +197 -0
  152. package/dist/resources/extensions/gsd/tests/auto-preflight.test.ts +33 -39
  153. package/dist/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +108 -2
  154. package/dist/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +257 -0
  155. package/dist/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +3 -0
  156. package/dist/resources/extensions/gsd/tests/commands-logs.test.ts +241 -0
  157. package/dist/resources/extensions/gsd/tests/context-budget.test.ts +69 -0
  158. package/dist/resources/extensions/gsd/tests/detection.test.ts +398 -0
  159. package/dist/resources/extensions/gsd/tests/discuss-prompt.test.ts +12 -24
  160. package/dist/resources/extensions/gsd/tests/dispatch-guard.test.ts +118 -94
  161. package/dist/resources/extensions/gsd/tests/dispatch-stall-guard.test.ts +126 -0
  162. package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +7 -3
  163. package/dist/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +75 -0
  164. package/dist/resources/extensions/gsd/tests/doctor-git.test.ts +17 -55
  165. package/dist/resources/extensions/gsd/tests/export-html-all.test.ts +105 -0
  166. package/dist/resources/extensions/gsd/tests/export-html-enhancements.test.ts +375 -0
  167. package/dist/resources/extensions/gsd/tests/extension-selector-separator.test.ts +122 -0
  168. package/dist/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +3 -0
  169. package/dist/resources/extensions/gsd/tests/headless-answers.test.ts +340 -0
  170. package/dist/resources/extensions/gsd/tests/headless-query.test.ts +162 -0
  171. package/dist/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +24 -82
  172. package/dist/resources/extensions/gsd/tests/init-wizard.test.ts +197 -0
  173. package/dist/resources/extensions/gsd/tests/key-manager.test.ts +414 -0
  174. package/dist/resources/extensions/gsd/tests/metrics.test.ts +173 -305
  175. package/dist/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +3 -0
  176. package/dist/resources/extensions/gsd/tests/model-isolation.test.ts +59 -1
  177. package/dist/resources/extensions/gsd/tests/next-milestone-id.test.ts +18 -61
  178. package/dist/resources/extensions/gsd/tests/none-mode-gates.test.ts +17 -8
  179. package/dist/resources/extensions/gsd/tests/parallel-merge.test.ts +3 -0
  180. package/dist/resources/extensions/gsd/tests/park-edge-cases.test.ts +276 -0
  181. package/dist/resources/extensions/gsd/tests/park-milestone.test.ts +401 -0
  182. package/dist/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +23 -47
  183. package/dist/resources/extensions/gsd/tests/preferences.test.ts +284 -0
  184. package/dist/resources/extensions/gsd/tests/prompt-cache-optimizer.test.ts +314 -0
  185. package/dist/resources/extensions/gsd/tests/prompt-compressor.test.ts +529 -0
  186. package/dist/resources/extensions/gsd/tests/prompt-ordering.test.ts +296 -0
  187. package/dist/resources/extensions/gsd/tests/provider-errors.test.ts +338 -0
  188. package/dist/resources/extensions/gsd/tests/reassess-detection.test.ts +154 -0
  189. package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +1 -1
  190. package/dist/resources/extensions/gsd/tests/remote-status.test.ts +2 -2
  191. package/dist/resources/extensions/gsd/tests/roadmap-slices.test.ts +43 -60
  192. package/dist/resources/extensions/gsd/tests/semantic-chunker.test.ts +426 -0
  193. package/dist/resources/extensions/gsd/tests/session-lock.test.ts +315 -0
  194. package/dist/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +3 -0
  195. package/dist/resources/extensions/gsd/tests/stop-auto-remote.test.ts +8 -5
  196. package/dist/resources/extensions/gsd/tests/structured-data-formatter.test.ts +365 -0
  197. package/dist/resources/extensions/gsd/tests/summary-distiller.test.ts +323 -0
  198. package/dist/resources/extensions/gsd/tests/token-counter.test.ts +129 -0
  199. package/dist/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +1272 -0
  200. package/dist/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +164 -0
  201. package/dist/resources/extensions/gsd/tests/token-profile.test.ts +8 -1
  202. package/dist/resources/extensions/gsd/tests/triage-dispatch.test.ts +69 -73
  203. package/dist/resources/extensions/gsd/tests/update-command.test.ts +67 -0
  204. package/dist/resources/extensions/gsd/tests/validate-directory.test.ts +222 -0
  205. package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +55 -0
  206. package/dist/resources/extensions/gsd/tests/verification-gate.test.ts +115 -1
  207. package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +2 -2
  208. package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +2 -1
  209. package/dist/resources/extensions/gsd/tests/workspace-index.test.ts +24 -61
  210. package/dist/resources/extensions/gsd/tests/worktree-e2e.test.ts +5 -2
  211. package/dist/resources/extensions/gsd/tests/write-gate.test.ts +132 -43
  212. package/dist/resources/extensions/gsd/token-counter.ts +20 -0
  213. package/dist/resources/extensions/gsd/triage-ui.ts +1 -1
  214. package/dist/resources/extensions/gsd/types.ts +4 -1
  215. package/dist/resources/extensions/gsd/validate-directory.ts +164 -0
  216. package/dist/resources/extensions/gsd/verification-evidence.ts +7 -4
  217. package/dist/resources/extensions/gsd/verification-gate.ts +70 -5
  218. package/dist/resources/extensions/gsd/visualizer-data.ts +1 -1
  219. package/dist/resources/extensions/gsd/visualizer-overlay.ts +5 -2
  220. package/dist/resources/extensions/gsd/visualizer-views.ts +2 -3
  221. package/dist/resources/extensions/gsd/worktree-command.ts +4 -51
  222. package/dist/resources/extensions/gsd/worktree-manager.ts +7 -9
  223. package/dist/resources/extensions/gsd/worktree.ts +41 -1
  224. package/dist/resources/extensions/mcporter/index.ts +27 -14
  225. package/dist/resources/extensions/remote-questions/manager.ts +6 -24
  226. package/dist/resources/extensions/remote-questions/mod.ts +16 -0
  227. package/dist/resources/extensions/remote-questions/notify.ts +91 -0
  228. package/dist/resources/extensions/remote-questions/remote-command.ts +1 -1
  229. package/dist/resources/extensions/remote-questions/store.ts +5 -1
  230. package/dist/resources/extensions/remote-questions/types.ts +26 -3
  231. package/dist/resources/extensions/search-the-web/native-search.ts +7 -0
  232. package/dist/resources/extensions/search-the-web/provider.ts +15 -3
  233. package/dist/resources/extensions/search-the-web/tool-llm-context.ts +1 -13
  234. package/dist/resources/extensions/search-the-web/tool-search.ts +1 -13
  235. package/dist/resources/extensions/shared/format-utils.ts +53 -0
  236. package/dist/resources/extensions/shared/frontmatter.ts +117 -0
  237. package/dist/resources/extensions/shared/mod.ts +30 -0
  238. package/dist/resources/extensions/shared/sanitize.ts +19 -0
  239. package/dist/resources/extensions/slash-commands/create-extension.ts +1 -1
  240. package/dist/resources/extensions/slash-commands/create-slash-command.ts +1 -1
  241. package/dist/resources/extensions/subagent/index.ts +1 -2
  242. package/dist/resources/extensions/ttsr/index.ts +5 -0
  243. package/dist/resources/extensions/ttsr/rule-loader.ts +4 -51
  244. package/dist/resources/extensions/universal-config/discovery.ts +37 -15
  245. package/dist/resources/extensions/voice/index.ts +1 -1
  246. package/dist/resources/skills/accessibility/SKILL.md +522 -0
  247. package/dist/resources/skills/accessibility/references/WCAG.md +162 -0
  248. package/dist/resources/skills/agent-browser/SKILL.md +517 -0
  249. package/dist/resources/skills/agent-browser/references/authentication.md +202 -0
  250. package/dist/resources/skills/agent-browser/references/commands.md +263 -0
  251. package/dist/resources/skills/agent-browser/references/profiling.md +120 -0
  252. package/dist/resources/skills/agent-browser/references/proxy-support.md +194 -0
  253. package/dist/resources/skills/agent-browser/references/session-management.md +193 -0
  254. package/dist/resources/skills/agent-browser/references/snapshot-refs.md +194 -0
  255. package/dist/resources/skills/agent-browser/references/video-recording.md +173 -0
  256. package/dist/resources/skills/agent-browser/templates/authenticated-session.sh +105 -0
  257. package/dist/resources/skills/agent-browser/templates/capture-workflow.sh +69 -0
  258. package/dist/resources/skills/agent-browser/templates/form-automation.sh +62 -0
  259. package/dist/resources/skills/best-practices/SKILL.md +583 -0
  260. package/dist/resources/skills/code-optimizer/SKILL.md +160 -0
  261. package/dist/resources/skills/code-optimizer/references/algorithmic-complexity.md +66 -0
  262. package/dist/resources/skills/code-optimizer/references/build-compilation.md +90 -0
  263. package/dist/resources/skills/code-optimizer/references/bundle-dependencies.md +82 -0
  264. package/dist/resources/skills/code-optimizer/references/caching-memoization.md +76 -0
  265. package/dist/resources/skills/code-optimizer/references/concurrency-async.md +80 -0
  266. package/dist/resources/skills/code-optimizer/references/config-infra.md +71 -0
  267. package/dist/resources/skills/code-optimizer/references/data-structures.md +80 -0
  268. package/dist/resources/skills/code-optimizer/references/database-queries.md +76 -0
  269. package/dist/resources/skills/code-optimizer/references/dead-code-redundancy.md +84 -0
  270. package/dist/resources/skills/code-optimizer/references/error-resilience.md +80 -0
  271. package/dist/resources/skills/code-optimizer/references/io-network.md +89 -0
  272. package/dist/resources/skills/code-optimizer/references/logging-observability.md +64 -0
  273. package/dist/resources/skills/code-optimizer/references/memory-resources.md +66 -0
  274. package/dist/resources/skills/code-optimizer/references/rendering-ui.md +90 -0
  275. package/dist/resources/skills/code-optimizer/references/security-performance.md +68 -0
  276. package/dist/resources/skills/core-web-vitals/SKILL.md +441 -0
  277. package/dist/resources/skills/core-web-vitals/references/LCP.md +208 -0
  278. package/dist/resources/skills/make-interfaces-feel-better/SKILL.md +122 -0
  279. package/dist/resources/skills/make-interfaces-feel-better/animations.md +379 -0
  280. package/dist/resources/skills/make-interfaces-feel-better/performance.md +88 -0
  281. package/dist/resources/skills/make-interfaces-feel-better/surfaces.md +247 -0
  282. package/dist/resources/skills/make-interfaces-feel-better/typography.md +123 -0
  283. package/dist/resources/skills/react-best-practices/README.md +123 -0
  284. package/dist/resources/skills/react-best-practices/SKILL.md +136 -0
  285. package/dist/resources/skills/react-best-practices/metadata.json +15 -0
  286. package/dist/resources/skills/react-best-practices/rules/_sections.md +46 -0
  287. package/dist/resources/skills/react-best-practices/rules/_template.md +28 -0
  288. package/dist/resources/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  289. package/dist/resources/skills/react-best-practices/rules/advanced-init-once.md +42 -0
  290. package/dist/resources/skills/react-best-practices/rules/advanced-use-latest.md +39 -0
  291. package/dist/resources/skills/react-best-practices/rules/async-api-routes.md +38 -0
  292. package/dist/resources/skills/react-best-practices/rules/async-defer-await.md +80 -0
  293. package/dist/resources/skills/react-best-practices/rules/async-dependencies.md +51 -0
  294. package/dist/resources/skills/react-best-practices/rules/async-parallel.md +28 -0
  295. package/dist/resources/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
  296. package/dist/resources/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
  297. package/dist/resources/skills/react-best-practices/rules/bundle-conditional.md +31 -0
  298. package/dist/resources/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
  299. package/dist/resources/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
  300. package/dist/resources/skills/react-best-practices/rules/bundle-preload.md +50 -0
  301. package/dist/resources/skills/react-best-practices/rules/client-event-listeners.md +74 -0
  302. package/dist/resources/skills/react-best-practices/rules/client-localstorage-schema.md +71 -0
  303. package/dist/resources/skills/react-best-practices/rules/client-passive-event-listeners.md +48 -0
  304. package/dist/resources/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
  305. package/dist/resources/skills/react-best-practices/rules/js-batch-dom-css.md +107 -0
  306. package/dist/resources/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
  307. package/dist/resources/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
  308. package/dist/resources/skills/react-best-practices/rules/js-cache-storage.md +70 -0
  309. package/dist/resources/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
  310. package/dist/resources/skills/react-best-practices/rules/js-early-exit.md +50 -0
  311. package/dist/resources/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
  312. package/dist/resources/skills/react-best-practices/rules/js-index-maps.md +37 -0
  313. package/dist/resources/skills/react-best-practices/rules/js-length-check-first.md +49 -0
  314. package/dist/resources/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
  315. package/dist/resources/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
  316. package/dist/resources/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
  317. package/dist/resources/skills/react-best-practices/rules/rendering-activity.md +26 -0
  318. package/dist/resources/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
  319. package/dist/resources/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
  320. package/dist/resources/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
  321. package/dist/resources/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
  322. package/dist/resources/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
  323. package/dist/resources/skills/react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
  324. package/dist/resources/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
  325. package/dist/resources/skills/react-best-practices/rules/rendering-usetransition-loading.md +75 -0
  326. package/dist/resources/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
  327. package/dist/resources/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
  328. package/dist/resources/skills/react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
  329. package/dist/resources/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
  330. package/dist/resources/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
  331. package/dist/resources/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
  332. package/dist/resources/skills/react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
  333. package/dist/resources/skills/react-best-practices/rules/rerender-memo.md +44 -0
  334. package/dist/resources/skills/react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
  335. package/dist/resources/skills/react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
  336. package/dist/resources/skills/react-best-practices/rules/rerender-transitions.md +40 -0
  337. package/dist/resources/skills/react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
  338. package/dist/resources/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
  339. package/dist/resources/skills/react-best-practices/rules/server-auth-actions.md +96 -0
  340. package/dist/resources/skills/react-best-practices/rules/server-cache-lru.md +41 -0
  341. package/dist/resources/skills/react-best-practices/rules/server-cache-react.md +76 -0
  342. package/dist/resources/skills/react-best-practices/rules/server-dedup-props.md +65 -0
  343. package/dist/resources/skills/react-best-practices/rules/server-parallel-fetching.md +83 -0
  344. package/dist/resources/skills/react-best-practices/rules/server-serialization.md +38 -0
  345. package/dist/resources/skills/userinterface-wiki/SKILL.md +253 -0
  346. package/dist/resources/skills/userinterface-wiki/rules/_sections.md +66 -0
  347. package/dist/resources/skills/userinterface-wiki/rules/_template.md +24 -0
  348. package/dist/resources/skills/userinterface-wiki/rules/a11y-reduced-motion-check.md +30 -0
  349. package/dist/resources/skills/userinterface-wiki/rules/a11y-toggle-setting.md +30 -0
  350. package/dist/resources/skills/userinterface-wiki/rules/a11y-visual-equivalent.md +36 -0
  351. package/dist/resources/skills/userinterface-wiki/rules/a11y-volume-control.md +28 -0
  352. package/dist/resources/skills/userinterface-wiki/rules/appropriate-confirmations-only.md +19 -0
  353. package/dist/resources/skills/userinterface-wiki/rules/appropriate-errors-warnings.md +18 -0
  354. package/dist/resources/skills/userinterface-wiki/rules/appropriate-no-decorative.md +21 -0
  355. package/dist/resources/skills/userinterface-wiki/rules/appropriate-no-high-frequency.md +28 -0
  356. package/dist/resources/skills/userinterface-wiki/rules/appropriate-no-punishing.md +27 -0
  357. package/dist/resources/skills/userinterface-wiki/rules/container-callback-ref.md +31 -0
  358. package/dist/resources/skills/userinterface-wiki/rules/container-guard-initial-zero.md +25 -0
  359. package/dist/resources/skills/userinterface-wiki/rules/container-no-excessive-use.md +13 -0
  360. package/dist/resources/skills/userinterface-wiki/rules/container-overflow-hidden.md +25 -0
  361. package/dist/resources/skills/userinterface-wiki/rules/container-transition-delay.md +21 -0
  362. package/dist/resources/skills/userinterface-wiki/rules/container-two-div-pattern.md +35 -0
  363. package/dist/resources/skills/userinterface-wiki/rules/container-use-resize-observer.md +48 -0
  364. package/dist/resources/skills/userinterface-wiki/rules/context-cleanup-nodes.md +25 -0
  365. package/dist/resources/skills/userinterface-wiki/rules/context-resume-suspended.md +28 -0
  366. package/dist/resources/skills/userinterface-wiki/rules/context-reuse-single.md +30 -0
  367. package/dist/resources/skills/userinterface-wiki/rules/design-filter-for-character.md +25 -0
  368. package/dist/resources/skills/userinterface-wiki/rules/design-noise-for-percussion.md +26 -0
  369. package/dist/resources/skills/userinterface-wiki/rules/design-oscillator-for-tonal.md +22 -0
  370. package/dist/resources/skills/userinterface-wiki/rules/duration-max-300ms.md +21 -0
  371. package/dist/resources/skills/userinterface-wiki/rules/duration-press-hover.md +21 -0
  372. package/dist/resources/skills/userinterface-wiki/rules/duration-shorten-before-curve.md +21 -0
  373. package/dist/resources/skills/userinterface-wiki/rules/duration-small-state.md +15 -0
  374. package/dist/resources/skills/userinterface-wiki/rules/easing-entrance-ease-out.md +21 -0
  375. package/dist/resources/skills/userinterface-wiki/rules/easing-exit-ease-in.md +21 -0
  376. package/dist/resources/skills/userinterface-wiki/rules/easing-for-state-change.md +27 -0
  377. package/dist/resources/skills/userinterface-wiki/rules/easing-linear-only-progress.md +21 -0
  378. package/dist/resources/skills/userinterface-wiki/rules/easing-natural-decay.md +22 -0
  379. package/dist/resources/skills/userinterface-wiki/rules/easing-no-linear-motion.md +22 -0
  380. package/dist/resources/skills/userinterface-wiki/rules/easing-transition-ease-in-out.md +15 -0
  381. package/dist/resources/skills/userinterface-wiki/rules/envelope-exponential-decay.md +21 -0
  382. package/dist/resources/skills/userinterface-wiki/rules/envelope-no-zero-target.md +21 -0
  383. package/dist/resources/skills/userinterface-wiki/rules/envelope-set-initial-value.md +22 -0
  384. package/dist/resources/skills/userinterface-wiki/rules/exit-key-required.md +29 -0
  385. package/dist/resources/skills/userinterface-wiki/rules/exit-matches-initial.md +29 -0
  386. package/dist/resources/skills/userinterface-wiki/rules/exit-prop-required.md +33 -0
  387. package/dist/resources/skills/userinterface-wiki/rules/exit-requires-wrapper.md +27 -0
  388. package/dist/resources/skills/userinterface-wiki/rules/impl-default-subtle.md +21 -0
  389. package/dist/resources/skills/userinterface-wiki/rules/impl-preload-audio.md +34 -0
  390. package/dist/resources/skills/userinterface-wiki/rules/impl-reset-current-time.md +26 -0
  391. package/dist/resources/skills/userinterface-wiki/rules/mode-pop-layout-for-lists.md +25 -0
  392. package/dist/resources/skills/userinterface-wiki/rules/mode-sync-layout-conflict.md +29 -0
  393. package/dist/resources/skills/userinterface-wiki/rules/mode-wait-doubles-duration.md +25 -0
  394. package/dist/resources/skills/userinterface-wiki/rules/morphing-aria-hidden.md +21 -0
  395. package/dist/resources/skills/userinterface-wiki/rules/morphing-consistent-viewbox.md +23 -0
  396. package/dist/resources/skills/userinterface-wiki/rules/morphing-group-variants.md +33 -0
  397. package/dist/resources/skills/userinterface-wiki/rules/morphing-jump-non-grouped.md +29 -0
  398. package/dist/resources/skills/userinterface-wiki/rules/morphing-reduced-motion.md +28 -0
  399. package/dist/resources/skills/userinterface-wiki/rules/morphing-spring-rotation.md +23 -0
  400. package/dist/resources/skills/userinterface-wiki/rules/morphing-strokelinecap-round.md +21 -0
  401. package/dist/resources/skills/userinterface-wiki/rules/morphing-three-lines.md +32 -0
  402. package/dist/resources/skills/userinterface-wiki/rules/morphing-use-collapsed.md +33 -0
  403. package/dist/resources/skills/userinterface-wiki/rules/native-backdrop-styling.md +27 -0
  404. package/dist/resources/skills/userinterface-wiki/rules/native-placeholder-styling.md +27 -0
  405. package/dist/resources/skills/userinterface-wiki/rules/native-selection-styling.md +18 -0
  406. package/dist/resources/skills/userinterface-wiki/rules/nested-consistent-timing.md +25 -0
  407. package/dist/resources/skills/userinterface-wiki/rules/nested-propagate-required.md +41 -0
  408. package/dist/resources/skills/userinterface-wiki/rules/none-context-menu-entrance.md +25 -0
  409. package/dist/resources/skills/userinterface-wiki/rules/none-high-frequency.md +29 -0
  410. package/dist/resources/skills/userinterface-wiki/rules/none-keyboard-navigation.md +32 -0
  411. package/dist/resources/skills/userinterface-wiki/rules/param-click-duration.md +21 -0
  412. package/dist/resources/skills/userinterface-wiki/rules/param-filter-frequency-range.md +21 -0
  413. package/dist/resources/skills/userinterface-wiki/rules/param-q-value-range.md +21 -0
  414. package/dist/resources/skills/userinterface-wiki/rules/param-reasonable-gain.md +21 -0
  415. package/dist/resources/skills/userinterface-wiki/rules/physics-active-state.md +23 -0
  416. package/dist/resources/skills/userinterface-wiki/rules/physics-no-excessive-stagger.md +22 -0
  417. package/dist/resources/skills/userinterface-wiki/rules/physics-spring-for-overshoot.md +23 -0
  418. package/dist/resources/skills/userinterface-wiki/rules/physics-subtle-deformation.md +22 -0
  419. package/dist/resources/skills/userinterface-wiki/rules/prefetch-hit-slop.md +27 -0
  420. package/dist/resources/skills/userinterface-wiki/rules/prefetch-keyboard-tab.md +19 -0
  421. package/dist/resources/skills/userinterface-wiki/rules/prefetch-not-everything.md +22 -0
  422. package/dist/resources/skills/userinterface-wiki/rules/prefetch-touch-fallback.md +34 -0
  423. package/dist/resources/skills/userinterface-wiki/rules/prefetch-trajectory-over-hover.md +32 -0
  424. package/dist/resources/skills/userinterface-wiki/rules/prefetch-use-selectively.md +13 -0
  425. package/dist/resources/skills/userinterface-wiki/rules/presence-disable-interactions.md +31 -0
  426. package/dist/resources/skills/userinterface-wiki/rules/presence-hook-in-child.md +31 -0
  427. package/dist/resources/skills/userinterface-wiki/rules/presence-safe-to-remove.md +37 -0
  428. package/dist/resources/skills/userinterface-wiki/rules/pseudo-content-required.md +28 -0
  429. package/dist/resources/skills/userinterface-wiki/rules/pseudo-first-line-styling.md +27 -0
  430. package/dist/resources/skills/userinterface-wiki/rules/pseudo-hit-target-expansion.md +31 -0
  431. package/dist/resources/skills/userinterface-wiki/rules/pseudo-marker-styling.md +28 -0
  432. package/dist/resources/skills/userinterface-wiki/rules/pseudo-over-dom-node.md +32 -0
  433. package/dist/resources/skills/userinterface-wiki/rules/pseudo-position-relative-parent.md +33 -0
  434. package/dist/resources/skills/userinterface-wiki/rules/pseudo-z-index-layering.md +37 -0
  435. package/dist/resources/skills/userinterface-wiki/rules/spring-for-gestures.md +27 -0
  436. package/dist/resources/skills/userinterface-wiki/rules/spring-for-interruptible.md +27 -0
  437. package/dist/resources/skills/userinterface-wiki/rules/spring-params-balanced.md +29 -0
  438. package/dist/resources/skills/userinterface-wiki/rules/spring-preserves-velocity.md +28 -0
  439. package/dist/resources/skills/userinterface-wiki/rules/staging-dim-background.md +22 -0
  440. package/dist/resources/skills/userinterface-wiki/rules/staging-one-focal-point.md +24 -0
  441. package/dist/resources/skills/userinterface-wiki/rules/staging-z-index-hierarchy.md +22 -0
  442. package/dist/resources/skills/userinterface-wiki/rules/timing-consistent.md +24 -0
  443. package/dist/resources/skills/userinterface-wiki/rules/timing-no-entrance-context-menu.md +22 -0
  444. package/dist/resources/skills/userinterface-wiki/rules/timing-under-300ms.md +22 -0
  445. package/dist/resources/skills/userinterface-wiki/rules/transition-name-cleanup.md +28 -0
  446. package/dist/resources/skills/userinterface-wiki/rules/transition-name-required.md +27 -0
  447. package/dist/resources/skills/userinterface-wiki/rules/transition-name-unique.md +24 -0
  448. package/dist/resources/skills/userinterface-wiki/rules/transition-over-js-library.md +32 -0
  449. package/dist/resources/skills/userinterface-wiki/rules/transition-style-pseudo-elements.md +24 -0
  450. package/dist/resources/skills/userinterface-wiki/rules/type-antialiased-on-retina.md +18 -0
  451. package/dist/resources/skills/userinterface-wiki/rules/type-disambiguation-stylistic-set.md +15 -0
  452. package/dist/resources/skills/userinterface-wiki/rules/type-font-display-swap.md +28 -0
  453. package/dist/resources/skills/userinterface-wiki/rules/type-justify-with-hyphens.md +24 -0
  454. package/dist/resources/skills/userinterface-wiki/rules/type-letter-spacing-uppercase.md +28 -0
  455. package/dist/resources/skills/userinterface-wiki/rules/type-no-font-synthesis.md +18 -0
  456. package/dist/resources/skills/userinterface-wiki/rules/type-oldstyle-nums-for-prose.md +21 -0
  457. package/dist/resources/skills/userinterface-wiki/rules/type-opentype-contextual-alternates.md +15 -0
  458. package/dist/resources/skills/userinterface-wiki/rules/type-optical-sizing-auto.md +25 -0
  459. package/dist/resources/skills/userinterface-wiki/rules/type-proper-fractions.md +15 -0
  460. package/dist/resources/skills/userinterface-wiki/rules/type-slashed-zero.md +17 -0
  461. package/dist/resources/skills/userinterface-wiki/rules/type-tabular-nums-for-data.md +21 -0
  462. package/dist/resources/skills/userinterface-wiki/rules/type-text-wrap-balance-headings.md +21 -0
  463. package/dist/resources/skills/userinterface-wiki/rules/type-text-wrap-pretty.md +16 -0
  464. package/dist/resources/skills/userinterface-wiki/rules/type-underline-offset.md +25 -0
  465. package/dist/resources/skills/userinterface-wiki/rules/type-variable-weight-continuous.md +23 -0
  466. package/dist/resources/skills/userinterface-wiki/rules/ux-aesthetic-usability.md +32 -0
  467. package/dist/resources/skills/userinterface-wiki/rules/ux-cognitive-load-reduce.md +49 -0
  468. package/dist/resources/skills/userinterface-wiki/rules/ux-common-region-boundaries.md +50 -0
  469. package/dist/resources/skills/userinterface-wiki/rules/ux-doherty-perceived-speed.md +29 -0
  470. package/dist/resources/skills/userinterface-wiki/rules/ux-doherty-under-400ms.md +30 -0
  471. package/dist/resources/skills/userinterface-wiki/rules/ux-fitts-hit-area.md +32 -0
  472. package/dist/resources/skills/userinterface-wiki/rules/ux-fitts-target-size.md +31 -0
  473. package/dist/resources/skills/userinterface-wiki/rules/ux-goal-gradient-progress.md +33 -0
  474. package/dist/resources/skills/userinterface-wiki/rules/ux-hicks-minimize-choices.md +45 -0
  475. package/dist/resources/skills/userinterface-wiki/rules/ux-jakobs-familiar-patterns.md +37 -0
  476. package/dist/resources/skills/userinterface-wiki/rules/ux-millers-chunking.md +23 -0
  477. package/dist/resources/skills/userinterface-wiki/rules/ux-pareto-prioritize-features.md +36 -0
  478. package/dist/resources/skills/userinterface-wiki/rules/ux-peak-end-finish-strong.md +35 -0
  479. package/dist/resources/skills/userinterface-wiki/rules/ux-postels-accept-messy-input.md +45 -0
  480. package/dist/resources/skills/userinterface-wiki/rules/ux-pragnanz-simplify.md +33 -0
  481. package/dist/resources/skills/userinterface-wiki/rules/ux-progressive-disclosure.md +41 -0
  482. package/dist/resources/skills/userinterface-wiki/rules/ux-proximity-grouping.md +38 -0
  483. package/dist/resources/skills/userinterface-wiki/rules/ux-serial-position.md +31 -0
  484. package/dist/resources/skills/userinterface-wiki/rules/ux-similarity-consistency.md +35 -0
  485. package/dist/resources/skills/userinterface-wiki/rules/ux-teslers-complexity.md +28 -0
  486. package/dist/resources/skills/userinterface-wiki/rules/ux-uniform-connectedness.md +43 -0
  487. package/dist/resources/skills/userinterface-wiki/rules/ux-von-restorff-emphasis.md +29 -0
  488. package/dist/resources/skills/userinterface-wiki/rules/ux-zeigarnik-show-incomplete.md +36 -0
  489. package/dist/resources/skills/userinterface-wiki/rules/visual-animate-shadow-pseudo.md +49 -0
  490. package/dist/resources/skills/userinterface-wiki/rules/visual-border-alpha-colors.md +25 -0
  491. package/dist/resources/skills/userinterface-wiki/rules/visual-button-shadow-anatomy.md +49 -0
  492. package/dist/resources/skills/userinterface-wiki/rules/visual-concentric-radius.md +40 -0
  493. package/dist/resources/skills/userinterface-wiki/rules/visual-consistent-spacing-scale.md +35 -0
  494. package/dist/resources/skills/userinterface-wiki/rules/visual-layered-shadows.md +30 -0
  495. package/dist/resources/skills/userinterface-wiki/rules/visual-no-pure-black-shadow.md +25 -0
  496. package/dist/resources/skills/userinterface-wiki/rules/visual-shadow-direction.md +25 -0
  497. package/dist/resources/skills/userinterface-wiki/rules/visual-shadow-matches-elevation.md +23 -0
  498. package/dist/resources/skills/userinterface-wiki/rules/weight-duration-matches-action.md +29 -0
  499. package/dist/resources/skills/userinterface-wiki/rules/weight-match-action.md +32 -0
  500. package/dist/resources/skills/web-design-guidelines/SKILL.md +39 -0
  501. package/dist/resources/skills/web-quality-audit/SKILL.md +170 -0
  502. package/dist/resources/skills/web-quality-audit/scripts/analyze.sh +91 -0
  503. package/dist/update-check.js +1 -1
  504. package/package.json +20 -8
  505. package/packages/native/package.json +28 -0
  506. package/packages/pi-agent-core/package.json +6 -0
  507. package/packages/pi-ai/dist/models.generated.d.ts +43 -11
  508. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  509. package/packages/pi-ai/dist/models.generated.js +34 -26
  510. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  511. package/packages/pi-ai/dist/providers/anthropic.js +3 -2
  512. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  513. package/packages/pi-ai/dist/providers/openai-codex-responses.js +14 -4
  514. package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
  515. package/packages/pi-ai/dist/utils/oauth/anthropic.d.ts.map +1 -1
  516. package/packages/pi-ai/dist/utils/oauth/anthropic.js +2 -0
  517. package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
  518. package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  519. package/packages/pi-ai/dist/utils/oauth/github-copilot.js +5 -1
  520. package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
  521. package/packages/pi-ai/dist/utils/oauth/google-antigravity.d.ts.map +1 -1
  522. package/packages/pi-ai/dist/utils/oauth/google-antigravity.js +4 -0
  523. package/packages/pi-ai/dist/utils/oauth/google-antigravity.js.map +1 -1
  524. package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.d.ts.map +1 -1
  525. package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.js +6 -0
  526. package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.js.map +1 -1
  527. package/packages/pi-ai/dist/utils/oauth/index.js +2 -2
  528. package/packages/pi-ai/dist/utils/oauth/index.js.map +1 -1
  529. package/packages/pi-ai/dist/utils/oauth/openai-codex.d.ts.map +1 -1
  530. package/packages/pi-ai/dist/utils/oauth/openai-codex.js +2 -0
  531. package/packages/pi-ai/dist/utils/oauth/openai-codex.js.map +1 -1
  532. package/packages/pi-ai/oauth.d.ts +1 -0
  533. package/packages/pi-ai/oauth.js +1 -0
  534. package/packages/pi-ai/package.json +2 -2
  535. package/packages/pi-ai/src/models.generated.ts +42 -34
  536. package/packages/pi-ai/src/providers/anthropic.ts +3 -2
  537. package/packages/pi-ai/src/providers/openai-codex-responses.ts +15 -4
  538. package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -0
  539. package/packages/pi-ai/src/utils/oauth/github-copilot.ts +5 -1
  540. package/packages/pi-ai/src/utils/oauth/google-antigravity.ts +4 -0
  541. package/packages/pi-ai/src/utils/oauth/google-gemini-cli.ts +6 -0
  542. package/packages/pi-ai/src/utils/oauth/index.ts +2 -2
  543. package/packages/pi-ai/src/utils/oauth/openai-codex.ts +2 -0
  544. package/packages/pi-coding-agent/dist/core/agent-session.js +1 -1
  545. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  546. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  547. package/packages/pi-coding-agent/dist/core/auth-storage.js +2 -1
  548. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  549. package/packages/pi-coding-agent/dist/core/bash-executor.d.ts.map +1 -1
  550. package/packages/pi-coding-agent/dist/core/bash-executor.js +23 -1
  551. package/packages/pi-coding-agent/dist/core/bash-executor.js.map +1 -1
  552. package/packages/pi-coding-agent/dist/core/blob-store.d.ts +8 -0
  553. package/packages/pi-coding-agent/dist/core/blob-store.d.ts.map +1 -1
  554. package/packages/pi-coding-agent/dist/core/blob-store.js +50 -1
  555. package/packages/pi-coding-agent/dist/core/blob-store.js.map +1 -1
  556. package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  557. package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.js +2 -1
  558. package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.js.map +1 -1
  559. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
  560. package/packages/pi-coding-agent/dist/core/compaction/compaction.js +3 -2
  561. package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
  562. package/packages/pi-coding-agent/dist/core/compaction/utils.d.ts.map +1 -1
  563. package/packages/pi-coding-agent/dist/core/compaction/utils.js +2 -2
  564. package/packages/pi-coding-agent/dist/core/compaction/utils.js.map +1 -1
  565. package/packages/pi-coding-agent/dist/core/constants.d.ts +29 -0
  566. package/packages/pi-coding-agent/dist/core/constants.d.ts.map +1 -0
  567. package/packages/pi-coding-agent/dist/core/constants.js +44 -0
  568. package/packages/pi-coding-agent/dist/core/constants.js.map +1 -0
  569. package/packages/pi-coding-agent/dist/core/extensions/loader.js +1 -1
  570. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  571. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  572. package/packages/pi-coding-agent/dist/core/extensions/runner.js +32 -10
  573. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  574. package/packages/pi-coding-agent/dist/core/extensions/runner.test.d.ts +2 -0
  575. package/packages/pi-coding-agent/dist/core/extensions/runner.test.d.ts.map +1 -0
  576. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +77 -0
  577. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -0
  578. package/packages/pi-coding-agent/dist/core/fs-utils.d.ts +7 -0
  579. package/packages/pi-coding-agent/dist/core/fs-utils.d.ts.map +1 -0
  580. package/packages/pi-coding-agent/dist/core/fs-utils.js +12 -0
  581. package/packages/pi-coding-agent/dist/core/fs-utils.js.map +1 -0
  582. package/packages/pi-coding-agent/dist/core/fs-utils.test.d.ts +2 -0
  583. package/packages/pi-coding-agent/dist/core/fs-utils.test.d.ts.map +1 -0
  584. package/packages/pi-coding-agent/dist/core/fs-utils.test.js +67 -0
  585. package/packages/pi-coding-agent/dist/core/fs-utils.test.js.map +1 -0
  586. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  587. package/packages/pi-coding-agent/dist/core/lsp/client.js +22 -2
  588. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  589. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
  590. package/packages/pi-coding-agent/dist/core/lsp/index.js +30 -4
  591. package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
  592. package/packages/pi-coding-agent/dist/core/lsp/lspmux.d.ts.map +1 -1
  593. package/packages/pi-coding-agent/dist/core/lsp/lspmux.js +13 -5
  594. package/packages/pi-coding-agent/dist/core/lsp/lspmux.js.map +1 -1
  595. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  596. package/packages/pi-coding-agent/dist/core/model-resolver.js +14 -0
  597. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  598. package/packages/pi-coding-agent/dist/core/resolve-config-value.d.ts.map +1 -1
  599. package/packages/pi-coding-agent/dist/core/resolve-config-value.js +12 -4
  600. package/packages/pi-coding-agent/dist/core/resolve-config-value.js.map +1 -1
  601. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js +49 -0
  602. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js.map +1 -1
  603. package/packages/pi-coding-agent/dist/core/session-manager.d.ts +7 -0
  604. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  605. package/packages/pi-coding-agent/dist/core/session-manager.js +108 -14
  606. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  607. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
  608. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  609. package/packages/pi-coding-agent/dist/core/settings-manager.js +14 -5
  610. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  611. package/packages/pi-coding-agent/dist/core/tools/edit-diff.js +2 -2
  612. package/packages/pi-coding-agent/dist/core/tools/edit-diff.js.map +1 -1
  613. package/packages/pi-coding-agent/dist/core/tools/find.d.ts.map +1 -1
  614. package/packages/pi-coding-agent/dist/core/tools/find.js +2 -1
  615. package/packages/pi-coding-agent/dist/core/tools/find.js.map +1 -1
  616. package/packages/pi-coding-agent/dist/core/tools/hashline-edit.js +4 -4
  617. package/packages/pi-coding-agent/dist/core/tools/hashline-edit.js.map +1 -1
  618. package/packages/pi-coding-agent/dist/core/tools/hashline.d.ts +1 -1
  619. package/packages/pi-coding-agent/dist/core/tools/hashline.js +1 -1
  620. package/packages/pi-coding-agent/dist/core/tools/hashline.js.map +1 -1
  621. package/packages/pi-coding-agent/dist/core/tools/hashline.test.js +8 -8
  622. package/packages/pi-coding-agent/dist/core/tools/hashline.test.js.map +1 -1
  623. package/packages/pi-coding-agent/dist/core/tools/index.d.ts +1 -1
  624. package/packages/pi-coding-agent/dist/core/tools/index.js +1 -1
  625. package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
  626. package/packages/pi-coding-agent/dist/core/tools/path-utils.d.ts +1 -0
  627. package/packages/pi-coding-agent/dist/core/tools/path-utils.d.ts.map +1 -1
  628. package/packages/pi-coding-agent/dist/core/tools/path-utils.js +1 -1
  629. package/packages/pi-coding-agent/dist/core/tools/path-utils.js.map +1 -1
  630. package/packages/pi-coding-agent/dist/core/tools/truncate.d.ts.map +1 -1
  631. package/packages/pi-coding-agent/dist/core/tools/truncate.js +2 -1
  632. package/packages/pi-coding-agent/dist/core/tools/truncate.js.map +1 -1
  633. package/packages/pi-coding-agent/dist/index.d.ts +2 -1
  634. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  635. package/packages/pi-coding-agent/dist/index.js +3 -1
  636. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  637. package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
  638. package/packages/pi-coding-agent/dist/main.js +6 -0
  639. package/packages/pi-coding-agent/dist/main.js.map +1 -1
  640. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-selector.d.ts +9 -0
  641. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  642. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-selector.js +47 -5
  643. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-selector.js.map +1 -1
  644. package/packages/pi-coding-agent/dist/modes/interactive/components/index.d.ts +1 -1
  645. package/packages/pi-coding-agent/dist/modes/interactive/components/index.d.ts.map +1 -1
  646. package/packages/pi-coding-agent/dist/modes/interactive/components/index.js +1 -1
  647. package/packages/pi-coding-agent/dist/modes/interactive/components/index.js.map +1 -1
  648. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +4 -4
  649. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js.map +1 -1
  650. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  651. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  652. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +12 -0
  653. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  654. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  655. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +30 -2
  656. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  657. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  658. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js +11 -0
  659. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js.map +1 -1
  660. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  661. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +12 -2
  662. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  663. package/packages/pi-coding-agent/dist/resources/extensions/memory/pipeline.d.ts.map +1 -1
  664. package/packages/pi-coding-agent/dist/resources/extensions/memory/pipeline.js +25 -3
  665. package/packages/pi-coding-agent/dist/resources/extensions/memory/pipeline.js.map +1 -1
  666. package/packages/pi-coding-agent/package.json +0 -4
  667. package/packages/pi-coding-agent/src/core/agent-session.ts +1 -1
  668. package/packages/pi-coding-agent/src/core/auth-storage.ts +2 -1
  669. package/packages/pi-coding-agent/src/core/bash-executor.ts +23 -1
  670. package/packages/pi-coding-agent/src/core/blob-store.ts +46 -1
  671. package/packages/pi-coding-agent/src/core/compaction/branch-summarization.ts +2 -1
  672. package/packages/pi-coding-agent/src/core/compaction/compaction.ts +3 -2
  673. package/packages/pi-coding-agent/src/core/compaction/utils.ts +2 -2
  674. package/packages/pi-coding-agent/src/core/constants.ts +59 -0
  675. package/packages/pi-coding-agent/src/core/extensions/loader.ts +1 -1
  676. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +84 -0
  677. package/packages/pi-coding-agent/src/core/extensions/runner.ts +31 -10
  678. package/packages/pi-coding-agent/src/core/fs-utils.test.ts +66 -0
  679. package/packages/pi-coding-agent/src/core/fs-utils.ts +12 -0
  680. package/packages/pi-coding-agent/src/core/lsp/client.ts +22 -2
  681. package/packages/pi-coding-agent/src/core/lsp/index.ts +31 -5
  682. package/packages/pi-coding-agent/src/core/lsp/lspmux.ts +13 -5
  683. package/packages/pi-coding-agent/src/core/model-resolver.ts +14 -0
  684. package/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +58 -0
  685. package/packages/pi-coding-agent/src/core/resolve-config-value.ts +14 -4
  686. package/packages/pi-coding-agent/src/core/session-manager.ts +113 -18
  687. package/packages/pi-coding-agent/src/core/settings-manager.ts +22 -5
  688. package/packages/pi-coding-agent/src/core/tools/edit-diff.ts +2 -2
  689. package/packages/pi-coding-agent/src/core/tools/find.ts +2 -1
  690. package/packages/pi-coding-agent/src/core/tools/hashline-edit.ts +4 -4
  691. package/packages/pi-coding-agent/src/core/tools/hashline.test.ts +8 -8
  692. package/packages/pi-coding-agent/src/core/tools/hashline.ts +1 -1
  693. package/packages/pi-coding-agent/src/core/tools/index.ts +1 -1
  694. package/packages/pi-coding-agent/src/core/tools/path-utils.ts +1 -1
  695. package/packages/pi-coding-agent/src/core/tools/truncate.ts +3 -1
  696. package/packages/pi-coding-agent/src/index.ts +9 -0
  697. package/packages/pi-coding-agent/src/main.ts +7 -0
  698. package/packages/pi-coding-agent/src/modes/interactive/components/extension-selector.ts +49 -5
  699. package/packages/pi-coding-agent/src/modes/interactive/components/index.ts +1 -1
  700. package/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts +4 -4
  701. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +15 -0
  702. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +32 -1
  703. package/packages/pi-coding-agent/src/modes/rpc/rpc-client.ts +12 -0
  704. package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +11 -2
  705. package/packages/pi-coding-agent/src/resources/extensions/memory/pipeline.ts +23 -3
  706. package/packages/pi-tui/dist/__tests__/autocomplete.test.d.ts +2 -0
  707. package/packages/pi-tui/dist/__tests__/autocomplete.test.d.ts.map +1 -0
  708. package/packages/pi-tui/dist/__tests__/autocomplete.test.js +149 -0
  709. package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -0
  710. package/packages/pi-tui/dist/__tests__/fuzzy.test.d.ts +2 -0
  711. package/packages/pi-tui/dist/__tests__/fuzzy.test.d.ts.map +1 -0
  712. package/packages/pi-tui/dist/__tests__/fuzzy.test.js +94 -0
  713. package/packages/pi-tui/dist/__tests__/fuzzy.test.js.map +1 -0
  714. package/packages/pi-tui/dist/autocomplete.d.ts +5 -1
  715. package/packages/pi-tui/dist/autocomplete.d.ts.map +1 -1
  716. package/packages/pi-tui/dist/autocomplete.js +6 -2
  717. package/packages/pi-tui/dist/autocomplete.js.map +1 -1
  718. package/packages/pi-tui/package.json +8 -2
  719. package/packages/pi-tui/src/__tests__/autocomplete.test.ts +186 -0
  720. package/packages/pi-tui/src/__tests__/fuzzy.test.ts +112 -0
  721. package/packages/pi-tui/src/autocomplete.ts +8 -1
  722. package/src/resources/extensions/ask-user-questions.ts +3 -2
  723. package/src/resources/extensions/bg-shell/bg-shell-command.ts +219 -0
  724. package/src/resources/extensions/bg-shell/bg-shell-lifecycle.ts +400 -0
  725. package/src/resources/extensions/bg-shell/bg-shell-tool.ts +985 -0
  726. package/src/resources/extensions/bg-shell/index.ts +17 -1561
  727. package/src/resources/extensions/bg-shell/overlay.ts +4 -0
  728. package/src/resources/extensions/bg-shell/utilities.ts +4 -16
  729. package/src/resources/extensions/browser-tools/capture.ts +34 -2
  730. package/src/resources/extensions/browser-tools/lifecycle.ts +5 -5
  731. package/src/resources/extensions/browser-tools/settle.ts +1 -1
  732. package/src/resources/extensions/browser-tools/state.ts +5 -5
  733. package/src/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +1 -1
  734. package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +3 -3
  735. package/src/resources/extensions/browser-tools/tools/assertions.ts +1 -1
  736. package/src/resources/extensions/browser-tools/tools/device.ts +1 -1
  737. package/src/resources/extensions/browser-tools/tools/extract.ts +1 -1
  738. package/src/resources/extensions/browser-tools/tools/navigation.ts +6 -6
  739. package/src/resources/extensions/browser-tools/tools/network-mock.ts +1 -1
  740. package/src/resources/extensions/browser-tools/tools/pages.ts +1 -1
  741. package/src/resources/extensions/browser-tools/tools/screenshot.ts +28 -10
  742. package/src/resources/extensions/browser-tools/tools/state-persistence.ts +1 -1
  743. package/src/resources/extensions/browser-tools/tools/visual-diff.ts +1 -1
  744. package/src/resources/extensions/browser-tools/utils.ts +5 -5
  745. package/src/resources/extensions/get-secrets-from-user.ts +1 -1
  746. package/src/resources/extensions/google-search/index.ts +21 -8
  747. package/src/resources/extensions/gsd/activity-log.ts +2 -1
  748. package/src/resources/extensions/gsd/atomic-write.ts +35 -0
  749. package/src/resources/extensions/gsd/auto/session.ts +12 -0
  750. package/src/resources/extensions/gsd/auto-dashboard.ts +84 -3
  751. package/src/resources/extensions/gsd/auto-idempotency.ts +150 -0
  752. package/src/resources/extensions/gsd/auto-post-unit.ts +591 -0
  753. package/src/resources/extensions/gsd/auto-prompts.ts +116 -22
  754. package/src/resources/extensions/gsd/auto-recovery.ts +20 -9
  755. package/src/resources/extensions/gsd/auto-start.ts +500 -0
  756. package/src/resources/extensions/gsd/auto-stuck-detection.ts +220 -0
  757. package/src/resources/extensions/gsd/auto-timers.ts +223 -0
  758. package/src/resources/extensions/gsd/auto-unit-closeout.ts +3 -1
  759. package/src/resources/extensions/gsd/auto-verification.ts +195 -0
  760. package/src/resources/extensions/gsd/auto-worktree-sync.ts +8 -31
  761. package/src/resources/extensions/gsd/auto-worktree.ts +83 -67
  762. package/src/resources/extensions/gsd/auto.ts +364 -1873
  763. package/src/resources/extensions/gsd/commands-config.ts +102 -0
  764. package/src/resources/extensions/gsd/commands-handlers.ts +402 -0
  765. package/src/resources/extensions/gsd/commands-inspect.ts +90 -0
  766. package/src/resources/extensions/gsd/commands-logs.ts +537 -0
  767. package/src/resources/extensions/gsd/commands-maintenance.ts +206 -0
  768. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +747 -0
  769. package/src/resources/extensions/gsd/commands.ts +378 -1431
  770. package/src/resources/extensions/gsd/constants.ts +21 -0
  771. package/src/resources/extensions/gsd/context-budget.ts +25 -2
  772. package/src/resources/extensions/gsd/crash-recovery.ts +4 -2
  773. package/src/resources/extensions/gsd/dashboard-overlay.ts +13 -4
  774. package/src/resources/extensions/gsd/db-writer.ts +21 -2
  775. package/src/resources/extensions/gsd/detection.ts +469 -0
  776. package/src/resources/extensions/gsd/diff-context.ts +2 -1
  777. package/src/resources/extensions/gsd/dispatch-guard.ts +4 -0
  778. package/src/resources/extensions/gsd/docs/preferences-reference.md +83 -0
  779. package/src/resources/extensions/gsd/doctor-checks.ts +564 -0
  780. package/src/resources/extensions/gsd/doctor-format.ts +78 -0
  781. package/src/resources/extensions/gsd/doctor-types.ts +72 -0
  782. package/src/resources/extensions/gsd/doctor.ts +64 -701
  783. package/src/resources/extensions/gsd/errors.ts +0 -2
  784. package/src/resources/extensions/gsd/export-html.ts +367 -11
  785. package/src/resources/extensions/gsd/export.ts +119 -30
  786. package/src/resources/extensions/gsd/files.ts +8 -126
  787. package/src/resources/extensions/gsd/forensics.ts +2 -12
  788. package/src/resources/extensions/gsd/git-constants.ts +11 -0
  789. package/src/resources/extensions/gsd/git-service.ts +13 -9
  790. package/src/resources/extensions/gsd/gsd-db.ts +26 -6
  791. package/src/resources/extensions/gsd/guided-flow-queue.ts +451 -0
  792. package/src/resources/extensions/gsd/guided-flow.ts +231 -514
  793. package/src/resources/extensions/gsd/history.ts +2 -20
  794. package/src/resources/extensions/gsd/index.ts +206 -45
  795. package/src/resources/extensions/gsd/init-wizard.ts +615 -0
  796. package/src/resources/extensions/gsd/jsonl-utils.ts +21 -0
  797. package/src/resources/extensions/gsd/key-manager.ts +995 -0
  798. package/src/resources/extensions/gsd/metrics.ts +32 -5
  799. package/src/resources/extensions/gsd/migrate/command.ts +1 -1
  800. package/src/resources/extensions/gsd/migrate/parsers.ts +10 -95
  801. package/src/resources/extensions/gsd/milestone-actions.ts +126 -0
  802. package/src/resources/extensions/gsd/milestone-ids.ts +95 -0
  803. package/src/resources/extensions/gsd/native-git-bridge.ts +5 -10
  804. package/src/resources/extensions/gsd/parallel-eligibility.ts +3 -3
  805. package/src/resources/extensions/gsd/paths.ts +1 -3
  806. package/src/resources/extensions/gsd/plugin-importer.ts +3 -2
  807. package/src/resources/extensions/gsd/preferences-hooks.ts +10 -0
  808. package/src/resources/extensions/gsd/preferences-models.ts +323 -0
  809. package/src/resources/extensions/gsd/preferences-skills.ts +169 -0
  810. package/src/resources/extensions/gsd/preferences-types.ts +223 -0
  811. package/src/resources/extensions/gsd/preferences-validation.ts +597 -0
  812. package/src/resources/extensions/gsd/preferences.ts +219 -1305
  813. package/src/resources/extensions/gsd/prompt-cache-optimizer.ts +213 -0
  814. package/src/resources/extensions/gsd/prompt-compressor.ts +508 -0
  815. package/src/resources/extensions/gsd/prompt-loader.ts +4 -2
  816. package/src/resources/extensions/gsd/prompt-ordering.ts +200 -0
  817. package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -2
  818. package/src/resources/extensions/gsd/prompts/complete-slice.md +4 -4
  819. package/src/resources/extensions/gsd/prompts/discuss-headless.md +2 -4
  820. package/src/resources/extensions/gsd/prompts/discuss.md +13 -5
  821. package/src/resources/extensions/gsd/prompts/execute-task.md +1 -2
  822. package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  823. package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +0 -1
  824. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -2
  825. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -2
  826. package/src/resources/extensions/gsd/prompts/queue.md +30 -0
  827. package/src/resources/extensions/gsd/prompts/quick-task.md +0 -6
  828. package/src/resources/extensions/gsd/prompts/replan-slice.md +0 -1
  829. package/src/resources/extensions/gsd/prompts/rewrite-docs.md +0 -1
  830. package/src/resources/extensions/gsd/prompts/system.md +5 -2
  831. package/src/resources/extensions/gsd/provider-error-pause.ts +59 -10
  832. package/src/resources/extensions/gsd/queue-order.ts +1 -1
  833. package/src/resources/extensions/gsd/queue-reorder-ui.ts +15 -2
  834. package/src/resources/extensions/gsd/quick.ts +18 -15
  835. package/src/resources/extensions/gsd/reports.ts +1 -7
  836. package/src/resources/extensions/gsd/safe-fs.ts +47 -0
  837. package/src/resources/extensions/gsd/semantic-chunker.ts +336 -0
  838. package/src/resources/extensions/gsd/session-forensics.ts +8 -23
  839. package/src/resources/extensions/gsd/session-lock.ts +284 -0
  840. package/src/resources/extensions/gsd/skills/gsd-headless/SKILL.md +80 -16
  841. package/src/resources/extensions/gsd/skills/gsd-headless/references/answer-injection.md +35 -6
  842. package/src/resources/extensions/gsd/skills/gsd-headless/references/commands.md +7 -2
  843. package/src/resources/extensions/gsd/skills/gsd-headless/references/multi-session.md +4 -13
  844. package/src/resources/extensions/gsd/state.ts +54 -2
  845. package/src/resources/extensions/gsd/structured-data-formatter.ts +144 -0
  846. package/src/resources/extensions/gsd/summary-distiller.ts +258 -0
  847. package/src/resources/extensions/gsd/templates/preferences.md +34 -0
  848. package/src/resources/extensions/gsd/tests/activity-log.test.ts +213 -0
  849. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +107 -0
  850. package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +197 -0
  851. package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +33 -39
  852. package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +108 -2
  853. package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +257 -0
  854. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +3 -0
  855. package/src/resources/extensions/gsd/tests/commands-logs.test.ts +241 -0
  856. package/src/resources/extensions/gsd/tests/context-budget.test.ts +69 -0
  857. package/src/resources/extensions/gsd/tests/detection.test.ts +398 -0
  858. package/src/resources/extensions/gsd/tests/discuss-prompt.test.ts +12 -24
  859. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +118 -94
  860. package/src/resources/extensions/gsd/tests/dispatch-stall-guard.test.ts +126 -0
  861. package/src/resources/extensions/gsd/tests/dist-redirect.mjs +7 -3
  862. package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +75 -0
  863. package/src/resources/extensions/gsd/tests/doctor-git.test.ts +17 -55
  864. package/src/resources/extensions/gsd/tests/export-html-all.test.ts +105 -0
  865. package/src/resources/extensions/gsd/tests/export-html-enhancements.test.ts +375 -0
  866. package/src/resources/extensions/gsd/tests/extension-selector-separator.test.ts +122 -0
  867. package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +3 -0
  868. package/src/resources/extensions/gsd/tests/headless-answers.test.ts +340 -0
  869. package/src/resources/extensions/gsd/tests/headless-query.test.ts +162 -0
  870. package/src/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +24 -82
  871. package/src/resources/extensions/gsd/tests/init-wizard.test.ts +197 -0
  872. package/src/resources/extensions/gsd/tests/key-manager.test.ts +414 -0
  873. package/src/resources/extensions/gsd/tests/metrics.test.ts +173 -305
  874. package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +3 -0
  875. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +59 -1
  876. package/src/resources/extensions/gsd/tests/next-milestone-id.test.ts +18 -61
  877. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +17 -8
  878. package/src/resources/extensions/gsd/tests/parallel-merge.test.ts +3 -0
  879. package/src/resources/extensions/gsd/tests/park-edge-cases.test.ts +276 -0
  880. package/src/resources/extensions/gsd/tests/park-milestone.test.ts +401 -0
  881. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +23 -47
  882. package/src/resources/extensions/gsd/tests/preferences.test.ts +284 -0
  883. package/src/resources/extensions/gsd/tests/prompt-cache-optimizer.test.ts +314 -0
  884. package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +529 -0
  885. package/src/resources/extensions/gsd/tests/prompt-ordering.test.ts +296 -0
  886. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +338 -0
  887. package/src/resources/extensions/gsd/tests/reassess-detection.test.ts +154 -0
  888. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +1 -1
  889. package/src/resources/extensions/gsd/tests/remote-status.test.ts +2 -2
  890. package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +43 -60
  891. package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +426 -0
  892. package/src/resources/extensions/gsd/tests/session-lock.test.ts +315 -0
  893. package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +3 -0
  894. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +8 -5
  895. package/src/resources/extensions/gsd/tests/structured-data-formatter.test.ts +365 -0
  896. package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +323 -0
  897. package/src/resources/extensions/gsd/tests/token-counter.test.ts +129 -0
  898. package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +1272 -0
  899. package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +164 -0
  900. package/src/resources/extensions/gsd/tests/token-profile.test.ts +8 -1
  901. package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +69 -73
  902. package/src/resources/extensions/gsd/tests/update-command.test.ts +67 -0
  903. package/src/resources/extensions/gsd/tests/validate-directory.test.ts +222 -0
  904. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +55 -0
  905. package/src/resources/extensions/gsd/tests/verification-gate.test.ts +115 -1
  906. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +2 -2
  907. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +2 -1
  908. package/src/resources/extensions/gsd/tests/workspace-index.test.ts +24 -61
  909. package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +5 -2
  910. package/src/resources/extensions/gsd/tests/write-gate.test.ts +132 -43
  911. package/src/resources/extensions/gsd/token-counter.ts +20 -0
  912. package/src/resources/extensions/gsd/triage-ui.ts +1 -1
  913. package/src/resources/extensions/gsd/types.ts +4 -1
  914. package/src/resources/extensions/gsd/validate-directory.ts +164 -0
  915. package/src/resources/extensions/gsd/verification-evidence.ts +7 -4
  916. package/src/resources/extensions/gsd/verification-gate.ts +70 -5
  917. package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
  918. package/src/resources/extensions/gsd/visualizer-overlay.ts +5 -2
  919. package/src/resources/extensions/gsd/visualizer-views.ts +2 -3
  920. package/src/resources/extensions/gsd/worktree-command.ts +4 -51
  921. package/src/resources/extensions/gsd/worktree-manager.ts +7 -9
  922. package/src/resources/extensions/gsd/worktree.ts +41 -1
  923. package/src/resources/extensions/mcporter/index.ts +27 -14
  924. package/src/resources/extensions/remote-questions/manager.ts +6 -24
  925. package/src/resources/extensions/remote-questions/mod.ts +16 -0
  926. package/src/resources/extensions/remote-questions/notify.ts +91 -0
  927. package/src/resources/extensions/remote-questions/remote-command.ts +1 -1
  928. package/src/resources/extensions/remote-questions/store.ts +5 -1
  929. package/src/resources/extensions/remote-questions/types.ts +26 -3
  930. package/src/resources/extensions/search-the-web/native-search.ts +7 -0
  931. package/src/resources/extensions/search-the-web/provider.ts +15 -3
  932. package/src/resources/extensions/search-the-web/tool-llm-context.ts +1 -13
  933. package/src/resources/extensions/search-the-web/tool-search.ts +1 -13
  934. package/src/resources/extensions/shared/format-utils.ts +53 -0
  935. package/src/resources/extensions/shared/frontmatter.ts +117 -0
  936. package/src/resources/extensions/shared/mod.ts +30 -0
  937. package/src/resources/extensions/shared/sanitize.ts +19 -0
  938. package/src/resources/extensions/slash-commands/create-extension.ts +1 -1
  939. package/src/resources/extensions/slash-commands/create-slash-command.ts +1 -1
  940. package/src/resources/extensions/subagent/index.ts +1 -2
  941. package/src/resources/extensions/ttsr/index.ts +5 -0
  942. package/src/resources/extensions/ttsr/rule-loader.ts +4 -51
  943. package/src/resources/extensions/universal-config/discovery.ts +37 -15
  944. package/src/resources/extensions/voice/index.ts +1 -1
  945. package/src/resources/skills/accessibility/SKILL.md +522 -0
  946. package/src/resources/skills/accessibility/references/WCAG.md +162 -0
  947. package/src/resources/skills/agent-browser/SKILL.md +517 -0
  948. package/src/resources/skills/agent-browser/references/authentication.md +202 -0
  949. package/src/resources/skills/agent-browser/references/commands.md +263 -0
  950. package/src/resources/skills/agent-browser/references/profiling.md +120 -0
  951. package/src/resources/skills/agent-browser/references/proxy-support.md +194 -0
  952. package/src/resources/skills/agent-browser/references/session-management.md +193 -0
  953. package/src/resources/skills/agent-browser/references/snapshot-refs.md +194 -0
  954. package/src/resources/skills/agent-browser/references/video-recording.md +173 -0
  955. package/src/resources/skills/agent-browser/templates/authenticated-session.sh +105 -0
  956. package/src/resources/skills/agent-browser/templates/capture-workflow.sh +69 -0
  957. package/src/resources/skills/agent-browser/templates/form-automation.sh +62 -0
  958. package/src/resources/skills/best-practices/SKILL.md +583 -0
  959. package/src/resources/skills/code-optimizer/SKILL.md +160 -0
  960. package/src/resources/skills/code-optimizer/references/algorithmic-complexity.md +66 -0
  961. package/src/resources/skills/code-optimizer/references/build-compilation.md +90 -0
  962. package/src/resources/skills/code-optimizer/references/bundle-dependencies.md +82 -0
  963. package/src/resources/skills/code-optimizer/references/caching-memoization.md +76 -0
  964. package/src/resources/skills/code-optimizer/references/concurrency-async.md +80 -0
  965. package/src/resources/skills/code-optimizer/references/config-infra.md +71 -0
  966. package/src/resources/skills/code-optimizer/references/data-structures.md +80 -0
  967. package/src/resources/skills/code-optimizer/references/database-queries.md +76 -0
  968. package/src/resources/skills/code-optimizer/references/dead-code-redundancy.md +84 -0
  969. package/src/resources/skills/code-optimizer/references/error-resilience.md +80 -0
  970. package/src/resources/skills/code-optimizer/references/io-network.md +89 -0
  971. package/src/resources/skills/code-optimizer/references/logging-observability.md +64 -0
  972. package/src/resources/skills/code-optimizer/references/memory-resources.md +66 -0
  973. package/src/resources/skills/code-optimizer/references/rendering-ui.md +90 -0
  974. package/src/resources/skills/code-optimizer/references/security-performance.md +68 -0
  975. package/src/resources/skills/core-web-vitals/SKILL.md +441 -0
  976. package/src/resources/skills/core-web-vitals/references/LCP.md +208 -0
  977. package/src/resources/skills/make-interfaces-feel-better/SKILL.md +122 -0
  978. package/src/resources/skills/make-interfaces-feel-better/animations.md +379 -0
  979. package/src/resources/skills/make-interfaces-feel-better/performance.md +88 -0
  980. package/src/resources/skills/make-interfaces-feel-better/surfaces.md +247 -0
  981. package/src/resources/skills/make-interfaces-feel-better/typography.md +123 -0
  982. package/src/resources/skills/react-best-practices/README.md +123 -0
  983. package/src/resources/skills/react-best-practices/SKILL.md +136 -0
  984. package/src/resources/skills/react-best-practices/metadata.json +15 -0
  985. package/src/resources/skills/react-best-practices/rules/_sections.md +46 -0
  986. package/src/resources/skills/react-best-practices/rules/_template.md +28 -0
  987. package/src/resources/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  988. package/src/resources/skills/react-best-practices/rules/advanced-init-once.md +42 -0
  989. package/src/resources/skills/react-best-practices/rules/advanced-use-latest.md +39 -0
  990. package/src/resources/skills/react-best-practices/rules/async-api-routes.md +38 -0
  991. package/src/resources/skills/react-best-practices/rules/async-defer-await.md +80 -0
  992. package/src/resources/skills/react-best-practices/rules/async-dependencies.md +51 -0
  993. package/src/resources/skills/react-best-practices/rules/async-parallel.md +28 -0
  994. package/src/resources/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
  995. package/src/resources/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
  996. package/src/resources/skills/react-best-practices/rules/bundle-conditional.md +31 -0
  997. package/src/resources/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
  998. package/src/resources/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
  999. package/src/resources/skills/react-best-practices/rules/bundle-preload.md +50 -0
  1000. package/src/resources/skills/react-best-practices/rules/client-event-listeners.md +74 -0
  1001. package/src/resources/skills/react-best-practices/rules/client-localstorage-schema.md +71 -0
  1002. package/src/resources/skills/react-best-practices/rules/client-passive-event-listeners.md +48 -0
  1003. package/src/resources/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
  1004. package/src/resources/skills/react-best-practices/rules/js-batch-dom-css.md +107 -0
  1005. package/src/resources/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
  1006. package/src/resources/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
  1007. package/src/resources/skills/react-best-practices/rules/js-cache-storage.md +70 -0
  1008. package/src/resources/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
  1009. package/src/resources/skills/react-best-practices/rules/js-early-exit.md +50 -0
  1010. package/src/resources/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
  1011. package/src/resources/skills/react-best-practices/rules/js-index-maps.md +37 -0
  1012. package/src/resources/skills/react-best-practices/rules/js-length-check-first.md +49 -0
  1013. package/src/resources/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
  1014. package/src/resources/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
  1015. package/src/resources/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
  1016. package/src/resources/skills/react-best-practices/rules/rendering-activity.md +26 -0
  1017. package/src/resources/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
  1018. package/src/resources/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
  1019. package/src/resources/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
  1020. package/src/resources/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
  1021. package/src/resources/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
  1022. package/src/resources/skills/react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
  1023. package/src/resources/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
  1024. package/src/resources/skills/react-best-practices/rules/rendering-usetransition-loading.md +75 -0
  1025. package/src/resources/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
  1026. package/src/resources/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
  1027. package/src/resources/skills/react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
  1028. package/src/resources/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
  1029. package/src/resources/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
  1030. package/src/resources/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
  1031. package/src/resources/skills/react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
  1032. package/src/resources/skills/react-best-practices/rules/rerender-memo.md +44 -0
  1033. package/src/resources/skills/react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
  1034. package/src/resources/skills/react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
  1035. package/src/resources/skills/react-best-practices/rules/rerender-transitions.md +40 -0
  1036. package/src/resources/skills/react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
  1037. package/src/resources/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
  1038. package/src/resources/skills/react-best-practices/rules/server-auth-actions.md +96 -0
  1039. package/src/resources/skills/react-best-practices/rules/server-cache-lru.md +41 -0
  1040. package/src/resources/skills/react-best-practices/rules/server-cache-react.md +76 -0
  1041. package/src/resources/skills/react-best-practices/rules/server-dedup-props.md +65 -0
  1042. package/src/resources/skills/react-best-practices/rules/server-parallel-fetching.md +83 -0
  1043. package/src/resources/skills/react-best-practices/rules/server-serialization.md +38 -0
  1044. package/src/resources/skills/userinterface-wiki/SKILL.md +253 -0
  1045. package/src/resources/skills/userinterface-wiki/rules/_sections.md +66 -0
  1046. package/src/resources/skills/userinterface-wiki/rules/_template.md +24 -0
  1047. package/src/resources/skills/userinterface-wiki/rules/a11y-reduced-motion-check.md +30 -0
  1048. package/src/resources/skills/userinterface-wiki/rules/a11y-toggle-setting.md +30 -0
  1049. package/src/resources/skills/userinterface-wiki/rules/a11y-visual-equivalent.md +36 -0
  1050. package/src/resources/skills/userinterface-wiki/rules/a11y-volume-control.md +28 -0
  1051. package/src/resources/skills/userinterface-wiki/rules/appropriate-confirmations-only.md +19 -0
  1052. package/src/resources/skills/userinterface-wiki/rules/appropriate-errors-warnings.md +18 -0
  1053. package/src/resources/skills/userinterface-wiki/rules/appropriate-no-decorative.md +21 -0
  1054. package/src/resources/skills/userinterface-wiki/rules/appropriate-no-high-frequency.md +28 -0
  1055. package/src/resources/skills/userinterface-wiki/rules/appropriate-no-punishing.md +27 -0
  1056. package/src/resources/skills/userinterface-wiki/rules/container-callback-ref.md +31 -0
  1057. package/src/resources/skills/userinterface-wiki/rules/container-guard-initial-zero.md +25 -0
  1058. package/src/resources/skills/userinterface-wiki/rules/container-no-excessive-use.md +13 -0
  1059. package/src/resources/skills/userinterface-wiki/rules/container-overflow-hidden.md +25 -0
  1060. package/src/resources/skills/userinterface-wiki/rules/container-transition-delay.md +21 -0
  1061. package/src/resources/skills/userinterface-wiki/rules/container-two-div-pattern.md +35 -0
  1062. package/src/resources/skills/userinterface-wiki/rules/container-use-resize-observer.md +48 -0
  1063. package/src/resources/skills/userinterface-wiki/rules/context-cleanup-nodes.md +25 -0
  1064. package/src/resources/skills/userinterface-wiki/rules/context-resume-suspended.md +28 -0
  1065. package/src/resources/skills/userinterface-wiki/rules/context-reuse-single.md +30 -0
  1066. package/src/resources/skills/userinterface-wiki/rules/design-filter-for-character.md +25 -0
  1067. package/src/resources/skills/userinterface-wiki/rules/design-noise-for-percussion.md +26 -0
  1068. package/src/resources/skills/userinterface-wiki/rules/design-oscillator-for-tonal.md +22 -0
  1069. package/src/resources/skills/userinterface-wiki/rules/duration-max-300ms.md +21 -0
  1070. package/src/resources/skills/userinterface-wiki/rules/duration-press-hover.md +21 -0
  1071. package/src/resources/skills/userinterface-wiki/rules/duration-shorten-before-curve.md +21 -0
  1072. package/src/resources/skills/userinterface-wiki/rules/duration-small-state.md +15 -0
  1073. package/src/resources/skills/userinterface-wiki/rules/easing-entrance-ease-out.md +21 -0
  1074. package/src/resources/skills/userinterface-wiki/rules/easing-exit-ease-in.md +21 -0
  1075. package/src/resources/skills/userinterface-wiki/rules/easing-for-state-change.md +27 -0
  1076. package/src/resources/skills/userinterface-wiki/rules/easing-linear-only-progress.md +21 -0
  1077. package/src/resources/skills/userinterface-wiki/rules/easing-natural-decay.md +22 -0
  1078. package/src/resources/skills/userinterface-wiki/rules/easing-no-linear-motion.md +22 -0
  1079. package/src/resources/skills/userinterface-wiki/rules/easing-transition-ease-in-out.md +15 -0
  1080. package/src/resources/skills/userinterface-wiki/rules/envelope-exponential-decay.md +21 -0
  1081. package/src/resources/skills/userinterface-wiki/rules/envelope-no-zero-target.md +21 -0
  1082. package/src/resources/skills/userinterface-wiki/rules/envelope-set-initial-value.md +22 -0
  1083. package/src/resources/skills/userinterface-wiki/rules/exit-key-required.md +29 -0
  1084. package/src/resources/skills/userinterface-wiki/rules/exit-matches-initial.md +29 -0
  1085. package/src/resources/skills/userinterface-wiki/rules/exit-prop-required.md +33 -0
  1086. package/src/resources/skills/userinterface-wiki/rules/exit-requires-wrapper.md +27 -0
  1087. package/src/resources/skills/userinterface-wiki/rules/impl-default-subtle.md +21 -0
  1088. package/src/resources/skills/userinterface-wiki/rules/impl-preload-audio.md +34 -0
  1089. package/src/resources/skills/userinterface-wiki/rules/impl-reset-current-time.md +26 -0
  1090. package/src/resources/skills/userinterface-wiki/rules/mode-pop-layout-for-lists.md +25 -0
  1091. package/src/resources/skills/userinterface-wiki/rules/mode-sync-layout-conflict.md +29 -0
  1092. package/src/resources/skills/userinterface-wiki/rules/mode-wait-doubles-duration.md +25 -0
  1093. package/src/resources/skills/userinterface-wiki/rules/morphing-aria-hidden.md +21 -0
  1094. package/src/resources/skills/userinterface-wiki/rules/morphing-consistent-viewbox.md +23 -0
  1095. package/src/resources/skills/userinterface-wiki/rules/morphing-group-variants.md +33 -0
  1096. package/src/resources/skills/userinterface-wiki/rules/morphing-jump-non-grouped.md +29 -0
  1097. package/src/resources/skills/userinterface-wiki/rules/morphing-reduced-motion.md +28 -0
  1098. package/src/resources/skills/userinterface-wiki/rules/morphing-spring-rotation.md +23 -0
  1099. package/src/resources/skills/userinterface-wiki/rules/morphing-strokelinecap-round.md +21 -0
  1100. package/src/resources/skills/userinterface-wiki/rules/morphing-three-lines.md +32 -0
  1101. package/src/resources/skills/userinterface-wiki/rules/morphing-use-collapsed.md +33 -0
  1102. package/src/resources/skills/userinterface-wiki/rules/native-backdrop-styling.md +27 -0
  1103. package/src/resources/skills/userinterface-wiki/rules/native-placeholder-styling.md +27 -0
  1104. package/src/resources/skills/userinterface-wiki/rules/native-selection-styling.md +18 -0
  1105. package/src/resources/skills/userinterface-wiki/rules/nested-consistent-timing.md +25 -0
  1106. package/src/resources/skills/userinterface-wiki/rules/nested-propagate-required.md +41 -0
  1107. package/src/resources/skills/userinterface-wiki/rules/none-context-menu-entrance.md +25 -0
  1108. package/src/resources/skills/userinterface-wiki/rules/none-high-frequency.md +29 -0
  1109. package/src/resources/skills/userinterface-wiki/rules/none-keyboard-navigation.md +32 -0
  1110. package/src/resources/skills/userinterface-wiki/rules/param-click-duration.md +21 -0
  1111. package/src/resources/skills/userinterface-wiki/rules/param-filter-frequency-range.md +21 -0
  1112. package/src/resources/skills/userinterface-wiki/rules/param-q-value-range.md +21 -0
  1113. package/src/resources/skills/userinterface-wiki/rules/param-reasonable-gain.md +21 -0
  1114. package/src/resources/skills/userinterface-wiki/rules/physics-active-state.md +23 -0
  1115. package/src/resources/skills/userinterface-wiki/rules/physics-no-excessive-stagger.md +22 -0
  1116. package/src/resources/skills/userinterface-wiki/rules/physics-spring-for-overshoot.md +23 -0
  1117. package/src/resources/skills/userinterface-wiki/rules/physics-subtle-deformation.md +22 -0
  1118. package/src/resources/skills/userinterface-wiki/rules/prefetch-hit-slop.md +27 -0
  1119. package/src/resources/skills/userinterface-wiki/rules/prefetch-keyboard-tab.md +19 -0
  1120. package/src/resources/skills/userinterface-wiki/rules/prefetch-not-everything.md +22 -0
  1121. package/src/resources/skills/userinterface-wiki/rules/prefetch-touch-fallback.md +34 -0
  1122. package/src/resources/skills/userinterface-wiki/rules/prefetch-trajectory-over-hover.md +32 -0
  1123. package/src/resources/skills/userinterface-wiki/rules/prefetch-use-selectively.md +13 -0
  1124. package/src/resources/skills/userinterface-wiki/rules/presence-disable-interactions.md +31 -0
  1125. package/src/resources/skills/userinterface-wiki/rules/presence-hook-in-child.md +31 -0
  1126. package/src/resources/skills/userinterface-wiki/rules/presence-safe-to-remove.md +37 -0
  1127. package/src/resources/skills/userinterface-wiki/rules/pseudo-content-required.md +28 -0
  1128. package/src/resources/skills/userinterface-wiki/rules/pseudo-first-line-styling.md +27 -0
  1129. package/src/resources/skills/userinterface-wiki/rules/pseudo-hit-target-expansion.md +31 -0
  1130. package/src/resources/skills/userinterface-wiki/rules/pseudo-marker-styling.md +28 -0
  1131. package/src/resources/skills/userinterface-wiki/rules/pseudo-over-dom-node.md +32 -0
  1132. package/src/resources/skills/userinterface-wiki/rules/pseudo-position-relative-parent.md +33 -0
  1133. package/src/resources/skills/userinterface-wiki/rules/pseudo-z-index-layering.md +37 -0
  1134. package/src/resources/skills/userinterface-wiki/rules/spring-for-gestures.md +27 -0
  1135. package/src/resources/skills/userinterface-wiki/rules/spring-for-interruptible.md +27 -0
  1136. package/src/resources/skills/userinterface-wiki/rules/spring-params-balanced.md +29 -0
  1137. package/src/resources/skills/userinterface-wiki/rules/spring-preserves-velocity.md +28 -0
  1138. package/src/resources/skills/userinterface-wiki/rules/staging-dim-background.md +22 -0
  1139. package/src/resources/skills/userinterface-wiki/rules/staging-one-focal-point.md +24 -0
  1140. package/src/resources/skills/userinterface-wiki/rules/staging-z-index-hierarchy.md +22 -0
  1141. package/src/resources/skills/userinterface-wiki/rules/timing-consistent.md +24 -0
  1142. package/src/resources/skills/userinterface-wiki/rules/timing-no-entrance-context-menu.md +22 -0
  1143. package/src/resources/skills/userinterface-wiki/rules/timing-under-300ms.md +22 -0
  1144. package/src/resources/skills/userinterface-wiki/rules/transition-name-cleanup.md +28 -0
  1145. package/src/resources/skills/userinterface-wiki/rules/transition-name-required.md +27 -0
  1146. package/src/resources/skills/userinterface-wiki/rules/transition-name-unique.md +24 -0
  1147. package/src/resources/skills/userinterface-wiki/rules/transition-over-js-library.md +32 -0
  1148. package/src/resources/skills/userinterface-wiki/rules/transition-style-pseudo-elements.md +24 -0
  1149. package/src/resources/skills/userinterface-wiki/rules/type-antialiased-on-retina.md +18 -0
  1150. package/src/resources/skills/userinterface-wiki/rules/type-disambiguation-stylistic-set.md +15 -0
  1151. package/src/resources/skills/userinterface-wiki/rules/type-font-display-swap.md +28 -0
  1152. package/src/resources/skills/userinterface-wiki/rules/type-justify-with-hyphens.md +24 -0
  1153. package/src/resources/skills/userinterface-wiki/rules/type-letter-spacing-uppercase.md +28 -0
  1154. package/src/resources/skills/userinterface-wiki/rules/type-no-font-synthesis.md +18 -0
  1155. package/src/resources/skills/userinterface-wiki/rules/type-oldstyle-nums-for-prose.md +21 -0
  1156. package/src/resources/skills/userinterface-wiki/rules/type-opentype-contextual-alternates.md +15 -0
  1157. package/src/resources/skills/userinterface-wiki/rules/type-optical-sizing-auto.md +25 -0
  1158. package/src/resources/skills/userinterface-wiki/rules/type-proper-fractions.md +15 -0
  1159. package/src/resources/skills/userinterface-wiki/rules/type-slashed-zero.md +17 -0
  1160. package/src/resources/skills/userinterface-wiki/rules/type-tabular-nums-for-data.md +21 -0
  1161. package/src/resources/skills/userinterface-wiki/rules/type-text-wrap-balance-headings.md +21 -0
  1162. package/src/resources/skills/userinterface-wiki/rules/type-text-wrap-pretty.md +16 -0
  1163. package/src/resources/skills/userinterface-wiki/rules/type-underline-offset.md +25 -0
  1164. package/src/resources/skills/userinterface-wiki/rules/type-variable-weight-continuous.md +23 -0
  1165. package/src/resources/skills/userinterface-wiki/rules/ux-aesthetic-usability.md +32 -0
  1166. package/src/resources/skills/userinterface-wiki/rules/ux-cognitive-load-reduce.md +49 -0
  1167. package/src/resources/skills/userinterface-wiki/rules/ux-common-region-boundaries.md +50 -0
  1168. package/src/resources/skills/userinterface-wiki/rules/ux-doherty-perceived-speed.md +29 -0
  1169. package/src/resources/skills/userinterface-wiki/rules/ux-doherty-under-400ms.md +30 -0
  1170. package/src/resources/skills/userinterface-wiki/rules/ux-fitts-hit-area.md +32 -0
  1171. package/src/resources/skills/userinterface-wiki/rules/ux-fitts-target-size.md +31 -0
  1172. package/src/resources/skills/userinterface-wiki/rules/ux-goal-gradient-progress.md +33 -0
  1173. package/src/resources/skills/userinterface-wiki/rules/ux-hicks-minimize-choices.md +45 -0
  1174. package/src/resources/skills/userinterface-wiki/rules/ux-jakobs-familiar-patterns.md +37 -0
  1175. package/src/resources/skills/userinterface-wiki/rules/ux-millers-chunking.md +23 -0
  1176. package/src/resources/skills/userinterface-wiki/rules/ux-pareto-prioritize-features.md +36 -0
  1177. package/src/resources/skills/userinterface-wiki/rules/ux-peak-end-finish-strong.md +35 -0
  1178. package/src/resources/skills/userinterface-wiki/rules/ux-postels-accept-messy-input.md +45 -0
  1179. package/src/resources/skills/userinterface-wiki/rules/ux-pragnanz-simplify.md +33 -0
  1180. package/src/resources/skills/userinterface-wiki/rules/ux-progressive-disclosure.md +41 -0
  1181. package/src/resources/skills/userinterface-wiki/rules/ux-proximity-grouping.md +38 -0
  1182. package/src/resources/skills/userinterface-wiki/rules/ux-serial-position.md +31 -0
  1183. package/src/resources/skills/userinterface-wiki/rules/ux-similarity-consistency.md +35 -0
  1184. package/src/resources/skills/userinterface-wiki/rules/ux-teslers-complexity.md +28 -0
  1185. package/src/resources/skills/userinterface-wiki/rules/ux-uniform-connectedness.md +43 -0
  1186. package/src/resources/skills/userinterface-wiki/rules/ux-von-restorff-emphasis.md +29 -0
  1187. package/src/resources/skills/userinterface-wiki/rules/ux-zeigarnik-show-incomplete.md +36 -0
  1188. package/src/resources/skills/userinterface-wiki/rules/visual-animate-shadow-pseudo.md +49 -0
  1189. package/src/resources/skills/userinterface-wiki/rules/visual-border-alpha-colors.md +25 -0
  1190. package/src/resources/skills/userinterface-wiki/rules/visual-button-shadow-anatomy.md +49 -0
  1191. package/src/resources/skills/userinterface-wiki/rules/visual-concentric-radius.md +40 -0
  1192. package/src/resources/skills/userinterface-wiki/rules/visual-consistent-spacing-scale.md +35 -0
  1193. package/src/resources/skills/userinterface-wiki/rules/visual-layered-shadows.md +30 -0
  1194. package/src/resources/skills/userinterface-wiki/rules/visual-no-pure-black-shadow.md +25 -0
  1195. package/src/resources/skills/userinterface-wiki/rules/visual-shadow-direction.md +25 -0
  1196. package/src/resources/skills/userinterface-wiki/rules/visual-shadow-matches-elevation.md +23 -0
  1197. package/src/resources/skills/userinterface-wiki/rules/weight-duration-matches-action.md +29 -0
  1198. package/src/resources/skills/userinterface-wiki/rules/weight-match-action.md +32 -0
  1199. package/src/resources/skills/web-design-guidelines/SKILL.md +39 -0
  1200. package/src/resources/skills/web-quality-audit/SKILL.md +170 -0
  1201. package/src/resources/skills/web-quality-audit/scripts/analyze.sh +91 -0
  1202. package/dist/resources/extensions/gsd/complexity.ts +0 -237
  1203. package/dist/resources/extensions/gsd/github-client.ts +0 -235
  1204. package/dist/resources/extensions/gsd/mcp-server.ts +0 -108
  1205. package/dist/resources/extensions/gsd/tests/activity-log-prune.test.ts +0 -297
  1206. package/dist/resources/extensions/gsd/tests/activity-log-save.test.ts +0 -127
  1207. package/dist/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +0 -110
  1208. package/dist/resources/extensions/gsd/tests/auto-draft-pause.test.ts +0 -115
  1209. package/dist/resources/extensions/gsd/tests/complexity-routing.test.ts +0 -294
  1210. package/dist/resources/extensions/gsd/tests/marketplace-discovery.test.ts +0 -202
  1211. package/dist/resources/extensions/gsd/tests/metrics-io.test.ts +0 -176
  1212. package/dist/resources/extensions/gsd/tests/network-error-fallback.test.ts +0 -104
  1213. package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +0 -120
  1214. package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -226
  1215. package/dist/resources/extensions/gsd/tests/preferences-mode.test.ts +0 -110
  1216. package/dist/resources/extensions/gsd/tests/preferences-models.test.ts +0 -207
  1217. package/dist/resources/extensions/gsd/tests/preferences-schema-validation.test.ts +0 -183
  1218. package/dist/resources/extensions/gsd/tests/preferences-wizard-fields.test.ts +0 -168
  1219. package/dist/resources/extensions/shared/bundled-extension-paths.ts +0 -11
  1220. package/src/resources/extensions/gsd/complexity.ts +0 -237
  1221. package/src/resources/extensions/gsd/github-client.ts +0 -235
  1222. package/src/resources/extensions/gsd/mcp-server.ts +0 -108
  1223. package/src/resources/extensions/gsd/tests/activity-log-prune.test.ts +0 -297
  1224. package/src/resources/extensions/gsd/tests/activity-log-save.test.ts +0 -127
  1225. package/src/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +0 -110
  1226. package/src/resources/extensions/gsd/tests/auto-draft-pause.test.ts +0 -115
  1227. package/src/resources/extensions/gsd/tests/complexity-routing.test.ts +0 -294
  1228. package/src/resources/extensions/gsd/tests/marketplace-discovery.test.ts +0 -202
  1229. package/src/resources/extensions/gsd/tests/metrics-io.test.ts +0 -176
  1230. package/src/resources/extensions/gsd/tests/network-error-fallback.test.ts +0 -104
  1231. package/src/resources/extensions/gsd/tests/preferences-git.test.ts +0 -120
  1232. package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -226
  1233. package/src/resources/extensions/gsd/tests/preferences-mode.test.ts +0 -110
  1234. package/src/resources/extensions/gsd/tests/preferences-models.test.ts +0 -207
  1235. package/src/resources/extensions/gsd/tests/preferences-schema-validation.test.ts +0 -183
  1236. package/src/resources/extensions/gsd/tests/preferences-wizard-fields.test.ts +0 -168
  1237. package/src/resources/extensions/shared/bundled-extension-paths.ts +0 -11
@@ -33,6 +33,12 @@ import { invalidateAllCaches } from "./cache.js";
33
33
  import { saveActivityLog, clearActivityLogState } from "./activity-log.js";
34
34
  import { synthesizeCrashRecovery, getDeepDiagnostic } from "./session-forensics.js";
35
35
  import { writeLock, clearLock, readCrashLock, formatCrashInfo, isLockProcessAlive } from "./crash-recovery.js";
36
+ import {
37
+ acquireSessionLock,
38
+ validateSessionLock,
39
+ releaseSessionLock,
40
+ updateSessionLock,
41
+ } from "./session-lock.js";
36
42
  import {
37
43
  clearUnitRuntimeRecord,
38
44
  inspectExecuteTaskDurability,
@@ -70,7 +76,6 @@ import {
70
76
  checkResourcesStale,
71
77
  escapeStaleWorktree,
72
78
  } from "./auto-worktree-sync.js";
73
- // complexity-classifier + model-router imports moved to auto-model-selection.ts
74
79
  import { initRoutingHistory, resetRoutingHistory, recordOutcome } from "./routing-history.js";
75
80
  import {
76
81
  checkPostUnitHooks,
@@ -83,7 +88,6 @@ import {
83
88
  restoreHookState,
84
89
  clearPersistedHookState,
85
90
  } from "./post-unit-hooks.js";
86
- // observability-validator imports moved to auto-observability.ts
87
91
  import { ensureGitignore, untrackRuntimeFiles } from "./gitignore.js";
88
92
  import { runGSDDoctor, rebuildState, summarizeDoctorIssues } from "./doctor.js";
89
93
  import {
@@ -101,10 +105,12 @@ import {
101
105
  getProjectTotals, formatCost, formatTokenCount,
102
106
  } from "./metrics.js";
103
107
  import { computeBudgets, resolveExecutorContextWindow } from "./context-budget.js";
108
+ import { GSDError, GSD_ARTIFACT_MISSING } from "./errors.js";
104
109
  import { join } from "node:path";
105
110
  import { sep as pathSep } from "node:path";
106
111
  import { readdirSync, readFileSync, existsSync, mkdirSync, writeFileSync, unlinkSync, statSync } from "node:fs";
107
- import { nativeIsRepo, nativeInit, nativeAddPaths, nativeCommit } from "./native-git-bridge.js";
112
+ import { atomicWriteSync } from "./atomic-write.js";
113
+ import { nativeIsRepo, nativeInit, nativeAddAll, nativeCommit } from "./native-git-bridge.js";
108
114
  import {
109
115
  autoCommitCurrentBranch,
110
116
  captureIntegrationBranch,
@@ -130,7 +136,7 @@ import {
130
136
  } from "./auto-worktree.js";
131
137
  import { pruneQueueOrder } from "./queue-order.js";
132
138
  import { consumeSignal } from "./session-status-io.js";
133
- import { showNextAction } from "../shared/next-action-ui.js";
139
+ import { showNextAction } from "../shared/mod.js";
134
140
  import { debugLog, debugTime, debugCount, debugPeak, enableDebug, isDebugEnabled, writeDebugSummary, getDebugLogPath } from "./debug-logger.js";
135
141
  import {
136
142
  resolveExpectedArtifactPath,
@@ -147,7 +153,6 @@ import {
147
153
  reconcileMergeState,
148
154
  } from "./auto-recovery.js";
149
155
  import { resolveDispatch, resetRewriteCircuitBreaker } from "./auto-dispatch.js";
150
- // Prompt builders moved to auto-direct-dispatch.ts (only used there now)
151
156
  import {
152
157
  type AutoDashboardData,
153
158
  updateProgressWidget as _updateProgressWidget,
@@ -168,6 +173,14 @@ import {
168
173
  import { isDbAvailable } from "./gsd-db.js";
169
174
  import { hasPendingCaptures, loadPendingCaptures, countPendingCaptures } from "./captures.js";
170
175
 
176
+ // ── Extracted modules ──────────────────────────────────────────────────────
177
+ import { startUnitSupervision, type SupervisionContext } from "./auto-timers.js";
178
+ import { checkIdempotency, type IdempotencyContext } from "./auto-idempotency.js";
179
+ import { checkStuckAndRecover, type StuckContext } from "./auto-stuck-detection.js";
180
+ import { runPostUnitVerification, type VerificationContext } from "./auto-verification.js";
181
+ import { postUnitPreVerification, postUnitPostVerification, type PostUnitContext } from "./auto-post-unit.js";
182
+ import { bootstrapAutoSession, type BootstrapDeps } from "./auto-start.js";
183
+
171
184
  // Worktree sync, resource staleness, stale worktree escape → auto-worktree-sync.ts
172
185
 
173
186
  // ─── Session State ─────────────────────────────────────────────────────────
@@ -176,14 +189,28 @@ import {
176
189
  AutoSession,
177
190
  MAX_UNIT_DISPATCHES, STUB_RECOVERY_THRESHOLD, MAX_LIFETIME_DISPATCHES,
178
191
  MAX_CONSECUTIVE_SKIPS, DISPATCH_GAP_TIMEOUT_MS, MAX_SKIP_DEPTH,
192
+ NEW_SESSION_TIMEOUT_MS, DISPATCH_HANG_TIMEOUT_MS,
179
193
  } from "./auto/session.js";
180
194
  import type { CompletedUnit, CurrentUnit, UnitRouting, StartModel, PendingVerificationRetry } from "./auto/session.js";
181
195
  export {
182
196
  MAX_UNIT_DISPATCHES, STUB_RECOVERY_THRESHOLD, MAX_LIFETIME_DISPATCHES,
183
197
  MAX_CONSECUTIVE_SKIPS, DISPATCH_GAP_TIMEOUT_MS, MAX_SKIP_DEPTH,
198
+ NEW_SESSION_TIMEOUT_MS, DISPATCH_HANG_TIMEOUT_MS,
184
199
  } from "./auto/session.js";
185
200
  export type { CompletedUnit, CurrentUnit, UnitRouting, StartModel } from "./auto/session.js";
186
201
 
202
+ // ── ENCAPSULATION INVARIANT ─────────────────────────────────────────────────
203
+ // ALL mutable auto-mode state lives in the AutoSession class (auto/session.ts).
204
+ // This file must NOT declare module-level `let` or `var` variables for state.
205
+ // The single `s` instance below is the only mutable module-level binding.
206
+ //
207
+ // When adding features or fixing bugs:
208
+ // - New mutable state → add a property to AutoSession, not a module-level variable
209
+ // - New constants → module-level `const` is fine (immutable)
210
+ // - New state that needs reset on stopAuto → add to AutoSession.reset()
211
+ //
212
+ // Tests in auto-session-encapsulation.test.ts enforce this invariant.
213
+ // ─────────────────────────────────────────────────────────────────────────────
187
214
  const s = new AutoSession();
188
215
 
189
216
  /** Throttle STATE.md rebuilds — at most once per 30 seconds */
@@ -291,6 +318,15 @@ export function isAutoPaused(): boolean {
291
318
  return s.paused;
292
319
  }
293
320
 
321
+ /**
322
+ * Return the model captured at auto-mode start for this session.
323
+ * Used by error-recovery to fall back to the session's own model
324
+ * instead of reading (potentially stale) preferences from disk (#1065).
325
+ */
326
+ export function getAutoModeStartModel(): { provider: string; id: string } | null {
327
+ return s.autoModeStartModel;
328
+ }
329
+
294
330
  // Tool tracking — delegates to auto-tool-tracking.ts
295
331
  export function markToolStart(toolCallId: string): void {
296
332
  _markToolStart(toolCallId, s.active);
@@ -396,8 +432,6 @@ function startDispatchGapWatchdog(ctx: ExtensionContext, pi: ExtensionAPI): void
396
432
  s.dispatchGapHandle = null;
397
433
  if (!s.active || !s.cmdCtx) return;
398
434
 
399
- // Auto-mode is active but no unit was dispatched — the state machine stalled.
400
- // Re-derive state and attempt a fresh dispatch.
401
435
  if (s.verbose) {
402
436
  ctx.ui.notify(
403
437
  "Dispatch gap detected — re-evaluating state.",
@@ -413,9 +447,6 @@ function startDispatchGapWatchdog(ctx: ExtensionContext, pi: ExtensionAPI): void
413
447
  return;
414
448
  }
415
449
 
416
- // If dispatchNextUnit returned normally but still didn't dispatch a unit
417
- // (no sendMessage called → no timeout set), auto-mode is permanently
418
- // stalled. Stop cleanly instead of leaving it s.active but idle (#537).
419
450
  if (s.active && !s.unitTimeoutHandle && !s.wrapupWarningHandle) {
420
451
  await stopAuto(ctx, pi, "Stalled — no dispatchable unit after retry");
421
452
  }
@@ -426,7 +457,10 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
426
457
  if (!s.active && !s.paused) return;
427
458
  const reasonSuffix = reason ? ` — ${reason}` : "";
428
459
  clearUnitTimeout();
429
- if (lockBase()) clearLock(lockBase());
460
+ if (lockBase()) {
461
+ releaseSessionLock(lockBase());
462
+ clearLock(lockBase());
463
+ }
430
464
  clearSkillSnapshot();
431
465
  resetSkillTelemetry();
432
466
  s.dispatching = false;
@@ -436,13 +470,9 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
436
470
  deregisterSigtermHandler();
437
471
 
438
472
  // ── Auto-worktree: exit worktree and reset s.basePath on stop ──
439
- // Preserve the milestone branch so the next /gsd auto can re-enter
440
- // where it left off. The branch is only deleted during milestone
441
- // completion (mergeMilestoneToMain) after the work has been squash-merged.
442
473
  if (s.currentMilestoneId && isInAutoWorktree(s.basePath)) {
443
474
  try {
444
- // Auto-commit any dirty state before leaving so work isn't lost
445
- try { autoCommitCurrentBranch(s.basePath, "stop", s.currentMilestoneId); } catch { /* non-fatal */ }
475
+ try { autoCommitCurrentBranch(s.basePath, "stop", s.currentMilestoneId); } catch (e) { debugLog("stop-auto-commit-failed", { error: e instanceof Error ? e.message : String(e) }); }
446
476
  teardownAutoWorktree(s.originalBasePath, s.currentMilestoneId, { preserveBranch: true });
447
477
  s.basePath = s.originalBasePath;
448
478
  s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
@@ -460,13 +490,9 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
460
490
  try {
461
491
  const { closeDatabase } = await import("./gsd-db.js");
462
492
  closeDatabase();
463
- } catch { /* non-fatal */ }
493
+ } catch (e) { debugLog("db-close-failed", { error: e instanceof Error ? e.message : String(e) }); }
464
494
  }
465
495
 
466
- // Always restore cwd to project root on stop (#608).
467
- // Even if isInAutoWorktree returned false (e.g., module state was already
468
- // cleared by mergeMilestoneToMain), the process cwd may still be inside
469
- // the worktree directory. Force it back to s.originalBasePath.
470
496
  if (s.originalBasePath) {
471
497
  s.basePath = s.originalBasePath;
472
498
  try { process.chdir(s.basePath); } catch { /* best-effort */ }
@@ -483,12 +509,10 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
483
509
  ctx?.ui.notify(`Auto-mode stopped${reasonSuffix}.`, "info");
484
510
  }
485
511
 
486
- // Sync disk state so next resume starts from accurate state
487
512
  if (s.basePath) {
488
- try { await rebuildState(s.basePath); } catch { /* non-fatal */ }
513
+ try { await rebuildState(s.basePath); } catch (e) { debugLog("stop-rebuild-state-failed", { error: e instanceof Error ? e.message : String(e) }); }
489
514
  }
490
515
 
491
- // Write debug summary before resetting state
492
516
  if (isDebugEnabled()) {
493
517
  const logPath = writeDebugSummary();
494
518
  if (logPath) {
@@ -529,7 +553,6 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
529
553
  ctx?.ui.setWidget("gsd-progress", undefined);
530
554
  ctx?.ui.setFooter(undefined);
531
555
 
532
- // Restore the user's original model
533
556
  if (pi && ctx && s.originalModelId && s.originalModelProvider) {
534
557
  const original = ctx.modelRegistry.find(s.originalModelProvider, s.originalModelId);
535
558
  if (original) await pi.setModel(original);
@@ -549,22 +572,19 @@ export async function pauseAuto(ctx?: ExtensionContext, _pi?: ExtensionAPI): Pro
549
572
  if (!s.active) return;
550
573
  clearUnitTimeout();
551
574
 
552
- // Capture the current session file before clearing state — used for
553
- // recovery briefing on resume so the next agent knows what already happened.
554
575
  s.pausedSessionFile = ctx?.sessionManager?.getSessionFile() ?? null;
555
576
 
556
- if (lockBase()) clearLock(lockBase());
577
+ if (lockBase()) {
578
+ releaseSessionLock(lockBase());
579
+ clearLock(lockBase());
580
+ }
557
581
 
558
- // Remove SIGTERM handler registered at auto-mode start
559
582
  deregisterSigtermHandler();
560
583
 
561
584
  s.active = false;
562
585
  s.paused = true;
563
586
  s.pendingVerificationRetry = null;
564
587
  s.verificationRetryCount.clear();
565
- // Preserve: s.unitDispatchCount, s.currentUnit, s.basePath, s.verbose, s.cmdCtx,
566
- // s.completedUnits, s.autoStartTime, s.currentMilestoneId, s.originalModelId
567
- // — all needed for resume and dashboard display
568
588
  ctx?.ui.setStatus("gsd-auto", "paused");
569
589
  ctx?.ui.setWidget("gsd-progress", undefined);
570
590
  ctx?.ui.setFooter(undefined);
@@ -586,31 +606,33 @@ export async function startAuto(
586
606
  const requestedStepMode = options?.step ?? false;
587
607
 
588
608
  // Escape stale worktree cwd from a previous milestone (#608).
589
- // After milestone merge + worktree removal, the process cwd may still point
590
- // inside .gsd/worktrees/<MID>/ — detect and chdir back to project root.
591
609
  base = escapeStaleWorktree(base);
592
610
 
593
611
  // If resuming from paused state, just re-activate and dispatch next unit.
594
- // The conversation is still intact — no need to reinitialize everything.
595
612
  if (s.paused) {
613
+ // Re-acquire session lock before resuming
614
+ const resumeLock = acquireSessionLock(base);
615
+ if (!resumeLock.acquired) {
616
+ ctx.ui.notify(
617
+ `Cannot resume: ${resumeLock.reason}`,
618
+ "error",
619
+ );
620
+ return;
621
+ }
622
+
596
623
  s.paused = false;
597
624
  s.active = true;
598
625
  s.verbose = verboseMode;
599
- // Allow switching between step/auto on resume
600
626
  s.stepMode = requestedStepMode;
601
627
  s.cmdCtx = ctx;
602
628
  s.basePath = base;
603
629
  s.unitDispatchCount.clear();
604
630
  s.unitLifetimeDispatches.clear();
605
631
  s.unitConsecutiveSkips.clear();
606
- // Re-initialize metrics in case ledger was lost during pause
607
632
  if (!getLedger()) initMetrics(base);
608
- // Ensure milestone ID is set on git service for integration branch resolution
609
633
  if (s.currentMilestoneId) setActiveMilestoneId(base, s.currentMilestoneId);
610
634
 
611
- // ── Auto-worktree: re-enter worktree on resume if not already inside ──
612
- // Skip if already inside a worktree (manual /worktree) to prevent nesting.
613
- // Skip entirely in branch or none isolation mode (#531).
635
+ // ── Auto-worktree: re-enter worktree on resume ──
614
636
  if (s.currentMilestoneId && shouldUseWorktreeIsolation() && s.originalBasePath && !isInAutoWorktree(s.basePath) && !detectWorktreeName(s.basePath) && !detectWorktreeName(s.originalBasePath)) {
615
637
  try {
616
638
  const existingWtPath = getAutoWorktreePath(s.originalBasePath, s.currentMilestoneId);
@@ -620,7 +642,6 @@ export async function startAuto(
620
642
  s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
621
643
  ctx.ui.notify(`Re-entered auto-worktree at ${wtPath}`, "info");
622
644
  } else {
623
- // Worktree was deleted while paused — recreate it.
624
645
  const wtPath = createAutoWorktree(s.originalBasePath, s.currentMilestoneId);
625
646
  s.basePath = wtPath;
626
647
  s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
@@ -634,28 +655,22 @@ export async function startAuto(
634
655
  }
635
656
  }
636
657
 
637
- // Re-register SIGTERM handler for the resumed session (use original base for lock)
638
658
  registerSigtermHandler(lockBase());
639
659
 
640
660
  ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
641
661
  ctx.ui.setFooter(hideFooter);
642
662
  ctx.ui.notify(s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "info");
643
- // Restore hook state from disk in case session was interrupted
644
663
  restoreHookState(s.basePath);
645
- // Rebuild disk state before resuming user interaction during pause may have changed files
646
- try { await rebuildState(s.basePath); } catch { /* non-fatal */ }
664
+ try { await rebuildState(s.basePath); } catch (e) { debugLog("resume-rebuild-state-failed", { error: e instanceof Error ? e.message : String(e) }); }
647
665
  try {
648
666
  const report = await runGSDDoctor(s.basePath, { fix: true });
649
667
  if (report.fixesApplied.length > 0) {
650
668
  ctx.ui.notify(`Resume: applied ${report.fixesApplied.length} fix(es) to state.`, "info");
651
669
  }
652
- } catch { /* non-fatal */ }
653
- // Self-heal: clear stale runtime records where artifacts already exist
670
+ } catch (e) { debugLog("resume-doctor-failed", { error: e instanceof Error ? e.message : String(e) }); }
654
671
  await selfHealRuntimeRecords(s.basePath, ctx, s.completedKeySet);
655
672
  invalidateAllCaches();
656
673
 
657
- // Synthesize recovery briefing from the paused session so the next agent
658
- // knows what already happened (reuses crash recovery infrastructure).
659
674
  if (s.pausedSessionFile) {
660
675
  const activityDir = join(gsdRoot(s.basePath), "activity");
661
676
  const recovery = synthesizeCrashRecovery(
@@ -674,462 +689,54 @@ export async function startAuto(
674
689
  s.pausedSessionFile = null;
675
690
  }
676
691
 
677
- // Write lock on resume so cross-process status detection works (#723).
678
- writeLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown", s.completedUnits.length);
679
-
680
- await dispatchNextUnit(ctx, pi);
681
- return;
682
- }
683
-
684
- // Ensure git repo exists — GSD needs it for commits and state tracking
685
- if (!nativeIsRepo(base)) {
686
- const mainBranch = loadEffectiveGSDPreferences()?.preferences?.git?.main_branch || "main";
687
- nativeInit(base, mainBranch);
688
- }
689
-
690
- // Ensure .gitignore has baseline patterns
691
- const gitPrefs = loadEffectiveGSDPreferences()?.preferences?.git;
692
- const commitDocs = gitPrefs?.commit_docs;
693
- const manageGitignore = gitPrefs?.manage_gitignore;
694
- ensureGitignore(base, { commitDocs, manageGitignore });
695
- if (manageGitignore !== false) untrackRuntimeFiles(base);
696
-
697
- // Bootstrap .gsd/ if it doesn't exist
698
- const gsdDir = join(base, ".gsd");
699
- if (!existsSync(gsdDir)) {
700
- mkdirSync(join(gsdDir, "milestones"), { recursive: true });
701
- // Only commit .gsd/ init when commit_docs is not explicitly false
702
- if (commitDocs !== false) {
692
+ // If resuming from a secrets pause, re-collect before dispatching (#1146)
693
+ if (s.pausedForSecrets && s.currentMilestoneId) {
703
694
  try {
704
- nativeAddPaths(base, [".gsd", ".gitignore"]);
705
- nativeCommit(base, "chore: init gsd");
706
- } catch { /* nothing to commit */ }
707
- }
708
- }
709
-
710
- // Initialize GitServiceImpl — s.basePath is set and git repo confirmed
711
- s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
712
-
713
- // Check for crash from previous session
714
- const crashLock = readCrashLock(base);
715
- if (crashLock) {
716
- if (isLockProcessAlive(crashLock)) {
717
- // The lock belongs to a process that is still running not a crash.
718
- // Warn the user and abort to avoid two concurrent auto-mode sessions.
719
- ctx.ui.notify(
720
- `Another auto-mode session (PID ${crashLock.pid}) appears to be running.\nStop it with \`kill ${crashLock.pid}\` before starting a new session.`,
721
- "error",
722
- );
723
- return;
724
- }
725
- // Stale lock from a dead process — validate before synthesizing recovery context.
726
- // If the recovered unit belongs to a fully-completed milestone (SUMMARY exists),
727
- // discard recovery context to prevent phantom skip loops (#790).
728
- const recoveredMid = crashLock.unitId.split("/")[0];
729
- const milestoneAlreadyComplete = recoveredMid
730
- ? !!resolveMilestoneFile(base, recoveredMid, "SUMMARY")
731
- : false;
732
-
733
- if (milestoneAlreadyComplete) {
734
- ctx.ui.notify(
735
- `Crash recovery: discarding stale context for ${crashLock.unitId} — milestone ${recoveredMid} is already complete.`,
736
- "info",
737
- );
738
- } else {
739
- const activityDir = join(gsdRoot(base), "activity");
740
- const recovery = synthesizeCrashRecovery(
741
- base, crashLock.unitType, crashLock.unitId,
742
- crashLock.sessionFile, activityDir,
743
- );
744
- if (recovery && recovery.trace.toolCallCount > 0) {
745
- s.pendingCrashRecovery = recovery.prompt;
746
- ctx.ui.notify(
747
- `${formatCrashInfo(crashLock)}\nRecovered ${recovery.trace.toolCallCount} tool calls from crashed session. Resuming with full context.`,
748
- "warning",
749
- );
750
- } else {
695
+ const manifestStatus = await getManifestStatus(s.basePath, s.currentMilestoneId);
696
+ if (manifestStatus && manifestStatus.pending.length > 0) {
697
+ const result = await collectSecretsFromManifest(s.basePath, s.currentMilestoneId, ctx);
698
+ if (result && result.applied.length > 0) {
699
+ ctx.ui.notify(
700
+ `Secrets collected: ${result.applied.length} applied, ${result.skipped.length} skipped, ${result.existingSkipped.length} already set.`,
701
+ "info",
702
+ );
703
+ } else if (result && result.applied.length === 0 && result.skipped.length > 0) {
704
+ // All keys were skipped still pending, re-pause
705
+ s.paused = true;
706
+ s.active = false;
707
+ ctx.ui.notify(
708
+ `All env variables were skipped. Auto-mode remains paused.\nCollect them with /gsd secrets, then resume with /gsd auto.`,
709
+ "warning",
710
+ );
711
+ ctx.ui.setStatus("gsd-auto", "paused");
712
+ return;
713
+ }
714
+ }
715
+ } catch (err) {
751
716
  ctx.ui.notify(
752
- `${formatCrashInfo(crashLock)}\nNo session data recovered. Resuming from disk state.`,
717
+ `Secrets check error: ${err instanceof Error ? err.message : String(err)}. Continuing without secrets.`,
753
718
  "warning",
754
719
  );
755
720
  }
721
+ s.pausedForSecrets = false;
756
722
  }
757
- clearLock(base);
758
- }
759
723
 
760
- // ── Debug mode: env-var activation ──────────────────────────────────────
761
- if (!isDebugEnabled() && process.env.GSD_DEBUG === "1") {
762
- enableDebug(base);
763
- }
764
- if (isDebugEnabled()) {
765
- const { isNativeParserAvailable } = await import("./native-parser-bridge.js");
766
- debugLog("debug-start", {
767
- platform: process.platform,
768
- arch: process.arch,
769
- node: process.version,
770
- model: ctx.model?.id ?? "unknown",
771
- provider: ctx.model?.provider ?? "unknown",
772
- nativeParser: isNativeParserAvailable(),
773
- cwd: base,
774
- });
775
- ctx.ui.notify(`Debug logging enabled → ${getDebugLogPath()}`, "info");
776
- }
777
-
778
- // Invalidate all caches before initial state derivation to ensure we read
779
- // fresh disk state. Without this, a stale cache from a prior session (e.g.
780
- // after a discussion that wrote new artifacts) may cause deriveState to
781
- // return pre-planning when the roadmap already exists (#800).
782
- invalidateAllCaches();
783
-
784
- // ── Clean stale runtime unit files for completed milestones (#887) ───────
785
- // After resource-update restart, stale runtime/units/*.json files from
786
- // previously completed milestones can cause deriveState to resume the wrong
787
- // milestone. If a milestone has a SUMMARY file, its unit files are stale.
788
- try {
789
- const runtimeUnitsDir = join(gsdRoot(base), "runtime", "units");
790
- if (existsSync(runtimeUnitsDir)) {
791
- for (const file of readdirSync(runtimeUnitsDir)) {
792
- if (!file.endsWith(".json")) continue;
793
- const midMatch = file.match(/(M\d+(?:-[a-z0-9]{6})?)/);
794
- if (!midMatch) continue;
795
- const mid = midMatch[1];
796
- if (resolveMilestoneFile(base, mid, "SUMMARY")) {
797
- try { unlinkSync(join(runtimeUnitsDir, file)); } catch { /* non-fatal */ }
798
- }
799
- }
800
- }
801
- } catch { /* non-fatal — don't block startup */ }
802
-
803
- let state = await deriveState(base);
804
-
805
- // ── Stale worktree state recovery (#654) ─────────────────────────────────
806
- // When auto-mode was previously stopped and restarted, the project root's
807
- // .gsd/ directory may have stale metadata (completed units showing as
808
- // incomplete). If an auto-worktree exists for the active milestone, it has
809
- // the current state — re-derive from there to avoid re-dispatching
810
- // finished work.
811
- if (
812
- state.activeMilestone &&
813
- shouldUseWorktreeIsolation() &&
814
- !detectWorktreeName(base)
815
- ) {
816
- const wtPath = getAutoWorktreePath(base, state.activeMilestone.id);
817
- if (wtPath) {
818
- state = await deriveState(wtPath);
819
- }
820
- }
821
-
822
- // ── Milestone branch recovery (#601) ─────────────────────────────────────
823
- // When auto-mode was previously stopped, the milestone branch is preserved
824
- // but the worktree is removed. The project root (integration branch) may
825
- // not have the roadmap/artifacts — they live on the milestone branch.
826
- // If state looks like pre-planning but a milestone branch exists with prior
827
- // work, skip the early-return checks and let worktree setup + dispatch
828
- // handle it correctly from the branch's state.
829
- let hasSurvivorBranch = false;
830
- if (
831
- state.activeMilestone &&
832
- (state.phase === "pre-planning" || state.phase === "needs-discussion") &&
833
- shouldUseWorktreeIsolation() &&
834
- !detectWorktreeName(base) &&
835
- !base.includes(`${pathSep}.gsd${pathSep}worktrees${pathSep}`)
836
- ) {
837
- const milestoneBranch = `milestone/${state.activeMilestone.id}`;
838
- const { nativeBranchExists } = await import("./native-git-bridge.js");
839
- hasSurvivorBranch = nativeBranchExists(base, milestoneBranch);
840
- if (hasSurvivorBranch) {
841
- ctx.ui.notify(
842
- `Found prior session branch ${milestoneBranch}. Resuming.`,
843
- "info",
844
- );
845
- }
846
- }
847
-
848
- if (!hasSurvivorBranch) {
849
- // No active work at all — start a new milestone via the discuss flow.
850
- // After discussion completes, checkAutoStartAfterDiscuss() (fired from
851
- // agent_end) will detect the new CONTEXT.md and restart auto mode.
852
- // If the LLM didn't follow the discussion protocol (e.g. started editing
853
- // files directly for a simple task), we re-derive state and either proceed
854
- // with what was created or notify the user clearly (#609).
855
- if (!state.activeMilestone || state.phase === "complete") {
856
- const { showSmartEntry } = await import("./guided-flow.js");
857
- await showSmartEntry(ctx, pi, base, { step: requestedStepMode });
858
-
859
- // Re-derive state after discussion — the LLM may have created artifacts
860
- // even if it didn't follow the full protocol.
861
- invalidateAllCaches();
862
- const postState = await deriveState(base);
863
- if (postState.activeMilestone && postState.phase !== "complete" && postState.phase !== "pre-planning") {
864
- state = postState;
865
- } else if (postState.activeMilestone && postState.phase === "pre-planning") {
866
- const contextFile = resolveMilestoneFile(base, postState.activeMilestone.id, "CONTEXT");
867
- const hasContext = !!(contextFile && await loadFile(contextFile));
868
- if (hasContext) {
869
- state = postState;
870
- } else {
871
- ctx.ui.notify(
872
- "Discussion completed but no milestone context was written. Run /gsd to try the discussion again, or /gsd auto after creating the milestone manually.",
873
- "warning",
874
- );
875
- return;
876
- }
877
- } else {
878
- return;
879
- }
880
- }
881
-
882
- // Active milestone exists but has no roadmap — check if context exists.
883
- // If context was pre-written (multi-milestone planning), auto-mode can
884
- // research and plan it. If no context either, need user discussion.
885
- if (state.phase === "pre-planning") {
886
- const mid = state.activeMilestone!.id;
887
- const contextFile = resolveMilestoneFile(base, mid, "CONTEXT");
888
- const hasContext = !!(contextFile && await loadFile(contextFile));
889
- if (!hasContext) {
890
- const { showSmartEntry } = await import("./guided-flow.js");
891
- await showSmartEntry(ctx, pi, base, { step: requestedStepMode });
892
-
893
- // Same re-derive pattern as above
894
- invalidateAllCaches();
895
- const postState = await deriveState(base);
896
- if (postState.activeMilestone && postState.phase !== "pre-planning") {
897
- state = postState;
898
- } else {
899
- ctx.ui.notify(
900
- "Discussion completed but milestone context is still missing. Run /gsd to try again.",
901
- "warning",
902
- );
903
- return;
904
- }
905
- }
906
- // Has context, no roadmap — auto-mode will research + plan it
907
- }
908
- }
724
+ updateSessionLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown", s.completedUnits.length);
725
+ writeLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown", s.completedUnits.length);
909
726
 
910
- // At this point activeMilestone is guaranteed non-null: either
911
- // hasSurvivorBranch is true (which requires activeMilestone) or
912
- // the !activeMilestone early-return above would have fired.
913
- if (!state.activeMilestone) {
914
- // Unreachable — satisfies TypeScript's null check
915
- const { showSmartEntry } = await import("./guided-flow.js");
916
- await showSmartEntry(ctx, pi, base, { step: requestedStepMode });
727
+ await dispatchNextUnit(ctx, pi);
917
728
  return;
918
729
  }
919
730
 
920
- s.active = true;
921
- s.stepMode = requestedStepMode;
922
- s.verbose = verboseMode;
923
- s.cmdCtx = ctx;
924
- s.basePath = base;
925
- s.unitDispatchCount.clear();
926
- s.unitRecoveryCount.clear();
927
- s.unitConsecutiveSkips.clear();
928
- s.lastBudgetAlertLevel = 0;
929
- s.unitLifetimeDispatches.clear();
930
- s.completedKeySet.clear();
931
- loadPersistedKeys(base, s.completedKeySet);
932
- resetHookState();
933
- restoreHookState(base);
934
- resetProactiveHealing();
935
- s.autoStartTime = Date.now();
936
- s.resourceVersionOnStart = readResourceVersion();
937
- s.completedUnits = [];
938
- s.pendingQuickTasks = [];
939
- s.currentUnit = null;
940
- s.currentMilestoneId = state.activeMilestone?.id ?? null;
941
- s.originalModelId = ctx.model?.id ?? null;
942
- s.originalModelProvider = ctx.model?.provider ?? null;
943
-
944
- // Register a SIGTERM handler so `kill <pid>` cleans up the lock and exits.
945
- registerSigtermHandler(base);
946
-
947
- // Capture the integration branch — records the branch the user was on when
948
- // auto-mode started. Slice branches will merge back to this branch instead
949
- // of the repo's default (main/master). Idempotent when the branch is the
950
- // same; updates the record when started from a different branch (#300).
951
- if (s.currentMilestoneId) {
952
- if (getIsolationMode() !== "none") {
953
- captureIntegrationBranch(base, s.currentMilestoneId, { commitDocs });
954
- }
955
- setActiveMilestoneId(base, s.currentMilestoneId);
956
- }
957
-
958
- // ── Auto-worktree: create or enter worktree for the active milestone ──
959
- // Store the original project root before any chdir so we can restore on stop.
960
- // Skip if already inside a worktree (manual /worktree or another auto-worktree)
961
- // to prevent nested worktree creation.
962
- s.originalBasePath = base;
963
-
964
- const isUnderGsdWorktrees = (p: string): boolean => {
965
- // Prevent creating nested auto-worktrees when running from within any
966
- // `.gsd/worktrees/...` directory (including manual worktrees).
967
- const marker = `${pathSep}.gsd${pathSep}worktrees${pathSep}`;
968
- if (p.includes(marker)) {
969
- return true;
970
- }
971
- const worktreesSuffix = `${pathSep}.gsd${pathSep}worktrees`;
972
- return p.endsWith(worktreesSuffix);
731
+ // ── Fresh start path — delegated to auto-start.ts ──
732
+ const bootstrapDeps: BootstrapDeps = {
733
+ shouldUseWorktreeIsolation,
734
+ registerSigtermHandler,
735
+ lockBase,
973
736
  };
974
737
 
975
- if (s.currentMilestoneId && shouldUseWorktreeIsolation() && !detectWorktreeName(base) && !isUnderGsdWorktrees(base)) {
976
- try {
977
- const existingWtPath = getAutoWorktreePath(base, s.currentMilestoneId);
978
- if (existingWtPath) {
979
- // Worktree already exists (e.g., previous session created it) — enter it.
980
- const wtPath = enterAutoWorktree(base, s.currentMilestoneId);
981
- s.basePath = wtPath;
982
- s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
983
- ctx.ui.notify(`Entered auto-worktree at ${wtPath}`, "info");
984
- } else {
985
- // Fresh start — create worktree and enter it.
986
- const wtPath = createAutoWorktree(base, s.currentMilestoneId);
987
- s.basePath = wtPath;
988
- s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
989
- ctx.ui.notify(`Created auto-worktree at ${wtPath}`, "info");
990
- }
991
- // Re-register SIGTERM handler with the original s.basePath (lock lives there)
992
- registerSigtermHandler(s.originalBasePath);
993
-
994
- // After worktree entry, load completed keys from BOTH locations (project root
995
- // + worktree) so the in-memory set is the union. Prevents re-dispatch of units
996
- // completed in either location after crash/restart (#769).
997
- if (s.basePath !== s.originalBasePath) {
998
- loadPersistedKeys(s.basePath, s.completedKeySet);
999
- }
1000
- } catch (err) {
1001
- // Worktree creation is non-fatal — continue in the project root.
1002
- ctx.ui.notify(
1003
- `Auto-worktree setup failed: ${err instanceof Error ? err.message : String(err)}. Continuing in project root.`,
1004
- "warning",
1005
- );
1006
- }
1007
- }
1008
-
1009
- // ── DB lifecycle: auto-migrate or open existing database ──
1010
- const gsdDbPath = join(s.basePath, ".gsd", "gsd.db");
1011
- const gsdDirPath = join(s.basePath, ".gsd");
1012
- if (existsSync(gsdDirPath) && !existsSync(gsdDbPath)) {
1013
- const hasDecisions = existsSync(join(gsdDirPath, "DECISIONS.md"));
1014
- const hasRequirements = existsSync(join(gsdDirPath, "REQUIREMENTS.md"));
1015
- const hasMilestones = existsSync(join(gsdDirPath, "milestones"));
1016
- if (hasDecisions || hasRequirements || hasMilestones) {
1017
- try {
1018
- const { openDatabase: openDb } = await import("./gsd-db.js");
1019
- const { migrateFromMarkdown } = await import("./md-importer.js");
1020
- openDb(gsdDbPath);
1021
- migrateFromMarkdown(s.basePath);
1022
- } catch (err) {
1023
- process.stderr.write(`gsd-migrate: auto-migration failed: ${(err as Error).message}\n`);
1024
- }
1025
- }
1026
- }
1027
- if (existsSync(gsdDbPath) && !isDbAvailable()) {
1028
- try {
1029
- const { openDatabase: openDb } = await import("./gsd-db.js");
1030
- openDb(gsdDbPath);
1031
- } catch (err) {
1032
- process.stderr.write(`gsd-db: failed to open existing database: ${(err as Error).message}\n`);
1033
- }
1034
- }
1035
-
1036
- // Initialize metrics — loads existing ledger from disk.
1037
- // Use s.basePath (not base) so worktree-mode reads the worktree ledger (#769).
1038
- initMetrics(s.basePath);
1039
-
1040
- // Initialize routing history for adaptive learning
1041
- initRoutingHistory(s.basePath);
1042
-
1043
- // Capture the session's current model at auto-mode start (#650).
1044
- // This prevents model bleed when multiple GSD instances share the
1045
- // same global settings.json — each instance remembers its own model.
1046
- const currentModel = ctx.model;
1047
- if (currentModel) {
1048
- s.autoModeStartModel = { provider: currentModel.provider, id: currentModel.id };
1049
- }
1050
-
1051
- // Snapshot installed skills so we can detect new ones after research
1052
- if (resolveSkillDiscoveryMode() !== "off") {
1053
- snapshotSkills();
1054
- }
1055
-
1056
- ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
1057
- ctx.ui.setFooter(hideFooter);
1058
- const modeLabel = s.stepMode ? "Step-mode" : "Auto-mode";
1059
- const pendingCount = state.registry.filter(m => m.status !== 'complete').length;
1060
- const scopeMsg = pendingCount > 1
1061
- ? `Will loop through ${pendingCount} milestones.`
1062
- : "Will loop until milestone complete.";
1063
- ctx.ui.notify(`${modeLabel} started. ${scopeMsg}`, "info");
1064
-
1065
- // Write initial lock file immediately so cross-process status detection
1066
- // works even before the first unit is dispatched (#723).
1067
- // The lock is updated with unit-specific info on each dispatch and cleared on stop.
1068
- writeLock(lockBase(), "starting", s.currentMilestoneId ?? "unknown", 0);
1069
-
1070
- // Secrets collection gate — collect pending secrets before first dispatch
1071
- const mid = state.activeMilestone!.id;
1072
- try {
1073
- const manifestStatus = await getManifestStatus(base, mid);
1074
- if (manifestStatus && manifestStatus.pending.length > 0) {
1075
- const result = await collectSecretsFromManifest(base, mid, ctx);
1076
- if (result && result.applied && result.skipped && result.existingSkipped) {
1077
- ctx.ui.notify(
1078
- `Secrets collected: ${result.applied.length} applied, ${result.skipped.length} skipped, ${result.existingSkipped.length} already set.`,
1079
- "info",
1080
- );
1081
- } else {
1082
- ctx.ui.notify("Secrets collection skipped.", "info");
1083
- }
1084
- }
1085
- } catch (err) {
1086
- ctx.ui.notify(
1087
- `Secrets collection error: ${err instanceof Error ? err.message : String(err)}. Continuing with next task.`,
1088
- "warning",
1089
- );
1090
- }
1091
-
1092
- // Self-heal: clear stale runtime records where artifacts already exist.
1093
- // Use s.basePath (not base) — in worktree mode, s.basePath points to the worktree
1094
- // where runtime records and artifacts actually live (#769).
1095
- await selfHealRuntimeRecords(s.basePath, ctx, s.completedKeySet);
1096
-
1097
- // Self-heal: remove stale .git/index.lock from prior crash.
1098
- // A stale lock file blocks all git operations (commit, merge, checkout).
1099
- // Only remove if older than 60 seconds (not from a concurrent process).
1100
- try {
1101
- const gitLockFile = join(base, ".git", "index.lock");
1102
- if (existsSync(gitLockFile)) {
1103
- const lockAge = Date.now() - statSync(gitLockFile).mtimeMs;
1104
- if (lockAge > 60_000) {
1105
- unlinkSync(gitLockFile);
1106
- ctx.ui.notify("Removed stale .git/index.lock from prior crash.", "info");
1107
- }
1108
- }
1109
- } catch { /* non-fatal */ }
1110
-
1111
- // Pre-flight: validate milestone queue for multi-milestone runs.
1112
- // Warn about issues that will cause auto-mode to pause or block.
1113
- try {
1114
- const msDir = join(base, ".gsd", "milestones");
1115
- if (existsSync(msDir)) {
1116
- const milestoneIds = readdirSync(msDir, { withFileTypes: true })
1117
- .filter(d => d.isDirectory() && /^M\d{3}/.test(d.name))
1118
- .map(d => d.name.match(/^(M\d{3})/)?.[1] ?? d.name);
1119
- if (milestoneIds.length > 1) {
1120
- const issues: string[] = [];
1121
- for (const id of milestoneIds) {
1122
- const draft = resolveMilestoneFile(base, id, "CONTEXT-DRAFT");
1123
- if (draft) issues.push(`${id}: has CONTEXT-DRAFT.md (will pause for discussion)`);
1124
- }
1125
- if (issues.length > 0) {
1126
- ctx.ui.notify(`Pre-flight: ${milestoneIds.length} milestones queued.\n${issues.map(i => ` ⚠ ${i}`).join("\n")}`, "warning");
1127
- } else {
1128
- ctx.ui.notify(`Pre-flight: ${milestoneIds.length} milestones queued. All have full context.`, "info");
1129
- }
1130
- }
1131
- }
1132
- } catch { /* non-fatal — pre-flight should never block auto-mode */ }
738
+ const ready = await bootstrapAutoSession(s, ctx, pi, base, verboseMode, requestedStepMode, bootstrapDeps);
739
+ if (!ready) return;
1133
740
 
1134
741
  // Dispatch the first unit
1135
742
  await dispatchNextUnit(ctx, pi);
@@ -1137,19 +744,22 @@ export async function startAuto(
1137
744
 
1138
745
  // ─── Agent End Handler ────────────────────────────────────────────────────────
1139
746
 
1140
- /** Guard against concurrent handleAgentEnd execution. Background job
1141
- * notifications and other system messages can trigger multiple agent_end
1142
- * events before the first handler finishes (the handler yields at every
1143
- * await). Without this guard, concurrent dispatchNextUnit calls race on
1144
- * newSession(), causing one to cancel the other and silently stopping
1145
- * auto-mode. */
747
+ /** Guard against concurrent handleAgentEnd execution. */
1146
748
 
1147
749
  export async function handleAgentEnd(
1148
750
  ctx: ExtensionContext,
1149
751
  pi: ExtensionAPI,
1150
752
  ): Promise<void> {
1151
753
  if (!s.active || !s.cmdCtx) return;
1152
- if (s.handlingAgentEnd) return;
754
+ if (s.handlingAgentEnd) {
755
+ // Another agent_end arrived while we're still processing the previous one.
756
+ // This happens when a unit dispatched inside handleAgentEnd (e.g. via hooks,
757
+ // triage, or quick-task early-dispatch paths) completes before the outer
758
+ // handleAgentEnd returns. Queue a retry so the completed unit's agent_end
759
+ // is not silently dropped (#1072).
760
+ s.pendingAgentEndRetry = true;
761
+ return;
762
+ }
1153
763
  s.handlingAgentEnd = true;
1154
764
 
1155
765
  try {
@@ -1157,714 +767,94 @@ export async function handleAgentEnd(
1157
767
  // Unit completed — clear its timeout
1158
768
  clearUnitTimeout();
1159
769
 
1160
- // ── Parallel worker signal check ─────────────────────────────────────
1161
- // When running as a parallel worker (GSD_MILESTONE_LOCK set), check for
1162
- // coordinator signals before dispatching the next unit.
1163
- const milestoneLock = process.env.GSD_MILESTONE_LOCK;
1164
- if (milestoneLock) {
1165
- const signal = consumeSignal(s.basePath, milestoneLock);
1166
- if (signal) {
1167
- if (signal.signal === "stop") {
1168
- s.handlingAgentEnd = false;
1169
- await stopAuto(ctx, pi);
1170
- return;
1171
- }
1172
- if (signal.signal === "pause") {
1173
- s.handlingAgentEnd = false;
1174
- await pauseAuto(ctx, pi);
1175
- return;
1176
- }
1177
- // "resume" and "rebase" signals are handled elsewhere or no-op here
1178
- }
1179
- }
1180
-
1181
- // Invalidate all caches — the unit just completed and may have
1182
- // written planning files (task summaries, roadmap checkboxes, etc.)
1183
- invalidateAllCaches();
1184
-
1185
- // Small delay to let files settle (git commits, file writes)
1186
- await new Promise(r => setTimeout(r, 500));
1187
-
1188
- // Commit any dirty files the LLM left behind on the current branch.
1189
- // For execute-task units, build a meaningful commit message from the
1190
- // task summary (one-liner, key_files, inferred type). For other unit
1191
- // types, fall back to the generic chore() message.
1192
- if (s.currentUnit) {
1193
- try {
1194
- let taskContext: TaskCommitContext | undefined;
1195
-
1196
- if (s.currentUnit.type === "execute-task") {
1197
- const parts = s.currentUnit.id.split("/");
1198
- const [mid, sid, tid] = parts;
1199
- if (mid && sid && tid) {
1200
- const summaryPath = resolveTaskFile(s.basePath, mid, sid, tid, "SUMMARY");
1201
- if (summaryPath) {
1202
- try {
1203
- const summaryContent = await loadFile(summaryPath);
1204
- if (summaryContent) {
1205
- const summary = parseSummary(summaryContent);
1206
- taskContext = {
1207
- taskId: `${sid}/${tid}`,
1208
- taskTitle: summary.title?.replace(/^T\d+:\s*/, "") || tid,
1209
- oneLiner: summary.oneLiner || undefined,
1210
- keyFiles: summary.frontmatter.key_files?.filter(f => !f.includes("{{")) || undefined,
1211
- };
1212
- }
1213
- } catch {
1214
- // Non-fatal — fall back to generic message
1215
- }
1216
- }
1217
- }
1218
- }
1219
-
1220
- const commitMsg = autoCommitCurrentBranch(s.basePath, s.currentUnit.type, s.currentUnit.id, taskContext);
1221
- if (commitMsg) {
1222
- ctx.ui.notify(`Committed: ${commitMsg.split("\n")[0]}`, "info");
1223
- }
1224
- } catch {
1225
- // Non-fatal
1226
- }
1227
-
1228
- // Post-hook: fix mechanical bookkeeping the LLM may have skipped.
1229
- // 1. Doctor handles: checkbox marking (task-level bookkeeping).
1230
- // 2. STATE.md is always rebuilt from disk state (purely derived, no LLM needed).
1231
- // fixLevel:"task" ensures doctor only fixes task-level issues (e.g. marking
1232
- // checkboxes). Slice/milestone completion transitions (summary stubs,
1233
- // roadmap [x] marking) are left for the complete-slice dispatch unit.
1234
- // Exception: after complete-slice itself, use fixLevel:"all" so roadmap
1235
- // checkboxes get fixed even if complete-slice crashed (#839).
1236
- try {
1237
- const scopeParts = s.currentUnit.id.split("/").slice(0, 2);
1238
- const doctorScope = scopeParts.join("/");
1239
- const effectiveFixLevel = s.currentUnit.type === "complete-slice" ? "all" as const : "task" as const;
1240
- const report = await runGSDDoctor(s.basePath, { fix: true, scope: doctorScope, fixLevel: effectiveFixLevel });
1241
- if (report.fixesApplied.length > 0) {
1242
- ctx.ui.notify(`Post-hook: applied ${report.fixesApplied.length} fix(es).`, "info");
1243
- }
1244
-
1245
- // ── Proactive health tracking ──────────────────────────────────────
1246
- // Record health snapshot for trend analysis and escalation logic.
1247
- const summary = summarizeDoctorIssues(report.issues);
1248
- recordHealthSnapshot(summary.errors, summary.warnings, report.fixesApplied.length);
1249
-
1250
- // Check if we should escalate to LLM-assisted heal
1251
- if (summary.errors > 0) {
1252
- const unresolvedErrors = report.issues
1253
- .filter(i => i.severity === "error" && !i.fixable)
1254
- .map(i => ({ code: i.code, message: i.message, unitId: i.unitId }));
1255
- const escalation = checkHealEscalation(summary.errors, unresolvedErrors);
1256
- if (escalation.shouldEscalate) {
1257
- ctx.ui.notify(
1258
- `Doctor heal escalation: ${escalation.reason}. Dispatching LLM-assisted heal.`,
1259
- "warning",
1260
- );
1261
- try {
1262
- const { formatDoctorIssuesForPrompt, formatDoctorReport } = await import("./doctor.js");
1263
- const { dispatchDoctorHeal } = await import("./commands.js");
1264
- const actionable = report.issues.filter(i => i.severity === "error");
1265
- const reportText = formatDoctorReport(report, { scope: doctorScope, includeWarnings: true });
1266
- const structuredIssues = formatDoctorIssuesForPrompt(actionable);
1267
- dispatchDoctorHeal(pi, doctorScope, reportText, structuredIssues);
1268
- } catch {
1269
- // Non-fatal — escalation dispatch failure
1270
- }
1271
- }
1272
- }
1273
- } catch {
1274
- // Non-fatal — doctor failure should never block dispatch
1275
- }
1276
- // Throttle STATE.md rebuilds to reduce I/O spikes on long sessions.
1277
- // STATE.md is a derived diagnostic artifact — skipping a rebuild is safe;
1278
- // the next unit or stop/pause will rebuild it.
1279
- const now = Date.now();
1280
- if (now - s.lastStateRebuildAt >= STATE_REBUILD_MIN_INTERVAL_MS) {
1281
- try {
1282
- await rebuildState(s.basePath);
1283
- s.lastStateRebuildAt = now;
1284
- // State rebuild commit is bookkeeping — generic message is appropriate
1285
- autoCommitCurrentBranch(s.basePath, "state-rebuild", s.currentUnit.id);
1286
- } catch {
1287
- // Non-fatal
1288
- }
1289
- }
1290
-
1291
- // ── Prune dead bg-shell processes ──────────────────────────────────────
1292
- // Dead processes retain ~500KB-1MB of output buffers each. Without pruning,
1293
- // they accumulate during long auto-mode sessions causing memory pressure.
1294
- try {
1295
- const { pruneDeadProcesses } = await import("../bg-shell/process-manager.js");
1296
- pruneDeadProcesses();
1297
- } catch {
1298
- // Non-fatal — bg-shell may not be available
1299
- }
1300
-
1301
- // ── Sync worktree state back to project root ──────────────────────────
1302
- // Ensures that if auto-mode restarts, deriveState(projectRoot) reads
1303
- // current milestone progress instead of stale pre-worktree state (#654).
1304
- if (s.originalBasePath && s.originalBasePath !== s.basePath) {
1305
- try {
1306
- syncStateToProjectRoot(s.basePath, s.originalBasePath, s.currentMilestoneId);
1307
- } catch {
1308
- // Non-fatal — stale state is the existing behavior, sync is an improvement
1309
- }
1310
- }
1311
-
1312
- // ── Rewrite-docs completion: resolve overrides and reset circuit breaker ──
1313
- if (s.currentUnit.type === "rewrite-docs") {
1314
- try {
1315
- await resolveAllOverrides(s.basePath);
1316
- resetRewriteCircuitBreaker();
1317
- ctx.ui.notify("Override(s) resolved — rewrite-docs completed.", "info");
1318
- } catch {
1319
- // Non-fatal — verifyExpectedArtifact will catch unresolved overrides
1320
- }
1321
- }
1322
-
1323
- // ── Post-triage: execute actionable resolutions (inject, replan, queue quick-tasks) ──
1324
- // After a triage-captures unit completes, the LLM has classified captures and
1325
- // updated CAPTURES.md. Now we execute those classifications: inject tasks into
1326
- // the plan, write replan triggers, and queue quick-tasks for dispatch.
1327
- if (s.currentUnit.type === "triage-captures") {
1328
- try {
1329
- const { executeTriageResolutions } = await import("./triage-resolution.js");
1330
- const state = await deriveState(s.basePath);
1331
- const mid = state.activeMilestone?.id;
1332
- const sid = state.activeSlice?.id;
1333
-
1334
- if (mid && sid) {
1335
- const triageResult = executeTriageResolutions(s.basePath, mid, sid);
1336
-
1337
- if (triageResult.injected > 0) {
1338
- ctx.ui.notify(
1339
- `Triage: injected ${triageResult.injected} task${triageResult.injected === 1 ? "" : "s"} into ${sid} plan.`,
1340
- "info",
1341
- );
1342
- }
1343
- if (triageResult.replanned > 0) {
1344
- ctx.ui.notify(
1345
- `Triage: replan trigger written for ${sid} — next dispatch will enter replanning.`,
1346
- "info",
1347
- );
1348
- }
1349
- if (triageResult.quickTasks.length > 0) {
1350
- // Queue quick-tasks for dispatch. They'll be picked up by the
1351
- // quick-task dispatch block below the triage check.
1352
- for (const qt of triageResult.quickTasks) {
1353
- s.pendingQuickTasks.push(qt);
1354
- }
1355
- ctx.ui.notify(
1356
- `Triage: ${triageResult.quickTasks.length} quick-task${triageResult.quickTasks.length === 1 ? "" : "s"} queued for execution.`,
1357
- "info",
1358
- );
1359
- }
1360
- for (const action of triageResult.actions) {
1361
- process.stderr.write(`gsd-triage: ${action}\n`);
1362
- }
1363
- }
1364
- } catch (err) {
1365
- // Non-fatal — triage resolution failure shouldn't block dispatch
1366
- process.stderr.write(`gsd-triage: resolution execution failed: ${(err as Error).message}\n`);
1367
- }
1368
- }
1369
-
1370
- // ── Path A fix: verify artifact and persist completion before re-entering dispatch ──
1371
- // After doctor + rebuildState, check whether the just-completed unit actually
1372
- // produced its expected artifact. If so, persist the completion key now so the
1373
- // idempotency check at the top of dispatchNextUnit() skips it — even if
1374
- // deriveState() still returns this unit as s.active (e.g. branch mismatch).
1375
- //
1376
- // IMPORTANT: For non-hook units, defer persistence until after the hook check.
1377
- // If a post-unit hook requests a retry, we need to remove the completion key
1378
- // so dispatchNextUnit re-dispatches the trigger unit.
1379
- let triggerArtifactVerified = false;
1380
- if (!s.currentUnit.type.startsWith("hook/")) {
1381
- try {
1382
- triggerArtifactVerified = verifyExpectedArtifact(s.currentUnit.type, s.currentUnit.id, s.basePath);
1383
- if (triggerArtifactVerified) {
1384
- const completionKey = `${s.currentUnit.type}/${s.currentUnit.id}`;
1385
- if (!s.completedKeySet.has(completionKey)) {
1386
- persistCompletedKey(s.basePath, completionKey);
1387
- s.completedKeySet.add(completionKey);
1388
- }
1389
- invalidateAllCaches();
1390
- }
1391
- } catch {
1392
- // Non-fatal — worst case we fall through to normal dispatch which has its own checks
1393
- }
1394
- } else {
1395
- // Hook unit completed — finalize its runtime record and clear it
1396
- try {
1397
- writeUnitRuntimeRecord(s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, {
1398
- phase: "finalized",
1399
- progressCount: 1,
1400
- lastProgressKind: "hook-completed",
1401
- });
1402
- clearUnitRuntimeRecord(s.basePath, s.currentUnit.type, s.currentUnit.id);
1403
- } catch {
1404
- // Non-fatal
1405
- }
1406
- }
1407
- }
1408
-
1409
- // ── Verification gate: run typecheck/lint/test after execute-task ──
1410
- if (s.currentUnit && s.currentUnit.type === "execute-task") {
1411
- try {
1412
- const effectivePrefs = loadEffectiveGSDPreferences();
1413
- const prefs = effectivePrefs?.preferences;
1414
-
1415
- // Read task plan verify field from the current task's slice plan
1416
- // unitId format is "M001/S01/T03" — extract mid, sid, tid
1417
- const parts = s.currentUnit.id.split("/");
1418
- let taskPlanVerify: string | undefined;
1419
- if (parts.length >= 3) {
1420
- const [mid, sid, tid] = parts;
1421
- const planFile = resolveSliceFile(s.basePath, mid, sid, "PLAN");
1422
- if (planFile) {
1423
- const planContent = await loadFile(planFile);
1424
- if (planContent) {
1425
- const slicePlan = parsePlan(planContent);
1426
- const taskEntry = slicePlan?.tasks?.find(t => t.id === tid);
1427
- taskPlanVerify = taskEntry?.verify;
1428
- }
1429
- }
1430
- }
1431
-
1432
- const result = runVerificationGate({ basePath: s.basePath,
1433
- unitId: s.currentUnit.id,
1434
- cwd: s.basePath,
1435
- preferenceCommands: prefs?.verification_commands,
1436
- taskPlanVerify,
1437
- });
1438
-
1439
- // Capture runtime errors from bg-shell and browser console
1440
- const runtimeErrors = await captureRuntimeErrors();
1441
- if (runtimeErrors.length > 0) {
1442
- result.runtimeErrors = runtimeErrors;
1443
- // Blocking runtime errors override gate pass
1444
- if (runtimeErrors.some(e => e.blocking)) {
1445
- result.passed = false;
1446
- }
1447
- }
1448
-
1449
- // Conditional dependency audit (R008)
1450
- const auditWarnings = runDependencyAudit(s.basePath);
1451
- if (auditWarnings.length > 0) {
1452
- result.auditWarnings = auditWarnings;
1453
- process.stderr.write(`verification-gate: ${auditWarnings.length} audit warning(s)\n`);
1454
- for (const w of auditWarnings) {
1455
- process.stderr.write(` [${w.severity}] ${w.name}: ${w.title}\n`);
1456
- }
1457
- }
1458
-
1459
- // Auto-fix retry preferences (R005 / D005)
1460
- const autoFixEnabled = prefs?.verification_auto_fix !== false; // default true
1461
- const maxRetries = typeof prefs?.verification_max_retries === "number" ? prefs.verification_max_retries : 2;
1462
- const completionKey = `${s.currentUnit.type}/${s.currentUnit.id}`;
1463
-
1464
- if (result.checks.length > 0) {
1465
- const passCount = result.checks.filter(c => c.exitCode === 0).length;
1466
- const total = result.checks.length;
1467
- if (result.passed) {
1468
- ctx.ui.notify(`Verification gate: ${passCount}/${total} checks passed`);
1469
- } else {
1470
- const failures = result.checks.filter(c => c.exitCode !== 0);
1471
- const failNames = failures.map(f => f.command).join(", ");
1472
- ctx.ui.notify(`Verification gate: FAILED — ${failNames}`);
1473
- process.stderr.write(`verification-gate: ${total - passCount}/${total} checks failed\n`);
1474
- for (const f of failures) {
1475
- process.stderr.write(` ${f.command} exited ${f.exitCode}\n`);
1476
- if (f.stderr) process.stderr.write(` stderr: ${f.stderr.slice(0, 500)}\n`);
1477
- }
1478
- }
1479
- }
1480
-
1481
- // Log blocking runtime errors to stderr
1482
- if (result.runtimeErrors?.some(e => e.blocking)) {
1483
- const blockingErrors = result.runtimeErrors.filter(e => e.blocking);
1484
- process.stderr.write(`verification-gate: ${blockingErrors.length} blocking runtime error(s) detected\n`);
1485
- for (const err of blockingErrors) {
1486
- process.stderr.write(` [${err.source}] ${err.severity}: ${err.message.slice(0, 200)}\n`);
1487
- }
1488
- }
1489
-
1490
- // Write verification evidence JSON artifact
1491
- const attempt = s.verificationRetryCount.get(s.currentUnit.id) ?? 0;
1492
- if (parts.length >= 3) {
1493
- try {
1494
- const [mid, sid, tid] = parts;
1495
- const sDir = resolveSlicePath(s.basePath, mid, sid);
1496
- if (sDir) {
1497
- const tasksDir = join(sDir, "tasks");
1498
- if (result.passed) {
1499
- writeVerificationJSON(result, tasksDir, tid, s.currentUnit.id);
1500
- } else {
1501
- const nextAttempt = attempt + 1;
1502
- writeVerificationJSON(result, tasksDir, tid, s.currentUnit.id, nextAttempt, maxRetries);
1503
- }
1504
- }
1505
- } catch (evidenceErr) {
1506
- process.stderr.write(`verification-evidence: write error — ${(evidenceErr as Error).message}\n`);
1507
- }
1508
- }
1509
-
1510
- // ── Auto-fix retry logic ──
1511
- if (result.passed) {
1512
- // Gate passed — clear retry state and continue normal flow
1513
- s.verificationRetryCount.delete(s.currentUnit.id);
1514
- s.pendingVerificationRetry = null;
1515
- } else if (autoFixEnabled && attempt + 1 <= maxRetries) {
1516
- // Gate failed, retries remaining — set up retry and return early
1517
- const nextAttempt = attempt + 1;
1518
- s.verificationRetryCount.set(s.currentUnit.id, nextAttempt);
1519
- s.pendingVerificationRetry = {
1520
- unitId: s.currentUnit.id,
1521
- failureContext: formatFailureContext(result),
1522
- attempt: nextAttempt,
1523
- };
1524
- ctx.ui.notify(`Verification failed — auto-fix attempt ${nextAttempt}/${maxRetries}`, "warning");
1525
- // Remove completion key so dispatchNextUnit re-dispatches this unit
1526
- s.completedKeySet.delete(completionKey);
1527
- removePersistedKey(s.basePath, completionKey);
1528
- return; // ← Critical: exit before DB dual-write and post-unit hooks
1529
- } else {
1530
- // Gate failed, retries exhausted (or auto-fix disabled) — pause for human review
1531
- const exhaustedAttempt = attempt + 1;
1532
- s.verificationRetryCount.delete(s.currentUnit.id);
1533
- s.pendingVerificationRetry = null;
1534
- ctx.ui.notify(
1535
- `Verification gate FAILED after ${exhaustedAttempt > maxRetries ? exhaustedAttempt - 1 : exhaustedAttempt} retries — pausing for human review`,
1536
- "error",
1537
- );
1538
- await pauseAuto(ctx, pi);
1539
- return;
1540
- }
1541
- } catch (err) {
1542
- // Gate errors are non-fatal — log and continue
1543
- process.stderr.write(`verification-gate: error — ${(err as Error).message}\n`);
1544
- }
1545
- }
1546
-
1547
- // ── DB dual-write: re-import changed markdown files so next unit's prompts use fresh data ──
1548
- if (isDbAvailable()) {
1549
- try {
1550
- const { migrateFromMarkdown } = await import("./md-importer.js");
1551
- migrateFromMarkdown(s.basePath);
1552
- } catch (err) {
1553
- process.stderr.write(`gsd-db: re-import failed: ${(err as Error).message}\n`);
1554
- }
1555
- }
1556
-
1557
- // ── Post-unit hooks: check if a configured hook should run before normal dispatch ──
1558
- if (s.currentUnit && !s.stepMode) {
1559
- const hookUnit = checkPostUnitHooks(s.currentUnit.type, s.currentUnit.id, s.basePath);
1560
- if (hookUnit) {
1561
- // Dispatch the hook unit instead of normal flow
1562
- const hookStartedAt = Date.now();
1563
- if (s.currentUnit) {
1564
- await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
1565
- }
1566
- s.currentUnit = { type: hookUnit.unitType, id: hookUnit.unitId, startedAt: hookStartedAt };
1567
- writeUnitRuntimeRecord(s.basePath, hookUnit.unitType, hookUnit.unitId, hookStartedAt, {
1568
- phase: "dispatched",
1569
- wrapupWarningSent: false,
1570
- timeoutAt: null,
1571
- lastProgressAt: hookStartedAt,
1572
- progressCount: 0,
1573
- lastProgressKind: "dispatch",
1574
- });
1575
-
1576
- const state = await deriveState(s.basePath);
1577
- updateProgressWidget(ctx, hookUnit.unitType, hookUnit.unitId, state);
1578
- const hookState = getActiveHook();
1579
- ctx.ui.notify(
1580
- `Running post-unit hook: ${hookUnit.hookName} (cycle ${hookState?.cycle ?? 1})`,
1581
- "info",
1582
- );
1583
-
1584
- // Switch model if the hook specifies one
1585
- if (hookUnit.model) {
1586
- const availableModels = ctx.modelRegistry.getAvailable();
1587
- const match = availableModels.find(m =>
1588
- m.id === hookUnit.model || `${m.provider}/${m.id}` === hookUnit.model,
1589
- );
1590
- if (match) {
1591
- try {
1592
- await pi.setModel(match);
1593
- } catch { /* non-fatal — use current model */ }
1594
- }
1595
- }
1596
-
1597
- const result = await s.cmdCtx!.newSession();
1598
- if (result.cancelled) {
1599
- resetHookState();
1600
- await stopAuto(ctx, pi, "Hook session cancelled");
1601
- return;
1602
- }
1603
- const sessionFile = ctx.sessionManager.getSessionFile();
1604
- writeLock(lockBase(), hookUnit.unitType, hookUnit.unitId, s.completedUnits.length, sessionFile);
1605
- // Persist hook state so cycle counts survive crashes
1606
- persistHookState(s.basePath);
1607
-
1608
- // Start supervision timers for hook units — hooks can get stuck just
1609
- // like normal units, and without a watchdog auto-mode would hang forever.
1610
- clearUnitTimeout();
1611
- const supervisor = resolveAutoSupervisorConfig();
1612
- const hookHardTimeoutMs = (supervisor.hard_timeout_minutes ?? 30) * 60 * 1000;
1613
- s.unitTimeoutHandle = setTimeout(async () => {
1614
- s.unitTimeoutHandle = null;
1615
- if (!s.active) return;
1616
- if (s.currentUnit) {
1617
- writeUnitRuntimeRecord(s.basePath, hookUnit.unitType, hookUnit.unitId, s.currentUnit.startedAt, {
1618
- phase: "timeout",
1619
- timeoutAt: Date.now(),
1620
- });
1621
- }
1622
- ctx.ui.notify(
1623
- `Hook ${hookUnit.hookName} exceeded ${supervisor.hard_timeout_minutes ?? 30}min timeout. Pausing auto-mode.`,
1624
- "warning",
1625
- );
1626
- resetHookState();
1627
- await pauseAuto(ctx, pi);
1628
- }, hookHardTimeoutMs);
1629
-
1630
- // Guard against race with timeout/pause before sending
1631
- if (!s.active) return;
1632
- pi.sendMessage(
1633
- { customType: "gsd-auto", content: hookUnit.prompt, display: s.verbose },
1634
- { triggerTurn: true },
1635
- );
1636
- return; // handleAgentEnd will fire again when hook session completes
1637
- }
1638
-
1639
- // Check if a hook requested a retry of the trigger unit
1640
- if (isRetryPending()) {
1641
- const trigger = consumeRetryTrigger();
1642
- if (trigger) {
1643
- // Remove the trigger unit's completion key so dispatchNextUnit
1644
- // will re-dispatch it instead of skipping it as already-complete.
1645
- const triggerKey = `${trigger.unitType}/${trigger.unitId}`;
1646
- s.completedKeySet.delete(triggerKey);
1647
- removePersistedKey(s.basePath, triggerKey);
1648
- ctx.ui.notify(
1649
- `Hook requested retry of ${trigger.unitType} ${trigger.unitId}.`,
1650
- "info",
1651
- );
1652
- // Fall through to normal dispatchNextUnit — state derivation will
1653
- // re-select the same unit since it hasn't been marked complete
1654
- }
1655
- }
1656
- }
1657
-
1658
- // ── Triage check: dispatch triage unit if pending captures exist ──────────
1659
- // Fires after hooks complete, before normal dispatch. Follows the same
1660
- // early-dispatch-and-return pattern as hooks and fix-merge.
1661
- // Skip for: step mode (shows wizard instead), triage units (prevent triage-on-triage),
1662
- // hook units (hooks run before triage conceptually).
1663
- if (
1664
- !s.stepMode &&
1665
- s.currentUnit &&
1666
- !s.currentUnit.type.startsWith("hook/") &&
1667
- s.currentUnit.type !== "triage-captures" &&
1668
- s.currentUnit.type !== "quick-task"
1669
- ) {
1670
- try {
1671
- if (hasPendingCaptures(s.basePath)) {
1672
- const pending = loadPendingCaptures(s.basePath);
1673
- if (pending.length > 0) {
1674
- const state = await deriveState(s.basePath);
1675
- const mid = state.activeMilestone?.id;
1676
- const sid = state.activeSlice?.id;
1677
-
1678
- if (mid && sid) {
1679
- // Build triage prompt with current context
1680
- let currentPlan = "";
1681
- let roadmapContext = "";
1682
- const planFile = resolveSliceFile(s.basePath, mid, sid, "PLAN");
1683
- if (planFile) currentPlan = (await loadFile(planFile)) ?? "";
1684
- const roadmapFile = resolveMilestoneFile(s.basePath, mid, "ROADMAP");
1685
- if (roadmapFile) roadmapContext = (await loadFile(roadmapFile)) ?? "";
1686
-
1687
- const capturesList = pending.map(c =>
1688
- `- **${c.id}**: "${c.text}" (captured: ${c.timestamp})`
1689
- ).join("\n");
1690
-
1691
- const prompt = loadPrompt("triage-captures", {
1692
- pendingCaptures: capturesList,
1693
- currentPlan: currentPlan || "(no active slice plan)",
1694
- roadmapContext: roadmapContext || "(no active roadmap)",
1695
- });
1696
-
1697
- ctx.ui.notify(
1698
- `Triaging ${pending.length} pending capture${pending.length === 1 ? "" : "s"}...`,
1699
- "info",
1700
- );
1701
-
1702
- // Close out previous unit metrics
1703
- if (s.currentUnit) {
1704
- await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt);
1705
- }
1706
-
1707
- // Dispatch triage as a new unit (early-dispatch-and-return)
1708
- const triageUnitType = "triage-captures";
1709
- const triageUnitId = `${mid}/${sid}/triage`;
1710
- const triageStartedAt = Date.now();
1711
- s.currentUnit = { type: triageUnitType, id: triageUnitId, startedAt: triageStartedAt };
1712
- writeUnitRuntimeRecord(s.basePath, triageUnitType, triageUnitId, triageStartedAt, {
1713
- phase: "dispatched",
1714
- wrapupWarningSent: false,
1715
- timeoutAt: null,
1716
- lastProgressAt: triageStartedAt,
1717
- progressCount: 0,
1718
- lastProgressKind: "dispatch",
1719
- });
1720
- updateProgressWidget(ctx, triageUnitType, triageUnitId, state);
1721
-
1722
- const result = await s.cmdCtx!.newSession();
1723
- if (result.cancelled) {
1724
- await stopAuto(ctx, pi);
1725
- return;
1726
- }
1727
- const sessionFile = ctx.sessionManager.getSessionFile();
1728
- writeLock(lockBase(), triageUnitType, triageUnitId, s.completedUnits.length, sessionFile);
1729
-
1730
- // Start unit timeout for triage (use same supervisor config as hooks)
1731
- clearUnitTimeout();
1732
- const supervisor = resolveAutoSupervisorConfig();
1733
- const triageTimeoutMs = (supervisor.hard_timeout_minutes ?? 30) * 60 * 1000;
1734
- s.unitTimeoutHandle = setTimeout(async () => {
1735
- s.unitTimeoutHandle = null;
1736
- if (!s.active) return;
1737
- ctx.ui.notify(
1738
- `Triage unit exceeded timeout. Pausing auto-mode.`,
1739
- "warning",
1740
- );
1741
- await pauseAuto(ctx, pi);
1742
- }, triageTimeoutMs);
1743
-
1744
- if (!s.active) return;
1745
- pi.sendMessage(
1746
- { customType: "gsd-auto", content: prompt, display: s.verbose },
1747
- { triggerTurn: true },
1748
- );
1749
- return; // handleAgentEnd will fire again when triage session completes
1750
- }
1751
- }
1752
- }
1753
- } catch {
1754
- // Triage check failure is non-fatal — proceed to normal dispatch
1755
- }
1756
- }
1757
-
1758
- // ── Quick-task dispatch: execute queued quick-tasks from triage resolution ──
1759
- // Quick-tasks are self-contained one-off tasks that don't modify the plan.
1760
- // They're queued during post-triage resolution and dispatched here one at a time.
1761
- if (
1762
- !s.stepMode &&
1763
- s.pendingQuickTasks.length > 0 &&
1764
- s.currentUnit &&
1765
- s.currentUnit.type !== "quick-task"
1766
- ) {
1767
- try {
1768
- const capture = s.pendingQuickTasks.shift()!;
1769
- const { buildQuickTaskPrompt } = await import("./triage-resolution.js");
1770
- const { markCaptureExecuted } = await import("./captures.js");
1771
- const prompt = buildQuickTaskPrompt(capture);
1772
-
1773
- ctx.ui.notify(
1774
- `Executing quick-task: ${capture.id} — "${capture.text}"`,
1775
- "info",
1776
- );
1777
-
1778
- // Close out previous unit metrics
1779
- if (s.currentUnit) {
1780
- await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt);
1781
- }
1782
-
1783
- // Dispatch quick-task as a new unit
1784
- const qtUnitType = "quick-task";
1785
- const qtUnitId = `${ s.currentMilestoneId }/${capture.id}`;
1786
- const qtStartedAt = Date.now();
1787
- s.currentUnit = { type: qtUnitType, id: qtUnitId, startedAt: qtStartedAt };
1788
- writeUnitRuntimeRecord(s.basePath, qtUnitType, qtUnitId, qtStartedAt, {
1789
- phase: "dispatched",
1790
- wrapupWarningSent: false,
1791
- timeoutAt: null,
1792
- lastProgressAt: qtStartedAt,
1793
- progressCount: 0,
1794
- lastProgressKind: "dispatch",
1795
- });
1796
- const state = await deriveState(s.basePath);
1797
- updateProgressWidget(ctx, qtUnitType, qtUnitId, state);
1798
-
1799
- const result = await s.cmdCtx!.newSession();
1800
- if (result.cancelled) {
1801
- await stopAuto(ctx, pi);
1802
- return;
1803
- }
1804
- const sessionFile = ctx.sessionManager.getSessionFile();
1805
- writeLock(lockBase(), qtUnitType, qtUnitId, s.completedUnits.length, sessionFile);
1806
-
1807
- // Mark capture as executed now that the unit is dispatched
1808
- markCaptureExecuted(s.basePath, capture.id);
1809
-
1810
- // Start unit timeout for quick-task
1811
- clearUnitTimeout();
1812
- const supervisor = resolveAutoSupervisorConfig();
1813
- const qtTimeoutMs = (supervisor.hard_timeout_minutes ?? 30) * 60 * 1000;
1814
- s.unitTimeoutHandle = setTimeout(async () => {
1815
- s.unitTimeoutHandle = null;
1816
- if (!s.active) return;
1817
- ctx.ui.notify(
1818
- `Quick-task ${capture.id} exceeded timeout. Pausing auto-mode.`,
1819
- "warning",
1820
- );
1821
- await pauseAuto(ctx, pi);
1822
- }, qtTimeoutMs);
770
+ // ── Pre-verification processing (commit, doctor, state rebuild, etc.) ──
771
+ const postUnitCtx: PostUnitContext = {
772
+ s,
773
+ ctx,
774
+ pi,
775
+ buildSnapshotOpts,
776
+ lockBase,
777
+ stopAuto,
778
+ pauseAuto,
779
+ updateProgressWidget,
780
+ };
1823
781
 
1824
- if (!s.active) return;
1825
- pi.sendMessage(
1826
- { customType: "gsd-auto", content: prompt, display: s.verbose },
1827
- { triggerTurn: true },
1828
- );
1829
- return; // handleAgentEnd will fire again when quick-task session completes
1830
- } catch {
1831
- // Non-fatal — proceed to normal dispatch
1832
- }
1833
- }
782
+ const preResult = await postUnitPreVerification(postUnitCtx);
783
+ if (preResult === "dispatched") return;
1834
784
 
1835
- // In step mode, pause and show a wizard instead of immediately dispatching
1836
- if (s.stepMode) {
785
+ // ── Verification gate: run typecheck/lint/test after execute-task ──
786
+ const verificationResult = await runPostUnitVerification(
787
+ { s, ctx, pi },
788
+ dispatchNextUnit,
789
+ startDispatchGapWatchdog,
790
+ pauseAuto,
791
+ );
792
+ if (verificationResult === "retry" || verificationResult === "pause") return;
793
+
794
+ // ── Post-verification processing (DB dual-write, hooks, triage, quick-tasks) ──
795
+ const postResult = await postUnitPostVerification(postUnitCtx);
796
+ if (postResult === "dispatched" || postResult === "stopped") return;
797
+ if (postResult === "step-wizard") {
1837
798
  await showStepWizard(ctx, pi);
1838
799
  return;
1839
800
  }
1840
801
 
802
+ // ── Dispatch with hang detection (#1073) ────────────────────────────────
803
+ // Start a safety watchdog BEFORE calling dispatchNextUnit. If dispatch
804
+ // hangs at any await (newSession, model selection, etc.), the gap watchdog
805
+ // inside handleAgentEnd never fires because we never reach the check.
806
+ // This pre-dispatch watchdog ensures recovery even when dispatchNextUnit
807
+ // itself is permanently blocked.
808
+ const dispatchHangGuard = setTimeout(() => {
809
+ if (!s.active) return;
810
+ // dispatchNextUnit has been running for too long — it's likely hung.
811
+ // Start the gap watchdog which will retry dispatch from scratch.
812
+ if (!s.unitTimeoutHandle && !s.wrapupWarningHandle) {
813
+ ctx.ui.notify(
814
+ `Dispatch hang detected (${DISPATCH_HANG_TIMEOUT_MS / 1000}s without completion). Starting recovery watchdog.`,
815
+ "warning",
816
+ );
817
+ startDispatchGapWatchdog(ctx, pi);
818
+ }
819
+ }, DISPATCH_HANG_TIMEOUT_MS);
820
+
1841
821
  try {
1842
822
  await dispatchNextUnit(ctx, pi);
1843
823
  } catch (dispatchErr) {
1844
- // dispatchNextUnit threw — without this catch the error would propagate
1845
- // to the pi event emitter which may silently swallow async rejections,
1846
- // leaving auto-mode s.active but permanently stalled (see #381).
1847
824
  const message = dispatchErr instanceof Error ? dispatchErr.message : String(dispatchErr);
1848
825
  ctx.ui.notify(
1849
826
  `Dispatch error after unit completion: ${message}. Retrying in ${DISPATCH_GAP_TIMEOUT_MS / 1000}s.`,
1850
827
  "error",
1851
828
  );
1852
-
1853
- // Start the dispatch gap watchdog to retry after a delay.
1854
- // This gives transient issues (dirty working tree, branch state) time to settle.
1855
829
  startDispatchGapWatchdog(ctx, pi);
1856
830
  return;
831
+ } finally {
832
+ clearTimeout(dispatchHangGuard);
1857
833
  }
1858
834
 
1859
- // If dispatchNextUnit returned normally but auto-mode is still s.active and
1860
- // no new unit timeout was set (meaning sendMessage was never called), start
1861
- // the dispatch gap watchdog as a safety net.
1862
835
  if (s.active && !s.unitTimeoutHandle && !s.wrapupWarningHandle) {
1863
836
  startDispatchGapWatchdog(ctx, pi);
1864
837
  }
1865
838
 
1866
839
  } finally {
1867
840
  s.handlingAgentEnd = false;
841
+
842
+ // If an agent_end event was dropped by the reentrancy guard while we were
843
+ // processing, re-enter handleAgentEnd on the next microtask. This prevents
844
+ // the summarizing phase stall (#1072) where a unit dispatched inside
845
+ // handleAgentEnd (hooks, triage, quick-task) completes before we return,
846
+ // and its agent_end is silently dropped — leaving auto-mode active but
847
+ // permanently stalled with no unit running and no watchdog set.
848
+ if (s.pendingAgentEndRetry) {
849
+ s.pendingAgentEndRetry = false;
850
+ setImmediate(() => {
851
+ handleAgentEnd(ctx, pi).catch((err) => {
852
+ const msg = err instanceof Error ? err.message : String(err);
853
+ ctx.ui.notify(`Deferred agent_end retry failed: ${msg}`, "error");
854
+ pauseAuto(ctx, pi).catch(() => {});
855
+ });
856
+ });
857
+ }
1868
858
  }
1869
859
  }
1870
860
 
@@ -1872,8 +862,6 @@ export async function handleAgentEnd(
1872
862
 
1873
863
  /**
1874
864
  * Show the step-mode wizard after a unit completes.
1875
- * Derives the next unit from disk state and presents it to the user.
1876
- * If the user confirms, dispatches the next unit. If not, pauses.
1877
865
  */
1878
866
  async function showStepWizard(
1879
867
  ctx: ExtensionContext,
@@ -1884,15 +872,13 @@ async function showStepWizard(
1884
872
  const state = await deriveState(s.basePath);
1885
873
  const mid = state.activeMilestone?.id;
1886
874
 
1887
- // Build summary of what just completed
1888
875
  const justFinished = s.currentUnit
1889
876
  ? `${unitVerb(s.currentUnit.type)} ${s.currentUnit.id}`
1890
877
  : "previous unit";
1891
878
 
1892
- // If no active milestone or everything is complete, stop
1893
879
  if (!mid || state.phase === "complete") {
1894
- const incomplete = state.registry.filter(m => m.status !== "complete");
1895
- if (incomplete.length > 0 && state.phase !== "complete" && state.phase !== "blocked") {
880
+ const incomplete = state.registry.filter(m => m.status !== "complete" && m.status !== "parked");
881
+ if (incomplete.length > 0 && state.phase !== "complete" && state.phase !== "blocked" && state.phase !== "pre-planning") {
1896
882
  const ids = incomplete.map(m => m.id).join(", ");
1897
883
  const diag = `basePath=${s.basePath}, milestones=[${state.registry.map(m => `${m.id}:${m.status}`).join(", ")}], phase=${state.phase}`;
1898
884
  ctx.ui.notify(`Unexpected: ${incomplete.length} incomplete milestone(s) (${ids}) but no active milestone.\n Diagnostic: ${diag}`, "error");
@@ -1903,7 +889,6 @@ async function showStepWizard(
1903
889
  return;
1904
890
  }
1905
891
 
1906
- // Peek at what's next by examining state
1907
892
  const nextDesc = _describeNextUnit(state);
1908
893
 
1909
894
  const choice = await showNextAction(s.cmdCtx, {
@@ -1941,12 +926,10 @@ async function showStepWizard(
1941
926
  ctx.ui.notify("Switched to auto-mode.", "info");
1942
927
  await dispatchNextUnit(ctx, pi);
1943
928
  } else if (choice === "status") {
1944
- // Show status then re-show the wizard
1945
929
  const { fireStatusViaCommand } = await import("./commands.js");
1946
930
  await fireStatusViaCommand(ctx as ExtensionCommandContext);
1947
931
  await showStepWizard(ctx, pi);
1948
932
  } else {
1949
- // "not_yet" — pause
1950
933
  await pauseAuto(ctx, pi);
1951
934
  }
1952
935
  }
@@ -1978,15 +961,6 @@ const widgetStateAccessors: WidgetStateAccessors = {
1978
961
 
1979
962
  // ─── Core Loop ────────────────────────────────────────────────────────────────
1980
963
 
1981
- /** Tracks recursive skip depth to prevent TUI freeze on cascading completed-unit skips */
1982
-
1983
- /** Reentrancy guard for dispatchNextUnit itself (not just handleAgentEnd).
1984
- * Prevents concurrent dispatch from watchdog timers, step wizard, and direct calls
1985
- * that bypass the s.handlingAgentEnd guard. Recursive calls (from skip paths) are
1986
- * allowed via s.skipDepth > 0. */
1987
-
1988
- /** Keys recently evicted by skip-loop breaker — prevents re-persistence in the fallback path (#912). */
1989
-
1990
964
  async function dispatchNextUnit(
1991
965
  ctx: ExtensionContext,
1992
966
  pi: ExtensionAPI,
@@ -1999,44 +973,50 @@ async function dispatchNextUnit(
1999
973
  return;
2000
974
  }
2001
975
 
2002
- // Reentrancy guard: allow recursive calls from skip paths (s.skipDepth > 0)
2003
- // but block concurrent external calls (watchdog, step wizard, etc.)
976
+ // ── Session lock validation: detect if another process has taken over ──
977
+ if (lockBase() && !validateSessionLock(lockBase())) {
978
+ debugLog("dispatchNextUnit session-lock-lost — another process may have taken over");
979
+ ctx.ui.notify(
980
+ "Session lock lost — another GSD process appears to have taken over. Stopping gracefully.",
981
+ "error",
982
+ );
983
+ // Don't call stopAuto here to avoid releasing the lock we don't own
984
+ s.active = false;
985
+ s.paused = false;
986
+ clearUnitTimeout();
987
+ deregisterSigtermHandler();
988
+ ctx.ui.setStatus("gsd-auto", undefined);
989
+ ctx.ui.setWidget("gsd-progress", undefined);
990
+ ctx.ui.setFooter(undefined);
991
+ return;
992
+ }
993
+
994
+ // Reentrancy guard
2004
995
  if (s.dispatching && s.skipDepth === 0) {
2005
996
  debugLog("dispatchNextUnit reentrancy guard — another dispatch in progress, bailing");
2006
- return; // Another dispatch is in progress — bail silently
997
+ return;
2007
998
  }
2008
999
  s.dispatching = true;
2009
1000
  try {
2010
- // Recursion depth guard: when many units are skipped in sequence (e.g., after
2011
- // crash recovery with 10+ completed units), recursive dispatchNextUnit calls
2012
- // can freeze the TUI or overflow the stack. Yield generously after MAX_SKIP_DEPTH.
1001
+ // Recursion depth guard
2013
1002
  if (s.skipDepth > MAX_SKIP_DEPTH) {
2014
1003
  s.skipDepth = 0;
2015
1004
  ctx.ui.notify(`Skipped ${MAX_SKIP_DEPTH}+ completed units. Yielding to UI before continuing.`, "info");
2016
1005
  await new Promise(r => setTimeout(r, 200));
2017
1006
  }
2018
1007
 
2019
- // Resource version guard: detect mid-session resource updates.
2020
- // Templates are read from disk on each dispatch but extension code is loaded
2021
- // once at startup. If resources were re-synced (e.g. /gsd:update, npm update,
2022
- // or dev copy-resources), templates may expect variables the in-memory code
2023
- // doesn't provide. Stop gracefully instead of crashing.
1008
+ // Resource version guard
2024
1009
  const staleMsg = checkResourcesStale(s.resourceVersionOnStart);
2025
1010
  if (staleMsg) {
2026
1011
  await stopAuto(ctx, pi, staleMsg);
2027
1012
  return;
2028
1013
  }
2029
1014
 
2030
- // Clear all caches so deriveState sees fresh disk state (#431).
2031
- // Parse cache is also cleared — doctor may have re-populated it with
2032
- // stale data between handleAgentEnd and this dispatch call (Path B fix).
2033
1015
  invalidateAllCaches();
2034
1016
  s.lastPromptCharCount = undefined;
2035
1017
  s.lastBaselineCharCount = undefined;
2036
1018
 
2037
- // ── Pre-dispatch health gate ──────────────────────────────────────────
2038
- // Lightweight check for critical issues that would cause the next unit
2039
- // to fail or corrupt state. Auto-heals what it can, blocks on the rest.
1019
+ // ── Pre-dispatch health gate ──
2040
1020
  try {
2041
1021
  const healthGate = await preDispatchHealthGate(s.basePath);
2042
1022
  if (healthGate.fixesApplied.length > 0) {
@@ -2048,13 +1028,10 @@ async function dispatchNextUnit(
2048
1028
  return;
2049
1029
  }
2050
1030
  } catch {
2051
- // Non-fatal — health gate failure should never block dispatch
1031
+ // Non-fatal
2052
1032
  }
2053
1033
 
2054
- // ── Sync project root artifacts into worktree (#853) ─────────────────
2055
- // When the LLM writes artifacts to the main repo filesystem instead of
2056
- // the worktree, the worktree's gsd.db becomes stale. Sync before
2057
- // deriveState to ensure the worktree has the latest artifacts.
1034
+ // ── Sync project root artifacts into worktree ──
2058
1035
  if (s.originalBasePath && s.basePath !== s.originalBasePath && s.currentMilestoneId) {
2059
1036
  syncProjectRootToWorktree(s.originalBasePath, s.basePath, s.currentMilestoneId);
2060
1037
  }
@@ -2077,12 +1054,10 @@ async function dispatchNextUnit(
2077
1054
  "info",
2078
1055
  );
2079
1056
  sendDesktopNotification("GSD", `Milestone ${s.currentMilestoneId} complete!`, "success", "milestone");
2080
- // Hint: visualizer available after milestone transition
2081
1057
  const vizPrefs = loadEffectiveGSDPreferences()?.preferences;
2082
1058
  if (vizPrefs?.auto_visualize) {
2083
1059
  ctx.ui.notify("Run /gsd visualize to see progress overview.", "info");
2084
1060
  }
2085
- // Auto-generate HTML report snapshot on milestone completion (default: on, disable with auto_report: false)
2086
1061
  if (vizPrefs?.auto_report !== false) {
2087
1062
  try {
2088
1063
  const { loadVisualizerData } = await import("./visualizer-data.js");
@@ -2135,20 +1110,15 @@ async function dispatchNextUnit(
2135
1110
  s.unitRecoveryCount.clear();
2136
1111
  s.unitConsecutiveSkips.clear();
2137
1112
  s.unitLifetimeDispatches.clear();
2138
- // Clear completed-units.json for the finished milestone
2139
1113
  try {
2140
1114
  const file = completedKeysPath(s.basePath);
2141
- if (existsSync(file)) writeFileSync(file, JSON.stringify([]), "utf-8");
1115
+ if (existsSync(file)) {
1116
+ atomicWriteSync(file, JSON.stringify([]));
1117
+ }
2142
1118
  s.completedKeySet.clear();
2143
- } catch { /* non-fatal */ }
2144
-
2145
- // ── Worktree lifecycle on milestone transition (#616) ──────────────
2146
- // When transitioning from M_old to M_new inside a worktree, we must:
2147
- // 1. Merge the completed milestone's worktree back to main
2148
- // 2. Re-derive state from the project root
2149
- // 3. Create a new worktree for the incoming milestone
2150
- // Without this, M_new runs inside M_old's worktree on the wrong branch,
2151
- // and artifact paths resolve against the wrong .gsd/ directory.
1119
+ } catch (e) { debugLog("completed-keys-reset-failed", { error: e instanceof Error ? e.message : String(e) }); }
1120
+
1121
+ // ── Worktree lifecycle on milestone transition (#616) ──
2152
1122
  if (isInAutoWorktree(s.basePath) && s.originalBasePath && shouldUseWorktreeIsolation()) {
2153
1123
  try {
2154
1124
  const roadmapPath = resolveMilestoneFile(s.originalBasePath, s.currentMilestoneId, "ROADMAP");
@@ -2160,7 +1130,6 @@ async function dispatchNextUnit(
2160
1130
  "info",
2161
1131
  );
2162
1132
  } else {
2163
- // No roadmap found — teardown worktree without merge
2164
1133
  teardownAutoWorktree(s.originalBasePath, s.currentMilestoneId);
2165
1134
  ctx.ui.notify(`Exited worktree for ${ s.currentMilestoneId } (no roadmap for merge).`, "info");
2166
1135
  }
@@ -2169,23 +1138,19 @@ async function dispatchNextUnit(
2169
1138
  `Milestone merge failed during transition: ${err instanceof Error ? err.message : String(err)}`,
2170
1139
  "warning",
2171
1140
  );
2172
- // Force cwd back to project root even if merge failed
2173
1141
  if (s.originalBasePath) {
2174
1142
  try { process.chdir(s.originalBasePath); } catch { /* best-effort */ }
2175
1143
  }
2176
1144
  }
2177
1145
 
2178
- // Update s.basePath to project root (mergeMilestoneToMain already chdir'd)
2179
1146
  s.basePath = s.originalBasePath;
2180
1147
  s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
2181
1148
  invalidateAllCaches();
2182
1149
 
2183
- // Re-derive state from project root before creating new worktree
2184
1150
  state = await deriveState(s.basePath);
2185
1151
  mid = state.activeMilestone?.id;
2186
1152
  midTitle = state.activeMilestone?.title;
2187
1153
 
2188
- // Create new worktree for the incoming milestone
2189
1154
  if (mid) {
2190
1155
  captureIntegrationBranch(s.basePath, mid, { commitDocs: loadEffectiveGSDPreferences()?.preferences?.git?.commit_docs });
2191
1156
  try {
@@ -2201,14 +1166,11 @@ async function dispatchNextUnit(
2201
1166
  }
2202
1167
  }
2203
1168
  } else {
2204
- // Not in worktree — capture integration branch for the new milestone (branch mode only).
2205
- // In none mode there's no milestone branch to merge back to, so skip.
2206
1169
  if (getIsolationMode() !== "none") {
2207
1170
  captureIntegrationBranch(s.originalBasePath || s.basePath, mid, { commitDocs: loadEffectiveGSDPreferences()?.preferences?.git?.commit_docs });
2208
1171
  }
2209
1172
  }
2210
1173
 
2211
- // Prune completed milestone from queue order file
2212
1174
  const pendingIds = state.registry
2213
1175
  .filter(m => m.status !== "complete")
2214
1176
  .map(m => m.id);
@@ -2220,24 +1182,67 @@ async function dispatchNextUnit(
2220
1182
  }
2221
1183
 
2222
1184
  if (!mid) {
2223
- // Save final session before stopping
2224
1185
  if (s.currentUnit) {
2225
1186
  await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
2226
1187
  }
2227
1188
 
2228
- const incomplete = state.registry.filter(m => m.status !== "complete");
1189
+ const incomplete = state.registry.filter(m => m.status !== "complete" && m.status !== "parked");
2229
1190
  if (incomplete.length === 0) {
2230
- // Genuinely all complete
1191
+ // Genuinely all complete (parked milestones excluded) — merge milestone branch to main before stopping (#962)
1192
+ if (s.currentMilestoneId && isInAutoWorktree(s.basePath) && s.originalBasePath) {
1193
+ try {
1194
+ const roadmapPath = resolveMilestoneFile(s.originalBasePath, s.currentMilestoneId, "ROADMAP");
1195
+ if (roadmapPath) {
1196
+ const roadmapContent = readFileSync(roadmapPath, "utf-8");
1197
+ const mergeResult = mergeMilestoneToMain(s.originalBasePath, s.currentMilestoneId, roadmapContent);
1198
+ s.basePath = s.originalBasePath;
1199
+ s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
1200
+ ctx.ui.notify(
1201
+ `Milestone ${ s.currentMilestoneId } merged to main.${mergeResult.pushed ? " Pushed to remote." : ""}`,
1202
+ "info",
1203
+ );
1204
+ }
1205
+ } catch (err) {
1206
+ ctx.ui.notify(
1207
+ `Milestone merge failed: ${err instanceof Error ? err.message : String(err)}`,
1208
+ "warning",
1209
+ );
1210
+ if (s.originalBasePath) {
1211
+ s.basePath = s.originalBasePath;
1212
+ try { process.chdir(s.basePath); } catch { /* best-effort */ }
1213
+ }
1214
+ }
1215
+ } else if (s.currentMilestoneId && !isInAutoWorktree(s.basePath) && getIsolationMode() !== "none") {
1216
+ try {
1217
+ const currentBranch = getCurrentBranch(s.basePath);
1218
+ const milestoneBranch = autoWorktreeBranch(s.currentMilestoneId);
1219
+ if (currentBranch === milestoneBranch) {
1220
+ const roadmapPath = resolveMilestoneFile(s.basePath, s.currentMilestoneId, "ROADMAP");
1221
+ if (roadmapPath) {
1222
+ const roadmapContent = readFileSync(roadmapPath, "utf-8");
1223
+ const mergeResult = mergeMilestoneToMain(s.basePath, s.currentMilestoneId, roadmapContent);
1224
+ s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
1225
+ ctx.ui.notify(
1226
+ `Milestone ${ s.currentMilestoneId } merged (branch mode).${mergeResult.pushed ? " Pushed to remote." : ""}`,
1227
+ "info",
1228
+ );
1229
+ }
1230
+ }
1231
+ } catch (err) {
1232
+ ctx.ui.notify(
1233
+ `Milestone merge failed (branch mode): ${err instanceof Error ? err.message : String(err)}`,
1234
+ "warning",
1235
+ );
1236
+ }
1237
+ }
2231
1238
  sendDesktopNotification("GSD", "All milestones complete!", "success", "milestone");
2232
1239
  await stopAuto(ctx, pi, "All milestones complete");
2233
1240
  } else if (state.phase === "blocked") {
2234
- // Milestones exist but are dependency-blocked
2235
1241
  const blockerMsg = `Blocked: ${state.blockers.join(", ")}`;
2236
1242
  await stopAuto(ctx, pi, blockerMsg);
2237
1243
  ctx.ui.notify(`${blockerMsg}. Fix and run /gsd auto.`, "warning");
2238
1244
  sendDesktopNotification("GSD", blockerMsg, "error", "attention");
2239
1245
  } else {
2240
- // Milestones with remaining work exist but none became s.active — unexpected
2241
1246
  const ids = incomplete.map(m => m.id).join(", ");
2242
1247
  const diag = `basePath=${s.basePath}, milestones=[${state.registry.map(m => `${m.id}:${m.status}`).join(", ")}], phase=${state.phase}`;
2243
1248
  ctx.ui.notify(`Unexpected: ${incomplete.length} incomplete milestone(s) (${ids}) but no active milestone.\n Diagnostic: ${diag}`, "error");
@@ -2246,15 +1251,12 @@ async function dispatchNextUnit(
2246
1251
  return;
2247
1252
  }
2248
1253
 
2249
- // Guard: mid/midTitle must be defined strings from this point onward.
2250
- // The !mid check above returns early if mid is falsy; midTitle comes from
2251
- // the same object so it should always be present when mid is.
2252
1254
  if (!midTitle) {
2253
- midTitle = mid; // Defensive fallback: use milestone ID as title
1255
+ midTitle = mid;
2254
1256
  ctx.ui.notify(`Milestone ${mid} has no title in roadmap — using ID as fallback.`, "warning");
2255
1257
  }
2256
1258
 
2257
- // ── Mid-merge safety check: detect leftover merge state from a prior session ──
1259
+ // ── Mid-merge safety check ──
2258
1260
  if (reconcileMergeState(s.basePath, ctx)) {
2259
1261
  invalidateAllCaches();
2260
1262
  state = await deriveState(s.basePath);
@@ -2262,7 +1264,6 @@ async function dispatchNextUnit(
2262
1264
  midTitle = state.activeMilestone?.title;
2263
1265
  }
2264
1266
 
2265
- // After merge guard removal (branchless architecture), mid/midTitle could be undefined
2266
1267
  if (!mid || !midTitle) {
2267
1268
  if (s.currentUnit) {
2268
1269
  await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
@@ -2283,17 +1284,18 @@ async function dispatchNextUnit(
2283
1284
  if (s.currentUnit) {
2284
1285
  await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
2285
1286
  }
2286
- // Clear completed-units.json for the finished milestone so it doesn't grow unbounded.
2287
1287
  try {
2288
1288
  const file = completedKeysPath(s.basePath);
2289
- if (existsSync(file)) writeFileSync(file, JSON.stringify([]), "utf-8");
1289
+ if (existsSync(file)) {
1290
+ atomicWriteSync(file, JSON.stringify([]));
1291
+ }
2290
1292
  s.completedKeySet.clear();
2291
- } catch { /* non-fatal */ }
2292
- // ── Milestone merge: squash-merge milestone branch to main before stopping ──
1293
+ } catch (e) { debugLog("completed-keys-reset-failed", { error: e instanceof Error ? e.message : String(e) }); }
1294
+ // ── Milestone merge ──
2293
1295
  if (s.currentMilestoneId && isInAutoWorktree(s.basePath) && s.originalBasePath) {
2294
1296
  try {
2295
1297
  const roadmapPath = resolveMilestoneFile(s.originalBasePath, s.currentMilestoneId, "ROADMAP");
2296
- if (!roadmapPath) throw new Error(`Cannot resolve ROADMAP file for milestone ${ s.currentMilestoneId }`);
1298
+ if (!roadmapPath) throw new GSDError(GSD_ARTIFACT_MISSING, `Cannot resolve ROADMAP file for milestone ${ s.currentMilestoneId }`);
2297
1299
  const roadmapContent = readFileSync(roadmapPath, "utf-8");
2298
1300
  const mergeResult = mergeMilestoneToMain(s.originalBasePath, s.currentMilestoneId, roadmapContent);
2299
1301
  s.basePath = s.originalBasePath;
@@ -2307,17 +1309,12 @@ async function dispatchNextUnit(
2307
1309
  `Milestone merge failed: ${err instanceof Error ? err.message : String(err)}`,
2308
1310
  "warning",
2309
1311
  );
2310
- // Ensure cwd is restored even if merge failed partway through (#608).
2311
- // mergeMilestoneToMain may have chdir'd but then thrown, leaving us
2312
- // in an indeterminate location.
2313
1312
  if (s.originalBasePath) {
2314
1313
  s.basePath = s.originalBasePath;
2315
1314
  try { process.chdir(s.basePath); } catch { /* best-effort */ }
2316
1315
  }
2317
1316
  }
2318
1317
  } else if (s.currentMilestoneId && !isInAutoWorktree(s.basePath) && getIsolationMode() !== "none") {
2319
- // Branch isolation mode (#603): no worktree, but we may be on a milestone/* branch.
2320
- // Squash-merge back to the integration branch (or main) before stopping.
2321
1318
  try {
2322
1319
  const currentBranch = getCurrentBranch(s.basePath);
2323
1320
  const milestoneBranch = autoWorktreeBranch(s.currentMilestoneId);
@@ -2325,8 +1322,6 @@ async function dispatchNextUnit(
2325
1322
  const roadmapPath = resolveMilestoneFile(s.basePath, s.currentMilestoneId, "ROADMAP");
2326
1323
  if (roadmapPath) {
2327
1324
  const roadmapContent = readFileSync(roadmapPath, "utf-8");
2328
- // mergeMilestoneToMain handles: auto-commit, checkout integration branch,
2329
- // squash merge, commit, optional push, branch deletion.
2330
1325
  const mergeResult = mergeMilestoneToMain(s.basePath, s.currentMilestoneId, roadmapContent);
2331
1326
  s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
2332
1327
  ctx.ui.notify(
@@ -2358,11 +1353,9 @@ async function dispatchNextUnit(
2358
1353
  return;
2359
1354
  }
2360
1355
 
2361
- // ── UAT Dispatch: run-uat fires after complete-slice merge, before reassessment ──
2362
- // Ensures the UAT file and slice summary are both on main when UAT runs.
1356
+ // Budget ceiling guard, context window guard, secrets gate, dispatch table
2363
1357
  const prefs = loadEffectiveGSDPreferences()?.preferences;
2364
1358
 
2365
- // Budget ceiling guard — enforce budget with configurable action
2366
1359
  const budgetCeiling = prefs?.budget_ceiling;
2367
1360
  if (budgetCeiling !== undefined && budgetCeiling > 0) {
2368
1361
  const currentLedger = getLedger();
@@ -2409,8 +1402,7 @@ async function dispatchNextUnit(
2409
1402
  s.lastBudgetAlertLevel = 0;
2410
1403
  }
2411
1404
 
2412
- // Context window guard pause if approaching context limits
2413
- const contextThreshold = prefs?.context_pause_threshold ?? 0; // 0 = disabled by default
1405
+ const contextThreshold = prefs?.context_pause_threshold ?? 0;
2414
1406
  if (contextThreshold > 0 && s.cmdCtx) {
2415
1407
  const contextUsage = s.cmdCtx.getContextUsage();
2416
1408
  if (contextUsage && contextUsage.percent !== null && contextUsage.percent >= contextThreshold) {
@@ -2422,11 +1414,7 @@ async function dispatchNextUnit(
2422
1414
  }
2423
1415
  }
2424
1416
 
2425
- // ── Secrets re-check gate — runs before every dispatch, not just at startAuto ──
2426
- // plan-milestone writes the milestone SECRETS file (e.g., M001-SECRETS.md) during its unit. By the time we
2427
- // reach the next dispatchNextUnit call the manifest exists but hasn't been
2428
- // presented to the user yet. Without this re-check the model would proceed
2429
- // into plan-slice / execute-task with no real credentials and mock everything.
1417
+ // Secrets re-check gate
2430
1418
  const runSecretsGate = async () => {
2431
1419
  try {
2432
1420
  const manifestStatus = await getManifestStatus(s.basePath, mid);
@@ -2451,7 +1439,7 @@ async function dispatchNextUnit(
2451
1439
 
2452
1440
  await runSecretsGate();
2453
1441
 
2454
- // ── Dispatch table: resolve phase → unit type + prompt ──
1442
+ // ── Dispatch table ──
2455
1443
  const dispatchResult = await resolveDispatch({ basePath: s.basePath, mid, midTitle: midTitle!, state, prefs,
2456
1444
  });
2457
1445
 
@@ -2464,7 +1452,6 @@ async function dispatchNextUnit(
2464
1452
  }
2465
1453
 
2466
1454
  if (dispatchResult.action !== "dispatch") {
2467
- // skip action — yield and re-dispatch
2468
1455
  await new Promise(r => setImmediate(r));
2469
1456
  await dispatchNextUnit(ctx, pi);
2470
1457
  return;
@@ -2475,7 +1462,7 @@ async function dispatchNextUnit(
2475
1462
  prompt = dispatchResult.prompt;
2476
1463
  let pauseAfterUatDispatch = dispatchResult.pauseAfterDispatch ?? false;
2477
1464
 
2478
- // ── Pre-dispatch hooks: modify, skip, or replace the unit before dispatch ──
1465
+ // ── Pre-dispatch hooks ──
2479
1466
  const preDispatchResult = runPreDispatchHooks(unitType, unitId, prompt, s.basePath);
2480
1467
  if (preDispatchResult.firedHooks.length > 0) {
2481
1468
  ctx.ui.notify(
@@ -2485,7 +1472,6 @@ async function dispatchNextUnit(
2485
1472
  }
2486
1473
  if (preDispatchResult.action === "skip") {
2487
1474
  ctx.ui.notify(`Skipping ${unitType} ${unitId} (pre-dispatch hook).`, "info");
2488
- // Yield then re-dispatch to advance to next unit
2489
1475
  await new Promise(r => setImmediate(r));
2490
1476
  await dispatchNextUnit(ctx, pi);
2491
1477
  return;
@@ -2505,378 +1491,76 @@ async function dispatchNextUnit(
2505
1491
 
2506
1492
  const observabilityIssues = await _collectObservabilityWarnings(ctx, s.basePath, unitType, unitId);
2507
1493
 
2508
- // Idempotency: skip units already completed in a prior session.
2509
- const idempotencyKey = `${unitType}/${unitId}`;
2510
- if (s.completedKeySet.has(idempotencyKey)) {
2511
- // Cross-validate: does the expected artifact actually exist?
2512
- const artifactExists = verifyExpectedArtifact(unitType, unitId, s.basePath);
2513
- if (artifactExists) {
2514
- // Guard against infinite skip loops: if deriveState keeps returning the
2515
- // same completed unit, consecutive skips will trip this breaker. Evict the
2516
- // key so the next dispatch forces full reconciliation instead of looping.
2517
- const skipCount = (s.unitConsecutiveSkips.get(idempotencyKey) ?? 0) + 1;
2518
- s.unitConsecutiveSkips.set(idempotencyKey, skipCount);
2519
- if (skipCount > MAX_CONSECUTIVE_SKIPS) {
2520
- // Cross-check: verify deriveState actually returns this unit (#790).
2521
- // If the unit's milestone is already complete, this is a phantom skip
2522
- // loop from stale crash recovery context — don't evict.
2523
- const skippedMid = unitId.split("/")[0];
2524
- const skippedMilestoneComplete = skippedMid
2525
- ? !!resolveMilestoneFile(s.basePath, skippedMid, "SUMMARY")
2526
- : false;
2527
- if (skippedMilestoneComplete) {
2528
- // Milestone is complete — evicting this key would fight self-heal.
2529
- // Clear skip counter and re-dispatch from fresh state.
2530
- s.unitConsecutiveSkips.delete(idempotencyKey);
2531
- invalidateAllCaches();
2532
- ctx.ui.notify(
2533
- `Phantom skip loop cleared: ${unitType} ${unitId} belongs to completed milestone ${skippedMid}. Re-dispatching from fresh state.`,
2534
- "info",
2535
- );
2536
- s.skipDepth++;
2537
- await new Promise(r => setTimeout(r, 50));
2538
- await dispatchNextUnit(ctx, pi);
2539
- s.skipDepth = Math.max(0, s.skipDepth - 1);
2540
- return;
2541
- }
2542
- s.unitConsecutiveSkips.delete(idempotencyKey);
2543
- s.completedKeySet.delete(idempotencyKey);
2544
- s.recentlyEvictedKeys.add(idempotencyKey);
2545
- removePersistedKey(s.basePath, idempotencyKey);
2546
- invalidateAllCaches();
2547
- ctx.ui.notify(
2548
- `Skip loop detected: ${unitType} ${unitId} skipped ${skipCount} times without advancing. Evicting completion record and forcing reconciliation.`,
2549
- "warning",
2550
- );
2551
- if (!s.active) return;
2552
- s.skipDepth++;
2553
- await new Promise(r => setTimeout(r, 150));
2554
- await dispatchNextUnit(ctx, pi);
2555
- s.skipDepth = Math.max(0, s.skipDepth - 1);
2556
- return;
2557
- }
2558
- // Count toward lifetime cap so hard-stop fires during skip loops (#792)
2559
- const lifeSkip = (s.unitLifetimeDispatches.get(idempotencyKey) ?? 0) + 1;
2560
- s.unitLifetimeDispatches.set(idempotencyKey, lifeSkip);
2561
- if (lifeSkip > MAX_LIFETIME_DISPATCHES) {
2562
- await stopAuto(ctx, pi, `Hard loop: ${unitType} ${unitId} (skip cycle)`);
2563
- ctx.ui.notify(
2564
- `Hard loop detected: ${unitType} ${unitId} hit lifetime cap during skip cycle (${lifeSkip} iterations).`,
2565
- "error",
2566
- );
2567
- return;
2568
- }
2569
- ctx.ui.notify(
2570
- `Skipping ${unitType} ${unitId} — already completed in a prior session. Advancing.`,
2571
- "info",
2572
- );
2573
- if (!s.active) return;
2574
- s.skipDepth++;
2575
- await new Promise(r => setTimeout(r, 150));
2576
- await dispatchNextUnit(ctx, pi);
2577
- s.skipDepth = Math.max(0, s.skipDepth - 1);
2578
- return;
2579
- } else {
2580
- // Stale completion record — artifact missing. Remove and re-run.
2581
- s.completedKeySet.delete(idempotencyKey);
2582
- removePersistedKey(s.basePath, idempotencyKey);
2583
- ctx.ui.notify(
2584
- `Re-running ${unitType} ${unitId} — marked complete but expected artifact missing.`,
2585
- "warning",
2586
- );
2587
- }
2588
- }
1494
+ // ── Idempotency check (delegated to auto-idempotency.ts) ──
1495
+ const idempotencyResult = checkIdempotency({
1496
+ s,
1497
+ unitType,
1498
+ unitId,
1499
+ basePath: s.basePath,
1500
+ notify: (msg, level) => ctx.ui.notify(msg, level),
1501
+ });
2589
1502
 
2590
- // Fallback: if the idempotency key is missing but the expected artifact already
2591
- // exists on disk, the task completed in a prior session without persisting the key.
2592
- // Persist it now and skip re-dispatch. This prevents infinite loops where a task
2593
- // completes successfully but the completion key was never written.
2594
- //
2595
- // EXCEPTION: if the key was just evicted by the skip-loop breaker above, do NOT
2596
- // re-persist — that would recreate the exact loop the breaker was trying to break (#912).
2597
- if (verifyExpectedArtifact(unitType, unitId, s.basePath) && !s.recentlyEvictedKeys.has(idempotencyKey)) {
2598
- persistCompletedKey(s.basePath, idempotencyKey);
2599
- s.completedKeySet.add(idempotencyKey);
2600
- invalidateAllCaches();
2601
- // Same consecutive-skip guard as the idempotency path above.
2602
- const skipCount2 = (s.unitConsecutiveSkips.get(idempotencyKey) ?? 0) + 1;
2603
- s.unitConsecutiveSkips.set(idempotencyKey, skipCount2);
2604
- if (skipCount2 > MAX_CONSECUTIVE_SKIPS) {
2605
- // Cross-check: verify the unit's milestone is still active (#790).
2606
- const skippedMid2 = unitId.split("/")[0];
2607
- const skippedMilestoneComplete2 = skippedMid2
2608
- ? !!resolveMilestoneFile(s.basePath, skippedMid2, "SUMMARY")
2609
- : false;
2610
- if (skippedMilestoneComplete2) {
2611
- s.unitConsecutiveSkips.delete(idempotencyKey);
2612
- invalidateAllCaches();
2613
- ctx.ui.notify(
2614
- `Phantom skip loop cleared: ${unitType} ${unitId} belongs to completed milestone ${skippedMid2}. Re-dispatching from fresh state.`,
2615
- "info",
2616
- );
2617
- s.skipDepth++;
2618
- await new Promise(r => setTimeout(r, 50));
2619
- await dispatchNextUnit(ctx, pi);
2620
- s.skipDepth = Math.max(0, s.skipDepth - 1);
2621
- return;
2622
- }
2623
- s.unitConsecutiveSkips.delete(idempotencyKey);
2624
- s.completedKeySet.delete(idempotencyKey);
2625
- removePersistedKey(s.basePath, idempotencyKey);
2626
- invalidateAllCaches();
2627
- ctx.ui.notify(
2628
- `Skip loop detected: ${unitType} ${unitId} skipped ${skipCount2} times without advancing. Evicting completion record and forcing reconciliation.`,
2629
- "warning",
2630
- );
1503
+ if (idempotencyResult.action === "skip") {
1504
+ if (idempotencyResult.reason === "completed" || idempotencyResult.reason === "fallback-persisted" || idempotencyResult.reason === "phantom-loop-cleared" || idempotencyResult.reason === "evicted") {
2631
1505
  if (!s.active) return;
2632
1506
  s.skipDepth++;
2633
- await new Promise(r => setTimeout(r, 150));
1507
+ await new Promise(r => setTimeout(r, idempotencyResult.reason === "phantom-loop-cleared" ? 50 : 150));
2634
1508
  await dispatchNextUnit(ctx, pi);
2635
1509
  s.skipDepth = Math.max(0, s.skipDepth - 1);
2636
1510
  return;
2637
1511
  }
2638
- // Count toward lifetime cap so hard-stop fires during skip loops (#792)
2639
- const lifeSkip2 = (s.unitLifetimeDispatches.get(idempotencyKey) ?? 0) + 1;
2640
- s.unitLifetimeDispatches.set(idempotencyKey, lifeSkip2);
2641
- if (lifeSkip2 > MAX_LIFETIME_DISPATCHES) {
2642
- await stopAuto(ctx, pi, `Hard loop: ${unitType} ${unitId} (skip cycle)`);
2643
- ctx.ui.notify(
2644
- `Hard loop detected: ${unitType} ${unitId} hit lifetime cap during skip cycle (${lifeSkip2} iterations).`,
2645
- "error",
2646
- );
2647
- return;
2648
- }
2649
- ctx.ui.notify(
2650
- `Skipping ${unitType} ${unitId} — artifact exists but completion key was missing. Repaired and advancing.`,
2651
- "info",
2652
- );
2653
- if (!s.active) return;
2654
- s.skipDepth++;
2655
- await new Promise(r => setTimeout(r, 150));
2656
- await dispatchNextUnit(ctx, pi);
2657
- s.skipDepth = Math.max(0, s.skipDepth - 1);
2658
- return;
2659
- }
2660
-
2661
- // Stuck detection — tracks total dispatches per unit (not just consecutive repeats).
2662
- // Pattern A→B→A→B would reset retryCount every time; this map catches it.
2663
- const dispatchKey = `${unitType}/${unitId}`;
2664
- const prevCount = s.unitDispatchCount.get(dispatchKey) ?? 0;
2665
- // Real dispatch reached — clear the consecutive-skip counter for this unit.
2666
- s.unitConsecutiveSkips.delete(dispatchKey);
2667
-
2668
- debugLog("dispatch-unit", {
2669
- type: unitType,
2670
- id: unitId,
2671
- cycle: prevCount + 1,
2672
- lifetime: (s.unitLifetimeDispatches.get(dispatchKey) ?? 0) + 1,
2673
- });
2674
- debugCount("dispatches");
2675
-
2676
- // Hard lifetime cap — survives counter resets from loop-recovery/self-repair.
2677
- // Catches the case where reconciliation "succeeds" (artifacts exist) but
2678
- // deriveState keeps returning the same unit, creating an infinite cycle.
2679
- const lifetimeCount = (s.unitLifetimeDispatches.get(dispatchKey) ?? 0) + 1;
2680
- s.unitLifetimeDispatches.set(dispatchKey, lifetimeCount);
2681
- if (lifetimeCount > MAX_LIFETIME_DISPATCHES) {
2682
- if (s.currentUnit) {
2683
- await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
2684
- } else {
2685
- saveActivityLog(ctx, s.basePath, unitType, unitId);
2686
- }
2687
- const expected = diagnoseExpectedArtifact(unitType, unitId, s.basePath);
2688
- await stopAuto(ctx, pi, `Hard loop: ${unitType} ${unitId}`);
1512
+ } else if (idempotencyResult.action === "stop") {
1513
+ await stopAuto(ctx, pi, idempotencyResult.reason);
2689
1514
  ctx.ui.notify(
2690
- `Hard loop detected: ${unitType} ${unitId} dispatched ${lifetimeCount} times total (across reconciliation cycles).${expected ? `\n Expected artifact: ${expected}` : ""}\n This may indicate deriveState() keeps returning the same unit despite artifacts existing.\n Check .gsd/completed-units.json and the slice plan checkbox state.`,
1515
+ `Hard loop detected: ${unitType} ${unitId} hit lifetime cap during skip cycle.`,
2691
1516
  "error",
2692
1517
  );
2693
1518
  return;
2694
1519
  }
2695
- if (prevCount >= MAX_UNIT_DISPATCHES) {
2696
- if (s.currentUnit) {
2697
- await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
2698
- } else {
2699
- saveActivityLog(ctx, s.basePath, unitType, unitId);
2700
- }
2701
-
2702
- // Final reconciliation pass for execute-task: write any missing durable
2703
- // artifacts (summary placeholder + [x] checkbox) so the pipeline can
2704
- // advance instead of stopping. This is the last resort before halting.
2705
- if (unitType === "execute-task") {
2706
- const [mid, sid, tid] = unitId.split("/");
2707
- if (mid && sid && tid) {
2708
- const status = await inspectExecuteTaskDurability(s.basePath, unitId);
2709
- if (status) {
2710
- const reconciled = skipExecuteTask(s.basePath, mid, sid, tid, status, "loop-recovery", prevCount);
2711
- // reconciled: skipExecuteTask attempted to write missing artifacts.
2712
- // verifyExpectedArtifact: confirms physical artifacts (summary + [x]) now exist on disk.
2713
- // Both must pass before we clear the dispatch counter and advance.
2714
- if (reconciled && verifyExpectedArtifact(unitType, unitId, s.basePath)) {
2715
- ctx.ui.notify(
2716
- `Loop recovery: ${unitId} reconciled after ${prevCount + 1} dispatches — blocker artifacts written, pipeline advancing.\n Review ${status.summaryPath} and replace the placeholder with real work.`,
2717
- "warning",
2718
- );
2719
- // Persist completion so idempotency check prevents re-dispatch
2720
- // if deriveState keeps returning this unit (#462).
2721
- const reconciledKey = `${unitType}/${unitId}`;
2722
- persistCompletedKey(s.basePath, reconciledKey);
2723
- s.completedKeySet.add(reconciledKey);
2724
- s.unitDispatchCount.delete(dispatchKey);
2725
- invalidateAllCaches();
2726
- await new Promise(r => setImmediate(r));
2727
- await dispatchNextUnit(ctx, pi);
2728
- return;
2729
- }
2730
- }
2731
- }
2732
- }
1520
+ // "rerun" and "proceed" fall through to stuck detection
2733
1521
 
2734
- // General reconciliation: if the last attempt DID produce the expected
2735
- // artifact on disk, clear the counter and advance instead of stopping.
2736
- // The execute-task path above handles its special case (writing placeholder
2737
- // summaries). This catch-all covers complete-slice, plan-slice,
2738
- // research-slice, and all other unit types where the Nth attempt at the
2739
- // dispatch limit succeeded but the counter check fires before anyone
2740
- // verifies disk state. Without this, a successful final attempt is
2741
- // indistinguishable from a failed one.
2742
- if (verifyExpectedArtifact(unitType, unitId, s.basePath)) {
2743
- ctx.ui.notify(
2744
- `Loop recovery: ${unitType} ${unitId} — artifact verified after ${prevCount + 1} dispatches. Advancing.`,
2745
- "info",
2746
- );
2747
- // Persist completion so the idempotency check prevents re-dispatch
2748
- // if deriveState keeps returning this unit (see #462).
2749
- persistCompletedKey(s.basePath, dispatchKey);
2750
- s.completedKeySet.add(dispatchKey);
2751
- s.unitDispatchCount.delete(dispatchKey);
2752
- invalidateAllCaches();
2753
- await new Promise(r => setImmediate(r));
2754
- await dispatchNextUnit(ctx, pi);
2755
- return;
2756
- }
1522
+ // ── Stuck detection (delegated to auto-stuck-detection.ts) ──
1523
+ const stuckResult = await checkStuckAndRecover({
1524
+ s,
1525
+ ctx,
1526
+ unitType,
1527
+ unitId,
1528
+ basePath: s.basePath,
1529
+ buildSnapshotOpts: () => buildSnapshotOpts(unitType, unitId),
1530
+ });
2757
1531
 
2758
- // Last resort for complete-milestone: generate stub summary to unblock pipeline.
2759
- // All slices are done (otherwise we wouldn't be in completing-milestone phase),
2760
- // but the LLM failed to write the summary N times. A stub lets the pipeline advance.
2761
- if (unitType === "complete-milestone") {
2762
- try {
2763
- const mPath = resolveMilestonePath(s.basePath, unitId);
2764
- if (mPath) {
2765
- const stubPath = join(mPath, `${unitId}-SUMMARY.md`);
2766
- if (!existsSync(stubPath)) {
2767
- writeFileSync(stubPath, `# ${unitId} Summary\n\nAuto-generated stub — milestone tasks completed but summary generation failed after ${prevCount + 1} attempts.\nReview and replace this stub with a proper summary.\n`);
2768
- ctx.ui.notify(`Generated stub summary for ${unitId} to unblock pipeline. Review later.`, "warning");
2769
- persistCompletedKey(s.basePath, dispatchKey);
2770
- s.completedKeySet.add(dispatchKey);
2771
- s.unitDispatchCount.delete(dispatchKey);
2772
- invalidateAllCaches();
2773
- await new Promise(r => setImmediate(r));
2774
- await dispatchNextUnit(ctx, pi);
2775
- return;
2776
- }
2777
- }
2778
- } catch { /* non-fatal — fall through to normal stop */ }
1532
+ if (stuckResult.action === "stop") {
1533
+ await stopAuto(ctx, pi, stuckResult.reason);
1534
+ if (stuckResult.notifyMessage) {
1535
+ ctx.ui.notify(stuckResult.notifyMessage, "error");
2779
1536
  }
2780
-
2781
- const expected = diagnoseExpectedArtifact(unitType, unitId, s.basePath);
2782
- const remediation = buildLoopRemediationSteps(unitType, unitId, s.basePath);
2783
- await stopAuto(ctx, pi, `Loop: ${unitType} ${unitId}`);
2784
- sendDesktopNotification("GSD", `Loop detected: ${unitType} ${unitId}`, "error", "error");
2785
- ctx.ui.notify(
2786
- `Loop detected: ${unitType} ${unitId} dispatched ${prevCount + 1} times total. Expected artifact not found.${expected ? `\n Expected: ${expected}` : ""}${remediation ? `\n\n Remediation steps:\n${remediation}` : "\n Check branch state and .gsd/ artifacts."}`,
2787
- "error",
2788
- );
2789
1537
  return;
2790
1538
  }
2791
- s.unitDispatchCount.set(dispatchKey, prevCount + 1);
2792
- if (prevCount > 0) {
2793
- // Adaptive self-repair: each retry attempts a different remediation step.
2794
- if (unitType === "execute-task") {
2795
- const status = await inspectExecuteTaskDurability(s.basePath, unitId);
2796
- const [mid, sid, tid] = unitId.split("/");
2797
- if (status && mid && sid && tid) {
2798
- if (status.summaryExists && !status.taskChecked) {
2799
- // Retry 1+: summary exists but checkbox not marked — mark [x] and advance.
2800
- const repaired = skipExecuteTask(s.basePath, mid, sid, tid, status, "self-repair", 0);
2801
- // repaired: skipExecuteTask updated metadata (returned early-true even if regex missed).
2802
- // verifyExpectedArtifact: confirms the physical artifact (summary + [x]) now exists.
2803
- if (repaired && verifyExpectedArtifact(unitType, unitId, s.basePath)) {
2804
- ctx.ui.notify(
2805
- `Self-repaired ${unitId}: summary existed but checkbox was unmarked. Marked [x] and advancing.`,
2806
- "warning",
2807
- );
2808
- // Persist completion so idempotency check prevents re-dispatch (#462).
2809
- const repairedKey = `${unitType}/${unitId}`;
2810
- persistCompletedKey(s.basePath, repairedKey);
2811
- s.completedKeySet.add(repairedKey);
2812
- s.unitDispatchCount.delete(dispatchKey);
2813
- invalidateAllCaches();
2814
- await new Promise(r => setImmediate(r));
2815
- await dispatchNextUnit(ctx, pi);
2816
- return;
2817
- }
2818
- } else if (prevCount >= STUB_RECOVERY_THRESHOLD && !status.summaryExists) {
2819
- // Retry STUB_RECOVERY_THRESHOLD+: summary still missing after multiple attempts.
2820
- // Write a minimal stub summary so the next agent session has a recovery artifact
2821
- // to overwrite, rather than starting from scratch again.
2822
- const tasksDir = resolveTasksDir(s.basePath, mid, sid);
2823
- const sDir = resolveSlicePath(s.basePath, mid, sid);
2824
- const targetDir = tasksDir ?? (sDir ? join(sDir, "tasks") : null);
2825
- if (targetDir) {
2826
- if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true });
2827
- const summaryPath = join(targetDir, buildTaskFileName(tid, "SUMMARY"));
2828
- if (!existsSync(summaryPath)) {
2829
- const stubContent = [
2830
- `# PARTIAL RECOVERY — attempt ${prevCount + 1} of ${MAX_UNIT_DISPATCHES}`,
2831
- ``,
2832
- `Task \`${tid}\` in slice \`${sid}\` (milestone \`${mid}\`) has not yet produced a real summary.`,
2833
- `This placeholder was written by auto-mode after ${prevCount} dispatch attempts.`,
2834
- ``,
2835
- `The next agent session will retry this task. Replace this file with real work when done.`,
2836
- ].join("\n");
2837
- writeFileSync(summaryPath, stubContent, "utf-8");
2838
- ctx.ui.notify(
2839
- `Stub recovery (attempt ${prevCount + 1}/${MAX_UNIT_DISPATCHES}): ${unitId} stub summary placeholder written. Retrying with recovery context.`,
2840
- "warning",
2841
- );
2842
- }
2843
- }
2844
- }
2845
- }
2846
- }
2847
- ctx.ui.notify(
2848
- `${unitType} ${unitId} didn't produce expected artifact. Retrying (${prevCount + 1}/${MAX_UNIT_DISPATCHES}).`,
2849
- "warning",
2850
- );
1539
+ if (stuckResult.action === "recovered" && stuckResult.dispatchAgain) {
1540
+ await new Promise(r => setImmediate(r));
1541
+ await dispatchNextUnit(ctx, pi);
1542
+ return;
2851
1543
  }
1544
+
2852
1545
  // Snapshot metrics + activity log for the PREVIOUS unit before we reassign.
2853
- // The session still holds the previous unit's data (newSession hasn't fired yet).
2854
1546
  if (s.currentUnit) {
2855
1547
  await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
2856
1548
 
2857
- // Record routing outcome for adaptive learning
2858
1549
  if (s.currentUnitRouting) {
2859
1550
  const isRetry = s.currentUnit.type === unitType && s.currentUnit.id === unitId;
2860
1551
  recordOutcome(
2861
1552
  s.currentUnit.type,
2862
1553
  s.currentUnitRouting.tier as "light" | "standard" | "heavy",
2863
- !isRetry, // success = not being retried
1554
+ !isRetry,
2864
1555
  );
2865
1556
  }
2866
1557
 
2867
- // Only mark the previous unit as completed if:
2868
- // 1. We're not about to re-dispatch the same unit (retry scenario)
2869
- // 2. The expected artifact actually exists on disk
2870
- // For hook units, skip artifact verification — hooks don't produce standard
2871
- // artifacts and their runtime records were already finalized in handleAgentEnd.
2872
1558
  const closeoutKey = `${s.currentUnit.type}/${s.currentUnit.id}`;
2873
1559
  const incomingKey = `${unitType}/${unitId}`;
2874
1560
  const isHookUnit = s.currentUnit.type.startsWith("hook/");
2875
1561
  const artifactVerified = isHookUnit || verifyExpectedArtifact(s.currentUnit.type, s.currentUnit.id, s.basePath);
2876
1562
  if (closeoutKey !== incomingKey && artifactVerified) {
2877
1563
  if (!isHookUnit) {
2878
- // Only persist completion keys for real units — hook keys are
2879
- // ephemeral and should not pollute the idempotency set.
2880
1564
  persistCompletedKey(s.basePath, closeoutKey);
2881
1565
  s.completedKeySet.add(closeoutKey);
2882
1566
  }
@@ -2887,7 +1571,6 @@ async function dispatchNextUnit(
2887
1571
  startedAt: s.currentUnit.startedAt,
2888
1572
  finishedAt: Date.now(),
2889
1573
  });
2890
- // Cap to last 200 entries to prevent unbounded growth (#611)
2891
1574
  if (s.completedUnits.length > 200) {
2892
1575
  s.completedUnits = s.completedUnits.slice(-200);
2893
1576
  }
@@ -2897,7 +1580,7 @@ async function dispatchNextUnit(
2897
1580
  }
2898
1581
  }
2899
1582
  s.currentUnit = { type: unitType, id: unitId, startedAt: Date.now() };
2900
- captureAvailableSkills(); // Capture skill telemetry at dispatch time (#599)
1583
+ captureAvailableSkills();
2901
1584
  writeUnitRuntimeRecord(s.basePath, unitType, unitId, s.currentUnit.startedAt, {
2902
1585
  phase: "dispatched",
2903
1586
  wrapupWarningSent: false,
@@ -2912,34 +1595,42 @@ async function dispatchNextUnit(
2912
1595
  if (mid) updateSliceProgressCache(s.basePath, mid, state.activeSlice?.id);
2913
1596
  updateProgressWidget(ctx, unitType, unitId, state);
2914
1597
 
2915
- // Ensure preconditions — create directories, branches, etc.
2916
- // so the LLM doesn't have to get these right
2917
1598
  ensurePreconditions(unitType, unitId, s.basePath, state);
2918
1599
 
2919
- // Fresh session
2920
- const result = await s.cmdCtx!.newSession();
1600
+ // Fresh session — with timeout to prevent permanent hangs (#1073).
1601
+ // If newSession() hangs (e.g., session manager deadlock, network issue),
1602
+ // without this timeout the entire dispatch chain stalls permanently: no
1603
+ // timeouts are set, no gap watchdog fires, and auto-mode is left active
1604
+ // but idle until the user Ctrl+C's.
1605
+ let result: { cancelled: boolean };
1606
+ try {
1607
+ const sessionPromise = s.cmdCtx!.newSession();
1608
+ const timeoutPromise = new Promise<{ cancelled: true }>((resolve) =>
1609
+ setTimeout(() => resolve({ cancelled: true }), NEW_SESSION_TIMEOUT_MS),
1610
+ );
1611
+ result = await Promise.race([sessionPromise, timeoutPromise]);
1612
+ } catch (sessionErr) {
1613
+ const msg = sessionErr instanceof Error ? sessionErr.message : String(sessionErr);
1614
+ ctx.ui.notify(`Session creation failed: ${msg}. Retrying via watchdog.`, "error");
1615
+ throw new Error(`newSession() failed: ${msg}`);
1616
+ }
2921
1617
  if (result.cancelled) {
2922
- await stopAuto(ctx, pi, "Session cancelled");
1618
+ ctx.ui.notify(
1619
+ `Session creation timed out or was cancelled for ${unitType} ${unitId}. Will retry.`,
1620
+ "warning",
1621
+ );
1622
+ await stopAuto(ctx, pi, "Session creation failed");
2923
1623
  return;
2924
1624
  }
2925
1625
 
2926
- // Branchless architecture: all work commits sequentially on the milestone
2927
- // branch — no per-slice branches or slice-level merges. Milestone merge
2928
- // happens when phase === "complete" (see mergeMilestoneToMain above).
2929
-
2930
- // Write lock AFTER newSession so we capture the session file path.
2931
- // Pi appends entries incrementally via appendFileSync, so on crash the
2932
- // session file survives with every tool call up to the crash point.
2933
1626
  const sessionFile = ctx.sessionManager.getSessionFile();
1627
+ updateSessionLock(lockBase(), unitType, unitId, s.completedUnits.length, sessionFile);
2934
1628
  writeLock(lockBase(), unitType, unitId, s.completedUnits.length, sessionFile);
2935
1629
 
2936
- // On crash recovery, prepend the full recovery briefing
2937
- // On retry (stuck detection), prepend deep diagnostic from last attempt
2938
- // Cap injected content to prevent unbounded prompt growth → OOM
1630
+ // Prompt injection
2939
1631
  const MAX_RECOVERY_CHARS = 50_000;
2940
1632
  let finalPrompt = prompt;
2941
1633
 
2942
- // Verification retry — inject failure context so the agent can auto-fix
2943
1634
  if (s.pendingVerificationRetry) {
2944
1635
  const retryCtx = s.pendingVerificationRetry;
2945
1636
  s.pendingVerificationRetry = null;
@@ -2965,14 +1656,12 @@ async function dispatchNextUnit(
2965
1656
  }
2966
1657
  }
2967
1658
 
2968
- // Inject observability repair instructions so the agent fixes gaps before
2969
- // proceeding with the unit (see #174).
2970
1659
  const repairBlock = buildObservabilityRepairBlock(observabilityIssues);
2971
1660
  if (repairBlock) {
2972
1661
  finalPrompt = `${finalPrompt}${repairBlock}`;
2973
1662
  }
2974
1663
 
2975
- // ── Prompt char measurement (R051) ──
1664
+ // ── Prompt char measurement ──
2976
1665
  s.lastPromptCharCount = finalPrompt.length;
2977
1666
  s.lastBaselineCharCount = undefined;
2978
1667
  if (isDbAvailable()) {
@@ -2988,221 +1677,44 @@ async function dispatchNextUnit(
2988
1677
  (requirementsContent?.length ?? 0) +
2989
1678
  (projectContent?.length ?? 0);
2990
1679
  } catch {
2991
- // Non-fatal — baseline measurement is best-effort
1680
+ // Non-fatal
2992
1681
  }
2993
1682
  }
2994
1683
 
2995
- // Select and apply model for this unit (dynamic routing, fallback chains, etc.)
1684
+ // Cache-optimize prompt section ordering
1685
+ try {
1686
+ const { reorderForCaching } = await import("./prompt-ordering.js");
1687
+ finalPrompt = reorderForCaching(finalPrompt);
1688
+ } catch (reorderErr) {
1689
+ const msg = reorderErr instanceof Error ? reorderErr.message : String(reorderErr);
1690
+ process.stderr.write(`[gsd] prompt reorder failed (non-fatal): ${msg}\n`);
1691
+ }
1692
+
1693
+ // Select and apply model
2996
1694
  const modelResult = await selectAndApplyModel(ctx, pi, unitType, unitId, s.basePath, prefs, s.verbose, s.autoModeStartModel);
2997
1695
  s.currentUnitRouting = modelResult.routing;
2998
1696
 
2999
- // Start progress-aware supervision: a soft warning, an idle watchdog, and
3000
- // a larger hard ceiling. Productive long-running tasks may continue past the
3001
- // soft timeout; only idle/stalled tasks pause early.
1697
+ // ── Start unit supervision (delegated to auto-timers.ts) ──
3002
1698
  clearUnitTimeout();
3003
- const supervisor = resolveAutoSupervisorConfig();
3004
- const softTimeoutMs = (supervisor.soft_timeout_minutes ?? 0) * 60 * 1000;
3005
- const idleTimeoutMs = (supervisor.idle_timeout_minutes ?? 0) * 60 * 1000;
3006
- const hardTimeoutMs = (supervisor.hard_timeout_minutes ?? 0) * 60 * 1000;
3007
-
3008
- s.wrapupWarningHandle = setTimeout(() => {
3009
- s.wrapupWarningHandle = null;
3010
- if (!s.active || !s.currentUnit) return;
3011
- writeUnitRuntimeRecord(s.basePath, unitType, unitId, s.currentUnit.startedAt, {
3012
- phase: "wrapup-warning-sent",
3013
- wrapupWarningSent: true,
3014
- });
3015
- pi.sendMessage(
3016
- {
3017
- customType: "gsd-auto-wrapup",
3018
- display: s.verbose,
3019
- content: [
3020
- "**TIME BUDGET WARNING — keep going only if progress is real.**",
3021
- "This unit crossed the soft time budget.",
3022
- "If you are making progress, continue. If not, switch to wrap-up mode now:",
3023
- "1. rerun the minimal required verification",
3024
- "2. write or update the required durable artifacts",
3025
- "3. mark task or slice state on disk correctly",
3026
- "4. leave precise resume notes if anything remains unfinished",
3027
- ].join("\n"),
3028
- },
3029
- { triggerTurn: true },
3030
- );
3031
- }, softTimeoutMs);
3032
-
3033
- s.idleWatchdogHandle = setInterval(async () => {
3034
- try {
3035
- if (!s.active || !s.currentUnit) return;
3036
- const runtime = readUnitRuntimeRecord(s.basePath, unitType, unitId);
3037
- if (!runtime) return;
3038
- if (Date.now() - runtime.lastProgressAt < idleTimeoutMs) return;
3039
-
3040
- // Agent has tool calls currently executing (await_job, long bash, etc.) —
3041
- // not idle, just waiting for tool completion. But only suppress recovery
3042
- // if the tool started recently. A tool in-flight for longer than the idle
3043
- // timeout is likely stuck — e.g., `python -m http.server 8080 &` keeps the
3044
- // shell's stdout/stderr open, causing the Bash tool to hang indefinitely.
3045
- if (getInFlightToolCount() > 0) {
3046
- const oldestStart = getOldestInFlightToolStart()!;
3047
- const toolAgeMs = Date.now() - oldestStart;
3048
- if (toolAgeMs < idleTimeoutMs) {
3049
- writeUnitRuntimeRecord(s.basePath, unitType, unitId, s.currentUnit.startedAt, {
3050
- lastProgressAt: Date.now(),
3051
- lastProgressKind: "tool-in-flight",
3052
- });
3053
- return;
3054
- }
3055
- // Oldest tool has been running >= idleTimeoutMs — treat as a stuck/hung
3056
- // tool (e.g., background process holding stdout open). Fall through to
3057
- // idle recovery without resetting the progress clock.
3058
- ctx.ui.notify(
3059
- `Stalled tool detected: a tool has been in-flight for ${Math.round(toolAgeMs / 60000)}min. Treating as hung — attempting idle recovery.`,
3060
- "warning",
3061
- );
3062
- }
3063
-
3064
- // Before triggering recovery, check if the agent is actually producing
3065
- // work on disk. `git status --porcelain` is cheap and catches any
3066
- // staged/unstaged/untracked changes the agent made since lastProgressAt.
3067
- if (detectWorkingTreeActivity(s.basePath)) {
3068
- writeUnitRuntimeRecord(s.basePath, unitType, unitId, s.currentUnit.startedAt, {
3069
- lastProgressAt: Date.now(),
3070
- lastProgressKind: "filesystem-activity",
3071
- });
3072
- return;
3073
- }
3074
-
3075
- if (s.currentUnit) {
3076
- await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
3077
- } else {
3078
- saveActivityLog(ctx, s.basePath, unitType, unitId);
3079
- }
3080
-
3081
- const recovery = await recoverTimedOutUnit(ctx, pi, unitType, unitId, "idle", buildRecoveryContext());
3082
- if (recovery === "recovered") return;
3083
-
3084
- writeUnitRuntimeRecord(s.basePath, unitType, unitId, s.currentUnit.startedAt, {
3085
- phase: "paused",
3086
- });
3087
- ctx.ui.notify(
3088
- `Unit ${unitType} ${unitId} made no meaningful progress for ${supervisor.idle_timeout_minutes}min. Pausing auto-mode.`,
3089
- "warning",
3090
- );
3091
- await pauseAuto(ctx, pi);
3092
- } catch (err) {
3093
- // Guard against unhandled rejections in the async interval callback.
3094
- // Without this, a thrown error leaves the interval running forever
3095
- // while the auto-mode state becomes inconsistent.
3096
- const message = err instanceof Error ? err.message : String(err);
3097
- console.error(`[idle-watchdog] Unhandled error: ${message}`);
3098
- try {
3099
- ctx.ui.notify(`Idle watchdog error: ${message}`, "warning");
3100
- } catch { /* best effort */ }
3101
- }
3102
- }, 15000);
3103
-
3104
- s.unitTimeoutHandle = setTimeout(async () => {
3105
- try {
3106
- s.unitTimeoutHandle = null;
3107
- if (!s.active) return;
3108
- if (s.currentUnit) {
3109
- writeUnitRuntimeRecord(s.basePath, unitType, unitId, s.currentUnit.startedAt, {
3110
- phase: "timeout",
3111
- timeoutAt: Date.now(),
3112
- });
3113
- await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
3114
- } else {
3115
- saveActivityLog(ctx, s.basePath, unitType, unitId);
3116
- }
3117
-
3118
- const recovery = await recoverTimedOutUnit(ctx, pi, unitType, unitId, "hard", buildRecoveryContext());
3119
- if (recovery === "recovered") return;
3120
-
3121
- ctx.ui.notify(
3122
- `Unit ${unitType} ${unitId} exceeded ${supervisor.hard_timeout_minutes}min hard timeout. Pausing auto-mode.`,
3123
- "warning",
3124
- );
3125
- await pauseAuto(ctx, pi);
3126
- } catch (err) {
3127
- const message = err instanceof Error ? err.message : String(err);
3128
- console.error(`[hard-timeout] Unhandled error: ${message}`);
3129
- try {
3130
- ctx.ui.notify(`Hard timeout error: ${message}`, "warning");
3131
- } catch { /* best effort */ }
3132
- }
3133
- }, hardTimeoutMs);
3134
-
3135
- // ── Continue-here context-pressure monitor ────────────────────────────
3136
- // Polls context usage every 15s. When usage hits the continue-here
3137
- // threshold (70%), sends a one-shot wrap-up signal so the agent finishes
3138
- // gracefully and the next unit gets a fresh session. This is softer than
3139
- // context_pause_threshold which hard-pauses auto-mode entirely.
3140
- if (s.continueHereHandle) {
3141
- clearInterval(s.continueHereHandle);
3142
- s.continueHereHandle = null;
3143
- }
3144
- const executorContextWindow = resolveExecutorContextWindow(
3145
- ctx.modelRegistry as Parameters<typeof resolveExecutorContextWindow>[0],
3146
- prefs as Parameters<typeof resolveExecutorContextWindow>[1],
3147
- ctx.model?.contextWindow,
3148
- );
3149
- const continueHereThreshold = computeBudgets(executorContextWindow).continueThresholdPercent;
3150
- s.continueHereHandle = setInterval(() => {
3151
- if (!s.active || !s.currentUnit || !s.cmdCtx) return;
3152
- // One-shot guard: skip if already fired for this unit
3153
- const runtime = readUnitRuntimeRecord(s.basePath, unitType, unitId);
3154
- if (runtime?.continueHereFired) return;
3155
-
3156
- const contextUsage = s.cmdCtx.getContextUsage();
3157
- if (!contextUsage || contextUsage.percent == null || contextUsage.percent < continueHereThreshold) return;
3158
-
3159
- // Fire once — mark runtime record and send wrap-up message
3160
- writeUnitRuntimeRecord(s.basePath, unitType, unitId, s.currentUnit!.startedAt, {
3161
- continueHereFired: true,
3162
- });
3163
-
3164
- if (s.verbose) {
3165
- ctx.ui.notify(
3166
- `Context at ${contextUsage.percent}% (threshold: ${continueHereThreshold}%) — sending wrap-up signal.`,
3167
- "info",
3168
- );
3169
- }
3170
-
3171
- pi.sendMessage(
3172
- {
3173
- customType: "gsd-auto-wrapup",
3174
- display: s.verbose,
3175
- content: [
3176
- "**CONTEXT BUDGET WARNING — wrap up this unit now.**",
3177
- `Context window is at ${contextUsage.percent}% (threshold: ${continueHereThreshold}%).`,
3178
- "The next unit needs a fresh context to work effectively. Wrap up now:",
3179
- "1. Finish any in-progress file writes",
3180
- "2. Write or update the required durable artifacts (summary, checkboxes)",
3181
- "3. Mark task state on disk correctly",
3182
- "4. Leave precise resume notes if anything remains unfinished",
3183
- "Do NOT start new sub-tasks or investigations.",
3184
- ].join("\n"),
3185
- },
3186
- { triggerTurn: true },
3187
- );
3188
-
3189
- // Clear the interval after firing — no need to keep polling
3190
- if (s.continueHereHandle) {
3191
- clearInterval(s.continueHereHandle);
3192
- s.continueHereHandle = null;
3193
- }
3194
- }, 15_000);
1699
+ startUnitSupervision({
1700
+ s,
1701
+ ctx,
1702
+ pi,
1703
+ unitType,
1704
+ unitId,
1705
+ prefs,
1706
+ buildSnapshotOpts: () => buildSnapshotOpts(unitType, unitId),
1707
+ buildRecoveryContext: () => buildRecoveryContext(),
1708
+ pauseAuto,
1709
+ });
3195
1710
 
3196
- // Inject prompt — verify auto-mode still s.active (guards against race with timeout/pause)
1711
+ // Inject prompt
3197
1712
  if (!s.active) return;
3198
1713
  pi.sendMessage(
3199
1714
  { customType: "gsd-auto", content: finalPrompt, display: s.verbose },
3200
1715
  { triggerTurn: true },
3201
1716
  );
3202
1717
 
3203
- // For non-artifact-driven UAT types, pause auto-mode after sending the prompt.
3204
- // The agent will write the UAT result file surfacing it for human review,
3205
- // then on resume the result file exists and run-uat is skipped automatically.
3206
1718
  if (pauseAfterUatDispatch) {
3207
1719
  ctx.ui.notify(
3208
1720
  "UAT requires human execution. Auto-mode will pause after this unit writes the result file.",
@@ -3227,32 +1739,26 @@ function ensurePreconditions(
3227
1739
  const parts = unitId.split("/");
3228
1740
  const mid = parts[0]!;
3229
1741
 
3230
- // Always ensure milestone dir exists
3231
1742
  const mDir = resolveMilestonePath(base, mid);
3232
1743
  if (!mDir) {
3233
1744
  const newDir = join(milestonesDir(base), mid);
3234
1745
  mkdirSync(join(newDir, "slices"), { recursive: true });
3235
1746
  }
3236
1747
 
3237
- // For slice-level units, ensure slice dir exists
3238
1748
  if (parts.length >= 2) {
3239
1749
  const sid = parts[1]!;
3240
1750
 
3241
- // Re-resolve milestone path after potential creation
3242
1751
  const mDirResolved = resolveMilestonePath(base, mid);
3243
1752
  if (mDirResolved) {
3244
1753
  const slicesDir = join(mDirResolved, "slices");
3245
1754
  const sDir = resolveDir(slicesDir, sid);
3246
1755
  if (!sDir) {
3247
- // Create slice dir with bare ID
3248
- const newSliceDir = join(slicesDir, sid);
3249
- mkdirSync(join(newSliceDir, "tasks"), { recursive: true });
3250
- } else {
3251
- // Ensure tasks/ subdir exists
3252
- const tasksDir = join(slicesDir, sDir, "tasks");
3253
- if (!existsSync(tasksDir)) {
3254
- mkdirSync(tasksDir, { recursive: true });
3255
- }
1756
+ mkdirSync(join(slicesDir, sid, "tasks"), { recursive: true });
1757
+ }
1758
+ const resolvedSliceDir = resolveDir(slicesDir, sid) ?? sid;
1759
+ const tasksDir = join(slicesDir, resolvedSliceDir, "tasks");
1760
+ if (!existsSync(tasksDir)) {
1761
+ mkdirSync(tasksDir, { recursive: true });
3256
1762
  }
3257
1763
  }
3258
1764
  }
@@ -3261,10 +1767,6 @@ function ensurePreconditions(
3261
1767
 
3262
1768
  // ─── Diagnostics ──────────────────────────────────────────────────────────────
3263
1769
 
3264
- // collectObservabilityWarnings + buildObservabilityRepairBlock → auto-observability.ts
3265
-
3266
- // recoverTimedOutUnit → auto-timeout-recovery.ts
3267
-
3268
1770
  /** Build recovery context from module state for recoverTimedOutUnit */
3269
1771
  function buildRecoveryContext(): import("./auto-timeout-recovery.js").RecoveryContext {
3270
1772
  return { basePath: s.basePath, verbose: s.verbose,
@@ -3288,7 +1790,6 @@ export {
3288
1790
  */
3289
1791
  export function _getUnitConsecutiveSkips(): Map<string, number> { return s.unitConsecutiveSkips; }
3290
1792
  export function _resetUnitConsecutiveSkips(): void { s.unitConsecutiveSkips.clear(); }
3291
- // MAX_CONSECUTIVE_SKIPS re-exported from auto/session.ts at top of file
3292
1793
 
3293
1794
  /**
3294
1795
  * Dispatch a hook unit directly, bypassing normal pre-dispatch hooks.
@@ -3304,9 +1805,7 @@ export async function dispatchHookUnit(
3304
1805
  hookModel: string | undefined,
3305
1806
  targetBasePath: string,
3306
1807
  ): Promise<boolean> {
3307
- // Ensure auto-mode is s.active
3308
1808
  if (!s.active) {
3309
- // Initialize auto-mode state minimally
3310
1809
  s.active = true;
3311
1810
  s.stepMode = true;
3312
1811
  s.cmdCtx = ctx as ExtensionCommandContext;
@@ -3319,21 +1818,17 @@ export async function dispatchHookUnit(
3319
1818
 
3320
1819
  const hookUnitType = `hook/${hookName}`;
3321
1820
  const hookStartedAt = Date.now();
3322
-
3323
- // Set up the trigger unit as the "current" unit so post-unit hooks can reference it
1821
+
3324
1822
  s.currentUnit = { type: triggerUnitType, id: triggerUnitId, startedAt: hookStartedAt };
3325
-
3326
- // Create a new session for the hook
1823
+
3327
1824
  const result = await s.cmdCtx!.newSession();
3328
1825
  if (result.cancelled) {
3329
1826
  await stopAuto(ctx, pi);
3330
1827
  return false;
3331
1828
  }
3332
1829
 
3333
- // Update current unit to the hook unit
3334
1830
  s.currentUnit = { type: hookUnitType, id: triggerUnitId, startedAt: hookStartedAt };
3335
-
3336
- // Write runtime record
1831
+
3337
1832
  writeUnitRuntimeRecord(s.basePath, hookUnitType, triggerUnitId, hookStartedAt, {
3338
1833
  phase: "dispatched",
3339
1834
  wrapupWarningSent: false,
@@ -3343,7 +1838,6 @@ export async function dispatchHookUnit(
3343
1838
  lastProgressKind: "dispatch",
3344
1839
  });
3345
1840
 
3346
- // Switch model if specified
3347
1841
  if (hookModel) {
3348
1842
  const availableModels = ctx.modelRegistry.getAvailable();
3349
1843
  const match = availableModels.find(m =>
@@ -3352,15 +1846,14 @@ export async function dispatchHookUnit(
3352
1846
  if (match) {
3353
1847
  try {
3354
1848
  await pi.setModel(match);
3355
- } catch { /* non-fatal — use current model */ }
1849
+ } catch { /* non-fatal */ }
3356
1850
  }
3357
1851
  }
3358
1852
 
3359
- // Write lock
3360
1853
  const sessionFile = ctx.sessionManager.getSessionFile();
1854
+ updateSessionLock(lockBase(), hookUnitType, triggerUnitId, s.completedUnits.length, sessionFile);
3361
1855
  writeLock(lockBase(), hookUnitType, triggerUnitId, s.completedUnits.length, sessionFile);
3362
1856
 
3363
- // Set up timeout
3364
1857
  clearUnitTimeout();
3365
1858
  const supervisor = resolveAutoSupervisorConfig();
3366
1859
  const hookHardTimeoutMs = (supervisor.hard_timeout_minutes ?? 30) * 60 * 1000;
@@ -3381,18 +1874,16 @@ export async function dispatchHookUnit(
3381
1874
  await pauseAuto(ctx, pi);
3382
1875
  }, hookHardTimeoutMs);
3383
1876
 
3384
- // Update status
3385
1877
  ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
3386
1878
  ctx.ui.notify(`Running post-unit hook: ${hookName}`, "info");
3387
1879
 
3388
- // Send the hook prompt
3389
1880
  console.log(`[dispatchHookUnit] Sending prompt of length ${hookPrompt.length}`);
3390
1881
  console.log(`[dispatchHookUnit] Prompt preview: ${hookPrompt.substring(0, 200)}...`);
3391
1882
  pi.sendMessage(
3392
1883
  { customType: "gsd-auto", content: hookPrompt, display: true },
3393
1884
  { triggerTurn: true },
3394
1885
  );
3395
-
1886
+
3396
1887
  return true;
3397
1888
  }
3398
1889