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

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 (303) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/GSD-WORKFLOW.md +2 -2
  3. package/dist/resources/extensions/github-sync/templates.js +39 -8
  4. package/dist/resources/extensions/gsd/auto/loop.js +16 -9
  5. package/dist/resources/extensions/gsd/auto/phases.js +37 -30
  6. package/dist/resources/extensions/gsd/auto/run-unit.js +19 -15
  7. package/dist/resources/extensions/gsd/auto-dashboard.js +51 -15
  8. package/dist/resources/extensions/gsd/auto-dispatch.js +10 -0
  9. package/dist/resources/extensions/gsd/auto-post-unit.js +10 -10
  10. package/dist/resources/extensions/gsd/auto-prompts.js +111 -1
  11. package/dist/resources/extensions/gsd/auto-recovery.js +154 -8
  12. package/dist/resources/extensions/gsd/auto-start.js +2 -3
  13. package/dist/resources/extensions/gsd/auto.js +9 -1
  14. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +15 -1
  15. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +11 -1
  16. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +129 -1
  17. package/dist/resources/extensions/gsd/clean-root-preflight.js +42 -4
  18. package/dist/resources/extensions/gsd/commands-extract-learnings.js +17 -12
  19. package/dist/resources/extensions/gsd/custom-workflow-engine.js +22 -2
  20. package/dist/resources/extensions/gsd/db-base-schema.js +14 -0
  21. package/dist/resources/extensions/gsd/db-migration-steps.js +16 -0
  22. package/dist/resources/extensions/gsd/detection.js +106 -0
  23. package/dist/resources/extensions/gsd/graph.js +9 -3
  24. package/dist/resources/extensions/gsd/gsd-db.js +102 -2
  25. package/dist/resources/extensions/gsd/guided-flow.js +2 -2
  26. package/dist/resources/extensions/gsd/pr-evidence.js +57 -16
  27. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +7 -8
  28. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
  29. package/dist/resources/extensions/gsd/safety/evidence-collector.js +10 -2
  30. package/dist/resources/extensions/gsd/working-output-messages.js +64 -0
  31. package/dist/resources/extensions/gsd/worktree-manager.js +16 -14
  32. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  33. package/dist/web/standalone/.next/BUILD_ID +1 -1
  34. package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
  35. package/dist/web/standalone/.next/build-manifest.json +3 -3
  36. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  37. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  38. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  39. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  47. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  55. package/dist/web/standalone/.next/server/app/index.html +1 -1
  56. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app-paths-manifest.json +11 -11
  63. package/dist/web/standalone/.next/server/chunks/6897.js +3 -3
  64. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  65. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  66. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  67. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  68. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  69. package/dist/web/standalone/.next/static/chunks/{8336.6f6f30e410419aff.js → 8336.631939fb583761fa.js} +1 -1
  70. package/dist/web/standalone/.next/static/chunks/{webpack-d82dbee6356c1733.js → webpack-0481f1221120a7c6.js} +1 -1
  71. package/package.json +10 -6
  72. package/packages/contracts/package.json +1 -1
  73. package/packages/pi-ai/dist/models/fake-model.d.ts +12 -0
  74. package/packages/pi-ai/dist/models/fake-model.d.ts.map +1 -0
  75. package/packages/pi-ai/dist/models/fake-model.js +27 -0
  76. package/packages/pi-ai/dist/models/fake-model.js.map +1 -0
  77. package/packages/pi-ai/dist/models/index.d.ts.map +1 -1
  78. package/packages/pi-ai/dist/models/index.js +8 -0
  79. package/packages/pi-ai/dist/models/index.js.map +1 -1
  80. package/packages/pi-ai/dist/providers/fake.d.ts +42 -0
  81. package/packages/pi-ai/dist/providers/fake.d.ts.map +1 -0
  82. package/packages/pi-ai/dist/providers/fake.js +319 -0
  83. package/packages/pi-ai/dist/providers/fake.js.map +1 -0
  84. package/packages/pi-ai/dist/providers/register-builtins.d.ts.map +1 -1
  85. package/packages/pi-ai/dist/providers/register-builtins.js +24 -0
  86. package/packages/pi-ai/dist/providers/register-builtins.js.map +1 -1
  87. package/packages/pi-ai/src/models/fake-model.ts +30 -0
  88. package/packages/pi-ai/src/models/index.ts +9 -0
  89. package/packages/pi-ai/src/providers/fake.ts +376 -0
  90. package/packages/pi-ai/src/providers/register-builtins.ts +23 -0
  91. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  92. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +74 -0
  93. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
  94. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +2 -0
  95. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  96. package/packages/pi-coding-agent/dist/core/extensions/runner.js +14 -1
  97. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  98. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +97 -0
  99. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
  100. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  101. package/packages/pi-coding-agent/dist/core/model-registry.js +5 -0
  102. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  103. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +4 -0
  104. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  105. package/packages/pi-coding-agent/dist/core/settings-manager.js +8 -0
  106. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
  108. package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
  109. package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
  110. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js +6 -4
  111. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js.map +1 -1
  112. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +54 -15
  113. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  114. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.d.ts +26 -0
  115. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.d.ts.map +1 -0
  116. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js +112 -0
  117. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js.map +1 -0
  118. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.d.ts +2 -0
  119. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.d.ts.map +1 -0
  120. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.js +51 -0
  121. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.js.map +1 -0
  122. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  123. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  124. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +10 -9
  126. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +3 -0
  128. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  129. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +11 -0
  130. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  131. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js +7 -6
  132. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js.map +1 -1
  133. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +16 -0
  134. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  135. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +106 -17
  136. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  137. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  138. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +60 -1
  139. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  140. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +40 -1
  141. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
  142. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +1 -0
  143. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
  144. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +1 -0
  145. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  146. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  147. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +3 -0
  148. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  149. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +23 -0
  150. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  151. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
  152. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +20 -0
  153. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  154. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.test.d.ts +2 -0
  155. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.test.d.ts.map +1 -0
  156. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.test.js +79 -0
  157. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.test.js.map +1 -0
  158. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.d.ts +12 -0
  159. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.d.ts.map +1 -1
  160. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.js +13 -0
  161. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.js.map +1 -1
  162. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts +1 -1
  163. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  164. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +18 -1
  165. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
  166. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  167. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +36 -27
  168. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  169. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.d.ts +11 -0
  170. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.d.ts.map +1 -0
  171. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.js +18 -0
  172. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.js.map +1 -0
  173. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.d.ts +2 -0
  174. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.d.ts.map +1 -0
  175. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js +48 -0
  176. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js.map +1 -0
  177. package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +87 -0
  178. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +108 -0
  179. package/packages/pi-coding-agent/src/core/extensions/runner.ts +16 -1
  180. package/packages/pi-coding-agent/src/core/model-registry.ts +4 -0
  181. package/packages/pi-coding-agent/src/core/settings-manager.ts +12 -0
  182. package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
  183. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.ts +7 -5
  184. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +78 -15
  185. package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.test.ts +59 -0
  186. package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.ts +160 -0
  187. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +1 -0
  188. package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +10 -9
  189. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +15 -0
  190. package/packages/pi-coding-agent/src/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.ts +10 -9
  191. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +118 -17
  192. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +43 -1
  193. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +75 -1
  194. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +1 -0
  195. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +1 -1
  196. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +25 -0
  197. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.test.ts +95 -0
  198. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +24 -1
  199. package/packages/pi-coding-agent/src/modes/interactive/theme/theme-schema.ts +13 -0
  200. package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +32 -2
  201. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +36 -27
  202. package/packages/pi-coding-agent/src/modes/interactive/tui-mode.test.ts +65 -0
  203. package/packages/pi-coding-agent/src/modes/interactive/tui-mode.ts +29 -0
  204. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  205. package/packages/pi-tui/dist/__tests__/style.test.d.ts +2 -0
  206. package/packages/pi-tui/dist/__tests__/style.test.d.ts.map +1 -0
  207. package/packages/pi-tui/dist/__tests__/style.test.js +63 -0
  208. package/packages/pi-tui/dist/__tests__/style.test.js.map +1 -0
  209. package/packages/pi-tui/dist/__tests__/tui.test.js +24 -3
  210. package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -1
  211. package/packages/pi-tui/dist/index.d.ts +1 -0
  212. package/packages/pi-tui/dist/index.d.ts.map +1 -1
  213. package/packages/pi-tui/dist/index.js +2 -0
  214. package/packages/pi-tui/dist/index.js.map +1 -1
  215. package/packages/pi-tui/dist/style.d.ts +41 -0
  216. package/packages/pi-tui/dist/style.d.ts.map +1 -0
  217. package/packages/pi-tui/dist/style.js +158 -0
  218. package/packages/pi-tui/dist/style.js.map +1 -0
  219. package/packages/pi-tui/dist/tui.d.ts +0 -1
  220. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  221. package/packages/pi-tui/dist/tui.js +3 -8
  222. package/packages/pi-tui/dist/tui.js.map +1 -1
  223. package/packages/pi-tui/src/__tests__/style.test.ts +76 -0
  224. package/packages/pi-tui/src/__tests__/tui.test.ts +29 -3
  225. package/packages/pi-tui/src/index.ts +9 -0
  226. package/packages/pi-tui/src/style.ts +225 -0
  227. package/packages/pi-tui/src/tui.ts +3 -8
  228. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  229. package/pkg/dist/modes/interactive/theme/theme-schema.d.ts +12 -0
  230. package/pkg/dist/modes/interactive/theme/theme-schema.d.ts.map +1 -1
  231. package/pkg/dist/modes/interactive/theme/theme-schema.js +13 -0
  232. package/pkg/dist/modes/interactive/theme/theme-schema.js.map +1 -1
  233. package/pkg/dist/modes/interactive/theme/theme.d.ts +1 -1
  234. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  235. package/pkg/dist/modes/interactive/theme/theme.js +18 -1
  236. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
  237. package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  238. package/pkg/dist/modes/interactive/theme/themes.js +36 -27
  239. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  240. package/src/resources/GSD-WORKFLOW.md +2 -2
  241. package/src/resources/extensions/github-sync/templates.ts +38 -8
  242. package/src/resources/extensions/github-sync/tests/inline-code.test.ts +66 -0
  243. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  244. package/src/resources/extensions/gsd/auto/loop.ts +17 -10
  245. package/src/resources/extensions/gsd/auto/phases.ts +42 -28
  246. package/src/resources/extensions/gsd/auto/run-unit.ts +24 -14
  247. package/src/resources/extensions/gsd/auto-dashboard.ts +57 -8
  248. package/src/resources/extensions/gsd/auto-dispatch.ts +17 -0
  249. package/src/resources/extensions/gsd/auto-post-unit.ts +10 -10
  250. package/src/resources/extensions/gsd/auto-prompts.ts +116 -1
  251. package/src/resources/extensions/gsd/auto-recovery.ts +153 -7
  252. package/src/resources/extensions/gsd/auto-start.ts +7 -6
  253. package/src/resources/extensions/gsd/auto.ts +12 -1
  254. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +16 -1
  255. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +17 -1
  256. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +135 -1
  257. package/src/resources/extensions/gsd/clean-root-preflight.ts +41 -3
  258. package/src/resources/extensions/gsd/commands-extract-learnings.ts +17 -12
  259. package/src/resources/extensions/gsd/custom-workflow-engine.ts +24 -1
  260. package/src/resources/extensions/gsd/db-base-schema.ts +15 -0
  261. package/src/resources/extensions/gsd/db-migration-steps.ts +17 -0
  262. package/src/resources/extensions/gsd/detection.ts +128 -0
  263. package/src/resources/extensions/gsd/graph.ts +12 -5
  264. package/src/resources/extensions/gsd/gsd-db.ts +119 -1
  265. package/src/resources/extensions/gsd/guided-flow.ts +2 -2
  266. package/src/resources/extensions/gsd/pr-evidence.ts +63 -5
  267. package/src/resources/extensions/gsd/prompts/complete-milestone.md +7 -8
  268. package/src/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
  269. package/src/resources/extensions/gsd/safety/evidence-collector.ts +11 -2
  270. package/src/resources/extensions/gsd/tests/auto-abort-pause-regression.test.ts +7 -1
  271. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +33 -0
  272. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +84 -5
  273. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +170 -1
  274. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +88 -2
  275. package/src/resources/extensions/gsd/tests/commands-extract-learnings.test.ts +9 -0
  276. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +112 -6
  277. package/src/resources/extensions/gsd/tests/custom-workflow-engine.test.ts +40 -2
  278. package/src/resources/extensions/gsd/tests/db-migration-steps.integration.test.ts +428 -0
  279. package/src/resources/extensions/gsd/tests/db-schema-metadata.test.ts +2 -2
  280. package/src/resources/extensions/gsd/tests/detection.test.ts +140 -0
  281. package/src/resources/extensions/gsd/tests/fixtures/pr-body/commands-ship-basic.md +52 -0
  282. package/src/resources/extensions/gsd/tests/fixtures/pr-body/commands-ship-empty-optionals.md +42 -0
  283. package/src/resources/extensions/gsd/tests/fixtures/pr-body/swarm-lane-no-blockers.md +55 -0
  284. package/src/resources/extensions/gsd/tests/fixtures/pr-body/swarm-lane-with-blockers.md +60 -0
  285. package/src/resources/extensions/gsd/tests/graph-operations.test.ts +10 -0
  286. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +44 -0
  287. package/src/resources/extensions/gsd/tests/has-pending-deep-stage.test.ts +33 -1
  288. package/src/resources/extensions/gsd/tests/pr-evidence-equivalence.test.ts +102 -0
  289. package/src/resources/extensions/gsd/tests/pr-evidence-hardening.test.ts +165 -0
  290. package/src/resources/extensions/gsd/tests/right-sized-workflow-prompts.test.ts +192 -0
  291. package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +29 -0
  292. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +46 -2
  293. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +1 -1
  294. package/src/resources/extensions/gsd/tests/working-output-messages.test.ts +93 -0
  295. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +37 -6
  296. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +7 -0
  297. package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +9 -2
  298. package/src/resources/extensions/gsd/tests/worktree-write-gate.test.ts +179 -0
  299. package/src/resources/extensions/gsd/working-output-messages.ts +120 -0
  300. package/src/resources/extensions/gsd/worktree-manager.ts +15 -4
  301. package/packages/contracts/tsconfig.tsbuildinfo +0 -1
  302. /package/dist/web/standalone/.next/static/{bQDK5_LtkGVS64AirQgQG → mPZbi5BH9dwokaPZlrYuQ}/_buildManifest.js +0 -0
  303. /package/dist/web/standalone/.next/static/{bQDK5_LtkGVS64AirQgQG → mPZbi5BH9dwokaPZlrYuQ}/_ssgManifest.js +0 -0
