@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
@@ -15,7 +15,7 @@
15
15
 
16
16
  import type { ExtensionContext, ExtensionAPI } from "@gsd/pi-coding-agent";
17
17
  import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
18
- import { gsdProjectionRoot, relMilestoneFile, resolveSliceFile, resolveSlicePath } from "./paths.js";
18
+ import { gsdProjectionRoot, legacyMilestonesDir, relMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath } from "./paths.js";
19
19
  import { resolveMilestoneValidationVerdict } from "./milestone-validation-verdict.js";
20
20
  import { resolveCanonicalMilestoneRoot } from "./worktree-manager.js";
21
21
  import { parseUnitId } from "./unit-id.js";
@@ -62,6 +62,30 @@ export interface VerificationContext {
62
62
  export type VerificationResult = "continue" | "retry" | "pause";
63
63
  type PauseAutoFn = (ctx?: ExtensionContext, pi?: ExtensionAPI, errorContext?: ErrorContext) => Promise<void>;
64
64
 
65
+ interface VerificationEvidenceLocation {
66
+ dir: string;
67
+ fileSliceId?: string;
68
+ }
69
+
70
+ function resolveVerificationEvidenceLocation(
71
+ basePath: string,
72
+ milestoneId: string,
73
+ sliceId: string,
74
+ ): VerificationEvidenceLocation | null {
75
+ const mDir = resolveMilestonePath(basePath, milestoneId);
76
+ if (!mDir) return null;
77
+
78
+ const legacyBase = legacyMilestonesDir(basePath);
79
+ const isLegacy = mDir.startsWith(legacyBase + "/") || mDir.startsWith(legacyBase + "\\");
80
+ if (!isLegacy) {
81
+ return { dir: mDir, fileSliceId: sliceId };
82
+ }
83
+
84
+ const sDir = resolveSlicePath(basePath, milestoneId, sliceId);
85
+ if (!sDir) return null;
86
+ return { dir: join(sDir, "tasks") };
87
+ }
88
+
65
89
  function getCurrentUnitCostStats(unitId: string): { unitCostUsd: number; rollingAvgUsd: number } {
66
90
  const ledger = getLedger();
67
91
  if (!ledger || !Array.isArray(ledger.units) || ledger.units.length === 0) {
@@ -551,11 +575,18 @@ export async function runPostUnitVerification(
551
575
  const taskAlreadyComplete = taskRow?.status === "complete" || taskRow?.status === "done";
552
576
  if (mid && sid && tid) {
553
577
  try {
554
- const sDir = resolveSlicePath(s.basePath, mid, sid);
555
- if (sDir) {
556
- const tasksDir = join(sDir, "tasks");
578
+ const evidenceLocation = resolveVerificationEvidenceLocation(s.basePath, mid, sid);
579
+ if (evidenceLocation) {
557
580
  if (result.passed) {
558
- writeVerificationJSON(result, tasksDir, tid, s.currentUnit.id);
581
+ writeVerificationJSON(
582
+ result,
583
+ evidenceLocation.dir,
584
+ tid,
585
+ s.currentUnit.id,
586
+ undefined,
587
+ undefined,
588
+ evidenceLocation.fileSliceId,
589
+ );
559
590
  } else {
560
591
  const nextAttempt = attempt + 1;
561
592
  const includeRetryMetadata =
@@ -566,11 +597,12 @@ export async function runPostUnitVerification(
566
597
  nextAttempt <= maxRetries;
567
598
  writeVerificationJSON(
568
599
  result,
569
- tasksDir,
600
+ evidenceLocation.dir,
570
601
  tid,
571
602
  s.currentUnit.id,
572
603
  includeRetryMetadata ? nextAttempt : undefined,
573
604
  includeRetryMetadata ? maxRetries : undefined,
605
+ evidenceLocation.fileSliceId,
574
606
  );
575
607
  }
576
608
  }
@@ -718,9 +750,8 @@ export async function runPostUnitVerification(
718
750
  // Re-write verification evidence JSON with post-execution checks
719
751
  if (postExecChecks && postExecChecks.length > 0 && mid && sid && tid) {
720
752
  try {
721
- const sDir = resolveSlicePath(s.basePath, mid, sid);
722
- if (sDir) {
723
- const tasksDir = join(sDir, "tasks");
753
+ const evidenceLocation = resolveVerificationEvidenceLocation(s.basePath, mid, sid);
754
+ if (evidenceLocation) {
724
755
  // Add postExecutionChecks to the result for the JSON write
725
756
  const resultWithPostExec = {
726
757
  ...result,
@@ -730,12 +761,13 @@ export async function runPostUnitVerification(
730
761
  // Manually write with postExecutionChecks field
731
762
  writeVerificationJSONWithPostExec(
732
763
  resultWithPostExec,
733
- tasksDir,
764
+ evidenceLocation.dir,
734
765
  tid,
735
766
  s.currentUnit.id,
736
767
  postExecChecks,
737
768
  postExecBlockingFailure ? attempt + 1 : undefined,
738
- postExecBlockingFailure ? maxRetries : undefined
769
+ postExecBlockingFailure ? maxRetries : undefined,
770
+ evidenceLocation.fileSliceId,
739
771
  );
740
772
  }
741
773
  } catch (evidenceErr) {
@@ -940,6 +972,7 @@ function writeVerificationJSONWithPostExec(
940
972
  postExecutionChecks: PostExecutionCheckJSON[],
941
973
  retryAttempt?: number,
942
974
  maxRetries?: number,
975
+ sliceId?: string,
943
976
  ): void {
944
977
  mkdirSync(tasksDir, { recursive: true });
945
978
 
@@ -980,6 +1013,7 @@ function writeVerificationJSONWithPostExec(
980
1013
  }));
981
1014
  }
982
1015
 
983
- const filePath = join(tasksDir, `${taskId}-VERIFY.json`);
1016
+ const fileName = sliceId ? `${sliceId}-${taskId}-VERIFY.json` : `${taskId}-VERIFY.json`;
1017
+ const filePath = join(tasksDir, fileName);
984
1018
  writeFileSync(filePath, JSON.stringify(evidence, null, 2) + "\n", "utf-8");
985
1019
  }
@@ -16,7 +16,7 @@ import {
16
16
  _isSamePath as isSamePath,
17
17
  _shouldReconcileWorktreeDb,
18
18
  } from "./auto-worktree-cleanup.js";
19
- import { resolveGsdPathContract } from "./paths.js";
19
+ import { dirIsContentBearingLegacyMilestone, resolveGsdPathContract } from "./paths.js";
20
20
  import type { MilestoneScope } from "./workspace.js";
21
21
  import { WorktreeStateProjection } from "./worktree-state-projection.js";
22
22
  import { logWarning } from "./workflow-logger.js";
@@ -211,10 +211,17 @@ function syncMilestoneLayout(
211
211
  if (!existsSync(mainMilestonesDir)) return;
212
212
 
213
213
  try {
214
- mkdirSync(wtMilestonesDir, { recursive: true });
215
214
  const mainMilestones = readdirSync(mainMilestonesDir, { withFileTypes: true })
216
215
  .filter((entry) => entry.isDirectory())
217
- .map((entry) => entry.name);
216
+ .map((entry) => entry.name)
217
+ .filter((name) =>
218
+ layoutSegment !== "milestones" ||
219
+ dirIsContentBearingLegacyMilestone(join(mainMilestonesDir, name)),
220
+ );
221
+
222
+ if (mainMilestones.length === 0) return;
223
+
224
+ mkdirSync(wtMilestonesDir, { recursive: true });
218
225
 
219
226
  for (const milestoneId of mainMilestones) {
220
227
  syncMilestoneDirectory(
@@ -3163,7 +3163,19 @@ export async function dispatchHookUnit(
3163
3163
  workspaceRoot: s.basePath,
3164
3164
  });
3165
3165
 
3166
- const result = await s.cmdCtx!.newSession({ workspaceRoot: s.basePath });
3166
+ const cmdCtx = s.cmdCtx;
3167
+ if (typeof cmdCtx?.newSession !== "function") {
3168
+ const message = "Auto-mode has no command context for dispatch.";
3169
+ ctx.ui.notify(message, "error");
3170
+ const unitToCommit = previousCurrentUnit ?? s.currentUnit;
3171
+ if (unitToCommit) {
3172
+ await autoCommitUnit(s.basePath, unitToCommit.type, unitToCommit.id, ctx);
3173
+ }
3174
+ await stopAuto(ctx, pi, message, { preserveWorktree: true });
3175
+ return false;
3176
+ }
3177
+
3178
+ const result = await cmdCtx.newSession({ workspaceRoot: s.basePath });
3167
3179
  if (result.cancelled) {
3168
3180
  await stopAuto(ctx, pi);
3169
3181
  return false;
@@ -7,7 +7,7 @@ import { minimatch } from "minimatch";
7
7
  import { GSD_PHASE_SCOPE_DISPLAY_REASON, shouldBlockAutoUnitToolCall } from "../auto-unit-tool-scope.js";
8
8
  import { canonicalToolName } from "../engine-hook-contract.js";
9
9
  import { loadJsonFileOrNull } from "../json-persistence.js";
10
- import { getIsolationMode } from "../preferences.js";
10
+ import { getIsolationMode, loadEffectiveGSDPreferences } from "../preferences.js";
11
11
  import { compileSubagentPermissionContract, type ToolsPolicy } from "../unit-context-manifest.js";
12
12
  import {
13
13
  allowedPlanningDispatchAgentsList,
@@ -1354,6 +1354,7 @@ export function shouldBlockPlanningUnit(
1354
1354
  const dispatchContract = compileSubagentPermissionContract(policy);
1355
1355
  const allowedSubagents = dispatchContract.allowedSubagents;
1356
1356
  const allowed = new Set(allowedSubagents);
1357
+ const planningSubagentRegistry = loadEffectiveGSDPreferences(basePath)?.preferences.planning_subagent_registry;
1357
1358
  // When agentClasses is undefined, the caller has not been updated to extract
1358
1359
  // agent identities yet. Block and warn so stale callers surface in telemetry
1359
1360
  // instead of silently bypassing the gate.
@@ -1371,12 +1372,12 @@ export function shouldBlockPlanningUnit(
1371
1372
  if (requested.length === 0) {
1372
1373
  return { block: false };
1373
1374
  }
1374
- const globallyDisallowed = requested.find(a => !isReadOnlyPlanningDispatchAgent(a));
1375
+ const globallyDisallowed = requested.find(a => !isReadOnlyPlanningDispatchAgent(a, planningSubagentRegistry));
1375
1376
  if (globallyDisallowed) {
1376
1377
  return planningBlock(
1377
1378
  unitType,
1378
1379
  policy.mode,
1379
- `subagent dispatch of "${globallyDisallowed}" not permitted; only read-only specialists (${allowedPlanningDispatchAgentsList()}) may be dispatched from ${policy.mode} units`,
1380
+ `subagent dispatch of "${globallyDisallowed}" not permitted; only read-only specialists (${allowedPlanningDispatchAgentsList(planningSubagentRegistry)}) may be dispatched from ${policy.mode} units`,
1380
1381
  );
1381
1382
  }
1382
1383
  const disallowedByPolicy = requested.find(a => !allowed.has(a));
@@ -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
  ```
@@ -32,6 +32,7 @@ import { parseRoadmapSlices } from "./roadmap-slices.js";
32
32
  import { parsePlan } from "./parsers-legacy.js";
33
33
  import { parseSummary } from "./files.js";
34
34
  import { LAYOUT_SEGMENTS } from "./layout-policy.js";
35
+ import { resolveCanonicalMilestoneRoot } from "./worktree-manager.js";
35
36
 
36
37
  const USER_AUTHORED_ARTIFACT_TYPES = new Set(["CONTEXT", "RESEARCH"]);
37
38
 
@@ -95,9 +96,29 @@ function reportCheckboxDbStatusDivergence(
95
96
  });
96
97
  }
97
98
 
99
+ function bareDuplicateMilestoneId(milestoneId: string): string | null {
100
+ const match = milestoneId.match(/^(M\d{3})-[a-z0-9]{6}$/i);
101
+ return match?.[1]?.toUpperCase() ?? null;
102
+ }
103
+
104
+ function checkboxDbStatusMilestoneIds(basePath: string, milestoneIds: string[]): string[] {
105
+ if (!hasFlatPhaseLayout(basePath)) return milestoneIds;
106
+
107
+ // Flat-phase directories are keyed by phase number. A restored
108
+ // M003-xxxxxx row alongside M003 is a stale duplicate for the same
109
+ // phases/03-* projection, so checking both can compare DB status against two
110
+ // competing PLAN files and report a persistent false divergence.
111
+ const allIds = new Set(milestoneIds.map((id) => id.toUpperCase()));
112
+ return milestoneIds.filter((milestoneId) => {
113
+ const bareId = bareDuplicateMilestoneId(milestoneId);
114
+ return !bareId || !allIds.has(bareId);
115
+ });
116
+ }
117
+
98
118
  function checkProjectionCheckboxDbStatus(basePath: string, milestoneIds: string[], issues: DoctorIssue[]): void {
99
119
  for (const milestoneId of milestoneIds) {
100
- const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
120
+ const artifactBasePath = resolveCanonicalMilestoneRoot(basePath, milestoneId);
121
+ const roadmapPath = resolveMilestoneFile(artifactBasePath, milestoneId, "ROADMAP");
101
122
  const slices = getMilestoneSlices(milestoneId);
102
123
 
103
124
  if (roadmapPath && existsSync(roadmapPath)) {
@@ -123,7 +144,7 @@ function checkProjectionCheckboxDbStatus(basePath: string, milestoneIds: string[
123
144
  }
124
145
 
125
146
  for (const slice of slices) {
126
- const planPath = resolveSliceFile(basePath, milestoneId, slice.id, "PLAN");
147
+ const planPath = resolveSliceFile(artifactBasePath, milestoneId, slice.id, "PLAN");
127
148
  if (!planPath || !existsSync(planPath)) continue;
128
149
  try {
129
150
  const plan = readFileSync(planPath, "utf-8");
@@ -173,7 +194,7 @@ function artifactExistsOnDisk(basePath: string, artifactPath: string, row?: Arti
173
194
  return resolveArtifactDiskPath(basePath, artifactPath, row) !== null;
174
195
  }
175
196
 
176
- function resolveLiteralArtifactDiskPath(basePath: string, artifactPath: string): string | null {
197
+ function resolveLiteralArtifactDiskPath(basePath: string, artifactPath: string, row?: ArtifactRow): string | null {
177
198
  const relativeArtifactPath = artifactPathRelativeToGsd(artifactPath);
178
199
  if (isAbsolute(relativeArtifactPath)) {
179
200
  return existsSync(relativeArtifactPath) ? relativeArtifactPath : null;
@@ -182,6 +203,15 @@ function resolveLiteralArtifactDiskPath(basePath: string, artifactPath: string):
182
203
  const candidate = join(root, relativeArtifactPath);
183
204
  if (isPathInside(root, candidate) && existsSync(candidate)) return candidate;
184
205
  }
206
+ if (row?.milestone_id) {
207
+ const artifactBasePath = resolveCanonicalMilestoneRoot(basePath, row.milestone_id);
208
+ if (artifactBasePath !== basePath) {
209
+ for (const root of [gsdProjectionRoot(artifactBasePath), gsdRoot(artifactBasePath)]) {
210
+ const candidate = join(root, relativeArtifactPath);
211
+ if (isPathInside(root, candidate) && existsSync(candidate)) return candidate;
212
+ }
213
+ }
214
+ }
185
215
  return null;
186
216
  }
187
217
 
@@ -224,7 +254,7 @@ function resolveArtifactDiskPath(basePath: string, artifactPath: string, row?: A
224
254
  const flatPath = resolveFlatPhaseArtifactDiskPath(basePath, row);
225
255
  if (flatPath) return flatPath;
226
256
  }
227
- return resolveLiteralArtifactDiskPath(basePath, artifactPath);
257
+ return resolveLiteralArtifactDiskPath(basePath, artifactPath, row);
228
258
  }
229
259
 
230
260
  function taskExistsInPlan(basePath: string, milestoneId: string, sliceId: string, taskId: string): boolean {
@@ -434,7 +464,9 @@ function staleMilestonesArtifactRowFixable(basePath: string, row: ArtifactRow, a
434
464
 
435
465
  function stalePhasesArtifactRowFixable(basePath: string, row: ArtifactRow, artifactRows: ArtifactRow[]): boolean {
436
466
  const issuePath = artifactPathRelativeToGsd(row.path);
437
- return issuePath.startsWith(`${LAYOUT_SEGMENTS.level1}/`) && hasPresentMilestonesReplacement(basePath, row, artifactRows);
467
+ return issuePath.startsWith(`${LAYOUT_SEGMENTS.level1}/`) &&
468
+ (hasPresentFlatPhaseReplacement(basePath, row, artifactRows) ||
469
+ hasPresentMilestonesReplacement(basePath, row, artifactRows));
438
470
  }
439
471
 
440
472
  function staleArtifactRowFixable(basePath: string, row: ArtifactRow, artifactRows: ArtifactRow[]): boolean {
@@ -807,7 +839,11 @@ export async function checkEngineHealth(
807
839
  // block doctor.
808
840
  try {
809
841
  if (isDbAvailable()) {
810
- checkProjectionCheckboxDbStatus(basePath, getAllMilestones().map((milestone) => milestone.id), issues);
842
+ checkProjectionCheckboxDbStatus(
843
+ basePath,
844
+ checkboxDbStatusMilestoneIds(basePath, getAllMilestones().map((milestone) => milestone.id)),
845
+ issues,
846
+ );
811
847
  }
812
848
  } catch {
813
849
  // Non-fatal: checkbox-vs-DB divergence check must never block doctor
@@ -155,6 +155,19 @@ function isInsideFlatProjectionBackup(backupDir: string, src: string): boolean {
155
155
  return src === phaseBackupDir || src.startsWith(`${phaseBackupDir}/`) || src.startsWith(`${phaseBackupDir}\\`);
156
156
  }
157
157
 
158
+ function removeFlatProjectionBackup(backupDir: string): void {
159
+ const phaseBackupDir = join(backupDir, "__phases");
160
+ if (!existsSync(phaseBackupDir)) return;
161
+ try {
162
+ removePathWithRetries(phaseBackupDir);
163
+ } catch (err) {
164
+ logWarning(
165
+ "migration",
166
+ `flat-phase migration succeeded but could not remove temporary ${LAYOUT_SEGMENTS.level1}/ backup: ${(err as Error).message}`,
167
+ );
168
+ }
169
+ }
170
+
158
171
  function restoreFlatProjectionFromBackup(basePath: string, backupDir: string): void {
159
172
  const phaseBackupDir = join(backupDir, "__phases");
160
173
  if (!existsSync(phaseBackupDir)) return;
@@ -494,4 +507,5 @@ export async function migrateToFlatPhase(basePath: string): Promise<void> {
494
507
  `flat-phase migration succeeded but could not remove ${LEGACY_MIGRATING_SEGMENT}: ${(err as Error).message}`,
495
508
  );
496
509
  }
510
+ removeFlatProjectionBackup(backupDir);
497
511
  }
@@ -67,6 +67,13 @@ export type { SliceRow, TaskRow } from "./db-task-slice-rows.js";
67
67
 
68
68
  import { TERMINAL_STATUS_SQL } from "./db/sql-constants.js";
69
69
  import { applyStatusTransition } from "./db/writers/status.js";
70
+ import {
71
+ LAYOUT_SEGMENTS,
72
+ derivePhaseSlug,
73
+ milestoneIdToPhaseNum,
74
+ milestoneIdUniqueSuffix,
75
+ phaseDirName,
76
+ } from "./layout-policy.js";
70
77
 
71
78
  export function insertDecision(d: Omit<Decision, "seq">): void {
72
79
  if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
@@ -206,6 +213,47 @@ export function insertArtifact(a: {
206
213
  });
207
214
  }
208
215
 
216
+ function canonicalPhaseDirNameForDb(milestoneId: string, title: string): string {
217
+ const phaseNum = milestoneIdToPhaseNum(milestoneId);
218
+ const slug = derivePhaseSlug(title || milestoneId);
219
+ const suffix = milestoneIdUniqueSuffix(milestoneId);
220
+ return phaseDirName(phaseNum, suffix ? `${suffix}-${slug}` : slug);
221
+ }
222
+
223
+ function reconcileMilestonePhaseArtifactPaths(milestoneId: string, title: string): void {
224
+ const db = getDbOrNull()!;
225
+ const phaseNumPrefix = String(milestoneIdToPhaseNum(milestoneId)).padStart(2, "0");
226
+ const canonicalDir = canonicalPhaseDirNameForDb(milestoneId, title);
227
+ const staleRows = db.prepare(
228
+ `SELECT path
229
+ FROM artifacts
230
+ WHERE milestone_id = :milestone_id
231
+ AND path LIKE :phase_prefix`,
232
+ ).all({
233
+ ":milestone_id": milestoneId,
234
+ ":phase_prefix": `${LAYOUT_SEGMENTS.level1}/${phaseNumPrefix}-%/%`,
235
+ }) as Array<{ path: string }>;
236
+
237
+ const updatePath = db.prepare("UPDATE artifacts SET path = :new_path WHERE path = :old_path");
238
+ const deletePath = db.prepare("DELETE FROM artifacts WHERE path = :path");
239
+ const existingPath = db.prepare("SELECT 1 AS present FROM artifacts WHERE path = :path");
240
+
241
+ for (const row of staleRows) {
242
+ const parts = row.path.split("/");
243
+ if (parts.length < 3) continue;
244
+ if (parts[0] !== LAYOUT_SEGMENTS.level1) continue;
245
+ if (parts[1] === canonicalDir) continue;
246
+ if (!parts[1]?.startsWith(`${phaseNumPrefix}-`)) continue;
247
+
248
+ const newPath = [LAYOUT_SEGMENTS.level1, canonicalDir, ...parts.slice(2)].join("/");
249
+ if (existingPath.get({ ":path": newPath })) {
250
+ deletePath.run({ ":path": row.path });
251
+ continue;
252
+ }
253
+ updatePath.run({ ":new_path": newPath, ":old_path": row.path });
254
+ }
255
+ }
256
+
209
257
  export interface MilestonePlanningRecord {
210
258
  vision: string;
211
259
  successCriteria: string[];
@@ -287,39 +335,43 @@ export function insertMilestone(m: {
287
335
 
288
336
  export function upsertMilestonePlanning(milestoneId: string, planning: Partial<MilestonePlanningRecord> & { title?: string; status?: string; depends_on?: string[] }): void {
289
337
  if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
290
- getDbOrNull()!.prepare(
291
- `UPDATE milestones SET
292
- title = COALESCE(NULLIF(:title, ''), title),
293
- status = COALESCE(NULLIF(:status, ''), status),
294
- depends_on = COALESCE(:depends_on, depends_on),
295
- vision = COALESCE(:vision, vision),
296
- success_criteria = COALESCE(:success_criteria, success_criteria),
297
- key_risks = COALESCE(:key_risks, key_risks),
298
- proof_strategy = COALESCE(:proof_strategy, proof_strategy),
299
- verification_contract = COALESCE(:verification_contract, verification_contract),
300
- verification_integration = COALESCE(:verification_integration, verification_integration),
301
- verification_operational = COALESCE(:verification_operational, verification_operational),
302
- verification_uat = COALESCE(:verification_uat, verification_uat),
303
- definition_of_done = COALESCE(:definition_of_done, definition_of_done),
304
- requirement_coverage = COALESCE(:requirement_coverage, requirement_coverage),
305
- boundary_map_markdown = COALESCE(:boundary_map_markdown, boundary_map_markdown)
306
- WHERE id = :id`,
307
- ).run({
308
- ":id": milestoneId,
309
- ":title": planning.title ?? "",
310
- ":status": planning.status ?? "",
311
- ":depends_on": planning.depends_on ? JSON.stringify(planning.depends_on) : null,
312
- ":vision": planning.vision ?? null,
313
- ":success_criteria": planning.successCriteria ? JSON.stringify(planning.successCriteria) : null,
314
- ":key_risks": planning.keyRisks ? JSON.stringify(planning.keyRisks) : null,
315
- ":proof_strategy": planning.proofStrategy ? JSON.stringify(planning.proofStrategy) : null,
316
- ":verification_contract": planning.verificationContract ?? null,
317
- ":verification_integration": planning.verificationIntegration ?? null,
318
- ":verification_operational": planning.verificationOperational ?? null,
319
- ":verification_uat": planning.verificationUat ?? null,
320
- ":definition_of_done": planning.definitionOfDone ? JSON.stringify(planning.definitionOfDone) : null,
321
- ":requirement_coverage": planning.requirementCoverage ?? null,
322
- ":boundary_map_markdown": planning.boundaryMapMarkdown ?? null,
338
+ transaction(() => {
339
+ getDbOrNull()!.prepare(
340
+ `UPDATE milestones SET
341
+ title = COALESCE(NULLIF(:title, ''), title),
342
+ status = COALESCE(NULLIF(:status, ''), status),
343
+ depends_on = COALESCE(:depends_on, depends_on),
344
+ vision = COALESCE(:vision, vision),
345
+ success_criteria = COALESCE(:success_criteria, success_criteria),
346
+ key_risks = COALESCE(:key_risks, key_risks),
347
+ proof_strategy = COALESCE(:proof_strategy, proof_strategy),
348
+ verification_contract = COALESCE(:verification_contract, verification_contract),
349
+ verification_integration = COALESCE(:verification_integration, verification_integration),
350
+ verification_operational = COALESCE(:verification_operational, verification_operational),
351
+ verification_uat = COALESCE(:verification_uat, verification_uat),
352
+ definition_of_done = COALESCE(:definition_of_done, definition_of_done),
353
+ requirement_coverage = COALESCE(:requirement_coverage, requirement_coverage),
354
+ boundary_map_markdown = COALESCE(:boundary_map_markdown, boundary_map_markdown)
355
+ WHERE id = :id`,
356
+ ).run({
357
+ ":id": milestoneId,
358
+ ":title": planning.title ?? "",
359
+ ":status": planning.status ?? "",
360
+ ":depends_on": planning.depends_on ? JSON.stringify(planning.depends_on) : null,
361
+ ":vision": planning.vision ?? null,
362
+ ":success_criteria": planning.successCriteria ? JSON.stringify(planning.successCriteria) : null,
363
+ ":key_risks": planning.keyRisks ? JSON.stringify(planning.keyRisks) : null,
364
+ ":proof_strategy": planning.proofStrategy ? JSON.stringify(planning.proofStrategy) : null,
365
+ ":verification_contract": planning.verificationContract ?? null,
366
+ ":verification_integration": planning.verificationIntegration ?? null,
367
+ ":verification_operational": planning.verificationOperational ?? null,
368
+ ":verification_uat": planning.verificationUat ?? null,
369
+ ":definition_of_done": planning.definitionOfDone ? JSON.stringify(planning.definitionOfDone) : null,
370
+ ":requirement_coverage": planning.requirementCoverage ?? null,
371
+ ":boundary_map_markdown": planning.boundaryMapMarkdown ?? null,
372
+ });
373
+ const finalTitle = planning.title?.trim();
374
+ if (finalTitle) reconcileMilestonePhaseArtifactPaths(milestoneId, finalTitle);
323
375
  });
324
376
  }
325
377
 
@@ -12,6 +12,12 @@ export interface ModelCostEntry {
12
12
  inputPer1k: number;
13
13
  /** Approximate cost per 1K output tokens in USD */
14
14
  outputPer1k: number;
15
+ /** Input-token-based long-context pricing tiers, when published */
16
+ tiers?: Array<{
17
+ inputTokensAbove: number;
18
+ inputPer1k: number;
19
+ outputPer1k: number;
20
+ }>;
15
21
  /** Last updated date */
16
22
  updatedAt: string;
17
23
  }
@@ -62,8 +68,9 @@ export const BUNDLED_COST_TABLE: ModelCostEntry[] = [
62
68
  // GPT-5.5 API list price, also used for live Codex OAuth routing.
63
69
  // Source: https://openai.com/api/pricing/
64
70
  { id: "gpt-5.5", inputPer1k: 0.005, outputPer1k: 0.03, updatedAt: "2026-04-23" },
65
- // Mirrors gpt-5.5 list pricing until OpenAI publishes official gpt-5.6 pricing.
66
- { id: "gpt-5.6", inputPer1k: 0.005, outputPer1k: 0.03, updatedAt: "2026-07-08" },
71
+ { id: "gpt-5.6-sol", inputPer1k: 0.005, outputPer1k: 0.03, tiers: [{ inputTokensAbove: 272000, inputPer1k: 0.01, outputPer1k: 0.045 }], updatedAt: "2026-07-11" },
72
+ { id: "gpt-5.6-terra", inputPer1k: 0.0025, outputPer1k: 0.015, tiers: [{ inputTokensAbove: 272000, inputPer1k: 0.005, outputPer1k: 0.0225 }], updatedAt: "2026-07-11" },
73
+ { id: "gpt-5.6-luna", inputPer1k: 0.001, outputPer1k: 0.006, tiers: [{ inputTokensAbove: 272000, inputPer1k: 0.002, outputPer1k: 0.009 }], updatedAt: "2026-07-11" },
67
74
 
68
75
  // Google
69
76
  { id: "gemini-2.0-flash", inputPer1k: 0.0001, outputPer1k: 0.0004, updatedAt: "2025-03-15" },
@@ -80,7 +87,11 @@ export const BUNDLED_COST_TABLE: ModelCostEntry[] = [
80
87
  export function lookupModelCost(modelId: string): ModelCostEntry | undefined {
81
88
  const bareId = modelId.includes("/") ? modelId.split("/").pop()! : modelId;
82
89
  return BUNDLED_COST_TABLE.find(e => e.id === bareId)
83
- ?? BUNDLED_COST_TABLE.find(e => bareId.includes(e.id) || e.id.includes(bareId));
90
+ ?? BUNDLED_COST_TABLE.find(e =>
91
+ bareId.startsWith(`${e.id}-`) ||
92
+ bareId.startsWith(`${e.id}:`) ||
93
+ bareId.startsWith(`${e.id}@`)
94
+ );
84
95
  }
85
96
 
86
97
  /**
@@ -111,7 +111,9 @@ export const MODEL_CAPABILITY_TIER: Record<string, ComplexityTier> = {
111
111
  "gpt-5.3-codex": "heavy",
112
112
  "gpt-5.4": "heavy",
113
113
  "gpt-5.5": "heavy",
114
- "gpt-5.6": "heavy",
114
+ "gpt-5.6-sol": "heavy",
115
+ "gpt-5.6-terra": "heavy",
116
+ "gpt-5.6-luna": "heavy",
115
117
  "o1": "heavy",
116
118
  "o3": "heavy",
117
119
  "o4-mini": "heavy",
@@ -151,7 +153,9 @@ const MODEL_COST_PER_1K_INPUT: Record<string, number> = {
151
153
  "gpt-5.3-codex-spark": 0.0003,
152
154
  "gpt-5.4": 0.005,
153
155
  "gpt-5.5": 0.005,
154
- "gpt-5.6": 0.005,
156
+ "gpt-5.6-sol": 0.005,
157
+ "gpt-5.6-terra": 0.0025,
158
+ "gpt-5.6-luna": 0.001,
155
159
  "o4-mini": 0.005,
156
160
  "o4-mini-deep-research": 0.005,
157
161
  "gemini-2.0-flash": 0.0001,
@@ -202,9 +206,10 @@ export const MODEL_CAPABILITY_PROFILES: Record<string, ModelCapabilities> = {
202
206
  // OpenAI's 2026-04-23 published eval deltas across coding, tool use, and long context.
203
207
  // Source: https://openai.com/index/introducing-gpt-5-5/
204
208
  "gpt-5.5": { coding: 96, debugging: 93, research: 89, reasoning: 95, speed: 42, longContext: 90, instruction: 93 },
205
- // GPT-5.6 profile is provisional: a small uniform step over gpt-5.5, to be
206
- // recalibrated once OpenAI publishes official eval results.
207
- "gpt-5.6": { coding: 97, debugging: 94, research: 90, reasoning: 96, speed: 42, longContext: 91, instruction: 94 },
209
+ // GPT-5.6 profiles are provisional until official eval results are available.
210
+ "gpt-5.6-sol": { coding: 97, debugging: 94, research: 90, reasoning: 96, speed: 42, longContext: 91, instruction: 94 },
211
+ "gpt-5.6-terra": { coding: 96, debugging: 93, research: 89, reasoning: 95, speed: 48, longContext: 91, instruction: 93 },
212
+ "gpt-5.6-luna": { coding: 92, debugging: 88, research: 84, reasoning: 91, speed: 72, longContext: 91, instruction: 90 },
208
213
 
209
214
  // ── OpenAI o-series (reasoning-first) ──────────────────────────────────────
210
215
  "o1": { coding: 78, debugging: 82, research: 78, reasoning: 90, speed: 20, longContext: 65, instruction: 82 },
@@ -6,6 +6,8 @@
6
6
  * preference validation before any configured planning allowlist takes effect.
7
7
  */
8
8
 
9
+ import type { PlanningSubagentRegistryConfig } from "./preferences-types.js";
10
+
9
11
  const PLANNING_DISPATCH_AGENT_REGISTRY = {
10
12
  mnemo: { readOnlySpecialist: true },
11
13
  scout: { readOnlySpecialist: true },
@@ -21,11 +23,23 @@ export const ALLOWED_PLANNING_DISPATCH_AGENTS = new Set<string>(
21
23
  .map(([agentId]) => agentId),
22
24
  );
23
25
 
24
- export function isReadOnlyPlanningDispatchAgent(agentId: string): boolean {
26
+ function configuredPlanningDispatchAgents(registry?: PlanningSubagentRegistryConfig): string[] {
27
+ return Object.entries(registry ?? {})
28
+ .filter(([, metadata]) => metadata.read_only_specialist === true)
29
+ .map(([agentId]) => agentId);
30
+ }
31
+
32
+ export function isReadOnlyPlanningDispatchAgent(
33
+ agentId: string,
34
+ registry?: PlanningSubagentRegistryConfig,
35
+ ): boolean {
25
36
  const metadata = PLANNING_DISPATCH_AGENT_REGISTRY[agentId as keyof typeof PLANNING_DISPATCH_AGENT_REGISTRY];
26
- return metadata?.readOnlySpecialist === true;
37
+ return metadata?.readOnlySpecialist === true || registry?.[agentId]?.read_only_specialist === true;
27
38
  }
28
39
 
29
- export function allowedPlanningDispatchAgentsList(): string {
30
- return [...ALLOWED_PLANNING_DISPATCH_AGENTS].join(", ");
40
+ export function allowedPlanningDispatchAgentsList(registry?: PlanningSubagentRegistryConfig): string {
41
+ return Array.from(new Set([
42
+ ...ALLOWED_PLANNING_DISPATCH_AGENTS,
43
+ ...configuredPlanningDispatchAgents(registry),
44
+ ])).join(", ");
31
45
  }