@opengsd/gsd-pi 1.9.0-dev.e10b60d4 → 1.10.0

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 (228) hide show
  1. package/README.md +10 -10
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/gsd/auto/loop.js +24 -4
  4. package/dist/resources/extensions/gsd/auto/run-unit.js +19 -1
  5. package/dist/resources/extensions/gsd/auto-start.js +29 -9
  6. package/dist/resources/extensions/gsd/auto-worktree-sync.js +7 -3
  7. package/dist/resources/extensions/gsd/auto.js +12 -1
  8. package/dist/resources/extensions/gsd/doctor-engine-checks.js +21 -2
  9. package/dist/resources/extensions/gsd/flat-phase-migration.js +12 -0
  10. package/dist/resources/extensions/gsd/gsd-db.js +75 -31
  11. package/dist/resources/extensions/gsd/worktree-state-projection.js +20 -18
  12. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  13. package/dist/web/standalone/.next/BUILD_ID +1 -1
  14. package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
  15. package/dist/web/standalone/.next/build-manifest.json +3 -3
  16. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  17. package/dist/web/standalone/.next/required-server-files.json +3 -3
  18. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  19. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  20. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  21. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  22. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  23. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  29. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  30. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  31. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  32. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  33. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  35. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  39. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  40. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  41. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  42. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  43. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  44. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  45. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  46. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  47. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  48. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  49. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  50. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  51. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  52. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  53. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  54. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  55. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  56. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  57. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  58. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  59. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  60. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  61. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  62. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  63. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  64. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  65. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  66. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  68. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  69. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  70. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  72. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  74. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  76. package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/mcp-connections/route_client-reference-manifest.js +1 -1
  78. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  79. package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
  80. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  89. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  95. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  111. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  115. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  124. package/dist/web/standalone/.next/server/app/index.html +1 -1
  125. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  126. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  127. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  128. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  129. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  130. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  131. package/dist/web/standalone/.next/server/app/page.js +2 -2
  132. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  133. package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
  134. package/dist/web/standalone/.next/server/chunks/1991.js +2 -2
  135. package/dist/web/standalone/.next/server/chunks/2842.js +4 -4
  136. package/dist/web/standalone/.next/server/chunks/5124.js +1 -1
  137. package/dist/web/standalone/.next/server/chunks/8357.js +3 -3
  138. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  139. package/dist/web/standalone/.next/server/middleware.js +3 -3
  140. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  141. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  142. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  143. package/dist/web/standalone/.next/static/chunks/app/_not-found/page-49f565245e1e4afe.js +1 -0
  144. package/dist/web/standalone/.next/static/chunks/app/{layout-4ae2d68984392bbf.js → layout-b35cbfff38aaf4cf.js} +1 -1
  145. package/dist/web/standalone/.next/static/chunks/app/page-bc4c723c6a19c6f0.js +1 -0
  146. package/dist/web/standalone/.next/static/chunks/{main-app-90d1d8d5e5d2dc6b.js → main-app-590a74400e35f685.js} +1 -1
  147. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ec3eaffc9785ba48.js +1 -0
  148. package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
  149. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  150. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  151. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  152. package/dist/web/standalone/server.js +1 -1
  153. package/integrations/hermes/open_gsd_hermes/gsd_client.py +1 -1
  154. package/integrations/hermes/pyproject.toml +1 -1
  155. package/package.json +6 -6
  156. package/packages/cloud-mcp-gateway/package.json +2 -2
  157. package/packages/contracts/package.json +1 -1
  158. package/packages/daemon/package.json +4 -4
  159. package/packages/gsd-agent-core/package.json +5 -5
  160. package/packages/gsd-agent-modes/package.json +7 -7
  161. package/packages/gsd-cloud/README.md +9 -3
  162. package/packages/gsd-cloud/dist/background-cli.test.d.ts +2 -0
  163. package/packages/gsd-cloud/dist/background-cli.test.d.ts.map +1 -0
  164. package/packages/gsd-cloud/dist/background-cli.test.js +248 -0
  165. package/packages/gsd-cloud/dist/background-cli.test.js.map +1 -0
  166. package/packages/gsd-cloud/dist/cli.d.ts.map +1 -1
  167. package/packages/gsd-cloud/dist/cli.js +121 -15
  168. package/packages/gsd-cloud/dist/cli.js.map +1 -1
  169. package/packages/gsd-cloud/dist/cloud-config.d.ts +1 -1
  170. package/packages/gsd-cloud/dist/cloud-config.d.ts.map +1 -1
  171. package/packages/gsd-cloud/dist/cloud-config.js +7 -1
  172. package/packages/gsd-cloud/dist/cloud-config.js.map +1 -1
  173. package/packages/gsd-cloud/dist/cloud-runtime.d.ts +1 -3
  174. package/packages/gsd-cloud/dist/cloud-runtime.d.ts.map +1 -1
  175. package/packages/gsd-cloud/dist/cloud-runtime.js +9 -7
  176. package/packages/gsd-cloud/dist/cloud-runtime.js.map +1 -1
  177. package/packages/gsd-cloud/dist/runtime-process.d.ts +27 -0
  178. package/packages/gsd-cloud/dist/runtime-process.d.ts.map +1 -0
  179. package/packages/gsd-cloud/dist/runtime-process.js +272 -0
  180. package/packages/gsd-cloud/dist/runtime-process.js.map +1 -0
  181. package/packages/gsd-cloud/dist/runtime-process.test.d.ts +2 -0
  182. package/packages/gsd-cloud/dist/runtime-process.test.d.ts.map +1 -0
  183. package/packages/gsd-cloud/dist/runtime-process.test.js +97 -0
  184. package/packages/gsd-cloud/dist/runtime-process.test.js.map +1 -0
  185. package/packages/gsd-cloud/package.json +2 -2
  186. package/packages/mcp-server/dist/cli-runner.d.ts +3 -0
  187. package/packages/mcp-server/dist/cli-runner.d.ts.map +1 -1
  188. package/packages/mcp-server/dist/cli-runner.js +119 -33
  189. package/packages/mcp-server/dist/cli-runner.js.map +1 -1
  190. package/packages/mcp-server/package.json +4 -4
  191. package/packages/native/package.json +1 -1
  192. package/packages/pi-agent-core/package.json +1 -1
  193. package/packages/pi-ai/dist/models.generated.d.ts +17 -0
  194. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  195. package/packages/pi-ai/dist/models.generated.js +17 -0
  196. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  197. package/packages/pi-ai/package.json +1 -1
  198. package/packages/pi-coding-agent/dist/core/skills.d.ts +5 -0
  199. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  200. package/packages/pi-coding-agent/dist/core/skills.js +54 -0
  201. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  202. package/packages/pi-coding-agent/package.json +7 -7
  203. package/packages/pi-tui/package.json +2 -2
  204. package/packages/rpc-client/package.json +2 -2
  205. package/pkg/package.json +1 -1
  206. package/src/resources/extensions/gsd/auto/loop.ts +48 -4
  207. package/src/resources/extensions/gsd/auto/run-unit.ts +20 -1
  208. package/src/resources/extensions/gsd/auto-start.ts +31 -8
  209. package/src/resources/extensions/gsd/auto-worktree-sync.ts +10 -3
  210. package/src/resources/extensions/gsd/auto.ts +13 -1
  211. package/src/resources/extensions/gsd/doctor-engine-checks.ts +27 -2
  212. package/src/resources/extensions/gsd/flat-phase-migration.ts +14 -0
  213. package/src/resources/extensions/gsd/gsd-db.ts +85 -33
  214. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +156 -0
  215. package/src/resources/extensions/gsd/tests/auto-start-index-lock.test.ts +21 -1
  216. package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +74 -1
  217. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +83 -0
  218. package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +8 -9
  219. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +32 -0
  220. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +13 -0
  221. package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +2 -2
  222. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +60 -0
  223. package/src/resources/extensions/gsd/worktree-state-projection.ts +21 -24
  224. package/dist/web/standalone/.next/static/chunks/app/_not-found/page-a6fb1847f67f167c.js +0 -1
  225. package/dist/web/standalone/.next/static/chunks/app/page-72a856634ad14c10.js +0 -1
  226. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-1115d46ac61b9823.js +0 -1
  227. /package/dist/web/standalone/.next/static/{V9MAVyYVPboQUB9KTTAMi → EAhnhYMt5UVOiG9-iPDjc}/_buildManifest.js +0 -0
  228. /package/dist/web/standalone/.next/static/{V9MAVyYVPboQUB9KTTAMi → EAhnhYMt5UVOiG9-iPDjc}/_ssgManifest.js +0 -0