@@ -8,7 +8,7 @@
8
8
 
9
9
  import { loadFile, parseContinue, parseSummary, loadActiveOverrides, formatOverridesSection, parseTaskPlanFile } from "./files.js";
10
10
  import type { Override, UatType } from "./files.js";
11
- import { hasVerdict, getUatType } from "./verdict-parser.js";
11
+ import { hasVerdict, getUatType, extractVerdict } from "./verdict-parser.js";
12
12
  import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
13
13
  import {
14
14
  resolveMilestoneFile, resolveSliceFile, resolveSlicePath,
@@ -39,6 +39,7 @@ import { logWarning } from "./workflow-logger.js";
39
39
  import { inlineGraphSubgraph } from "./graph-context.js";
40
40
  import { buildExtractionStepsBlock } from "./commands-extract-learnings.js";
41
41
  import { resolveSkillManifest, warnIfManifestHasMissingSkills } from "./skill-manifest.js";
42
+ import { classifyProject, type ProjectClassification } from "./detection.js";
42
43
 
43
44
  // ─── Preamble Cap ─────────────────────────────────────────────────────────────
44
45
 
@@ -80,6 +81,108 @@ function resolveSummaryBudgetChars(): number {
80
81
  return resolvePromptBudgets().summaryBudgetChars;
81
82
  }
82
83
 
84
+ function formatProjectClassificationForPlanning(classification: ProjectClassification): string {
85
+ const sampleFiles = classification.contentFiles.slice(0, 8);
86
+ const sample = sampleFiles.length > 0 ? sampleFiles.map((file) => `\`${file}\``).join(", ") : "(none)";
87
+ const lines = [
88
+ "### Project Classification",
89
+ "",
90
+ `- **Kind:** ${classification.kind}`,
91
+ `- **Content files:** ${classification.contentFiles.length}`,
92
+ `- **Sample files:** ${sample}`,
93
+ `- **Reason:** ${classification.reason}`,
94
+ "",
95
+ ];
96
+
97
+ if (classification.kind === "untyped-existing") {
98
+ if (classification.contentFiles.length <= 2) {
99
+ lines.push(
100
+ "**Workflow sizing:** This is a tiny existing untyped project. Prefer exactly one slice unless the milestone request clearly spans multiple independent user-visible capabilities.",
101
+ );
102
+ } else if (classification.contentFiles.length <= 5) {
103
+ lines.push(
104
+ "**Workflow sizing:** This is a small existing untyped project. Prefer 1-2 slices unless the milestone request clearly spans multiple independent user-visible capabilities.",
105
+ );
106
+ } else {
107
+ lines.push(
108
+ "**Workflow sizing:** Existing untyped project. Use generic file-level workflow guidance and size slices by real capability boundaries, not by missing tooling markers.",
109
+ );
110
+ }
111
+ } else if (classification.kind === "greenfield") {
112
+ lines.push("**Workflow sizing:** No project content exists yet. Use normal greenfield sizing for the requested scope.");
113
+ } else if (classification.kind === "typed-existing") {
114
+ lines.push("**Workflow sizing:** Known project markers exist. Use normal ecosystem-aware planning guidance.");
115
+ } else {
116
+ lines.push("**Workflow sizing:** Invalid repository state. Planning should surface this as a blocker rather than inventing project structure.");
117
+ }
118
+
119
+ return lines.join("\n");
120
+ }
121
+
122
+ function normalizeArtifactRef(value: string): string {
123
+ return value.trim().replace(/^[-\s]+/, "").replace(/^["'`]+|["'`]+$/g, "").replaceAll("\\", "/").replace(/^\.\//, "");
124
+ }
125
+
126
+ function parseCoveredArtifacts(validationContent: string): Set<string> {
127
+ const covered = new Set<string>();
128
+ const lines = validationContent.split(/\r?\n/);
129
+ let inCoveredArtifacts = false;
130
+ for (const line of lines) {
131
+ if (/^\s*covered[-_]?artifacts\s*:/i.test(line)) {
132
+ inCoveredArtifacts = true;
133
+ const inline = line.split(/covered[-_]?artifacts\s*:/i)[1]?.trim();
134
+ if (inline && inline !== "[]") {
135
+ inline.replace(/^\[|\]$/g, "").split(",").map(normalizeArtifactRef).filter(Boolean).forEach((item) => covered.add(item));
136
+ }
137
+ continue;
138
+ }
139
+ if (!inCoveredArtifacts) continue;
140
+ if (/^\S/.test(line) && !/^\s*-/.test(line)) break;
141
+ const item = line.match(/^\s*-\s*(.+)$/)?.[1];
142
+ if (item) covered.add(normalizeArtifactRef(item));
143
+ }
144
+ return covered;
145
+ }
146
+
147
+ function isValidationFreshOrApplicable(validationContent: string | null, currentArtifacts: string[]): boolean {
148
+ if (!validationContent) return false;
149
+ if (!/validation_metadata:/i.test(validationContent)) return false;
150
+ const coveredArtifacts = parseCoveredArtifacts(validationContent);
151
+ if (coveredArtifacts.size === 0) return false;
152
+ return currentArtifacts
153
+ .map(normalizeArtifactRef)
154
+ .filter(Boolean)
155
+ .every((artifact) => coveredArtifacts.has(artifact));
156
+ }
157
+
158
+ function formatCloseoutReviewInstructions(validationContent: string | null, validationRel: string, currentArtifacts: string[]): string {
159
+ const verdict = validationContent ? extractVerdict(validationContent) : null;
160
+ const validationFresh = isValidationFreshOrApplicable(validationContent, currentArtifacts);
161
+ if (verdict === "pass" && validationFresh) {
162
+ return [
163
+ "### Passing Validation Artifact",
164
+ "",
165
+ `A passing validation artifact is present at \`${validationRel}\`. Treat it as authoritative for success criteria, requirement coverage, verification classes, and cross-slice integration.`,
166
+ "",
167
+ "Do not delegate fresh reviewer/security/tester audits and do not redo the validation evidence review unless the artifact is internally inconsistent with the inlined summaries. Focus this unit on final milestone narrative, learnings, PROJECT/requirements updates, and `gsd_complete_milestone`.",
168
+ ].join("\n");
169
+ }
170
+
171
+ if (verdict) {
172
+ return [
173
+ "### Validation Requires Attention",
174
+ "",
175
+ `A validation artifact is present at \`${validationRel}\` with verdict \`${verdict}\`, but it is missing freshness metadata or does not cover current milestone artifacts. Do not treat the milestone as complete unless the issues are resolved and evidence supports completion.`,
176
+ ].join("\n");
177
+ }
178
+
179
+ return [
180
+ "### No Passing Validation Artifact",
181
+ "",
182
+ `No passing validation artifact was found at \`${validationRel}\`. Use the full closeout review path before completion.`,
183
+ ].join("\n");
184
+ }
185
+
83
186
  function capPreamble(preamble: string): string {
84
187
  // Cap inlined context at min(historical 30K ceiling, scaled inline budget).
85
188
  // The ceiling preserves pre-fix behavior for large-window users; the scaled
@@ -1658,6 +1761,8 @@ export async function buildPlanMilestonePrompt(mid: string, midTitle: string, ba
1658
1761
  const researchAnchor = readPhaseAnchor(base, mid, "research-milestone");
1659
1762
  if (researchAnchor) inlined.push(formatAnchorForPrompt(researchAnchor));
1660
1763
 
1764
+ inlined.push(formatProjectClassificationForPlanning(classifyProject(base)));
1765
+
1661
1766
  inlined.push(await inlineFile(contextPath, contextRel, "Milestone Context"));
1662
1767
  const researchInline = await inlineFileOptional(researchPath, researchRel, "Milestone Research");
1663
1768
  if (researchInline) inlined.push(researchInline);
@@ -2348,6 +2453,9 @@ export async function buildCompleteMilestonePrompt(
2348
2453
  const inlineLevel = level ?? resolveInlineLevel();
2349
2454
  const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP");
2350
2455
  const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
2456
+ const validationPath = resolveMilestoneFile(base, mid, "VALIDATION");
2457
+ const validationRel = relMilestoneFile(base, mid, "VALIDATION");
2458
+ const validationContent = validationPath ? await loadFile(validationPath) : null;
2351
2459
 
2352
2460
  const inlined: string[] = [];
2353
2461
  inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
@@ -2389,6 +2497,13 @@ export async function buildCompleteMilestonePrompt(
2389
2497
  `### On-demand Slice Summaries\n\nExcerpted above. Read the full file for any slice when the excerpt's section heads don't carry enough narrative for the milestone summary you're drafting:\n\n${pathList}`,
2390
2498
  );
2391
2499
  }
2500
+ const validationContext = [
2501
+ formatCloseoutReviewInstructions(validationContent, validationRel, [validationRel, roadmapRel, ...summaryRelPaths]),
2502
+ ];
2503
+ if (validationContent) {
2504
+ validationContext.push(`### Milestone Validation\nSource: \`${validationRel}\`\n\n${validationContent.trim()}`);
2505
+ }
2506
+ inlined.unshift(...validationContext);
2392
2507
 
2393
2508
  // Inline root GSD files (skip for minimal — completion can read these if needed)
2394
2509
  if (inlineLevel !== "minimal") {
@@ -14,7 +14,7 @@ import { appendEvent } from "./workflow-events.js";
14
14
  import { atomicWriteSync } from "./atomic-write.js";
15
15
  import { clearParseCache } from "./files.js";
16
16
  import { parseRoadmap as parseLegacyRoadmap, parsePlan as parseLegacyPlan } from "./parsers-legacy.js";
17
- import { isDbAvailable, getTask, getSlice, getSliceTasks, getPendingGates, updateTaskStatus, updateSliceStatus, insertSlice, getMilestone, refreshOpenDatabaseFromDisk } from "./gsd-db.js";
17
+ import { isDbAvailable, getTask, getSlice, getSliceTasks, getPendingGates, updateTaskStatus, updateSliceStatus, insertSlice, getMilestone, refreshOpenDatabaseFromDisk, getCompletedMilestoneTaskFileHints, getMilestoneCommitAttributionShas, recordMilestoneCommitAttribution } from "./gsd-db.js";
18
18
  import { isValidationTerminal } from "./state.js";
19
19
  import { getErrorMessage } from "./error-utils.js";
20
20
  import { logWarning, logError } from "./workflow-logger.js";
@@ -147,15 +147,29 @@ export function hasImplementationArtifacts(basePath: string, milestoneId?: strin
147
147
  // milestone commits instead of treating the self-diff as proof of no work.
148
148
  if (changedFiles.length === 0) {
149
149
  if (milestoneId && currentBranch === integrationBranch) {
150
- const tagged = getChangedFilesFromMilestoneTaggedCommits(basePath, milestoneId);
151
- if (!tagged.ok) return "unknown";
152
- if (tagged.matched) return classifyImplementationFiles(tagged.files);
150
+ const milestoneEvidence = getChangedFilesFromMilestoneEvidence(basePath, milestoneId);
151
+ if (!milestoneEvidence.ok) return "unknown";
152
+ if (milestoneEvidence.matched) return classifyImplementationFiles(milestoneEvidence.files);
153
153
  }
154
154
  if (currentBranch && currentBranch !== "HEAD") return "absent";
155
155
  return "unknown";
156
156
  }
157
157
 
158
- return classifyImplementationFiles(changedFiles);
158
+ const branchClassification = classifyImplementationFiles(changedFiles);
159
+ if (branchClassification === "present") return "present";
160
+
161
+ // A completing milestone branch can have a non-empty diff containing only
162
+ // .gsd/ closeout files after implementation commits already landed on the
163
+ // recorded integration branch. In that topology, the branch diff alone is
164
+ // insufficient; use the same milestone-tagged evidence fallback as the
165
+ // self-diff retry path before declaring the milestone implementation-free.
166
+ if (milestoneId) {
167
+ const milestoneEvidence = getChangedFilesFromMilestoneEvidence(basePath, milestoneId);
168
+ if (!milestoneEvidence.ok) return "unknown";
169
+ if (milestoneEvidence.matched) return classifyImplementationFiles(milestoneEvidence.files);
170
+ }
171
+
172
+ return "absent";
159
173
  } catch (e) {
160
174
  // Non-fatal — if git operations fail, return unknown so callers can decide
161
175
  logWarning("recovery", `implementation artifact check failed: ${(e as Error).message}`);
@@ -185,6 +199,10 @@ function isImplementationPath(file: string): boolean {
185
199
  return !file.startsWith(".gsd/") && !file.startsWith(".gsd\\");
186
200
  }
187
201
 
202
+ function normalizeRepoPath(file: string): string {
203
+ return file.trim().replace(/\\/g, "/").replace(/^\.\/+/, "");
204
+ }
205
+
188
206
  /**
189
207
  * Detect the main/master branch name.
190
208
  */
@@ -263,7 +281,7 @@ function getChangedFilesFromMilestoneTaggedCommits(
263
281
  "log", "--format=%H%x1f%B%x1e", "HEAD", "--", `.gsd/milestones/${milestoneId}`,
264
282
  ]);
265
283
  if (!scoped.ok) return scoped;
266
- if (scoped.matched) return scoped;
284
+ if (scoped.matched && classifyImplementationFiles(scoped.files) === "present") return scoped;
267
285
 
268
286
  // Fallback (#5033): when .gsd/ is gitignored / external / untracked, the
269
287
  // path-scoped scan matches no commits even though GSD-tagged commits
@@ -274,9 +292,137 @@ function getChangedFilesFromMilestoneTaggedCommits(
274
292
  // Intentionally unbounded — symmetric with the primary scan, and avoids
275
293
  // reintroducing the rolling-depth failure class removed in #4699 where
276
294
  // milestone evidence aged out behind unrelated activity.
277
- return scanGsdTaggedCommits(basePath, milestoneId, [
295
+ const unscoped = scanGsdTaggedCommits(basePath, milestoneId, [
278
296
  "log", "--format=%H%x1f%B%x1e", "HEAD",
279
297
  ]);
298
+ if (!unscoped.ok) return scoped.matched ? scoped : unscoped;
299
+ if (!unscoped.matched) return scoped;
300
+
301
+ return {
302
+ ok: true,
303
+ matched: true,
304
+ files: [...new Set([...scoped.files, ...unscoped.files])],
305
+ };
306
+ }
307
+
308
+ function getChangedFilesFromMilestoneEvidence(
309
+ basePath: string,
310
+ milestoneId: string,
311
+ ): { ok: boolean; matched: boolean; files: string[] } {
312
+ const tagged = getChangedFilesFromMilestoneTaggedCommits(basePath, milestoneId);
313
+ if (!tagged.ok) return tagged;
314
+ if (tagged.matched && classifyImplementationFiles(tagged.files) === "present") return tagged;
315
+
316
+ const attributed = getChangedFilesFromAttributedMilestoneCommits(basePath, milestoneId);
317
+ if (!attributed.ok) return tagged.matched ? tagged : attributed;
318
+ if (attributed.matched && classifyImplementationFiles(attributed.files) === "present") return attributed;
319
+
320
+ const backfilled = backfillChangedFilesFromUntaggedMilestoneCommits(basePath, milestoneId);
321
+ if (!backfilled.ok) return tagged.matched ? tagged : attributed.matched ? attributed : backfilled;
322
+ if (!backfilled.matched) {
323
+ if (tagged.matched) return tagged;
324
+ return attributed.matched ? attributed : backfilled;
325
+ }
326
+
327
+ return {
328
+ ok: true,
329
+ matched: true,
330
+ files: [...new Set([...tagged.files, ...attributed.files, ...backfilled.files])],
331
+ };
332
+ }
333
+
334
+ function getChangedFilesFromAttributedMilestoneCommits(
335
+ basePath: string,
336
+ milestoneId: string,
337
+ ): { ok: boolean; matched: boolean; files: string[] } {
338
+ try {
339
+ const shas = getMilestoneCommitAttributionShas(milestoneId);
340
+ if (shas.length === 0) return { ok: true, matched: false, files: [] };
341
+
342
+ const files = new Set<string>();
343
+ let matched = false;
344
+ for (const sha of shas) {
345
+ if (!isFullCommitSha(sha)) continue;
346
+ const commitFiles = getChangedFilesForCommit(basePath, sha);
347
+ if (commitFiles.length === 0) continue;
348
+ matched = true;
349
+ for (const file of commitFiles) files.add(file);
350
+ }
351
+ return { ok: true, matched, files: [...files] };
352
+ } catch (e) {
353
+ logWarning("recovery", `milestone attribution scan failed: ${(e as Error).message}`);
354
+ return { ok: false, matched: false, files: [] };
355
+ }
356
+ }
357
+
358
+ function backfillChangedFilesFromUntaggedMilestoneCommits(
359
+ basePath: string,
360
+ milestoneId: string,
361
+ ): { ok: boolean; matched: boolean; files: string[] } {
362
+ try {
363
+ const milestone = getMilestone(milestoneId);
364
+ const milestoneStartedAt = milestone?.created_at ? Math.floor(Date.parse(milestone.created_at) / 1000) * 1000 : NaN;
365
+ if (!Number.isFinite(milestoneStartedAt)) return { ok: true, matched: false, files: [] };
366
+
367
+ const taskFileHints = getCompletedMilestoneTaskFileHints(milestoneId);
368
+ if (taskFileHints.length === 0) return { ok: true, matched: false, files: [] };
369
+
370
+ const hintSet = new Set(taskFileHints.map(normalizeRepoPath).filter(Boolean));
371
+ if (hintSet.size === 0) return { ok: true, matched: false, files: [] };
372
+
373
+ const records = getCommitRecords(basePath);
374
+ const files = new Set<string>();
375
+ let matched = false;
376
+ for (const record of records) {
377
+ if (!isFullCommitSha(record.hash)) continue;
378
+ if (Date.parse(record.committedAt) < milestoneStartedAt) continue;
379
+ if (record.parents.trim().split(/\s+/).filter(Boolean).length > 1) continue;
380
+ if (commitMessageHasGsdTrailer(record.message)) continue;
381
+
382
+ const commitFiles = getChangedFilesForCommit(basePath, record.hash);
383
+ const implementationFiles = commitFiles.map(normalizeRepoPath).filter(isImplementationPath);
384
+ if (implementationFiles.length === 0) continue;
385
+ if (!implementationFiles.some((file) => hintSet.has(file))) continue;
386
+
387
+ matched = true;
388
+ for (const file of implementationFiles) files.add(file);
389
+ recordMilestoneCommitAttribution({
390
+ commitSha: record.hash,
391
+ milestoneId,
392
+ source: "backfill",
393
+ confidence: 0.8,
394
+ files: implementationFiles,
395
+ createdAt: new Date().toISOString(),
396
+ });
397
+ }
398
+
399
+ return { ok: true, matched, files: [...files] };
400
+ } catch (e) {
401
+ logWarning("recovery", `milestone attribution backfill failed: ${(e as Error).message}`);
402
+ return { ok: false, matched: false, files: [] };
403
+ }
404
+ }
405
+
406
+ function getCommitRecords(basePath: string): Array<{ hash: string; parents: string; committedAt: string; message: string }> {
407
+ const logOutput = execFileSync("git", ["log", "--format=%H%x1f%P%x1f%cI%x1f%B%x1e", "HEAD"], {
408
+ cwd: basePath,
409
+ stdio: ["ignore", "pipe", "pipe"],
410
+ encoding: "utf-8",
411
+ });
412
+ return logOutput
413
+ .split("\x1e")
414
+ .map((record) => record.trim())
415
+ .filter(Boolean)
416
+ .flatMap((record) => {
417
+ const parts = record.split("\x1f");
418
+ if (parts.length < 4) return [];
419
+ const [hash, parents, committedAt, ...messageParts] = parts;
420
+ return [{ hash: hash.trim(), parents: parents.trim(), committedAt: committedAt.trim(), message: messageParts.join("\x1f") }];
421
+ });
422
+ }
423
+
424
+ function isFullCommitSha(value: string): boolean {
425
+ return /^[0-9a-f]{40}$/i.test(value);
280
426
  }
281
427
 
282
428
  function scanGsdTaggedCommits(
@@ -664,12 +664,13 @@ export async function bootstrapAutoSession(
664
664
  }
665
665
 
666
666
  const effectivePrefs = loadEffectiveGSDPreferences(base)?.preferences;
667
- const deepProjectStagePending = !hasSurvivorBranch
668
- ? (await import("./auto-dispatch.js")).hasPendingDeepStage(
669
- effectivePrefs,
670
- base,
671
- )
672
- : false;
667
+ const { shouldRunDeepProjectSetup } = await import("./auto-dispatch.js");
668
+ const deepProjectStagePending = shouldRunDeepProjectSetup(
669
+ state,
670
+ effectivePrefs,
671
+ base,
672
+ { hasSurvivorBranch },
673
+ );
673
674
 
674
675
  if (deepProjectStagePending) {
675
676
  // Deep project-level setup runs before the first milestone exists. Let
@@ -420,6 +420,17 @@ export function _synthesizePausedSessionRecoveryForTest(
420
420
  return synthesizePausedSessionRecovery(basePath, unitType, unitId, sessionFile);
421
421
  }
422
422
 
423
+ const DETACHED_AUTO_KEEPALIVE_INTERVAL_MS = 30_000;
424
+
425
+ function withDetachedAutoKeepalive<T>(run: Promise<T>): Promise<T> {
426
+ const keepAlive = setInterval(() => {}, DETACHED_AUTO_KEEPALIVE_INTERVAL_MS);
427
+ return run.finally(() => {
428
+ clearInterval(keepAlive);
429
+ });
430
+ }
431
+
432
+ export const _withDetachedAutoKeepaliveForTest = withDetachedAutoKeepalive;
433
+
423
434
  export function startAutoDetached(
424
435
  ctx: ExtensionCommandContext,
425
436
  pi: ExtensionAPI,
@@ -431,7 +442,7 @@ export function startAutoDetached(
431
442
  milestoneLock?: string | null;
432
443
  },
433
444
  ): void {
434
- void startAuto(ctx, pi, base, verboseMode, options).catch((err) => {
445
+ void withDetachedAutoKeepalive(startAuto(ctx, pi, base, verboseMode, options)).catch((err) => {
435
446
  const message = getErrorMessage(err);
436
447
  ctx.ui.notify(`Auto-start failed: ${message}`, "error");
437
448
  logWarning("engine", `auto start error: ${message}`, { file: "auto.ts" });
@@ -1,3 +1,5 @@
1
+ // GSD-2 + src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts - Handles provider and agent-end recovery for GSD auto-mode.
2
+
1
3
  import type { ExtensionAPI, ExtensionContext } from "@gsd/pi-coding-agent";
2
4
 
3
5
  import { logWarning } from "../workflow-logger.js";
@@ -12,7 +14,7 @@ import { clearPathCache } from "../paths.js";
12
14
  import { getAutoDashboardData, getAutoModeStartModel, isAutoActive, pauseAuto, setCurrentDispatchedModelId } from "../auto.js";
13
15
  import { getNextFallbackModel, resolveModelWithFallbacksForUnit } from "../preferences.js";
14
16
  import { pauseAutoForProviderError } from "../provider-error-pause.js";
15
- import { isSessionSwitchInFlight, resolveAgentEnd } from "../auto/resolve.js";
17
+ import { isSessionSwitchInFlight, resolveAgentEnd, resolveAgentEndCancelled } from "../auto/resolve.js";
16
18
  import { resolveModelId } from "../auto-model-selection.js";
17
19
  import { resolveProjectRoot } from "../worktree.js";
18
20
  import { clearDiscussionFlowState } from "./write-gate.js";
@@ -69,6 +71,11 @@ export function _buildAbortedPauseContext(lastMsg: { errorMessage?: unknown }):
69
71
  };
70
72
  }
71
73
 
74
+ export function isUserInitiatedAbortMessage(message: string | undefined | null): boolean {
75
+ if (!message) return false;
76
+ return /\b(?:claude code process aborted by user|request aborted by user|process aborted by user)\b/i.test(message);
77
+ }
78
+
72
79
  async function pauseTransientWithBackoff(
73
80
  cls: ErrorClass,
74
81
  pi: ExtensionAPI,
@@ -180,6 +187,14 @@ export async function handleAgentEnd(
180
187
  // is in the assistant message text content. Fall back to content when
181
188
  // errorMessage looks uninformative.
182
189
  const rawErrorMsg = ("errorMessage" in lastMsg && lastMsg.errorMessage) ? String(lastMsg.errorMessage) : "";
190
+ if (isUserInitiatedAbortMessage(rawErrorMsg)) {
191
+ resolveAgentEndCancelled({
192
+ message: rawErrorMsg,
193
+ category: "aborted",
194
+ isTransient: false,
195
+ });
196
+ return;
197
+ }
183
198
  const isUseless = !rawErrorMsg || /^(success|ok|true|error|unknown)$/i.test(rawErrorMsg.trim());
184
199
  // #3588: When errorMessage is uninformative, extract the real error from
185
200
  // the assistant message text content for display purposes only.
@@ -7,7 +7,7 @@ import type { GSDEcosystemBeforeAgentStartHandler } from "../ecosystem/gsd-exten
7
7
  import { updateSnapshot } from "../ecosystem/gsd-extension-api.js";
8
8
 
9
9
  import { buildMilestoneFileName, resolveMilestonePath, resolveSliceFile, resolveSlicePath } from "../paths.js";
10
- import { canonicalToolName, clearDiscussionFlowState, isDepthConfirmationAnswer, isQueuePhaseActive, markApprovalGateVerified, markDepthVerified, resetWriteGateState, shouldBlockContextWrite, shouldBlockPlanningUnit, shouldBlockQueueExecution, isGateQuestionId, setPendingGate, clearPendingGate, getPendingGate, shouldBlockPendingGate, shouldBlockPendingGateBash, extractDepthVerificationMilestoneId } from "./write-gate.js";
10
+ import { canonicalToolName, clearDiscussionFlowState, isDepthConfirmationAnswer, isQueuePhaseActive, markApprovalGateVerified, markDepthVerified, resetWriteGateState, shouldBlockContextWrite, shouldBlockPlanningUnit, shouldBlockQueueExecution, shouldBlockWorktreeWrite, isGateQuestionId, setPendingGate, clearPendingGate, getPendingGate, shouldBlockPendingGate, shouldBlockPendingGateBash, extractDepthVerificationMilestoneId } from "./write-gate.js";
11
11
  import { resolveManifest } from "../unit-context-manifest.js";
12
12
  import { isBlockedStateFile, isBashWriteToStateFile, BLOCKED_WRITE_ERROR } from "../write-intercept.js";
13
13
  import { loadFile, saveFile, formatContinue } from "../files.js";
@@ -482,6 +482,22 @@ export function registerHooks(
482
482
  }
483
483
  }
484
484
 
485
+ // ── Worktree-isolation write gate (#5199) ────────────────────────────
486
+ // Block planning-write tools from landing code at the project root when
487
+ // git.isolation=worktree but auto-mode hasn't created the milestone
488
+ // worktree yet. Without this, writes silently orphan outside git history.
489
+ if (isToolCallEventType("write", event) || isToolCallEventType("edit", event)) {
490
+ const wtBasePath = resolveWorktreeProjectRoot(dash.basePath ?? discussionBasePath);
491
+ const wtGuard = shouldBlockWorktreeWrite(
492
+ event.toolName,
493
+ event.input.path,
494
+ wtBasePath,
495
+ isAutoActive(),
496
+ dash.currentUnit?.type,
497
+ );
498
+ if (wtGuard.block) return wtGuard;
499
+ }
500
+
485
501
  // ── Single-writer engine: block direct writes to STATE.md ──────────
486
502
  // Covers write, edit, and bash tools to prevent bypass vectors.
487
503
  if (isToolCallEventType("write", event)) {
@@ -1,10 +1,12 @@
1
- import { copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
1
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, realpathSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
2
2
  import { isAbsolute, join, relative, resolve, sep } from "node:path";
3
3
 
4
4
  import { minimatch } from "minimatch";
5
5
 
6
+ import { getIsolationMode } from "../preferences.js";
6
7
  import type { ToolsPolicy } from "../unit-context-manifest.js";
7
8
  import { logWarning } from "../workflow-logger.js";
9
+ import { isGsdWorktreePath, resolveWorktreeProjectRoot } from "../worktree-root.js";
8
10
 
9
11
  /**
10
12
  * Regex matching milestone CONTEXT.md file names in both legacy M001
@@ -882,3 +884,135 @@ export function shouldBlockPlanningUnit(
882
884
  // avoids breaking gsd_* MCP tools or future safe additions.
883
885
  return { block: false };
884
886
  }
887
+
888
+ // ─── Worktree isolation write gate (#5199) ────────────────────────────────
889
+ //
890
+ // When `git.isolation: worktree` is configured, the per-unit commit pipeline
891
+ // only runs inside the auto-mode loop (`auto-post-unit.ts`). If the LLM
892
+ // authors code at the project root before auto-mode is started, those writes
893
+ // land in the working tree but never reach a commit — they're silently
894
+ // orphaned outside git history. This guard blocks those writes at the
895
+ // tool_call seam so the agent receives a clear error instead.
896
+
897
+ const WORKTREE_GATE_BOOTSTRAP_UNITS = new Set([
898
+ "discuss-milestone",
899
+ "plan-milestone",
900
+ "init",
901
+ ]);
902
+
903
+ function realpathOrResolve(p: string): string {
904
+ const abs = resolve(p);
905
+ try {
906
+ return realpathSync(abs);
907
+ } catch {
908
+ // Path doesn't exist (yet) — realpath the deepest existing ancestor so
909
+ // platforms where /tmp -> /private/tmp don't break containment checks.
910
+ let dir = abs;
911
+ const tail: string[] = [];
912
+ while (dir && dir !== resolve(dir, "..")) {
913
+ try {
914
+ const real = realpathSync(dir);
915
+ return tail.length ? join(real, ...tail.reverse()) : real;
916
+ } catch {
917
+ const idx = dir.lastIndexOf(sep);
918
+ if (idx <= 0) break;
919
+ tail.push(dir.slice(idx + 1));
920
+ dir = dir.slice(0, idx) || sep;
921
+ }
922
+ }
923
+ return abs;
924
+ }
925
+ }
926
+
927
+ function isPathContained(target: string, container: string): boolean {
928
+ if (target === container) return true;
929
+ return target.startsWith(container.endsWith(sep) ? container : container + sep);
930
+ }
931
+
932
+ /**
933
+ * Block planning-write tool calls that would land code at the project root
934
+ * while `git.isolation: worktree` is in effect and auto-mode hasn't created
935
+ * (or flipped cwd into) the milestone worktree.
936
+ *
937
+ * Pure / unit-testable. Callers in `register-hooks.ts` supply the resolved
938
+ * project root and current auto liveness; this function does no I/O beyond
939
+ * realpath resolution.
940
+ *
941
+ * Allow rules (in order):
942
+ * 1. Tool isn't a planning-write (write/edit/multi_edit/notebook_edit).
943
+ * 2. `GSD_DISABLE_WORKTREE_WRITE_GUARD=1` self-hosting bypass.
944
+ * 3. Isolation mode is not "worktree".
945
+ * 4. Active unit is a bootstrap unit (discuss-milestone/plan-milestone/init).
946
+ * 5. Target is inside `<projectRoot>/.gsd/worktrees/` (a real worktree).
947
+ * 6. Target is inside `<projectRoot>/.gsd/` and isn't masquerading as a
948
+ * worktrees sibling (rejects the `.gsd/worktrees-extra/…` prefix trick).
949
+ * 7. Auto is live AND `effectiveBasePath` is itself a `.gsd/worktrees/…` path.
950
+ *
951
+ * Otherwise: block with a message that points the agent at `/gsd` to start
952
+ * auto-mode.
953
+ */
954
+ export function shouldBlockWorktreeWrite(
955
+ toolName: string,
956
+ targetPath: string,
957
+ effectiveBasePath: string,
958
+ isAutoLive: boolean,
959
+ currentUnitType?: string | null,
960
+ ): { block: boolean; reason?: string } {
961
+ const tool = canonicalToolName(toolName);
962
+ if (!PLANNING_WRITE_TOOLS.has(tool)) return { block: false };
963
+ if (process.env.GSD_DISABLE_WORKTREE_WRITE_GUARD === "1") return { block: false };
964
+ if (getIsolationMode(effectiveBasePath) !== "worktree") return { block: false };
965
+ if (currentUnitType && WORKTREE_GATE_BOOTSTRAP_UNITS.has(currentUnitType)) return { block: false };
966
+
967
+ if (!targetPath) {
968
+ return {
969
+ block: true,
970
+ reason: [
971
+ `HARD BLOCK: ${tool} called with empty path while \`git.isolation: worktree\` is configured`,
972
+ `and auto-mode is not active. Refusing to allow writes that cannot be located.`,
973
+ ].join(" "),
974
+ };
975
+ }
976
+
977
+ // Resolve the target relative to the project root, then realpath to defeat
978
+ // symlink-based escapes and prefix tricks (e.g. .gsd/worktrees-extra/).
979
+ const projectRoot = resolveWorktreeProjectRoot(effectiveBasePath);
980
+ const absTarget = isAbsolute(targetPath) ? targetPath : resolve(projectRoot, targetPath);
981
+ const realTarget = realpathOrResolve(absTarget);
982
+ const realRoot = realpathOrResolve(projectRoot);
983
+ const realGsd = realpathOrResolve(join(projectRoot, ".gsd"));
984
+ const realWorktreesDir = realpathOrResolve(join(projectRoot, ".gsd", "worktrees"));
985
+
986
+ // Allow writes inside the legitimate worktrees subtree.
987
+ if (isPathContained(realTarget, realWorktreesDir)) return { block: false };
988
+
989
+ // Allow writes to .gsd/ planning artifacts, but reject siblings whose name
990
+ // starts with "worktrees" (the worktrees-extra prefix trick — case 4).
991
+ if (isPathContained(realTarget, realGsd)) {
992
+ const rel = relative(realGsd, realTarget);
993
+ const firstSeg = rel.split(/[\/\\]/)[0] ?? "";
994
+ if (!firstSeg.startsWith("worktrees")) return { block: false };
995
+ // fall through: looks like worktrees<something> sibling — block
996
+ }
997
+
998
+ // Auto is live and the caller is operating inside a worktree path —
999
+ // host tool's write happens in worktree context; let it through.
1000
+ if (isAutoLive && isGsdWorktreePath(effectiveBasePath)) return { block: false };
1001
+
1002
+ // Block. Provide enough context that the agent can self-correct.
1003
+ const displayTarget = isPathContained(realTarget, realRoot)
1004
+ ? relative(realRoot, realTarget) || "."
1005
+ : realTarget;
1006
+ return {
1007
+ block: true,
1008
+ reason: [
1009
+ `HARD BLOCK: Worktree isolation is configured (\`git.isolation: worktree\`) but auto-mode is`,
1010
+ `not running and the target "${displayTarget}" is not inside \`.gsd/worktrees/<MID>/\`.`,
1011
+ `Code edits at the project root would be lost — only the auto-mode commit pipeline`,
1012
+ `(auto-post-unit) commits work, and it never runs outside the loop.`,
1013
+ `Required action: start auto-mode with \`/gsd\` so the milestone worktree is created,`,
1014
+ `then write inside it. To disable this guard for self-hosting development, set`,
1015
+ `GSD_DISABLE_WORKTREE_WRITE_GUARD=1.`,
1016
+ ].join(" "),
1017
+ };
1018
+ }