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

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 (306) 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/dispatcher.js +5 -0
  19. package/dist/resources/extensions/gsd/commands-extract-learnings.js +17 -12
  20. package/dist/resources/extensions/gsd/custom-workflow-engine.js +22 -2
  21. package/dist/resources/extensions/gsd/db-base-schema.js +14 -0
  22. package/dist/resources/extensions/gsd/db-migration-steps.js +16 -0
  23. package/dist/resources/extensions/gsd/detection.js +106 -0
  24. package/dist/resources/extensions/gsd/graph.js +9 -3
  25. package/dist/resources/extensions/gsd/gsd-db.js +102 -2
  26. package/dist/resources/extensions/gsd/guided-flow.js +33 -9
  27. package/dist/resources/extensions/gsd/pr-evidence.js +57 -16
  28. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +7 -8
  29. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
  30. package/dist/resources/extensions/gsd/safety/evidence-collector.js +10 -2
  31. package/dist/resources/extensions/gsd/working-output-messages.js +64 -0
  32. package/dist/resources/extensions/gsd/worktree-manager.js +16 -14
  33. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  34. package/dist/web/standalone/.next/BUILD_ID +1 -1
  35. package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
  36. package/dist/web/standalone/.next/build-manifest.json +3 -3
  37. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  38. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  39. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  40. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  48. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  56. package/dist/web/standalone/.next/server/app/index.html +1 -1
  57. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app-paths-manifest.json +13 -13
  64. package/dist/web/standalone/.next/server/chunks/6897.js +3 -3
  65. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  66. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  67. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  68. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  69. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  70. package/dist/web/standalone/.next/static/chunks/{8336.6f6f30e410419aff.js → 8336.631939fb583761fa.js} +1 -1
  71. package/dist/web/standalone/.next/static/chunks/{webpack-d82dbee6356c1733.js → webpack-0481f1221120a7c6.js} +1 -1
  72. package/package.json +10 -6
  73. package/packages/contracts/package.json +1 -1
  74. package/packages/pi-ai/dist/models/fake-model.d.ts +12 -0
  75. package/packages/pi-ai/dist/models/fake-model.d.ts.map +1 -0
  76. package/packages/pi-ai/dist/models/fake-model.js +27 -0
  77. package/packages/pi-ai/dist/models/fake-model.js.map +1 -0
  78. package/packages/pi-ai/dist/models/index.d.ts.map +1 -1
  79. package/packages/pi-ai/dist/models/index.js +8 -0
  80. package/packages/pi-ai/dist/models/index.js.map +1 -1
  81. package/packages/pi-ai/dist/providers/fake.d.ts +42 -0
  82. package/packages/pi-ai/dist/providers/fake.d.ts.map +1 -0
  83. package/packages/pi-ai/dist/providers/fake.js +319 -0
  84. package/packages/pi-ai/dist/providers/fake.js.map +1 -0
  85. package/packages/pi-ai/dist/providers/register-builtins.d.ts.map +1 -1
  86. package/packages/pi-ai/dist/providers/register-builtins.js +24 -0
  87. package/packages/pi-ai/dist/providers/register-builtins.js.map +1 -1
  88. package/packages/pi-ai/src/models/fake-model.ts +30 -0
  89. package/packages/pi-ai/src/models/index.ts +9 -0
  90. package/packages/pi-ai/src/providers/fake.ts +376 -0
  91. package/packages/pi-ai/src/providers/register-builtins.ts +23 -0
  92. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  93. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +74 -0
  94. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
  95. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +2 -0
  96. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  97. package/packages/pi-coding-agent/dist/core/extensions/runner.js +14 -1
  98. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  99. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +97 -0
  100. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
  101. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  102. package/packages/pi-coding-agent/dist/core/model-registry.js +5 -0
  103. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  104. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +4 -0
  105. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  106. package/packages/pi-coding-agent/dist/core/settings-manager.js +8 -0
  107. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  108. package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
  109. package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
  110. package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
  111. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js +6 -4
  112. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js.map +1 -1
  113. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +54 -15
  114. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  115. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.d.ts +26 -0
  116. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.d.ts.map +1 -0
  117. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js +112 -0
  118. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js.map +1 -0
  119. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.d.ts +2 -0
  120. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.d.ts.map +1 -0
  121. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.js +51 -0
  122. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.js.map +1 -0
  123. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  124. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  125. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
  126. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +10 -9
  127. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
  128. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +3 -0
  129. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  130. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +11 -0
  131. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  132. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js +7 -6
  133. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +16 -0
  135. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  136. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +106 -17
  137. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  138. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  139. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +60 -1
  140. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  141. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +40 -1
  142. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
  143. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +1 -0
  144. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
  145. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +1 -0
  146. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  147. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  148. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +3 -0
  149. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  150. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +23 -0
  151. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  152. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
  153. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +20 -0
  154. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  155. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.test.d.ts +2 -0
  156. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.test.d.ts.map +1 -0
  157. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.test.js +79 -0
  158. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.test.js.map +1 -0
  159. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.d.ts +12 -0
  160. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.d.ts.map +1 -1
  161. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.js +13 -0
  162. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.js.map +1 -1
  163. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts +1 -1
  164. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  165. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +18 -1
  166. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
  167. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  168. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +36 -27
  169. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  170. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.d.ts +11 -0
  171. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.d.ts.map +1 -0
  172. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.js +18 -0
  173. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.js.map +1 -0
  174. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.d.ts +2 -0
  175. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.d.ts.map +1 -0
  176. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js +48 -0
  177. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js.map +1 -0
  178. package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +87 -0
  179. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +108 -0
  180. package/packages/pi-coding-agent/src/core/extensions/runner.ts +16 -1
  181. package/packages/pi-coding-agent/src/core/model-registry.ts +4 -0
  182. package/packages/pi-coding-agent/src/core/settings-manager.ts +12 -0
  183. package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
  184. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.ts +7 -5
  185. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +78 -15
  186. package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.test.ts +59 -0
  187. package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.ts +160 -0
  188. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +1 -0
  189. package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +10 -9
  190. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +15 -0
  191. package/packages/pi-coding-agent/src/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.ts +10 -9
  192. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +118 -17
  193. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +43 -1
  194. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +75 -1
  195. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +1 -0
  196. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +1 -1
  197. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +25 -0
  198. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.test.ts +95 -0
  199. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +24 -1
  200. package/packages/pi-coding-agent/src/modes/interactive/theme/theme-schema.ts +13 -0
  201. package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +32 -2
  202. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +36 -27
  203. package/packages/pi-coding-agent/src/modes/interactive/tui-mode.test.ts +65 -0
  204. package/packages/pi-coding-agent/src/modes/interactive/tui-mode.ts +29 -0
  205. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  206. package/packages/pi-tui/dist/__tests__/style.test.d.ts +2 -0
  207. package/packages/pi-tui/dist/__tests__/style.test.d.ts.map +1 -0
  208. package/packages/pi-tui/dist/__tests__/style.test.js +63 -0
  209. package/packages/pi-tui/dist/__tests__/style.test.js.map +1 -0
  210. package/packages/pi-tui/dist/__tests__/tui.test.js +24 -3
  211. package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -1
  212. package/packages/pi-tui/dist/index.d.ts +1 -0
  213. package/packages/pi-tui/dist/index.d.ts.map +1 -1
  214. package/packages/pi-tui/dist/index.js +2 -0
  215. package/packages/pi-tui/dist/index.js.map +1 -1
  216. package/packages/pi-tui/dist/style.d.ts +41 -0
  217. package/packages/pi-tui/dist/style.d.ts.map +1 -0
  218. package/packages/pi-tui/dist/style.js +158 -0
  219. package/packages/pi-tui/dist/style.js.map +1 -0
  220. package/packages/pi-tui/dist/tui.d.ts +0 -1
  221. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  222. package/packages/pi-tui/dist/tui.js +3 -8
  223. package/packages/pi-tui/dist/tui.js.map +1 -1
  224. package/packages/pi-tui/src/__tests__/style.test.ts +76 -0
  225. package/packages/pi-tui/src/__tests__/tui.test.ts +29 -3
  226. package/packages/pi-tui/src/index.ts +9 -0
  227. package/packages/pi-tui/src/style.ts +225 -0
  228. package/packages/pi-tui/src/tui.ts +3 -8
  229. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  230. package/pkg/dist/modes/interactive/theme/theme-schema.d.ts +12 -0
  231. package/pkg/dist/modes/interactive/theme/theme-schema.d.ts.map +1 -1
  232. package/pkg/dist/modes/interactive/theme/theme-schema.js +13 -0
  233. package/pkg/dist/modes/interactive/theme/theme-schema.js.map +1 -1
  234. package/pkg/dist/modes/interactive/theme/theme.d.ts +1 -1
  235. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  236. package/pkg/dist/modes/interactive/theme/theme.js +18 -1
  237. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
  238. package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  239. package/pkg/dist/modes/interactive/theme/themes.js +36 -27
  240. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  241. package/src/resources/GSD-WORKFLOW.md +2 -2
  242. package/src/resources/extensions/github-sync/templates.ts +38 -8
  243. package/src/resources/extensions/github-sync/tests/inline-code.test.ts +66 -0
  244. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  245. package/src/resources/extensions/gsd/auto/loop.ts +17 -10
  246. package/src/resources/extensions/gsd/auto/phases.ts +42 -28
  247. package/src/resources/extensions/gsd/auto/run-unit.ts +24 -14
  248. package/src/resources/extensions/gsd/auto-dashboard.ts +57 -8
  249. package/src/resources/extensions/gsd/auto-dispatch.ts +17 -0
  250. package/src/resources/extensions/gsd/auto-post-unit.ts +10 -10
  251. package/src/resources/extensions/gsd/auto-prompts.ts +116 -1
  252. package/src/resources/extensions/gsd/auto-recovery.ts +153 -7
  253. package/src/resources/extensions/gsd/auto-start.ts +7 -6
  254. package/src/resources/extensions/gsd/auto.ts +12 -1
  255. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +16 -1
  256. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +17 -1
  257. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +135 -1
  258. package/src/resources/extensions/gsd/clean-root-preflight.ts +41 -3
  259. package/src/resources/extensions/gsd/commands/dispatcher.ts +6 -0
  260. package/src/resources/extensions/gsd/commands-extract-learnings.ts +17 -12
  261. package/src/resources/extensions/gsd/custom-workflow-engine.ts +24 -1
  262. package/src/resources/extensions/gsd/db-base-schema.ts +15 -0
  263. package/src/resources/extensions/gsd/db-migration-steps.ts +17 -0
  264. package/src/resources/extensions/gsd/detection.ts +128 -0
  265. package/src/resources/extensions/gsd/graph.ts +12 -5
  266. package/src/resources/extensions/gsd/gsd-db.ts +119 -1
  267. package/src/resources/extensions/gsd/guided-flow.ts +30 -9
  268. package/src/resources/extensions/gsd/pr-evidence.ts +63 -5
  269. package/src/resources/extensions/gsd/prompts/complete-milestone.md +7 -8
  270. package/src/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
  271. package/src/resources/extensions/gsd/safety/evidence-collector.ts +11 -2
  272. package/src/resources/extensions/gsd/tests/auto-abort-pause-regression.test.ts +7 -1
  273. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +33 -0
  274. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +84 -5
  275. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +170 -1
  276. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +88 -2
  277. package/src/resources/extensions/gsd/tests/commands-extract-learnings.test.ts +9 -0
  278. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +112 -6
  279. package/src/resources/extensions/gsd/tests/custom-workflow-engine.test.ts +40 -2
  280. package/src/resources/extensions/gsd/tests/db-migration-steps.integration.test.ts +428 -0
  281. package/src/resources/extensions/gsd/tests/db-schema-metadata.test.ts +2 -2
  282. package/src/resources/extensions/gsd/tests/detection.test.ts +140 -0
  283. package/src/resources/extensions/gsd/tests/fixtures/pr-body/commands-ship-basic.md +52 -0
  284. package/src/resources/extensions/gsd/tests/fixtures/pr-body/commands-ship-empty-optionals.md +42 -0
  285. package/src/resources/extensions/gsd/tests/fixtures/pr-body/swarm-lane-no-blockers.md +55 -0
  286. package/src/resources/extensions/gsd/tests/fixtures/pr-body/swarm-lane-with-blockers.md +60 -0
  287. package/src/resources/extensions/gsd/tests/graph-operations.test.ts +10 -0
  288. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +44 -0
  289. package/src/resources/extensions/gsd/tests/has-pending-deep-stage.test.ts +33 -1
  290. package/src/resources/extensions/gsd/tests/pr-evidence-equivalence.test.ts +102 -0
  291. package/src/resources/extensions/gsd/tests/pr-evidence-hardening.test.ts +165 -0
  292. package/src/resources/extensions/gsd/tests/right-sized-workflow-prompts.test.ts +192 -0
  293. package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +29 -0
  294. package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +18 -0
  295. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +46 -2
  296. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +1 -1
  297. package/src/resources/extensions/gsd/tests/working-output-messages.test.ts +93 -0
  298. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +37 -6
  299. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +7 -0
  300. package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +9 -2
  301. package/src/resources/extensions/gsd/tests/worktree-write-gate.test.ts +179 -0
  302. package/src/resources/extensions/gsd/working-output-messages.ts +120 -0
  303. package/src/resources/extensions/gsd/worktree-manager.ts +15 -4
  304. package/packages/contracts/tsconfig.tsbuildinfo +0 -1
  305. /package/dist/web/standalone/.next/static/{bQDK5_LtkGVS64AirQgQG → yTuahMMuJzVnsov5PreWl}/_buildManifest.js +0 -0
  306. /package/dist/web/standalone/.next/static/{bQDK5_LtkGVS64AirQgQG → yTuahMMuJzVnsov5PreWl}/_ssgManifest.js +0 -0
