gsd-pi 2.28.0 → 2.29.0-dev.2ccf3fb

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