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

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 (251) hide show
  1. package/README.md +10 -9
  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-model-selection.js +1 -0
  6. package/dist/resources/extensions/gsd/auto-start.js +30 -10
  7. package/dist/resources/extensions/gsd/auto-verification.js +25 -12
  8. package/dist/resources/extensions/gsd/auto-worktree-sync.js +7 -3
  9. package/dist/resources/extensions/gsd/auto.js +12 -1
  10. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +4 -3
  11. package/dist/resources/extensions/gsd/docs/preferences-reference.md +12 -3
  12. package/dist/resources/extensions/gsd/doctor-engine-checks.js +37 -6
  13. package/dist/resources/extensions/gsd/flat-phase-migration.js +12 -0
  14. package/dist/resources/extensions/gsd/gsd-db.js +75 -31
  15. package/dist/resources/extensions/gsd/model-cost-table.js +6 -3
  16. package/dist/resources/extensions/gsd/model-router.js +10 -5
  17. package/dist/resources/extensions/gsd/planning-subagent-registry.js +12 -4
  18. package/dist/resources/extensions/gsd/preferences-models.js +50 -10
  19. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  20. package/dist/resources/extensions/gsd/preferences-validation.js +41 -6
  21. package/dist/resources/extensions/gsd/preferences.js +7 -0
  22. package/dist/resources/extensions/gsd/templates/PREFERENCES.md +3 -0
  23. package/dist/resources/extensions/gsd/tools/plan-slice.js +5 -1
  24. package/dist/resources/extensions/gsd/verification-evidence.js +5 -3
  25. package/dist/resources/extensions/gsd/worktree-state-projection.js +20 -18
  26. package/dist/resources/extensions/ollama/ollama-chat-provider.js +2 -2
  27. package/dist/resources/extensions/subagent/index.js +1 -1
  28. package/dist/startup-model-validation.d.ts +1 -1
  29. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  30. package/dist/web/standalone/.next/BUILD_ID +1 -1
  31. package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
  32. package/dist/web/standalone/.next/build-manifest.json +2 -2
  33. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  34. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  35. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  43. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/index.html +1 -1
  51. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app-paths-manifest.json +6 -6
  58. package/dist/web/standalone/.next/server/chunks/1991.js +2 -2
  59. package/dist/web/standalone/.next/server/chunks/5124.js +1 -1
  60. package/dist/web/standalone/.next/server/chunks/8357.js +2 -2
  61. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  62. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  63. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  64. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  65. package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
  66. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  67. package/integrations/hermes/open_gsd_hermes/gsd_client.py +1 -1
  68. package/integrations/hermes/pyproject.toml +1 -1
  69. package/package.json +7 -6
  70. package/packages/cloud-mcp-gateway/package.json +2 -2
  71. package/packages/contracts/dist/rpc.d.ts +1 -1
  72. package/packages/contracts/dist/rpc.d.ts.map +1 -1
  73. package/packages/contracts/dist/rpc.js +1 -1
  74. package/packages/contracts/dist/rpc.js.map +1 -1
  75. package/packages/contracts/dist/rpc.test.js +1 -1
  76. package/packages/contracts/dist/rpc.test.js.map +1 -1
  77. package/packages/contracts/package.json +1 -1
  78. package/packages/daemon/package.json +4 -4
  79. package/packages/gsd-agent-core/package.json +5 -5
  80. package/packages/gsd-agent-modes/dist/cli/args.js +2 -2
  81. package/packages/gsd-agent-modes/dist/cli/args.js.map +1 -1
  82. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  83. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js +1 -0
  84. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js.map +1 -1
  85. package/packages/gsd-agent-modes/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  86. package/packages/gsd-agent-modes/dist/modes/interactive/components/thinking-selector.js +1 -0
  87. package/packages/gsd-agent-modes/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  88. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-autocomplete.d.ts.map +1 -1
  89. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-autocomplete.js +1 -0
  90. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-autocomplete.js.map +1 -1
  91. package/packages/gsd-agent-modes/package.json +7 -7
  92. package/packages/gsd-cloud/README.md +21 -5
  93. package/packages/gsd-cloud/dist/background-cli.test.d.ts +2 -0
  94. package/packages/gsd-cloud/dist/background-cli.test.d.ts.map +1 -0
  95. package/packages/gsd-cloud/dist/background-cli.test.js +325 -0
  96. package/packages/gsd-cloud/dist/background-cli.test.js.map +1 -0
  97. package/packages/gsd-cloud/dist/cli.d.ts.map +1 -1
  98. package/packages/gsd-cloud/dist/cli.js +146 -16
  99. package/packages/gsd-cloud/dist/cli.js.map +1 -1
  100. package/packages/gsd-cloud/dist/cloud-config.d.ts +1 -1
  101. package/packages/gsd-cloud/dist/cloud-config.d.ts.map +1 -1
  102. package/packages/gsd-cloud/dist/cloud-config.js +7 -1
  103. package/packages/gsd-cloud/dist/cloud-config.js.map +1 -1
  104. package/packages/gsd-cloud/dist/cloud-runtime.d.ts +7 -4
  105. package/packages/gsd-cloud/dist/cloud-runtime.d.ts.map +1 -1
  106. package/packages/gsd-cloud/dist/cloud-runtime.js +115 -31
  107. package/packages/gsd-cloud/dist/cloud-runtime.js.map +1 -1
  108. package/packages/gsd-cloud/dist/cloud-runtime.test.js +224 -59
  109. package/packages/gsd-cloud/dist/cloud-runtime.test.js.map +1 -1
  110. package/packages/gsd-cloud/dist/runtime-artifacts.d.ts +3 -0
  111. package/packages/gsd-cloud/dist/runtime-artifacts.d.ts.map +1 -0
  112. package/packages/gsd-cloud/dist/runtime-artifacts.js +36 -0
  113. package/packages/gsd-cloud/dist/runtime-artifacts.js.map +1 -0
  114. package/packages/gsd-cloud/dist/runtime-process.d.ts +32 -0
  115. package/packages/gsd-cloud/dist/runtime-process.d.ts.map +1 -0
  116. package/packages/gsd-cloud/dist/runtime-process.js +640 -0
  117. package/packages/gsd-cloud/dist/runtime-process.js.map +1 -0
  118. package/packages/gsd-cloud/dist/runtime-process.test.d.ts +2 -0
  119. package/packages/gsd-cloud/dist/runtime-process.test.d.ts.map +1 -0
  120. package/packages/gsd-cloud/dist/runtime-process.test.js +543 -0
  121. package/packages/gsd-cloud/dist/runtime-process.test.js.map +1 -0
  122. package/packages/gsd-cloud/dist/runtime-telemetry.d.ts +117 -0
  123. package/packages/gsd-cloud/dist/runtime-telemetry.d.ts.map +1 -0
  124. package/packages/gsd-cloud/dist/runtime-telemetry.js +291 -0
  125. package/packages/gsd-cloud/dist/runtime-telemetry.js.map +1 -0
  126. package/packages/gsd-cloud/dist/runtime-telemetry.test.d.ts +2 -0
  127. package/packages/gsd-cloud/dist/runtime-telemetry.test.d.ts.map +1 -0
  128. package/packages/gsd-cloud/dist/runtime-telemetry.test.js +434 -0
  129. package/packages/gsd-cloud/dist/runtime-telemetry.test.js.map +1 -0
  130. package/packages/gsd-cloud/package.json +2 -2
  131. package/packages/mcp-server/dist/cli-runner.d.ts +3 -0
  132. package/packages/mcp-server/dist/cli-runner.d.ts.map +1 -1
  133. package/packages/mcp-server/dist/cli-runner.js +119 -33
  134. package/packages/mcp-server/dist/cli-runner.js.map +1 -1
  135. package/packages/mcp-server/package.json +4 -4
  136. package/packages/native/package.json +1 -1
  137. package/packages/pi-agent-core/dist/types.d.ts +2 -2
  138. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  139. package/packages/pi-agent-core/dist/types.js.map +1 -1
  140. package/packages/pi-agent-core/package.json +1 -1
  141. package/packages/pi-ai/dist/models.d.ts.map +1 -1
  142. package/packages/pi-ai/dist/models.generated.d.ts +130 -2
  143. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  144. package/packages/pi-ai/dist/models.generated.js +101 -9
  145. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  146. package/packages/pi-ai/dist/models.js +18 -6
  147. package/packages/pi-ai/dist/models.js.map +1 -1
  148. package/packages/pi-ai/dist/providers/amazon-bedrock.js +3 -2
  149. package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
  150. package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts +1 -1
  151. package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
  152. package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
  153. package/packages/pi-ai/dist/providers/google-vertex.js.map +1 -1
  154. package/packages/pi-ai/dist/providers/google.js.map +1 -1
  155. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts +1 -1
  156. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
  157. package/packages/pi-ai/dist/providers/openai-codex-responses.js +1 -1
  158. package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
  159. package/packages/pi-ai/dist/providers/openai-completions.d.ts +1 -1
  160. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  161. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  162. package/packages/pi-ai/dist/providers/openai-responses.d.ts +1 -1
  163. package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  164. package/packages/pi-ai/dist/providers/openai-responses.js +1 -1
  165. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  166. package/packages/pi-ai/dist/providers/simple-options.d.ts +1 -1
  167. package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  168. package/packages/pi-ai/dist/providers/simple-options.js +1 -1
  169. package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
  170. package/packages/pi-ai/dist/types.d.ts +9 -1
  171. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  172. package/packages/pi-ai/dist/types.js.map +1 -1
  173. package/packages/pi-ai/package.json +1 -1
  174. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  175. package/packages/pi-coding-agent/dist/core/model-registry.js +1 -0
  176. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  177. package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -1
  178. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  179. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -3
  180. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  181. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  182. package/packages/pi-coding-agent/dist/core/skills.d.ts +5 -0
  183. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  184. package/packages/pi-coding-agent/dist/core/skills.js +54 -0
  185. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  186. package/packages/pi-coding-agent/dist/theme/theme.d.ts +1 -1
  187. package/packages/pi-coding-agent/dist/theme/theme.d.ts.map +1 -1
  188. package/packages/pi-coding-agent/dist/theme/theme.js +1 -0
  189. package/packages/pi-coding-agent/dist/theme/theme.js.map +1 -1
  190. package/packages/pi-coding-agent/package.json +7 -7
  191. package/packages/pi-tui/package.json +2 -2
  192. package/packages/rpc-client/package.json +2 -2
  193. package/pkg/dist/theme/theme.d.ts +1 -1
  194. package/pkg/dist/theme/theme.d.ts.map +1 -1
  195. package/pkg/dist/theme/theme.js +1 -0
  196. package/pkg/dist/theme/theme.js.map +1 -1
  197. package/pkg/package.json +1 -1
  198. package/src/resources/extensions/gsd/auto/loop.ts +48 -4
  199. package/src/resources/extensions/gsd/auto/run-unit.ts +20 -1
  200. package/src/resources/extensions/gsd/auto-model-selection.ts +1 -0
  201. package/src/resources/extensions/gsd/auto-start.ts +32 -9
  202. package/src/resources/extensions/gsd/auto-verification.ts +46 -12
  203. package/src/resources/extensions/gsd/auto-worktree-sync.ts +10 -3
  204. package/src/resources/extensions/gsd/auto.ts +13 -1
  205. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +4 -3
  206. package/src/resources/extensions/gsd/docs/preferences-reference.md +12 -3
  207. package/src/resources/extensions/gsd/doctor-engine-checks.ts +42 -6
  208. package/src/resources/extensions/gsd/flat-phase-migration.ts +14 -0
  209. package/src/resources/extensions/gsd/gsd-db.ts +85 -33
  210. package/src/resources/extensions/gsd/model-cost-table.ts +14 -3
  211. package/src/resources/extensions/gsd/model-router.ts +10 -5
  212. package/src/resources/extensions/gsd/planning-subagent-registry.ts +18 -4
  213. package/src/resources/extensions/gsd/preferences-models.ts +54 -10
  214. package/src/resources/extensions/gsd/preferences-types.ts +10 -2
  215. package/src/resources/extensions/gsd/preferences-validation.ts +49 -6
  216. package/src/resources/extensions/gsd/preferences.ts +13 -0
  217. package/src/resources/extensions/gsd/templates/PREFERENCES.md +3 -0
  218. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +156 -0
  219. package/src/resources/extensions/gsd/tests/auto-start-index-lock.test.ts +21 -1
  220. package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +74 -1
  221. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +147 -0
  222. package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +8 -9
  223. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +32 -0
  224. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +13 -0
  225. package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +22 -7
  226. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +55 -0
  227. package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
  228. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +24 -0
  229. package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +38 -1
  230. package/src/resources/extensions/gsd/tests/preferences.test.ts +71 -4
  231. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +3 -0
  232. package/src/resources/extensions/gsd/tests/service-tier.test.ts +2 -2
  233. package/src/resources/extensions/gsd/tests/thinking-level-resolution.test.ts +1 -1
  234. package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +12 -0
  235. package/src/resources/extensions/gsd/tests/workflow-authority-faults.test.ts +238 -0
  236. package/src/resources/extensions/gsd/tests/workflow-authority-fixture.test.ts +48 -0
  237. package/src/resources/extensions/gsd/tests/workflow-authority-fixture.ts +141 -0
  238. package/src/resources/extensions/gsd/tests/workflow-authority-projection-conflict.test.ts +177 -0
  239. package/src/resources/extensions/gsd/tests/workflow-fault-harness.test.ts +39 -0
  240. package/src/resources/extensions/gsd/tests/workflow-fault-harness.ts +72 -0
  241. package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +2 -2
  242. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +60 -0
  243. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +40 -0
  244. package/src/resources/extensions/gsd/tools/plan-slice.ts +5 -1
  245. package/src/resources/extensions/gsd/verification-evidence.ts +5 -2
  246. package/src/resources/extensions/gsd/worktree-state-projection.ts +21 -24
  247. package/src/resources/extensions/ollama/ollama-chat-provider.ts +2 -2
  248. package/src/resources/extensions/ollama/tests/ollama-chat-provider-think.test.ts +4 -0
  249. package/src/resources/extensions/subagent/index.ts +1 -1
  250. /package/dist/web/standalone/.next/static/{V9MAVyYVPboQUB9KTTAMi → xwUaOqui04jIIFVsIBLiL}/_buildManifest.js +0 -0
  251. /package/dist/web/standalone/.next/static/{V9MAVyYVPboQUB9KTTAMi → xwUaOqui04jIIFVsIBLiL}/_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**