@@ -6,7 +6,7 @@
6
6
  * utility.
7
7
  */
8
8
  import { loadFile, parseContinue, parseSummary, loadActiveOverrides, formatOverridesSection, parseTaskPlanFile } from "./files.js";
9
- import { hasVerdict, getUatType } from "./verdict-parser.js";
9
+ import { hasVerdict, getUatType, extractVerdict } from "./verdict-parser.js";
10
10
  import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
11
11
  import { resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveTasksDir, resolveTaskFiles, resolveTaskFile, relMilestoneFile, relSliceFile, relSlicePath, relMilestonePath, resolveGsdRootFile, relGsdRootFile, resolveRuntimeFile, } from "./paths.js";
12
12
  import { resolveSkillDiscoveryMode, resolveInlineLevel, loadEffectiveGSDPreferences, resolveAllSkillReferences } from "./preferences.js";
@@ -25,6 +25,7 @@ import { logWarning } from "./workflow-logger.js";
25
25
  import { inlineGraphSubgraph } from "./graph-context.js";
26
26
  import { buildExtractionStepsBlock } from "./commands-extract-learnings.js";
27
27
  import { resolveSkillManifest, warnIfManifestHasMissingSkills } from "./skill-manifest.js";
28
+ import { classifyProject } from "./detection.js";
28
29
  // ─── Preamble Cap ─────────────────────────────────────────────────────────────