package/README.md CHANGED
@@ -28,16 +28,16 @@ See [CHANGELOG.md](./CHANGELOG.md) for release-by-release fixes and [Legacy Rele
28
28
  ## Latest Release Highlights
29
29
 
30
30
  <!-- release-highlights:start -->
31
- Latest release: **v1.9.0**
32
-
33
- - **gsd:** Add task rework briefs.
34
- - **web:** Delete unused chart.tsx and recharts.
35
- - **deps:** Move ajv to devDependencies.
36
- - **daemon:** Dedupe @anthropic-ai/sdk to ^0.91.1.
37
- - **deps:** Override hono to >=4.12.25.
38
- - **gsd:** Batch plan-v2 task queries and skip unchanged graph writes.
39
- - **gsd:** Open the worker DB once per parallel-monitor tick.
40
- - **gsd:** Batch parallel-eligibility task queries.
31
+ Latest release: **v1.10.0**
32
+
33
+ - **pi-ai:** Add Meta Muse Spark 1.1 to the model catalog.
34
+ - **gsd:** Surface cacheRetention as a settable prompt-cache TTL preference.
35
+ - **models:** Add GPT-5.6 as a first-class model.
36
+ - **models:** Add Grok 4.5 as a first-class xAI model.
37
+ - **agent-core:** Pin compaction summarization reasoning to low, bound degenerate-chunk retry.
38
+ - **gsd:** Filter superseded context injections from provider payload.
39
+ - **gsd:** Stop advertising alias tool schemas on model-facing surfaces.
40
+ - **gsd:** Cap execute-task and discuss-slice inlined context; provider-aware budgets.
41
41
 
42
42
  <!-- release-highlights:end -->
43
43
 
@@ -1 +1 @@
1
- b54acda1c11b5a17
1
+ 5f0f1c788a126951
@@ -261,7 +261,22 @@ async function enforceMinRequestInterval(s, prefs) {
261
261
  await new Promise(r => setTimeout(r, decision.waitMs));
262
262
  }
263
263
  }