31
+ Latest release: **v1.10.0**
32
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.
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-sol`, `gpt-5.6-terra`, and `gpt-5.6-luna` as first-class models.
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
 
@@ -194,6 +194,7 @@ Then use slash commands inside the GSD session:
194
194
  | --- | --- |
195
195
  | `src/` | Core runtime resources and bundled extensions |
196
196
  | `packages/` | Workspace packages used by the CLI, agent, TUI, RPC, and native bridge |
197
+ | `apps/` | Standalone native apps, such as the GSD Cloud Monitor menu bar app |
197
198
  | `native/` | Native engine packaging and platform binaries |
198
199
  | `studio/` | Desktop studio app |
199
200
  | `web/` | Web UI and API surface |
@@ -1 +1 @@
1
- b54acda1c11b5a17
1
+ d05c1e678c1d85d7
@@ -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(() => {
@@ -314,6 +314,7 @@ const THINKING_LEVEL_ORDER = [
314
314
  "medium",
315
315
  "high",
316
316
  "xhigh",
317
+ "max",
317
318
  ];
318
319
  /**
319
320
  * Minimum reasoning level for code-writing units.
@@ -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
  }
@@ -796,7 +821,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
796
821
  // provider the user is not logged into and surface as "Not logged in · Please
797
822
  // run /login" before pausing and resetting to claude-code/claude-sonnet-4-6.
798
823
  const manualSessionOverride = getSessionModelOverride(ctx.sessionManager.getSessionId());
799
- const sessionProviderIsCustom = isCustomProvider(ctx.model?.provider);
824
+ const sessionProviderIsCustom = isCustomProvider(ctx.model?.provider, ctx.modelRegistry);
800
825
  const profileModelIds = modelIdsForProfileResolution(ctx.modelRegistry, resolveProfileAnchorProvider(ctx.model?.provider), resolveDisabledModelProvidersFromPreferences());
801
826
  const preferredModel = sessionProviderIsCustom
802
827
  ? null
@@ -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
  {
@@ -1,7 +1,7 @@
1
1
  // Project/App: gsd-pi
2
2
  // File Purpose: Post-unit verification gate for GSD auto-mode units.
3
3
  import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
4
- import { relMilestoneFile, resolveSlicePath } from "./paths.js";
4
+ import { legacyMilestonesDir, relMilestoneFile, resolveMilestonePath, resolveSlicePath } from "./paths.js";
5
5
  import { resolveMilestoneValidationVerdict } from "./milestone-validation-verdict.js";
6
6
  import { resolveCanonicalMilestoneRoot } from "./worktree-manager.js";
7
7
  import { parseUnitId } from "./unit-id.js";
@@ -24,6 +24,20 @@ import { getLedger } from "./metrics.js";
24
24
  import { getUnitCostSpikeAction, resolveUnitCostSpikeMultiplier } from "./auto-budget.js";
25
25
  import { formatPostUnitStatusCard } from "./auto-status-message.js";
26
26
  import { detectWebApp } from "./web-app-uat.js";
27
+ function resolveVerificationEvidenceLocation(basePath, milestoneId, sliceId) {
28
+ const mDir = resolveMilestonePath(basePath, milestoneId);
29
+ if (!mDir)
30
+ return null;
31
+ const legacyBase = legacyMilestonesDir(basePath);
32
+ const isLegacy = mDir.startsWith(legacyBase + "/") || mDir.startsWith(legacyBase + "\\");
33
+ if (!isLegacy) {
34
+ return { dir: mDir, fileSliceId: sliceId };
35
+ }
36
+ const sDir = resolveSlicePath(basePath, milestoneId, sliceId);
37
+ if (!sDir)
38
+ return null;
39
+ return { dir: join(sDir, "tasks") };
40
+ }
27
41
  function getCurrentUnitCostStats(unitId) {
28
42
  const ledger = getLedger();
29
43
  if (!ledger || !Array.isArray(ledger.units) || ledger.units.length === 0) {
@@ -431,11 +445,10 @@ export async function runPostUnitVerification(vctx, pauseAuto) {
431
445
  const taskAlreadyComplete = taskRow?.status === "complete" || taskRow?.status === "done";
432
446
  if (mid && sid && tid) {
433
447
  try {
434
- const sDir = resolveSlicePath(s.basePath, mid, sid);
435
- if (sDir) {
436
- const tasksDir = join(sDir, "tasks");
448
+ const evidenceLocation = resolveVerificationEvidenceLocation(s.basePath, mid, sid);
449
+ if (evidenceLocation) {
437
450
  if (result.passed) {
438
- writeVerificationJSON(result, tasksDir, tid, s.currentUnit.id);
451
+ writeVerificationJSON(result, evidenceLocation.dir, tid, s.currentUnit.id, undefined, undefined, evidenceLocation.fileSliceId);
439
452
  }
440
453
  else {
441
454
  const nextAttempt = attempt + 1;
@@ -444,7 +457,7 @@ export async function runPostUnitVerification(vctx, pauseAuto) {
444
457
  verdict.retryable &&
445
458
  autoFixEnabled &&
446
459
  nextAttempt <= maxRetries;
447
- writeVerificationJSON(result, tasksDir, tid, s.currentUnit.id, includeRetryMetadata ? nextAttempt : undefined, includeRetryMetadata ? maxRetries : undefined);
460
+ writeVerificationJSON(result, evidenceLocation.dir, tid, s.currentUnit.id, includeRetryMetadata ? nextAttempt : undefined, includeRetryMetadata ? maxRetries : undefined, evidenceLocation.fileSliceId);
448
461
  }
449
462
  }
450
463
  }
@@ -560,9 +573,8 @@ export async function runPostUnitVerification(vctx, pauseAuto) {
560
573
  // Re-write verification evidence JSON with post-execution checks
561
574
  if (postExecChecks && postExecChecks.length > 0 && mid && sid && tid) {
562
575
  try {
563
- const sDir = resolveSlicePath(s.basePath, mid, sid);
564
- if (sDir) {
565
- const tasksDir = join(sDir, "tasks");
576
+ const evidenceLocation = resolveVerificationEvidenceLocation(s.basePath, mid, sid);
577
+ if (evidenceLocation) {
566
578
  // Add postExecutionChecks to the result for the JSON write
567
579
  const resultWithPostExec = {
568
580
  ...result,
@@ -570,7 +582,7 @@ export async function runPostUnitVerification(vctx, pauseAuto) {
570
582
  passed: result.passed && !postExecBlockingFailure,
571
583
  };
572
584
  // Manually write with postExecutionChecks field
573
- writeVerificationJSONWithPostExec(resultWithPostExec, tasksDir, tid, s.currentUnit.id, postExecChecks, postExecBlockingFailure ? attempt + 1 : undefined, postExecBlockingFailure ? maxRetries : undefined);
585
+ writeVerificationJSONWithPostExec(resultWithPostExec, evidenceLocation.dir, tid, s.currentUnit.id, postExecChecks, postExecBlockingFailure ? attempt + 1 : undefined, postExecBlockingFailure ? maxRetries : undefined, evidenceLocation.fileSliceId);
574
586
  }
575
587
  }
576
588
  catch (evidenceErr) {
@@ -738,7 +750,7 @@ export async function runPostUnitVerification(vctx, pauseAuto) {
738
750
  * Write verification evidence JSON with post-execution checks included.
739
751
  * This is a variant of writeVerificationJSON that adds the postExecutionChecks field.
740
752
  */
741
- function writeVerificationJSONWithPostExec(result, tasksDir, taskId, unitId, postExecutionChecks, retryAttempt, maxRetries) {
753
+ function writeVerificationJSONWithPostExec(result, tasksDir, taskId, unitId, postExecutionChecks, retryAttempt, maxRetries, sliceId) {
742
754
  mkdirSync(tasksDir, { recursive: true });
743
755
  const evidence = {
744
756
  schemaVersion: 1,
@@ -774,6 +786,7 @@ function writeVerificationJSONWithPostExec(result, tasksDir, taskId, unitId, pos
774
786
  fixAvailable: w.fixAvailable,
775
787
  }));
776
788
  }
777
- const filePath = join(tasksDir, `${taskId}-VERIFY.json`);
789
+ const fileName = sliceId ? `${sliceId}-${taskId}-VERIFY.json` : `${taskId}-VERIFY.json`;
790
+ const filePath = join(tasksDir, fileName);
778
791
  writeFileSync(filePath, JSON.stringify(evidence, null, 2) + "\n", "utf-8");
779
792
  }
@@ -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;
@@ -5,7 +5,7 @@ import { minimatch } from "minimatch";
5
5
  import { GSD_PHASE_SCOPE_DISPLAY_REASON, shouldBlockAutoUnitToolCall } from "../auto-unit-tool-scope.js";
6
6
  import { canonicalToolName } from "../engine-hook-contract.js";
7
7
  import { loadJsonFileOrNull } from "../json-persistence.js";
8
- import { getIsolationMode } from "../preferences.js";
8
+ import { getIsolationMode, loadEffectiveGSDPreferences } from "../preferences.js";
9
9
  import { compileSubagentPermissionContract } from "../unit-context-manifest.js";
10
10
  import { allowedPlanningDispatchAgentsList, isReadOnlyPlanningDispatchAgent, } from "../planning-subagent-registry.js";
11
11
  import { logWarning } from "../workflow-logger.js";
@@ -1118,6 +1118,7 @@ export function shouldBlockPlanningUnit(toolName, pathOrCommand, basePath, unitT
1118
1118
  const dispatchContract = compileSubagentPermissionContract(policy);
1119
1119
  const allowedSubagents = dispatchContract.allowedSubagents;
1120
1120
  const allowed = new Set(allowedSubagents);
1121
+ const planningSubagentRegistry = loadEffectiveGSDPreferences(basePath)?.preferences.planning_subagent_registry;
1121
1122
  // When agentClasses is undefined, the caller has not been updated to extract
1122
1123
  // agent identities yet. Block and warn so stale callers surface in telemetry
1123
1124
  // instead of silently bypassing the gate.
@@ -1131,9 +1132,9 @@ export function shouldBlockPlanningUnit(toolName, pathOrCommand, basePath, unitT
1131
1132
  if (requested.length === 0) {
1132
1133
  return { block: false };
1133
1134
  }
1134
- const globallyDisallowed = requested.find(a => !isReadOnlyPlanningDispatchAgent(a));
1135
+ const globallyDisallowed = requested.find(a => !isReadOnlyPlanningDispatchAgent(a, planningSubagentRegistry));
1135
1136
  if (globallyDisallowed) {
1136
- return planningBlock(unitType, policy.mode, `subagent dispatch of "${globallyDisallowed}" not permitted; only read-only specialists (${allowedPlanningDispatchAgentsList()}) may be dispatched from ${policy.mode} units`);
1137
+ return planningBlock(unitType, policy.mode, `subagent dispatch of "${globallyDisallowed}" not permitted; only read-only specialists (${allowedPlanningDispatchAgentsList(planningSubagentRegistry)}) may be dispatched from ${policy.mode} units`);
1137
1138
  }
1138
1139
  const disallowedByPolicy = requested.find(a => !allowed.has(a));
1139
1140
  if (disallowedByPolicy) {
@@ -124,11 +124,11 @@ Diagnostics record the file path, scope (global/project), severity (error/warnin
124
124
  - `validation` — used for gate evaluation, roadmap reassessment, milestone validation, and doc rewrites. Falls back to `planning` if unset.
125
125
  - `uat` — used for UAT runs. Falls back to `completion` if unset.
126
126
 
127
- - `thinking`: per-phase reasoning effort (ADR-026), separate from `models`. Same phase keys as `models`. Values: `off`, `minimal`, `low`, `medium`, `high`, `xhigh`. Thinking travels with the model — model choice and reasoning effort are independent controls, so you can run one model across phases at different reasoning levels.
127
+ - `thinking`: per-phase reasoning effort (ADR-026), separate from `models`. Same phase keys as `models`. Values: `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`. Thinking travels with the model — model choice and reasoning effort are independent controls, so you can run one model across phases at different reasoning levels.
128
128
  - Two equivalent ways to set it: inline as `models.<phase>.thinking`, or as a separate `thinking:` block keyed by phase. For the same phase, the inline value wins over the block; project preferences win over global; a phase that's unset inherits via the same sibling chain as `models` (e.g. `discuss → planning`).
129
129
  - If no thinking is configured for a phase, the session level (set via `/model`) is used, exactly as before — this is fully backward-compatible.
130
130
  - `execute-task` (code-writing) has a measured reasoning floor of `medium`: at lower levels models stop planning edits and thrash on file re-reads. The floor applies to the session/default path. An **explicit** `execution` thinking level bypasses the floor and is honored verbatim (with a one-time advisory) — set `execution: low` deliberately if you want it.
131
- - Levels a model can't support are clamped to the nearest supported level at dispatch and never sent to the provider, so a model/level mismatch never fails a unit mid-run. `xhigh` support is model-dependent; current examples include OpenAI Codex Max models and Claude Sonnet 5 on Amazon Bedrock.
131
+ - Levels a model can't support are clamped to the nearest supported level at dispatch and never sent to the provider, so a model/level mismatch never fails a unit mid-run. `xhigh` and `max` support are model-dependent; current examples include `gpt-5.6-sol`, `gpt-5.6-terra`, and `gpt-5.6-luna`, which advertise both.
132
132
 
133
133
  - `skill_staleness_days`: number — skills unused for this many days get deprioritized during discovery. Set to `0` to disable staleness tracking. Default: `60`.
134
134
 
@@ -357,24 +357,33 @@ In `"parent"` mode, slice/task `targetRepositories` default to the declared chil
357
357
  - `"replace"` requires `prompt`.
358
358
  - `"skip"` is valid with no additional fields.
359
359
 
360
+ - `planning_subagent_registry`: object — optional classification for custom planning agents. Each key is an agent id and must set:
361
+ - `read_only_specialist`: boolean — set to `true` to classify the custom agent as safe for planning dispatch.
362
+
360
363
  - `planning_subagents`: object — project-local widening for read-only planning subagent dispatch. Supported unit keys are `plan-milestone` and `plan-slice`; each has:
361
364
  - `allowed`: string[] — additional planning-safe agents for that unit.
362
365
 
363
- Agents must already be classified by GSD as read-only planning specialists: `mnemo`, `scout`, `planner`, `reviewer`, `security`, or `tester`. The write gate still blocks source writes outside `.gsd/**`, unrestricted bash, stale subagent calls without agent identities, agents outside the global read-only registry, and agents not listed for the active unit.
366
+ Agents must be built-in read-only planning specialists (`mnemo`, `scout`, `planner`, `reviewer`, `security`, or `tester`) or registered in `planning_subagent_registry` with `read_only_specialist: true`. The write gate still blocks source writes outside `.gsd/**`, unrestricted bash, stale subagent calls without agent identities, agents outside the read-only registry, and agents not listed for the active unit.
364
367
 
365
368
  Example:
366
369
 
367
370
  ```yaml
371
+ planning_subagent_registry:
372
+ my-custom-planner:
373
+ read_only_specialist: true
374
+
368
375
  planning_subagents:
369
376
  plan-milestone:
370
377
  allowed:
371
378
  - scout
372
379
  - planner
380
+ - my-custom-planner
373
381
  - security
374
382
  plan-slice:
375
383
  allowed:
376
384
  - scout
377
385
  - planner
386
+ - my-custom-planner
378
387
  - reviewer
379
388
  - security
380
389
  ```
@@ -13,6 +13,7 @@ import { parseRoadmapSlices } from "./roadmap-slices.js";
13
13
  import { parsePlan } from "./parsers-legacy.js";
14
14
  import { parseSummary } from "./files.js";
15
15
  import { LAYOUT_SEGMENTS } from "./layout-policy.js";
16
+ import { resolveCanonicalMilestoneRoot } from "./worktree-manager.js";
16
17
  const USER_AUTHORED_ARTIFACT_TYPES = new Set(["CONTEXT", "RESEARCH"]);
17
18
  function escapeRegExp(value) {
18
19
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -58,9 +59,27 @@ function reportCheckboxDbStatusDivergence(issues, basePath, filePath, scope, uni
58
59
  fixable: false,
59
60
  });
60
61
  }
62
+ function bareDuplicateMilestoneId(milestoneId) {
63
+ const match = milestoneId.match(/^(M\d{3})-[a-z0-9]{6}$/i);
64
+ return match?.[1]?.toUpperCase() ?? null;
65
+ }
66
+ function checkboxDbStatusMilestoneIds(basePath, milestoneIds) {
67
+ if (!hasFlatPhaseLayout(basePath))
68
+ return milestoneIds;
69
+ // Flat-phase directories are keyed by phase number. A restored
70
+ // M003-xxxxxx row alongside M003 is a stale duplicate for the same
71
+ // phases/03-* projection, so checking both can compare DB status against two
72
+ // competing PLAN files and report a persistent false divergence.
73
+ const allIds = new Set(milestoneIds.map((id) => id.toUpperCase()));
74
+ return milestoneIds.filter((milestoneId) => {
75
+ const bareId = bareDuplicateMilestoneId(milestoneId);
76
+ return !bareId || !allIds.has(bareId);
77
+ });
78
+ }
61
79
  function checkProjectionCheckboxDbStatus(basePath, milestoneIds, issues) {
62
80
  for (const milestoneId of milestoneIds) {
63
- const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
81
+ const artifactBasePath = resolveCanonicalMilestoneRoot(basePath, milestoneId);
82
+ const roadmapPath = resolveMilestoneFile(artifactBasePath, milestoneId, "ROADMAP");
64
83
  const slices = getMilestoneSlices(milestoneId);
65
84
  if (roadmapPath && existsSync(roadmapPath)) {
66
85
  try {
@@ -78,7 +97,7 @@ function checkProjectionCheckboxDbStatus(basePath, milestoneIds, issues) {
78
97
  }
79
98
  }
80
99
  for (const slice of slices) {
81
- const planPath = resolveSliceFile(basePath, milestoneId, slice.id, "PLAN");
100
+ const planPath = resolveSliceFile(artifactBasePath, milestoneId, slice.id, "PLAN");
82
101
  if (!planPath || !existsSync(planPath))
83
102
  continue;
84
103
  try {
@@ -117,7 +136,7 @@ function isClearedByMilestoneShellProjectionFlush(basePath, issue, reRenderedMil
117
136
  function artifactExistsOnDisk(basePath, artifactPath, row) {
118
137
  return resolveArtifactDiskPath(basePath, artifactPath, row) !== null;
119
138
  }
120
- function resolveLiteralArtifactDiskPath(basePath, artifactPath) {
139
+ function resolveLiteralArtifactDiskPath(basePath, artifactPath, row) {
121
140
  const relativeArtifactPath = artifactPathRelativeToGsd(artifactPath);
122
141
  if (isAbsolute(relativeArtifactPath)) {
123
142
  return existsSync(relativeArtifactPath) ? relativeArtifactPath : null;
@@ -127,6 +146,16 @@ function resolveLiteralArtifactDiskPath(basePath, artifactPath) {
127
146
  if (isPathInside(root, candidate) && existsSync(candidate))
128
147
  return candidate;
129
148
  }
149
+ if (row?.milestone_id) {
150
+ const artifactBasePath = resolveCanonicalMilestoneRoot(basePath, row.milestone_id);
151
+ if (artifactBasePath !== basePath) {
152
+ for (const root of [gsdProjectionRoot(artifactBasePath), gsdRoot(artifactBasePath)]) {
153
+ const candidate = join(root, relativeArtifactPath);
154
+ if (isPathInside(root, candidate) && existsSync(candidate))
155
+ return candidate;
156
+ }
157
+ }
158
+ }
130
159
  return null;
131
160
  }
132
161
  function relativeToGsdRoot(basePath, diskPath) {
@@ -166,7 +195,7 @@ function resolveArtifactDiskPath(basePath, artifactPath, row) {
166
195
  if (flatPath)
167
196
  return flatPath;
168
197
  }
169
- return resolveLiteralArtifactDiskPath(basePath, artifactPath);
198
+ return resolveLiteralArtifactDiskPath(basePath, artifactPath, row);
170
199
  }
171
200
  function taskExistsInPlan(basePath, milestoneId, sliceId, taskId) {
172
201
  const planPath = resolveSliceFile(basePath, milestoneId, sliceId, "PLAN");
@@ -322,7 +351,9 @@ function staleMilestonesArtifactRowFixable(basePath, row, artifactRows) {
322
351
  }
323
352
  function stalePhasesArtifactRowFixable(basePath, row, artifactRows) {
324
353
  const issuePath = artifactPathRelativeToGsd(row.path);
325
- return issuePath.startsWith(`${LAYOUT_SEGMENTS.level1}/`) && hasPresentMilestonesReplacement(basePath, row, artifactRows);
354
+ return issuePath.startsWith(`${LAYOUT_SEGMENTS.level1}/`) &&
355
+ (hasPresentFlatPhaseReplacement(basePath, row, artifactRows) ||
356
+ hasPresentMilestonesReplacement(basePath, row, artifactRows));
326
357
  }
327
358
  function staleArtifactRowFixable(basePath, row, artifactRows) {
328
359
  return staleMilestonesArtifactRowFixable(basePath, row, artifactRows) ||
@@ -658,7 +689,7 @@ export async function checkEngineHealth(basePath, issues, fixesApplied, options)
658
689
  // block doctor.
659
690
  try {
660
691
  if (isDbAvailable()) {
661
- checkProjectionCheckboxDbStatus(basePath, getAllMilestones().map((milestone) => milestone.id), issues);
692
+ checkProjectionCheckboxDbStatus(basePath, checkboxDbStatusMilestoneIds(basePath, getAllMilestones().map((milestone) => milestone.id)), issues);
662
693
  }
663
694
  }
664
695
  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
  }