29
30
  /**
30
31
  * Historical static ceiling for the preamble cap. Kept as an upper bound even
@@ -62,6 +63,104 @@ function resolvePromptBudgets() {
62
63
  function resolveSummaryBudgetChars() {
63
64
  return resolvePromptBudgets().summaryBudgetChars;
64
65
  }
66
+ function formatProjectClassificationForPlanning(classification) {
67
+ const sampleFiles = classification.contentFiles.slice(0, 8);
68
+ const sample = sampleFiles.length > 0 ? sampleFiles.map((file) => `\`${file}\``).join(", ") : "(none)";
69
+ const lines = [
70
+ "### Project Classification",
71
+ "",
72
+ `- **Kind:** ${classification.kind}`,
73
+ `- **Content files:** ${classification.contentFiles.length}`,
74
+ `- **Sample files:** ${sample}`,
75
+ `- **Reason:** ${classification.reason}`,
76
+ "",
77
+ ];
78
+ if (classification.kind === "untyped-existing") {
79
+ if (classification.contentFiles.length <= 2) {
80
+ lines.push("**Workflow sizing:** This is a tiny existing untyped project. Prefer exactly one slice unless the milestone request clearly spans multiple independent user-visible capabilities.");
81
+ }
82
+ else if (classification.contentFiles.length <= 5) {
83
+ lines.push("**Workflow sizing:** This is a small existing untyped project. Prefer 1-2 slices unless the milestone request clearly spans multiple independent user-visible capabilities.");
84
+ }
85
+ else {
86
+ lines.push("**Workflow sizing:** Existing untyped project. Use generic file-level workflow guidance and size slices by real capability boundaries, not by missing tooling markers.");
87
+ }
88
+ }
89
+ else if (classification.kind === "greenfield") {
90
+ lines.push("**Workflow sizing:** No project content exists yet. Use normal greenfield sizing for the requested scope.");
91
+ }
92
+ else if (classification.kind === "typed-existing") {
93
+ lines.push("**Workflow sizing:** Known project markers exist. Use normal ecosystem-aware planning guidance.");
94
+ }
95
+ else {
96
+ lines.push("**Workflow sizing:** Invalid repository state. Planning should surface this as a blocker rather than inventing project structure.");
97
+ }
98
+ return lines.join("\n");
99
+ }
100
+ function normalizeArtifactRef(value) {
101
+ return value.trim().replace(/^[-\s]+/, "").replace(/^["'`]+|["'`]+$/g, "").replaceAll("\\", "/").replace(/^\.\//, "");
102
+ }
103
+ function parseCoveredArtifacts(validationContent) {
104
+ const covered = new Set();
105
+ const lines = validationContent.split(/\r?\n/);
106
+ let inCoveredArtifacts = false;
107
+ for (const line of lines) {
108
+ if (/^\s*covered[-_]?artifacts\s*:/i.test(line)) {
109
+ inCoveredArtifacts = true;
110
+ const inline = line.split(/covered[-_]?artifacts\s*:/i)[1]?.trim();
111
+ if (inline && inline !== "[]") {
112
+ inline.replace(/^\[|\]$/g, "").split(",").map(normalizeArtifactRef).filter(Boolean).forEach((item) => covered.add(item));
113
+ }
114
+ continue;
115
+ }
116
+ if (!inCoveredArtifacts)
117
+ continue;
118
+ if (/^\S/.test(line) && !/^\s*-/.test(line))
119
+ break;
120
+ const item = line.match(/^\s*-\s*(.+)$/)?.[1];
121
+ if (item)
122
+ covered.add(normalizeArtifactRef(item));
123
+ }
124
+ return covered;
125
+ }
126
+ function isValidationFreshOrApplicable(validationContent, currentArtifacts) {
127
+ if (!validationContent)
128
+ return false;
129
+ if (!/validation_metadata:/i.test(validationContent))
130
+ return false;
131
+ const coveredArtifacts = parseCoveredArtifacts(validationContent);
132
+ if (coveredArtifacts.size === 0)
133
+ return false;
134
+ return currentArtifacts
135
+ .map(normalizeArtifactRef)
136
+ .filter(Boolean)
137
+ .every((artifact) => coveredArtifacts.has(artifact));
138
+ }
139
+ function formatCloseoutReviewInstructions(validationContent, validationRel, currentArtifacts) {
140
+ const verdict = validationContent ? extractVerdict(validationContent) : null;
141
+ const validationFresh = isValidationFreshOrApplicable(validationContent, currentArtifacts);
142
+ if (verdict === "pass" && validationFresh) {
143
+ return [
144
+ "### Passing Validation Artifact",
145
+ "",
146
+ `A passing validation artifact is present at \`${validationRel}\`. Treat it as authoritative for success criteria, requirement coverage, verification classes, and cross-slice integration.`,
147
+ "",
148
+ "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`.",
149
+ ].join("\n");
150
+ }
151
+ if (verdict) {
152
+ return [
153
+ "### Validation Requires Attention",
154
+ "",
155
+ `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.`,
156
+ ].join("\n");
157
+ }
158
+ return [
159
+ "### No Passing Validation Artifact",
160
+ "",
161
+ `No passing validation artifact was found at \`${validationRel}\`. Use the full closeout review path before completion.`,
162
+ ].join("\n");
163
+ }
65
164
  function capPreamble(preamble) {
66
165
  // Cap inlined context at min(historical 30K ceiling, scaled inline budget).
67
166
  // The ceiling preserves pre-fix behavior for large-window users; the scaled
@@ -1465,6 +1564,7 @@ export async function buildPlanMilestonePrompt(mid, midTitle, base, level) {
1465
1564
  const researchAnchor = readPhaseAnchor(base, mid, "research-milestone");
1466
1565
  if (researchAnchor)
1467
1566
  inlined.push(formatAnchorForPrompt(researchAnchor));
1567
+ inlined.push(formatProjectClassificationForPlanning(classifyProject(base)));
1468
1568
  inlined.push(await inlineFile(contextPath, contextRel, "Milestone Context"));
1469
1569
  const researchInline = await inlineFileOptional(researchPath, researchRel, "Milestone Research");
1470
1570
  if (researchInline)
@@ -2017,6 +2117,9 @@ export async function buildCompleteMilestonePrompt(mid, midTitle, base, level) {
2017
2117
  const inlineLevel = level ?? resolveInlineLevel();
2018
2118
  const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP");
2019
2119
  const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
2120
+ const validationPath = resolveMilestoneFile(base, mid, "VALIDATION");
2121
+ const validationRel = relMilestoneFile(base, mid, "VALIDATION");
2122
+ const validationContent = validationPath ? await loadFile(validationPath) : null;
2020
2123
  const inlined = [];
2021
2124
  inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
2022
2125
  // Inline all slice summaries (deduplicated by slice ID)
@@ -2056,6 +2159,13 @@ export async function buildCompleteMilestonePrompt(mid, midTitle, base, level) {
2056
2159
  const pathList = summaryRelPaths.map(p => `- \`${p}\``).join("\n");
2057
2160
  inlined.push(`### 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}`);
2058
2161
  }
2162
+ const validationContext = [
2163
+ formatCloseoutReviewInstructions(validationContent, validationRel, [validationRel, roadmapRel, ...summaryRelPaths]),
2164
+ ];
2165
+ if (validationContent) {
2166
+ validationContext.push(`### Milestone Validation\nSource: \`${validationRel}\`\n\n${validationContent.trim()}`);
2167
+ }
2168
+ inlined.unshift(...validationContext);
2059
2169
  // Inline root GSD files (skip for minimal — completion can read these if needed)
2060
2170
  if (inlineLevel !== "minimal") {
2061
2171
  const requirementsInline = await inlineRequirementsFromDb(base, mid, undefined, inlineLevel);
@@ -12,7 +12,7 @@ import { appendEvent } from "./workflow-events.js";
12
12
  import { atomicWriteSync } from "./atomic-write.js";
13
13
  import { clearParseCache } from "./files.js";
14
14
  import { parseRoadmap as parseLegacyRoadmap, parsePlan as parseLegacyPlan } from "./parsers-legacy.js";
15
- import { isDbAvailable, getTask, getSlice, getSliceTasks, getPendingGates, updateTaskStatus, updateSliceStatus, insertSlice, getMilestone, refreshOpenDatabaseFromDisk } from "./gsd-db.js";
15
+ import { isDbAvailable, getTask, getSlice, getSliceTasks, getPendingGates, updateTaskStatus, updateSliceStatus, insertSlice, getMilestone, refreshOpenDatabaseFromDisk, getCompletedMilestoneTaskFileHints, getMilestoneCommitAttributionShas, recordMilestoneCommitAttribution } from "./gsd-db.js";
16
16
  import { isValidationTerminal } from "./state.js";
17
17
  import { getErrorMessage } from "./error-utils.js";
18
18
  import { logWarning, logError } from "./workflow-logger.js";
@@ -110,17 +110,32 @@ export function hasImplementationArtifacts(basePath, milestoneId) {
110
110
  // milestone commits instead of treating the self-diff as proof of no work.
111
111
  if (changedFiles.length === 0) {
112
112
  if (milestoneId && currentBranch === integrationBranch) {
113
- const tagged = getChangedFilesFromMilestoneTaggedCommits(basePath, milestoneId);
114
- if (!tagged.ok)
113
+ const milestoneEvidence = getChangedFilesFromMilestoneEvidence(basePath, milestoneId);
114
+ if (!milestoneEvidence.ok)
115
115
  return "unknown";
116
- if (tagged.matched)
117
- return classifyImplementationFiles(tagged.files);
116
+ if (milestoneEvidence.matched)
117
+ return classifyImplementationFiles(milestoneEvidence.files);
118
118
  }
119
119
  if (currentBranch && currentBranch !== "HEAD")
120
120
  return "absent";
121
121
  return "unknown";
122
122
  }
123
- return classifyImplementationFiles(changedFiles);
123
+ const branchClassification = classifyImplementationFiles(changedFiles);
124
+ if (branchClassification === "present")
125
+ return "present";
126
+ // A completing milestone branch can have a non-empty diff containing only
127
+ // .gsd/ closeout files after implementation commits already landed on the
128
+ // recorded integration branch. In that topology, the branch diff alone is
129
+ // insufficient; use the same milestone-tagged evidence fallback as the
130
+ // self-diff retry path before declaring the milestone implementation-free.
131
+ if (milestoneId) {
132
+ const milestoneEvidence = getChangedFilesFromMilestoneEvidence(basePath, milestoneId);
133
+ if (!milestoneEvidence.ok)
134
+ return "unknown";
135
+ if (milestoneEvidence.matched)
136
+ return classifyImplementationFiles(milestoneEvidence.files);
137
+ }
138
+ return "absent";
124
139
  }
125
140
  catch (e) {
126
141
  // Non-fatal — if git operations fail, return unknown so callers can decide
@@ -148,6 +163,9 @@ function classifyImplementationFiles(files) {
148
163
  function isImplementationPath(file) {
149
164
  return !file.startsWith(".gsd/") && !file.startsWith(".gsd\\");
150
165
  }
166
+ function normalizeRepoPath(file) {
167
+ return file.trim().replace(/\\/g, "/").replace(/^\.\/+/, "");
168
+ }
151
169
  /**
152
170
  * Detect the main/master branch name.
153
171
  */