264
- function closeOutCrashedUnit(s, iterData, err) {
264
+ async function snapshotCrashedUnitWork(ctx, s, deps, iterData) {
265
+ try {
266
+ const commitMsg = await deps.autoCommitUnit?.(s.basePath, iterData.unitType, iterData.unitId, ctx);
267
+ if (commitMsg) {
268
+ debugLog("autoLoop", {
269
+ phase: "crash-snapshot-commit",
270
+ unitType: iterData.unitType,
271
+ unitId: iterData.unitId,
272
+ });
273
+ }
274
+ }
275
+ catch (snapshotErr) {
276
+ logWarning("dispatch", `unit crash snapshot failed: ${snapshotErr instanceof Error ? snapshotErr.message : String(snapshotErr)}`);
277
+ }
278
+ }
279
+ async function closeOutCrashedUnit(ctx, s, deps, iterData, err) {
265
280
  const summary = formatDispatchExceptionSummary({ error: err });
266
281
  try {
267
282
  emitOpenUnitEndForUnit(s.basePath, iterData.unitType, iterData.unitId, "cancelled", {
@@ -278,6 +293,7 @@ function closeOutCrashedUnit(s, iterData, err) {
278
293
  catch (closeoutErr) {
279
294
  logWarning("dispatch", `unit crash closeout failed: ${closeoutErr instanceof Error ? closeoutErr.message : String(closeoutErr)}`);
280
295
  }
296
+ await snapshotCrashedUnitWork(ctx, s, deps, iterData);
281
297
  }
282
298
  /**
283
299
  * Main auto-mode execution loop. Iterates: orchestration.advance → guards →
@@ -400,11 +416,15 @@ export async function autoLoop(ctx, pi, s, deps, options) {
400
416
  active: s.active,
401
417
  iteration,
402
418
  maxIterations: MAX_LOOP_ITERATIONS,
403
- hasCommandContext: Boolean(s.cmdCtx),
419
+ hasCommandContext: typeof s.cmdCtx?.newSession === "function",
404
420
  sessionLockValid: true,
405
421
  });
406
422
  if (commandContextDecision.action === "stop" && commandContextDecision.reason === "missing-command-context") {
407
423
  debugLog("autoLoop", { phase: "exit", reason: "no-cmdCtx" });
424
+ if (s.currentUnit) {
425
+ await deps.autoCommitUnit?.(s.basePath, s.currentUnit.type, s.currentUnit.id, ctx);
426
+ }
427
+ await deps.stopAuto(ctx, pi, commandContextDecision.message, { preserveWorktree: true });
408
428
  finishTurn("stopped", "manual-attention", commandContextDecision.reason);
409
429
  break;
410
430
  }
@@ -586,7 +606,7 @@ export async function autoLoop(ctx, pi, s, deps, options) {
586
606
  if (err instanceof ModelPolicyDispatchBlockedError) {
587
607
  throw err;
588
608
  }
589
- closeOutCrashedUnit(s, iterData, err);
609
+ await closeOutCrashedUnit(ctx, s, deps, iterData, err);
590
610
  throw err;
591
611
  }
592
612
  if (unitPhaseResult.action === "next") {
@@ -1109,7 +1129,7 @@ export async function autoLoop(ctx, pi, s, deps, options) {
1109
1129
  if (err instanceof ModelPolicyDispatchBlockedError) {
1110
1130
  throw err;
1111
1131
  }
1112
- closeOutCrashedUnit(s, iterData, err);
1132
+ await closeOutCrashedUnit(ctx, s, deps, iterData, err);
1113
1133
  dispatchSettled = settleDispatchFailed(dispatchId, formatDispatchExceptionSummary({ error: err }), {
1114
1134
  markFailed: markDispatchFailed,
1115
1135
  logWriteFailure: logDispatchLedgerWriteFailure,
@@ -58,6 +58,24 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
58
58
  debugLog("runUnit", { phase: "start", unitType, unitId });
59
59
  // ── Session creation with timeout ──
60
60
  debugLog("runUnit", { phase: "session-create", unitType, unitId });
61
+ const cmdCtx = s.cmdCtx;
62
+ if (typeof cmdCtx?.newSession !== "function") {
63
+ const msg = "command context is missing newSession";
64
+ debugLog("runUnit", {
65
+ phase: "session-error",
66
+ unitType,
67
+ unitId,
68
+ error: msg,
69
+ });
70
+ return {
71
+ status: "cancelled",
72
+ errorContext: {
73
+ message: `Session creation failed: ${msg}`,
74
+ category: "session-failed",
75
+ isTransient: false,
76
+ },
77
+ };
78
+ }
61
79
  let sessionResult;
62
80
  let sessionTimeoutHandle;
63
81
  const mySessionSwitchGeneration = ++sessionSwitchGeneration;
@@ -67,7 +85,7 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
67
85
  const sessionAbortController = new AbortController();
68
86
  _setSessionSwitchInFlight(true);
69
87
  try {
70
- const sessionPromise = s.cmdCtx.newSession({
88
+ const sessionPromise = cmdCtx.newSession({
71
89
  abortSignal: sessionAbortController.signal,
72
90
  workspaceRoot: s.basePath,
73
91
  }).finally(() => {
@@ -46,9 +46,10 @@ import { classifyMilestoneSummaryContent } from "./milestone-summary-classifier.
46
46
  import { extractVerdict } from "./verdict-parser.js";
47
47
  import { auditOrphanedPreflightStashes } from "./orphan-stash-audit.js";
48
48
  import { parseProject } from "./schemas/parsers.js";
49
+ import { LAYOUT_SEGMENTS } from "./layout-policy.js";
49
50
  import { debugLog, enableDebug, isDebugEnabled, getDebugLogPath, } from "./debug-logger.js";
50
51
  import { logWarning, logError } from "./workflow-logger.js";
51
- import { existsSync, mkdirSync, readFileSync, rmSync, } from "node:fs";
52
+ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, } from "node:fs";
52
53
  import { join } from "node:path";
53
54
  import { validateDirectory } from "./validate-directory.js";
54
55
  import { isCustomProvider, resolveDefaultSessionModel, resolveDynamicRoutingConfig, } from "./preferences-models.js";
@@ -73,6 +74,30 @@ const MAX_CONSECUTIVE_COMPLETE_BOOTSTRAPS = 2;
73
74
  export function hasGitIndexLockForTest(basePath) {
74
75
  return existsSync(join(basePath, ".git", "index.lock"));
75
76
  }
77
+ function isEmptyDirectory(dirPath) {
78
+ try {
79
+ return existsSync(dirPath) && readdirSync(dirPath).length === 0;
80
+ }
81
+ catch {
82
+ return false;
83
+ }
84
+ }
85
+ export function reconcileFlatPhaseBootstrapLayout(basePath) {
86
+ const gsdDir = join(basePath, ".gsd");
87
+ const phasesPath = join(gsdDir, LAYOUT_SEGMENTS.level1);
88
+ const milestonesPath = join(gsdDir, "milestones");
89
+ if (isEmptyDirectory(milestonesPath)) {
90
+ if (!existsSync(phasesPath))
91
+ mkdirSync(phasesPath, { recursive: true });
92
+ rmSync(milestonesPath, { recursive: true, force: true });
93
+ return true;
94
+ }
95
+ if (!existsSync(phasesPath) && !existsSync(milestonesPath)) {
96
+ mkdirSync(phasesPath, { recursive: true });
97
+ return true;
98
+ }
99
+ return false;
100
+ }
76
101
  export function _shouldAbortBootstrapForUnavailableDbForTest(gsdDbPath, dbAvailable, pathExists = existsSync) {
77
102
  return pathExists(gsdDbPath) && !dbAvailable;
78
103
  }
@@ -868,20 +893,15 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
868
893
  ensureGitignore(base, { manageGitignore });
869
894
  if (manageGitignore !== false)
870
895
  untrackRuntimeFiles(base);
871
- // Bootstrap milestones/ if it doesn't exist.
872
- // Check milestones/ directly — ensureGsdSymlink above already created .gsd/,
873
- // so checking .gsd/ existence would be dead code (#2942).
874
- const gsdDir = join(base, ".gsd");
875
- const milestonesPath = join(gsdDir, "milestones");
876
- if (!existsSync(milestonesPath)) {
877
- mkdirSync(milestonesPath, { recursive: true });
896
+ // Bootstrap the flat-phase projection root and clean empty legacy scaffolds.
897
+ if (reconcileFlatPhaseBootstrapLayout(base)) {
878
898
  try {
879
899
  nativeAddAll(base);
880
900
  nativeCommit(base, "chore: init gsd");
881
901
  }
882
902
  catch (err) {
883
903
  /* nothing to commit */
884
- logWarning("engine", `mkdir failed: ${err instanceof Error ? err.message : String(err)}`);
904
+ logWarning("engine", `layout bootstrap commit failed: ${err instanceof Error ? err.message : String(err)}`);
885
905
  }
886
906
  }
887
907
  {
@@ -5,7 +5,7 @@
5
5
  import { cpSync, existsSync, lstatSync as lstatSyncFn, mkdirSync, readdirSync, } from "node:fs";
6
6
  import { join } from "node:path";
7
7
  import { _isSamePath as isSamePath, } from "./auto-worktree-cleanup.js";
8
- import { resolveGsdPathContract } from "./paths.js";
8
+ import { dirIsContentBearingLegacyMilestone, resolveGsdPathContract } from "./paths.js";
9
9
  import { WorktreeStateProjection } from "./worktree-state-projection.js";
10
10
  import { logWarning } from "./workflow-logger.js";
11
11
  const PROJECT_PREFERENCES_FILE = "PREFERENCES.md";
@@ -143,10 +143,14 @@ function syncMilestoneLayout(mainGsd, wtGsd, layoutSegment, synced) {
143
143
  if (!existsSync(mainMilestonesDir))
144
144
  return;
145
145
  try {
146
- mkdirSync(wtMilestonesDir, { recursive: true });
147
146
  const mainMilestones = readdirSync(mainMilestonesDir, { withFileTypes: true })
148
147
  .filter((entry) => entry.isDirectory())
149
- .map((entry) => entry.name);
148
+ .map((entry) => entry.name)
149
+ .filter((name) => layoutSegment !== "milestones" ||
150
+ dirIsContentBearingLegacyMilestone(join(mainMilestonesDir, name)));
151
+ if (mainMilestones.length === 0)
152
+ return;
153
+ mkdirSync(wtMilestonesDir, { recursive: true });
150
154
  for (const milestoneId of mainMilestones) {
151
155
  syncMilestoneDirectory(mainMilestonesDir, wtMilestonesDir, milestoneId, synced);
152
156
  }
@@ -2440,7 +2440,18 @@ export async function dispatchHookUnit(ctx, pi, hookName, triggerUnitType, trigg
2440
2440
  startedAt: hookStartedAt,
2441
2441
  workspaceRoot: s.basePath,
2442
2442
  });
2443
- const result = await s.cmdCtx.newSession({ workspaceRoot: s.basePath });
2443
+ const cmdCtx = s.cmdCtx;
2444
+ if (typeof cmdCtx?.newSession !== "function") {
2445
+ const message = "Auto-mode has no command context for dispatch.";
2446
+ ctx.ui.notify(message, "error");
2447
+ const unitToCommit = previousCurrentUnit ?? s.currentUnit;
2448
+ if (unitToCommit) {
2449
+ await autoCommitUnit(s.basePath, unitToCommit.type, unitToCommit.id, ctx);
2450
+ }
2451
+ await stopAuto(ctx, pi, message, { preserveWorktree: true });
2452
+ return false;
2453
+ }
2454
+ const result = await cmdCtx.newSession({ workspaceRoot: s.basePath });
2444
2455
  if (result.cancelled) {
2445
2456
  await stopAuto(ctx, pi);
2446
2457
  return false;
@@ -58,6 +58,23 @@ function reportCheckboxDbStatusDivergence(issues, basePath, filePath, scope, uni
58
58
  fixable: false,
59
59
  });
60
60
  }
61
+ function bareDuplicateMilestoneId(milestoneId) {
62
+ const match = milestoneId.match(/^(M\d{3})-[a-z0-9]{6}$/i);
63
+ return match?.[1]?.toUpperCase() ?? null;
64
+ }
65
+ function checkboxDbStatusMilestoneIds(basePath, milestoneIds) {
66
+ if (!hasFlatPhaseLayout(basePath))
67
+ return milestoneIds;
68
+ // Flat-phase directories are keyed by phase number. A restored
69
+ // M003-xxxxxx row alongside M003 is a stale duplicate for the same
70
+ // phases/03-* projection, so checking both can compare DB status against two
71
+ // competing PLAN files and report a persistent false divergence.
72
+ const allIds = new Set(milestoneIds.map((id) => id.toUpperCase()));
73
+ return milestoneIds.filter((milestoneId) => {
74
+ const bareId = bareDuplicateMilestoneId(milestoneId);
75
+ return !bareId || !allIds.has(bareId);
76
+ });
77
+ }
61
78
  function checkProjectionCheckboxDbStatus(basePath, milestoneIds, issues) {
62
79
  for (const milestoneId of milestoneIds) {
63
80
  const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
@@ -322,7 +339,9 @@ function staleMilestonesArtifactRowFixable(basePath, row, artifactRows) {
322
339
  }
323
340
  function stalePhasesArtifactRowFixable(basePath, row, artifactRows) {
324
341
  const issuePath = artifactPathRelativeToGsd(row.path);
325
- return issuePath.startsWith(`${LAYOUT_SEGMENTS.level1}/`) && hasPresentMilestonesReplacement(basePath, row, artifactRows);
342
+ return issuePath.startsWith(`${LAYOUT_SEGMENTS.level1}/`) &&
343
+ (hasPresentFlatPhaseReplacement(basePath, row, artifactRows) ||
344
+ hasPresentMilestonesReplacement(basePath, row, artifactRows));
326
345
  }
327
346
  function staleArtifactRowFixable(basePath, row, artifactRows) {
328
347
  return staleMilestonesArtifactRowFixable(basePath, row, artifactRows) ||
@@ -658,7 +677,7 @@ export async function checkEngineHealth(basePath, issues, fixesApplied, options)
658
677
  // block doctor.
659
678
  try {
660
679
  if (isDbAvailable()) {
661
- checkProjectionCheckboxDbStatus(basePath, getAllMilestones().map((milestone) => milestone.id), issues);
680
+ checkProjectionCheckboxDbStatus(basePath, checkboxDbStatusMilestoneIds(basePath, getAllMilestones().map((milestone) => milestone.id)), issues);
662
681
  }
663
682
  }
664
683
  catch {
@@ -130,6 +130,17 @@ function isInsideFlatProjectionBackup(backupDir, src) {
130
130
  const phaseBackupDir = join(backupDir, "__phases");
131
131
  return src === phaseBackupDir || src.startsWith(`${phaseBackupDir}/`) || src.startsWith(`${phaseBackupDir}\\`);
132
132
  }
133
+ function removeFlatProjectionBackup(backupDir) {
134
+ const phaseBackupDir = join(backupDir, "__phases");
135
+ if (!existsSync(phaseBackupDir))
136
+ return;
137
+ try {
138
+ removePathWithRetries(phaseBackupDir);
139
+ }
140
+ catch (err) {
141
+ logWarning("migration", `flat-phase migration succeeded but could not remove temporary ${LAYOUT_SEGMENTS.level1}/ backup: ${err.message}`);
142
+ }
143
+ }
133
144
  function restoreFlatProjectionFromBackup(basePath, backupDir) {
134
145
  const phaseBackupDir = join(backupDir, "__phases");
135
146
  if (!existsSync(phaseBackupDir))
@@ -433,4 +444,5 @@ export async function migrateToFlatPhase(basePath) {
433
444
  catch (err) {
434
445
  logWarning("migration", `flat-phase migration succeeded but could not remove ${LEGACY_MIGRATING_SEGMENT}: ${err.message}`);
435
446
  }
447
+ removeFlatProjectionBackup(backupDir);
436
448
  }
@@ -38,6 +38,7 @@ export * from "./db/queries.js";
38
38
  export * from "./db/writers/cascades.js";
39
39
  import { TERMINAL_STATUS_SQL } from "./db/sql-constants.js";
40
40
  import { applyStatusTransition } from "./db/writers/status.js";
41
+ import { LAYOUT_SEGMENTS, derivePhaseSlug, milestoneIdToPhaseNum, milestoneIdUniqueSuffix, phaseDirName, } from "./layout-policy.js";
41
42
  export function insertDecision(d) {
42
43
  if (!getDbOrNull())
43
44
  throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
@@ -169,6 +170,44 @@ export function insertArtifact(a) {
169
170
  ":content_hash": contentHash,
170
171
  });
171
172
  }
173
+ function canonicalPhaseDirNameForDb(milestoneId, title) {
174
+ const phaseNum = milestoneIdToPhaseNum(milestoneId);
175
+ const slug = derivePhaseSlug(title || milestoneId);
176
+ const suffix = milestoneIdUniqueSuffix(milestoneId);
177
+ return phaseDirName(phaseNum, suffix ? `${suffix}-${slug}` : slug);
178
+ }
179
+ function reconcileMilestonePhaseArtifactPaths(milestoneId, title) {
180
+ const db = getDbOrNull();
181
+ const phaseNumPrefix = String(milestoneIdToPhaseNum(milestoneId)).padStart(2, "0");
182
+ const canonicalDir = canonicalPhaseDirNameForDb(milestoneId, title);
183
+ const staleRows = db.prepare(`SELECT path
184
+ FROM artifacts
185
+ WHERE milestone_id = :milestone_id
186
+ AND path LIKE :phase_prefix`).all({
187
+ ":milestone_id": milestoneId,
188
+ ":phase_prefix": `${LAYOUT_SEGMENTS.level1}/${phaseNumPrefix}-%/%`,
189
+ });
190
+ const updatePath = db.prepare("UPDATE artifacts SET path = :new_path WHERE path = :old_path");
191
+ const deletePath = db.prepare("DELETE FROM artifacts WHERE path = :path");
192
+ const existingPath = db.prepare("SELECT 1 AS present FROM artifacts WHERE path = :path");
193
+ for (const row of staleRows) {
194
+ const parts = row.path.split("/");
195
+ if (parts.length < 3)
196
+ continue;
197
+ if (parts[0] !== LAYOUT_SEGMENTS.level1)
198
+ continue;
199
+ if (parts[1] === canonicalDir)
200
+ continue;
201
+ if (!parts[1]?.startsWith(`${phaseNumPrefix}-`))
202
+ continue;
203
+ const newPath = [LAYOUT_SEGMENTS.level1, canonicalDir, ...parts.slice(2)].join("/");
204
+ if (existingPath.get({ ":path": newPath })) {
205
+ deletePath.run({ ":path": row.path });
206
+ continue;
207
+ }
208
+ updatePath.run({ ":new_path": newPath, ":old_path": row.path });
209
+ }
210
+ }
172
211
  export function insertMilestone(m) {
173
212
  if (!getDbOrNull())
174
213
  throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
@@ -207,37 +246,42 @@ export function insertMilestone(m) {
207
246
  export function upsertMilestonePlanning(milestoneId, planning) {
208
247
  if (!getDbOrNull())
209
248
  throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
210
- getDbOrNull().prepare(`UPDATE milestones SET
211
- title = COALESCE(NULLIF(:title, ''), title),
212
- status = COALESCE(NULLIF(:status, ''), status),
213
- depends_on = COALESCE(:depends_on, depends_on),
214
- vision = COALESCE(:vision, vision),
215
- success_criteria = COALESCE(:success_criteria, success_criteria),
216
- key_risks = COALESCE(:key_risks, key_risks),
217
- proof_strategy = COALESCE(:proof_strategy, proof_strategy),
218
- verification_contract = COALESCE(:verification_contract, verification_contract),
219
- verification_integration = COALESCE(:verification_integration, verification_integration),
220
- verification_operational = COALESCE(:verification_operational, verification_operational),
221
- verification_uat = COALESCE(:verification_uat, verification_uat),
222
- definition_of_done = COALESCE(:definition_of_done, definition_of_done),
223
- requirement_coverage = COALESCE(:requirement_coverage, requirement_coverage),
224
- boundary_map_markdown = COALESCE(:boundary_map_markdown, boundary_map_markdown)
225
- WHERE id = :id`).run({
226
- ":id": milestoneId,
227
- ":title": planning.title ?? "",
228
- ":status": planning.status ?? "",
229
- ":depends_on": planning.depends_on ? JSON.stringify(planning.depends_on) : null,
230
- ":vision": planning.vision ?? null,
231
- ":success_criteria": planning.successCriteria ? JSON.stringify(planning.successCriteria) : null,
232
- ":key_risks": planning.keyRisks ? JSON.stringify(planning.keyRisks) : null,
233
- ":proof_strategy": planning.proofStrategy ? JSON.stringify(planning.proofStrategy) : null,
234
- ":verification_contract": planning.verificationContract ?? null,
235
- ":verification_integration": planning.verificationIntegration ?? null,
236
- ":verification_operational": planning.verificationOperational ?? null,
237
- ":verification_uat": planning.verificationUat ?? null,
238
- ":definition_of_done": planning.definitionOfDone ? JSON.stringify(planning.definitionOfDone) : null,
239
- ":requirement_coverage": planning.requirementCoverage ?? null,
240
- ":boundary_map_markdown": planning.boundaryMapMarkdown ?? null,
249
+ transaction(() => {
250
+ getDbOrNull().prepare(`UPDATE milestones SET
251
+ title = COALESCE(NULLIF(:title, ''), title),
252
+ status = COALESCE(NULLIF(:status, ''), status),
253
+ depends_on = COALESCE(:depends_on, depends_on),
254
+ vision = COALESCE(:vision, vision),
255
+ success_criteria = COALESCE(:success_criteria, success_criteria),
256
+ key_risks = COALESCE(:key_risks, key_risks),
257
+ proof_strategy = COALESCE(:proof_strategy, proof_strategy),
258
+ verification_contract = COALESCE(:verification_contract, verification_contract),
259
+ verification_integration = COALESCE(:verification_integration, verification_integration),
260
+ verification_operational = COALESCE(:verification_operational, verification_operational),
261
+ verification_uat = COALESCE(:verification_uat, verification_uat),
262
+ definition_of_done = COALESCE(:definition_of_done, definition_of_done),
263
+ requirement_coverage = COALESCE(:requirement_coverage, requirement_coverage),
264
+ boundary_map_markdown = COALESCE(:boundary_map_markdown, boundary_map_markdown)
265
+ WHERE id = :id`).run({
266
+ ":id": milestoneId,
267
+ ":title": planning.title ?? "",
268
+ ":status": planning.status ?? "",
269
+ ":depends_on": planning.depends_on ? JSON.stringify(planning.depends_on) : null,
270
+ ":vision": planning.vision ?? null,
271
+ ":success_criteria": planning.successCriteria ? JSON.stringify(planning.successCriteria) : null,
272
+ ":key_risks": planning.keyRisks ? JSON.stringify(planning.keyRisks) : null,
273
+ ":proof_strategy": planning.proofStrategy ? JSON.stringify(planning.proofStrategy) : null,
274
+ ":verification_contract": planning.verificationContract ?? null,
275
+ ":verification_integration": planning.verificationIntegration ?? null,
276
+ ":verification_operational": planning.verificationOperational ?? null,
277
+ ":verification_uat": planning.verificationUat ?? null,
278
+ ":definition_of_done": planning.definitionOfDone ? JSON.stringify(planning.definitionOfDone) : null,
279
+ ":requirement_coverage": planning.requirementCoverage ?? null,
280
+ ":boundary_map_markdown": planning.boundaryMapMarkdown ?? null,
281
+ });
282
+ const finalTitle = planning.title?.trim();
283
+ if (finalTitle)
284
+ reconcileMilestonePhaseArtifactPaths(milestoneId, finalTitle);
241
285
  });
242
286
  }
243
287
  export function insertSlice(s) {
@@ -21,7 +21,7 @@
21
21
  import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, statSync, unlinkSync, } from "node:fs";
22
22
  import { join } from "node:path";
23
23
  import { reconcileWorktreeDb } from "./gsd-db.js";
24
- import { resolveGsdPathContract } from "./paths.js";
24
+ import { dirIsContentBearingLegacyMilestone, resolveGsdPathContract } from "./paths.js";
25
25
  import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
26
26
  import { logError, logWarning } from "./workflow-logger.js";
27
27
  // ─── Private helpers ──────────────────────────────────────────────────────
@@ -129,6 +129,8 @@ function syncOtherMilestoneArtifacts(srcMilestonesDir, dstMilestonesDir, current
129
129
  if (milestoneEntry.name === currentMilestoneId)
130
130
  continue;
131
131
  const srcMilestoneDir = join(srcMilestonesDir, milestoneEntry.name);
132
+ if (!dirIsContentBearingLegacyMilestone(srcMilestoneDir))
133
+ continue;
132
134
  const dstMilestoneDir = join(dstMilestonesDir, milestoneEntry.name);
133
135
  // Additively project the entire milestone subtree (force:false), not just
134
136
  // top-level files. Prior completed milestones keep their per-slice and
@@ -219,25 +221,25 @@ export function _projectRootToWorktreeImpl(projectRoot, worktreePath_, milestone
219
221
  // Without this, worktree-local files (e.g. VALIDATION.md written
220
222
  // by validate-milestone) get clobbered by stale project root copies,
221
223
  // causing an infinite re-validation loop (#1886).
222
- safeCopyRecursive(join(prGsd, "milestones", milestoneId), join(wtGsd, "milestones", milestoneId), { force: false });
223
- // Additively project the full subtree of every OTHER milestone so
224
- // worktree-bound units can read prior-milestone context artifacts and so
225
- // completed milestones retain their per-slice/per-task SUMMARY.md and
226
- // UAT.md on the worktree filesystem (otherwise the stale-render detector
227
- // flags them as "complete in DB but missing on disk").
228
- syncOtherMilestoneArtifacts(join(prGsd, "milestones"), join(wtGsd, "milestones"), milestoneId);
229
- // Force-sync ASSESSMENT files that have a verdict from project root (#2821).
230
- // The additive-only copy above preserves worktree-local files, but
231
- // ASSESSMENT files are special: after run-uat writes a verdict and post-unit
232
- // syncs it to the project root, the worktree may retain a stale copy (e.g.
233
- // verdict:fail while the project root has verdict:pass from a retry). On
234
- // session resume the DB is rebuilt from disk, and if the stale ASSESSMENT
235
- // persists, checkNeedsRunUat finds no passing verdict → re-dispatches
236
- // run-uat indefinitely (stuck-loop ×9).
237
224
  const prMilestoneDir = join(prGsd, "milestones", milestoneId);
238
225
  const wtMilestoneDir = join(wtGsd, "milestones", milestoneId);
239
- forceOverwriteValidationWithVerdict(prMilestoneDir, wtMilestoneDir, milestoneId);
240
- forceOverwriteAssessmentsWithVerdict(prMilestoneDir, wtMilestoneDir);
226
+ if (dirIsContentBearingLegacyMilestone(prMilestoneDir)) {
227
+ safeCopyRecursive(prMilestoneDir, wtMilestoneDir, { force: false });
228
+ // Force-sync ASSESSMENT files that have a verdict from project root (#2821).
229
+ // The additive-only copy above preserves worktree-local files, but
230
+ // ASSESSMENT files are special: after run-uat writes a verdict and post-unit
231
+ // syncs it to the project root, the worktree may retain a stale copy (e.g.
232
+ // verdict:fail while the project root has verdict:pass from a retry). On
233
+ // session resume the DB is rebuilt from disk, and if the stale ASSESSMENT
234
+ // persists, checkNeedsRunUat finds no passing verdict → re-dispatches
235
+ // run-uat indefinitely (stuck-loop ×9).
236
+ forceOverwriteValidationWithVerdict(prMilestoneDir, wtMilestoneDir, milestoneId);
237
+ forceOverwriteAssessmentsWithVerdict(prMilestoneDir, wtMilestoneDir);
238
+ }
239
+ // Additively project the full subtree of every OTHER content-bearing legacy
240
+ // milestone so worktree-bound units can read prior-milestone context artifacts
241
+ // without recreating empty/meta-only milestones/ scaffolds in flat-phase projects.
242
+ syncOtherMilestoneArtifacts(join(prGsd, "milestones"), join(wtGsd, "milestones"), milestoneId);
241
243
  // Forward-sync completed-units.json from project root to worktree.
242
244
  // Project root is authoritative for completion state after crash recovery;
243
245
  // without this, the worktree re-dispatches already-completed units (#1886).