@@ -217,7 +235,7 @@ function getChangedFilesFromMilestoneTaggedCommits(basePath, milestoneId) {
217
235
  ]);
218
236
  if (!scoped.ok)
219
237
  return scoped;
220
- if (scoped.matched)
238
+ if (scoped.matched && classifyImplementationFiles(scoped.files) === "present")
221
239
  return scoped;
222
240
  // Fallback (#5033): when .gsd/ is gitignored / external / untracked, the
223
241
  // path-scoped scan matches no commits even though GSD-tagged commits
@@ -228,9 +246,137 @@ function getChangedFilesFromMilestoneTaggedCommits(basePath, milestoneId) {
228
246
  // Intentionally unbounded — symmetric with the primary scan, and avoids
229
247
  // reintroducing the rolling-depth failure class removed in #4699 where
230
248
  // milestone evidence aged out behind unrelated activity.
231
- return scanGsdTaggedCommits(basePath, milestoneId, [
249
+ const unscoped = scanGsdTaggedCommits(basePath, milestoneId, [
232
250
  "log", "--format=%H%x1f%B%x1e", "HEAD",
233
251
  ]);
252
+ if (!unscoped.ok)
253
+ return scoped.matched ? scoped : unscoped;
254
+ if (!unscoped.matched)
255
+ return scoped;
256
+ return {
257
+ ok: true,
258
+ matched: true,
259
+ files: [...new Set([...scoped.files, ...unscoped.files])],
260
+ };
261
+ }
262
+ function getChangedFilesFromMilestoneEvidence(basePath, milestoneId) {
263
+ const tagged = getChangedFilesFromMilestoneTaggedCommits(basePath, milestoneId);
264
+ if (!tagged.ok)
265
+ return tagged;
266
+ if (tagged.matched && classifyImplementationFiles(tagged.files) === "present")
267
+ return tagged;
268
+ const attributed = getChangedFilesFromAttributedMilestoneCommits(basePath, milestoneId);
269
+ if (!attributed.ok)
270
+ return tagged.matched ? tagged : attributed;
271
+ if (attributed.matched && classifyImplementationFiles(attributed.files) === "present")
272
+ return attributed;
273
+ const backfilled = backfillChangedFilesFromUntaggedMilestoneCommits(basePath, milestoneId);
274
+ if (!backfilled.ok)
275
+ return tagged.matched ? tagged : attributed.matched ? attributed : backfilled;
276
+ if (!backfilled.matched) {
277
+ if (tagged.matched)
278
+ return tagged;
279
+ return attributed.matched ? attributed : backfilled;
280
+ }
281
+ return {
282
+ ok: true,
283
+ matched: true,
284
+ files: [...new Set([...tagged.files, ...attributed.files, ...backfilled.files])],
285
+ };
286
+ }
287
+ function getChangedFilesFromAttributedMilestoneCommits(basePath, milestoneId) {
288
+ try {
289
+ const shas = getMilestoneCommitAttributionShas(milestoneId);
290
+ if (shas.length === 0)
291
+ return { ok: true, matched: false, files: [] };
292
+ const files = new Set();
293
+ let matched = false;
294
+ for (const sha of shas) {
295
+ if (!isFullCommitSha(sha))
296
+ continue;
297
+ const commitFiles = getChangedFilesForCommit(basePath, sha);
298
+ if (commitFiles.length === 0)
299
+ continue;
300
+ matched = true;
301
+ for (const file of commitFiles)
302
+ files.add(file);
303
+ }
304
+ return { ok: true, matched, files: [...files] };
305
+ }
306
+ catch (e) {
307
+ logWarning("recovery", `milestone attribution scan failed: ${e.message}`);
308
+ return { ok: false, matched: false, files: [] };
309
+ }
310
+ }
311
+ function backfillChangedFilesFromUntaggedMilestoneCommits(basePath, milestoneId) {
312
+ try {
313
+ const milestone = getMilestone(milestoneId);
314
+ const milestoneStartedAt = milestone?.created_at ? Math.floor(Date.parse(milestone.created_at) / 1000) * 1000 : NaN;
315
+ if (!Number.isFinite(milestoneStartedAt))
316
+ return { ok: true, matched: false, files: [] };
317
+ const taskFileHints = getCompletedMilestoneTaskFileHints(milestoneId);
318
+ if (taskFileHints.length === 0)
319
+ return { ok: true, matched: false, files: [] };
320
+ const hintSet = new Set(taskFileHints.map(normalizeRepoPath).filter(Boolean));
321
+ if (hintSet.size === 0)
322
+ return { ok: true, matched: false, files: [] };
323
+ const records = getCommitRecords(basePath);
324
+ const files = new Set();
325
+ let matched = false;
326
+ for (const record of records) {
327
+ if (!isFullCommitSha(record.hash))
328
+ continue;
329
+ if (Date.parse(record.committedAt) < milestoneStartedAt)
330
+ continue;
331
+ if (record.parents.trim().split(/\s+/).filter(Boolean).length > 1)
332
+ continue;
333
+ if (commitMessageHasGsdTrailer(record.message))
334
+ continue;
335
+ const commitFiles = getChangedFilesForCommit(basePath, record.hash);
336
+ const implementationFiles = commitFiles.map(normalizeRepoPath).filter(isImplementationPath);
337
+ if (implementationFiles.length === 0)
338
+ continue;
339
+ if (!implementationFiles.some((file) => hintSet.has(file)))
340
+ continue;
341
+ matched = true;
342
+ for (const file of implementationFiles)
343
+ files.add(file);
344
+ recordMilestoneCommitAttribution({
345
+ commitSha: record.hash,
346
+ milestoneId,
347
+ source: "backfill",
348
+ confidence: 0.8,
349
+ files: implementationFiles,
350
+ createdAt: new Date().toISOString(),
351
+ });
352
+ }
353
+ return { ok: true, matched, files: [...files] };
354
+ }
355
+ catch (e) {
356
+ logWarning("recovery", `milestone attribution backfill failed: ${e.message}`);
357
+ return { ok: false, matched: false, files: [] };
358
+ }
359
+ }
360
+ function getCommitRecords(basePath) {
361
+ const logOutput = execFileSync("git", ["log", "--format=%H%x1f%P%x1f%cI%x1f%B%x1e", "HEAD"], {
362
+ cwd: basePath,
363
+ stdio: ["ignore", "pipe", "pipe"],
364
+ encoding: "utf-8",
365
+ });
366
+ return logOutput
367
+ .split("\x1e")
368
+ .map((record) => record.trim())
369
+ .filter(Boolean)
370
+ .flatMap((record) => {
371
+ const parts = record.split("\x1f");
372
+ if (parts.length < 4)
373
+ return [];
374
+ const [hash, parents, committedAt, ...messageParts] = parts;
375
+ return [{ hash: hash.trim(), parents: parents.trim(), committedAt: committedAt.trim(), message: messageParts.join("\x1f") }];
376
+ });
377
+ }
378
+ function isFullCommitSha(value) {
379
+ return /^[0-9a-f]{40}$/i.test(value);
234
380
  }
235
381
  function scanGsdTaggedCommits(basePath, milestoneId, gitArgs) {
236
382
  try {
@@ -487,9 +487,8 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
487
487
  hasSurvivorBranch = false;
488
488
  }
489
489
  const effectivePrefs = loadEffectiveGSDPreferences(base)?.preferences;
490
- const deepProjectStagePending = !hasSurvivorBranch
491
- ? (await import("./auto-dispatch.js")).hasPendingDeepStage(effectivePrefs, base)
492
- : false;
490
+ const { shouldRunDeepProjectSetup } = await import("./auto-dispatch.js");
491
+ const deepProjectStagePending = shouldRunDeepProjectSetup(state, effectivePrefs, base, { hasSurvivorBranch });
493
492
  if (deepProjectStagePending) {
494
493
  // Deep project-level setup runs before the first milestone exists. Let
495
494
  // the auto loop dispatch workflow-preferences / project / requirements
@@ -225,8 +225,16 @@ function synthesizePausedSessionRecovery(basePath, unitType, unitId, sessionFile
225
225
  export function _synthesizePausedSessionRecoveryForTest(basePath, unitType, unitId, sessionFile) {
226
226
  return synthesizePausedSessionRecovery(basePath, unitType, unitId, sessionFile);
227
227
  }
228
+ const DETACHED_AUTO_KEEPALIVE_INTERVAL_MS = 30_000;
229
+ function withDetachedAutoKeepalive(run) {
230
+ const keepAlive = setInterval(() => { }, DETACHED_AUTO_KEEPALIVE_INTERVAL_MS);
231
+ return run.finally(() => {
232
+ clearInterval(keepAlive);
233
+ });
234
+ }
235
+ export const _withDetachedAutoKeepaliveForTest = withDetachedAutoKeepalive;
228
236
  export function startAutoDetached(ctx, pi, base, verboseMode, options) {
229
- void startAuto(ctx, pi, base, verboseMode, options).catch((err) => {
237
+ void withDetachedAutoKeepalive(startAuto(ctx, pi, base, verboseMode, options)).catch((err) => {
230
238
  const message = getErrorMessage(err);
231
239
  ctx.ui.notify(`Auto-start failed: ${message}`, "error");
232
240
  logWarning("engine", `auto start error: ${message}`, { file: "auto.ts" });
@@ -1,10 +1,11 @@
1
+ // GSD-2 + src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts - Handles provider and agent-end recovery for GSD auto-mode.
1
2
  import { logWarning } from "../workflow-logger.js";
2
3
  import { checkDeepProjectSetupAfterTurn, checkAutoStartAfterDiscuss, maybeHandleReadyPhraseWithoutFiles, maybeHandleEmptyIntentTurn, resetEmptyTurnCounter, } from "../guided-flow.js";
3
4
  import { clearPathCache } from "../paths.js";
4
5
  import { getAutoDashboardData, getAutoModeStartModel, isAutoActive, pauseAuto, setCurrentDispatchedModelId } from "../auto.js";
5
6
  import { getNextFallbackModel, resolveModelWithFallbacksForUnit } from "../preferences.js";
6
7
  import { pauseAutoForProviderError } from "../provider-error-pause.js";
7
- import { isSessionSwitchInFlight, resolveAgentEnd } from "../auto/resolve.js";
8
+ import { isSessionSwitchInFlight, resolveAgentEnd, resolveAgentEndCancelled } from "../auto/resolve.js";
8
9
  import { resolveModelId } from "../auto-model-selection.js";
9
10
  import { resolveProjectRoot } from "../worktree.js";
10
11
  import { clearDiscussionFlowState } from "./write-gate.js";
@@ -47,6 +48,11 @@ export function _buildAbortedPauseContext(lastMsg) {
47
48
  isTransient: true,
48
49
  };
49
50
  }
51
+ export function isUserInitiatedAbortMessage(message) {
52
+ if (!message)
53
+ return false;
54
+ return /\b(?:claude code process aborted by user|request aborted by user|process aborted by user)\b/i.test(message);
55
+ }
50
56
  async function pauseTransientWithBackoff(cls, pi, ctx, errorDetail, isRateLimit) {
51
57
  retryState.consecutiveTransientCount += 1;
52
58
  const baseRetryAfterMs = "retryAfterMs" in cls ? cls.retryAfterMs : 15_000;
@@ -150,6 +156,14 @@ export async function handleAgentEnd(pi, event, ctx) {
150
156
  // is in the assistant message text content. Fall back to content when
151
157
  // errorMessage looks uninformative.
152
158
  const rawErrorMsg = ("errorMessage" in lastMsg && lastMsg.errorMessage) ? String(lastMsg.errorMessage) : "";
159
+ if (isUserInitiatedAbortMessage(rawErrorMsg)) {
160
+ resolveAgentEndCancelled({
161
+ message: rawErrorMsg,
162
+ category: "aborted",
163
+ isTransient: false,
164
+ });
165
+ return;
166
+ }
153
167
  const isUseless = !rawErrorMsg || /^(success|ok|true|error|unknown)$/i.test(rawErrorMsg.trim());
154
168
  // #3588: When errorMessage is uninformative, extract the real error from
155
169
  // the assistant message text content for display purposes only.
@@ -2,7 +2,7 @@ import { join } from "node:path";
2
2
  import { isToolCallEventType } from "@gsd/pi-coding-agent";
3
3
  import { updateSnapshot } from "../ecosystem/gsd-extension-api.js";
4
4
  import { buildMilestoneFileName, resolveMilestonePath, resolveSliceFile, resolveSlicePath } from "../paths.js";
5
- import { canonicalToolName, clearDiscussionFlowState, isDepthConfirmationAnswer, isQueuePhaseActive, markApprovalGateVerified, markDepthVerified, resetWriteGateState, shouldBlockContextWrite, shouldBlockPlanningUnit, shouldBlockQueueExecution, isGateQuestionId, setPendingGate, clearPendingGate, getPendingGate, shouldBlockPendingGate, shouldBlockPendingGateBash, extractDepthVerificationMilestoneId } from "./write-gate.js";
5
+ import { canonicalToolName, clearDiscussionFlowState, isDepthConfirmationAnswer, isQueuePhaseActive, markApprovalGateVerified, markDepthVerified, resetWriteGateState, shouldBlockContextWrite, shouldBlockPlanningUnit, shouldBlockQueueExecution, shouldBlockWorktreeWrite, isGateQuestionId, setPendingGate, clearPendingGate, getPendingGate, shouldBlockPendingGate, shouldBlockPendingGateBash, extractDepthVerificationMilestoneId } from "./write-gate.js";
6
6
  import { resolveManifest } from "../unit-context-manifest.js";
7
7
  import { isBlockedStateFile, isBashWriteToStateFile, BLOCKED_WRITE_ERROR } from "../write-intercept.js";
8
8
  import { loadFile, saveFile, formatContinue } from "../files.js";
@@ -432,6 +432,16 @@ export function registerHooks(pi, ecosystemHandlers) {
432
432
  return planningGuard;
433
433
  }
434
434
  }
435
+ // ── Worktree-isolation write gate (#5199) ────────────────────────────
436
+ // Block planning-write tools from landing code at the project root when
437
+ // git.isolation=worktree but auto-mode hasn't created the milestone
438
+ // worktree yet. Without this, writes silently orphan outside git history.
439
+ if (isToolCallEventType("write", event) || isToolCallEventType("edit", event)) {
440
+ const wtBasePath = resolveWorktreeProjectRoot(dash.basePath ?? discussionBasePath);
441
+ const wtGuard = shouldBlockWorktreeWrite(event.toolName, event.input.path, wtBasePath, isAutoActive(), dash.currentUnit?.type);
442
+ if (wtGuard.block)
443
+ return wtGuard;
444
+ }
435
445
  // ── Single-writer engine: block direct writes to STATE.md ──────────
436
446
  // Covers write, edit, and bash tools to prevent bypass vectors.
437
447
  if (isToolCallEventType("write", event)) {
@@ -1,7 +1,9 @@
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
  import { minimatch } from "minimatch";
4
+ import { getIsolationMode } from "../preferences.js";
4
5
  import { logWarning } from "../workflow-logger.js";
6
+ import { isGsdWorktreePath, resolveWorktreeProjectRoot } from "../worktree-root.js";
5
7
  /**
6
8
  * Regex matching milestone CONTEXT.md file names in both legacy M001
7
9
  * and unique M001-abc123 formats. Exported so regex-hardening tests
@@ -728,3 +730,129 @@ export function shouldBlockPlanningUnit(toolName, pathOrCommand, basePath, unitT
728
730
  // avoids breaking gsd_* MCP tools or future safe additions.
729
731
  return { block: false };
730
732
  }
733
+ // ─── Worktree isolation write gate (#5199) ────────────────────────────────
734
+ //
735
+ // When `git.isolation: worktree` is configured, the per-unit commit pipeline
736
+ // only runs inside the auto-mode loop (`auto-post-unit.ts`). If the LLM
737
+ // authors code at the project root before auto-mode is started, those writes
738
+ // land in the working tree but never reach a commit — they're silently
739
+ // orphaned outside git history. This guard blocks those writes at the
740
+ // tool_call seam so the agent receives a clear error instead.
741
+ const WORKTREE_GATE_BOOTSTRAP_UNITS = new Set([
742
+ "discuss-milestone",
743
+ "plan-milestone",
744
+ "init",
745
+ ]);
746
+ function realpathOrResolve(p) {
747
+ const abs = resolve(p);
748
+ try {
749
+ return realpathSync(abs);
750
+ }
751
+ catch {
752
+ // Path doesn't exist (yet) — realpath the deepest existing ancestor so
753
+ // platforms where /tmp -> /private/tmp don't break containment checks.
754
+ let dir = abs;
755
+ const tail = [];
756
+ while (dir && dir !== resolve(dir, "..")) {
757
+ try {
758
+ const real = realpathSync(dir);
759
+ return tail.length ? join(real, ...tail.reverse()) : real;
760
+ }
761
+ catch {
762
+ const idx = dir.lastIndexOf(sep);
763
+ if (idx <= 0)
764
+ break;
765
+ tail.push(dir.slice(idx + 1));
766
+ dir = dir.slice(0, idx) || sep;
767
+ }
768
+ }
769
+ return abs;
770
+ }
771
+ }
772
+ function isPathContained(target, container) {
773
+ if (target === container)
774
+ return true;
775
+ return target.startsWith(container.endsWith(sep) ? container : container + sep);
776
+ }
777
+ /**
778
+ * Block planning-write tool calls that would land code at the project root
779
+ * while `git.isolation: worktree` is in effect and auto-mode hasn't created
780
+ * (or flipped cwd into) the milestone worktree.
781
+ *
782
+ * Pure / unit-testable. Callers in `register-hooks.ts` supply the resolved
783
+ * project root and current auto liveness; this function does no I/O beyond
784
+ * realpath resolution.
785
+ *
786
+ * Allow rules (in order):
787
+ * 1. Tool isn't a planning-write (write/edit/multi_edit/notebook_edit).
788
+ * 2. `GSD_DISABLE_WORKTREE_WRITE_GUARD=1` self-hosting bypass.
789
+ * 3. Isolation mode is not "worktree".
790
+ * 4. Active unit is a bootstrap unit (discuss-milestone/plan-milestone/init).
791
+ * 5. Target is inside `<projectRoot>/.gsd/worktrees/` (a real worktree).
792
+ * 6. Target is inside `<projectRoot>/.gsd/` and isn't masquerading as a
793
+ * worktrees sibling (rejects the `.gsd/worktrees-extra/…` prefix trick).
794
+ * 7. Auto is live AND `effectiveBasePath` is itself a `.gsd/worktrees/…` path.
795
+ *
796
+ * Otherwise: block with a message that points the agent at `/gsd` to start
797
+ * auto-mode.
798
+ */
799
+ export function shouldBlockWorktreeWrite(toolName, targetPath, effectiveBasePath, isAutoLive, currentUnitType) {
800
+ const tool = canonicalToolName(toolName);
801
+ if (!PLANNING_WRITE_TOOLS.has(tool))
802
+ return { block: false };
803
+ if (process.env.GSD_DISABLE_WORKTREE_WRITE_GUARD === "1")
804
+ return { block: false };
805
+ if (getIsolationMode(effectiveBasePath) !== "worktree")
806
+ return { block: false };
807
+ if (currentUnitType && WORKTREE_GATE_BOOTSTRAP_UNITS.has(currentUnitType))
808
+ return { block: false };
809
+ if (!targetPath) {
810
+ return {
811
+ block: true,
812
+ reason: [
813
+ `HARD BLOCK: ${tool} called with empty path while \`git.isolation: worktree\` is configured`,
814
+ `and auto-mode is not active. Refusing to allow writes that cannot be located.`,
815
+ ].join(" "),
816
+ };
817
+ }
818
+ // Resolve the target relative to the project root, then realpath to defeat
819
+ // symlink-based escapes and prefix tricks (e.g. .gsd/worktrees-extra/).
820
+ const projectRoot = resolveWorktreeProjectRoot(effectiveBasePath);
821
+ const absTarget = isAbsolute(targetPath) ? targetPath : resolve(projectRoot, targetPath);
822
+ const realTarget = realpathOrResolve(absTarget);
823
+ const realRoot = realpathOrResolve(projectRoot);
824
+ const realGsd = realpathOrResolve(join(projectRoot, ".gsd"));
825
+ const realWorktreesDir = realpathOrResolve(join(projectRoot, ".gsd", "worktrees"));
826
+ // Allow writes inside the legitimate worktrees subtree.
827
+ if (isPathContained(realTarget, realWorktreesDir))
828
+ return { block: false };
829
+ // Allow writes to .gsd/ planning artifacts, but reject siblings whose name
830
+ // starts with "worktrees" (the worktrees-extra prefix trick — case 4).
831
+ if (isPathContained(realTarget, realGsd)) {
832
+ const rel = relative(realGsd, realTarget);
833
+ const firstSeg = rel.split(/[\/\\]/)[0] ?? "";
834
+ if (!firstSeg.startsWith("worktrees"))
835
+ return { block: false };
836
+ // fall through: looks like worktrees<something> sibling — block
837
+ }
838
+ // Auto is live and the caller is operating inside a worktree path —
839
+ // host tool's write happens in worktree context; let it through.
840
+ if (isAutoLive && isGsdWorktreePath(effectiveBasePath))
841
+ return { block: false };
842
+ // Block. Provide enough context that the agent can self-correct.
843
+ const displayTarget = isPathContained(realTarget, realRoot)
844
+ ? relative(realRoot, realTarget) || "."
845
+ : realTarget;
846
+ return {
847
+ block: true,
848
+ reason: [
849
+ `HARD BLOCK: Worktree isolation is configured (\`git.isolation: worktree\`) but auto-mode is`,
850
+ `not running and the target "${displayTarget}" is not inside \`.gsd/worktrees/<MID>/\`.`,
851
+ `Code edits at the project root would be lost — only the auto-mode commit pipeline`,
852
+ `(auto-post-unit) commits work, and it never runs outside the loop.`,
853
+ `Required action: start auto-mode with \`/gsd\` so the milestone worktree is created,`,
854
+ `then write inside it. To disable this guard for self-hosting development, set`,
855
+ `GSD_DISABLE_WORKTREE_WRITE_GUARD=1.`,
856
+ ].join(" "),
857
+ };
858
+ }