@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
@@ -38,6 +38,7 @@ export * from "./db/queries.js";
38
38
  export * from "./db/writers/cascades.js";
39
39
  import { TERMINAL_STATUS_SQL } from "./db/sql-constants.js";
40
40
  import { applyStatusTransition } from "./db/writers/status.js";
41
+ import { LAYOUT_SEGMENTS, derivePhaseSlug, milestoneIdToPhaseNum, milestoneIdUniqueSuffix, phaseDirName, } from "./layout-policy.js";
41
42
  export function insertDecision(d) {
42
43
  if (!getDbOrNull())
43
44
  throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
@@ -169,6 +170,44 @@ export function insertArtifact(a) {
169
170
  ":content_hash": contentHash,
170
171
  });
171
172
  }
173
+ function canonicalPhaseDirNameForDb(milestoneId, title) {
174
+ const phaseNum = milestoneIdToPhaseNum(milestoneId);
175
+ const slug = derivePhaseSlug(title || milestoneId);
176
+ const suffix = milestoneIdUniqueSuffix(milestoneId);
177
+ return phaseDirName(phaseNum, suffix ? `${suffix}-${slug}` : slug);
178
+ }
179
+ function reconcileMilestonePhaseArtifactPaths(milestoneId, title) {
180
+ const db = getDbOrNull();
181
+ const phaseNumPrefix = String(milestoneIdToPhaseNum(milestoneId)).padStart(2, "0");
182
+ const canonicalDir = canonicalPhaseDirNameForDb(milestoneId, title);
183
+ const staleRows = db.prepare(`SELECT path
184
+ FROM artifacts
185
+ WHERE milestone_id = :milestone_id
186
+ AND path LIKE :phase_prefix`).all({
187
+ ":milestone_id": milestoneId,
188
+ ":phase_prefix": `${LAYOUT_SEGMENTS.level1}/${phaseNumPrefix}-%/%`,
189
+ });
190
+ const updatePath = db.prepare("UPDATE artifacts SET path = :new_path WHERE path = :old_path");
191
+ const deletePath = db.prepare("DELETE FROM artifacts WHERE path = :path");
192
+ const existingPath = db.prepare("SELECT 1 AS present FROM artifacts WHERE path = :path");
193
+ for (const row of staleRows) {
194
+ const parts = row.path.split("/");
195
+ if (parts.length < 3)
196
+ continue;
197
+ if (parts[0] !== LAYOUT_SEGMENTS.level1)
198
+ continue;
199
+ if (parts[1] === canonicalDir)
200
+ continue;
201
+ if (!parts[1]?.startsWith(`${phaseNumPrefix}-`))
202
+ continue;
203
+ const newPath = [LAYOUT_SEGMENTS.level1, canonicalDir, ...parts.slice(2)].join("/");
204
+ if (existingPath.get({ ":path": newPath })) {
205
+ deletePath.run({ ":path": row.path });
206
+ continue;
207
+ }
208
+ updatePath.run({ ":new_path": newPath, ":old_path": row.path });
209
+ }
210
+ }
172
211
  export function insertMilestone(m) {
173
212
  if (!getDbOrNull())
174
213
  throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
@@ -207,37 +246,42 @@ export function insertMilestone(m) {
207
246
  export function upsertMilestonePlanning(milestoneId, planning) {
208
247
  if (!getDbOrNull())
209
248
  throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
210
- getDbOrNull().prepare(`UPDATE milestones SET
211
- title = COALESCE(NULLIF(:title, ''), title),
212
- status = COALESCE(NULLIF(:status, ''), status),
213
- depends_on = COALESCE(:depends_on, depends_on),
214
- vision = COALESCE(:vision, vision),
215
- success_criteria = COALESCE(:success_criteria, success_criteria),
216
- key_risks = COALESCE(:key_risks, key_risks),
217
- proof_strategy = COALESCE(:proof_strategy, proof_strategy),
218
- verification_contract = COALESCE(:verification_contract, verification_contract),
219
- verification_integration = COALESCE(:verification_integration, verification_integration),
220
- verification_operational = COALESCE(:verification_operational, verification_operational),
221
- verification_uat = COALESCE(:verification_uat, verification_uat),
222
- definition_of_done = COALESCE(:definition_of_done, definition_of_done),
223
- requirement_coverage = COALESCE(:requirement_coverage, requirement_coverage),
224
- boundary_map_markdown = COALESCE(:boundary_map_markdown, boundary_map_markdown)
225
- WHERE id = :id`).run({
226
- ":id": milestoneId,
227
- ":title": planning.title ?? "",
228
- ":status": planning.status ?? "",
229
- ":depends_on": planning.depends_on ? JSON.stringify(planning.depends_on) : null,
230
- ":vision": planning.vision ?? null,
231
- ":success_criteria": planning.successCriteria ? JSON.stringify(planning.successCriteria) : null,
232
- ":key_risks": planning.keyRisks ? JSON.stringify(planning.keyRisks) : null,
233
- ":proof_strategy": planning.proofStrategy ? JSON.stringify(planning.proofStrategy) : null,
234
- ":verification_contract": planning.verificationContract ?? null,
235
- ":verification_integration": planning.verificationIntegration ?? null,
236
- ":verification_operational": planning.verificationOperational ?? null,
237
- ":verification_uat": planning.verificationUat ?? null,
238
- ":definition_of_done": planning.definitionOfDone ? JSON.stringify(planning.definitionOfDone) : null,
239
- ":requirement_coverage": planning.requirementCoverage ?? null,
240
- ":boundary_map_markdown": planning.boundaryMapMarkdown ?? null,
249
+ transaction(() => {
250
+ getDbOrNull().prepare(`UPDATE milestones SET
251
+ title = COALESCE(NULLIF(:title, ''), title),
252
+ status = COALESCE(NULLIF(:status, ''), status),
253
+ depends_on = COALESCE(:depends_on, depends_on),
254
+ vision = COALESCE(:vision, vision),
255
+ success_criteria = COALESCE(:success_criteria, success_criteria),
256
+ key_risks = COALESCE(:key_risks, key_risks),
257
+ proof_strategy = COALESCE(:proof_strategy, proof_strategy),
258
+ verification_contract = COALESCE(:verification_contract, verification_contract),
259
+ verification_integration = COALESCE(:verification_integration, verification_integration),
260
+ verification_operational = COALESCE(:verification_operational, verification_operational),
261
+ verification_uat = COALESCE(:verification_uat, verification_uat),
262
+ definition_of_done = COALESCE(:definition_of_done, definition_of_done),
263
+ requirement_coverage = COALESCE(:requirement_coverage, requirement_coverage),
264
+ boundary_map_markdown = COALESCE(:boundary_map_markdown, boundary_map_markdown)
265
+ WHERE id = :id`).run({
266
+ ":id": milestoneId,
267
+ ":title": planning.title ?? "",
268
+ ":status": planning.status ?? "",
269
+ ":depends_on": planning.depends_on ? JSON.stringify(planning.depends_on) : null,
270
+ ":vision": planning.vision ?? null,
271
+ ":success_criteria": planning.successCriteria ? JSON.stringify(planning.successCriteria) : null,
272
+ ":key_risks": planning.keyRisks ? JSON.stringify(planning.keyRisks) : null,
273
+ ":proof_strategy": planning.proofStrategy ? JSON.stringify(planning.proofStrategy) : null,
274
+ ":verification_contract": planning.verificationContract ?? null,
275
+ ":verification_integration": planning.verificationIntegration ?? null,
276
+ ":verification_operational": planning.verificationOperational ?? null,
277
+ ":verification_uat": planning.verificationUat ?? null,
278
+ ":definition_of_done": planning.definitionOfDone ? JSON.stringify(planning.definitionOfDone) : null,
279
+ ":requirement_coverage": planning.requirementCoverage ?? null,
280
+ ":boundary_map_markdown": planning.boundaryMapMarkdown ?? null,
281
+ });
282
+ const finalTitle = planning.title?.trim();
283
+ if (finalTitle)
284
+ reconcileMilestonePhaseArtifactPaths(milestoneId, finalTitle);
241
285
  });
242
286
  }
243
287
  export function insertSlice(s) {
@@ -48,8 +48,9 @@ export const BUNDLED_COST_TABLE = [
48
48
  // GPT-5.5 API list price, also used for live Codex OAuth routing.
49
49
  // Source: https://openai.com/api/pricing/
50
50
  { id: "gpt-5.5", inputPer1k: 0.005, outputPer1k: 0.03, updatedAt: "2026-04-23" },
51
- // Mirrors gpt-5.5 list pricing until OpenAI publishes official gpt-5.6 pricing.
52
- { id: "gpt-5.6", inputPer1k: 0.005, outputPer1k: 0.03, updatedAt: "2026-07-08" },
51
+ { id: "gpt-5.6-sol", inputPer1k: 0.005, outputPer1k: 0.03, tiers: [{ inputTokensAbove: 272000, inputPer1k: 0.01, outputPer1k: 0.045 }], updatedAt: "2026-07-11" },
52
+ { id: "gpt-5.6-terra", inputPer1k: 0.0025, outputPer1k: 0.015, tiers: [{ inputTokensAbove: 272000, inputPer1k: 0.005, outputPer1k: 0.0225 }], updatedAt: "2026-07-11" },
53
+ { id: "gpt-5.6-luna", inputPer1k: 0.001, outputPer1k: 0.006, tiers: [{ inputTokensAbove: 272000, inputPer1k: 0.002, outputPer1k: 0.009 }], updatedAt: "2026-07-11" },
53
54
  // Google
54
55
  { id: "gemini-2.0-flash", inputPer1k: 0.0001, outputPer1k: 0.0004, updatedAt: "2025-03-15" },
55
56
  { id: "gemini-flash-2.0", inputPer1k: 0.0001, outputPer1k: 0.0004, updatedAt: "2025-03-15" },
@@ -63,7 +64,9 @@ export const BUNDLED_COST_TABLE = [
63
64
  export function lookupModelCost(modelId) {
64
65
  const bareId = modelId.includes("/") ? modelId.split("/").pop() : modelId;
65
66
  return BUNDLED_COST_TABLE.find(e => e.id === bareId)
66
- ?? BUNDLED_COST_TABLE.find(e => bareId.includes(e.id) || e.id.includes(bareId));
67
+ ?? BUNDLED_COST_TABLE.find(e => bareId.startsWith(`${e.id}-`) ||
68
+ bareId.startsWith(`${e.id}:`) ||
69
+ bareId.startsWith(`${e.id}@`));
67
70
  }
68
71
  /**
69
72
  * Compare two models by input cost. Returns negative if a is cheaper.
@@ -46,7 +46,9 @@ export const MODEL_CAPABILITY_TIER = {
46
46
  "gpt-5.3-codex": "heavy",
47
47
  "gpt-5.4": "heavy",
48
48
  "gpt-5.5": "heavy",
49
- "gpt-5.6": "heavy",
49
+ "gpt-5.6-sol": "heavy",
50
+ "gpt-5.6-terra": "heavy",
51
+ "gpt-5.6-luna": "heavy",
50
52
  "o1": "heavy",
51
53
  "o3": "heavy",
52
54
  "o4-mini": "heavy",
@@ -84,7 +86,9 @@ const MODEL_COST_PER_1K_INPUT = {
84
86
  "gpt-5.3-codex-spark": 0.0003,
85
87
  "gpt-5.4": 0.005,
86
88
  "gpt-5.5": 0.005,
87
- "gpt-5.6": 0.005,
89
+ "gpt-5.6-sol": 0.005,
90
+ "gpt-5.6-terra": 0.0025,
91
+ "gpt-5.6-luna": 0.001,
88
92
  "o4-mini": 0.005,
89
93
  "o4-mini-deep-research": 0.005,
90
94
  "gemini-2.0-flash": 0.0001,
@@ -132,9 +136,10 @@ export const MODEL_CAPABILITY_PROFILES = {
132
136
  // OpenAI's 2026-04-23 published eval deltas across coding, tool use, and long context.
133
137
  // Source: https://openai.com/index/introducing-gpt-5-5/
134
138
  "gpt-5.5": { coding: 96, debugging: 93, research: 89, reasoning: 95, speed: 42, longContext: 90, instruction: 93 },
135
- // GPT-5.6 profile is provisional: a small uniform step over gpt-5.5, to be
136
- // recalibrated once OpenAI publishes official eval results.
137
- "gpt-5.6": { coding: 97, debugging: 94, research: 90, reasoning: 96, speed: 42, longContext: 91, instruction: 94 },
139
+ // GPT-5.6 profiles are provisional until official eval results are available.
140
+ "gpt-5.6-sol": { coding: 97, debugging: 94, research: 90, reasoning: 96, speed: 42, longContext: 91, instruction: 94 },
141
+ "gpt-5.6-terra": { coding: 96, debugging: 93, research: 89, reasoning: 95, speed: 48, longContext: 91, instruction: 93 },
142
+ "gpt-5.6-luna": { coding: 92, debugging: 88, research: 84, reasoning: 91, speed: 72, longContext: 91, instruction: 90 },
138
143
  // ── OpenAI o-series (reasoning-first) ──────────────────────────────────────
139
144
  "o1": { coding: 78, debugging: 82, research: 78, reasoning: 90, speed: 20, longContext: 65, instruction: 82 },
140
145
  "o3": { coding: 80, debugging: 85, research: 80, reasoning: 92, speed: 25, longContext: 70, instruction: 85 },
@@ -16,10 +16,18 @@ const PLANNING_DISPATCH_AGENT_REGISTRY = {
16
16
  export const ALLOWED_PLANNING_DISPATCH_AGENTS = new Set(Object.entries(PLANNING_DISPATCH_AGENT_REGISTRY)
17
17
  .filter(([, metadata]) => metadata.readOnlySpecialist)
18
18
  .map(([agentId]) => agentId));
19
- export function isReadOnlyPlanningDispatchAgent(agentId) {
19
+ function configuredPlanningDispatchAgents(registry) {
20
+ return Object.entries(registry ?? {})
21
+ .filter(([, metadata]) => metadata.read_only_specialist === true)
22
+ .map(([agentId]) => agentId);
23
+ }
24
+ export function isReadOnlyPlanningDispatchAgent(agentId, registry) {
20
25
  const metadata = PLANNING_DISPATCH_AGENT_REGISTRY[agentId];
21
- return metadata?.readOnlySpecialist === true;
26
+ return metadata?.readOnlySpecialist === true || registry?.[agentId]?.read_only_specialist === true;
22
27
  }
23
- export function allowedPlanningDispatchAgentsList() {
24
- return [...ALLOWED_PLANNING_DISPATCH_AGENTS].join(", ");
28
+ export function allowedPlanningDispatchAgentsList(registry) {
29
+ return Array.from(new Set([
30
+ ...ALLOWED_PLANNING_DISPATCH_AGENTS,
31
+ ...configuredPlanningDispatchAgents(registry),
32
+ ])).join(", ");
25
33
  }
@@ -8,10 +8,31 @@
8
8
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
9
9
  import { homedir } from "node:os";
10
10
  import { join } from "node:path";
11
+ import { getProviders } from "@gsd/pi-ai";
11
12
  import { gsdHome } from "./gsd-home.js";
12
13
  import { canonicalModelForTier, defaultRoutingConfig, resolveModelForTier } from "./model-router.js";
13
14
  import { clearGSDPreferencesCache, loadEffectiveGSDPreferences, getGlobalGSDPreferencesPath } from "./preferences.js";
14
15
  import { getUnitPhaseChain } from "./unit-registry.js";
16
+ /**
17
+ * First-party providers shipped as bundled extensions that delegate to a
18
+ * vendor CLI (`authMode: "externalCli"`). They are registered into the live
19
+ * model registry at runtime, so they are absent from the generated `@gsd/pi-ai`
20
+ * catalog (`getProviders()`), but they are NOT custom: `PREFERENCES.md` can
21
+ * route to them, so they must defer to `PREFERENCES.md` like any other built-in
22
+ * provider. Without this exclusion the registry check below would classify the
23
+ * default `claude-code` provider (and the other CLI providers) as custom and
24
+ * silently skip `PREFERENCES.md` in auto-mode (#801, extends #4122).
25
+ *
26
+ * Kept in sync with the `registerProvider(...)` calls in the bundled CLI
27
+ * extensions (claude-code-cli, cursor-cli, google-cli). Names are lowercase for
28
+ * direct comparison against normalized provider ids.
29
+ */
30
+ const BUILTIN_EXTENSION_PROVIDERS = new Set([
31
+ "claude-code",
32
+ "cursor-agent",
33
+ "google-gemini-cli",
34
+ "google-antigravity",
35
+ ]);
15
36
  /**
16
37
  * Resolve which model ID to use for a given auto-mode unit type.
17
38
  * Returns undefined if no model preference is set for this unit type.
@@ -281,9 +302,8 @@ export function resolveDefaultSessionModel(sessionProvider, basePath, availableM
281
302
  return undefined;
282
303
  }
283
304
  /**
284
- * Returns true if `provider` is defined as a custom provider in the user's
285
- * `~/.gsd/agent/models.json` (Ollama, vLLM, LM Studio, OpenAI-compatible
286
- * proxies, etc.).
305
+ * Returns true if `provider` is a custom provider. Custom providers may be
306
+ * declared in `models.json` or registered dynamically by a Pi extension.
287
307
  *
288
308
  * Used by auto-mode bootstrap to decide whether the session model
289
309
  * (set via `/gsd model`) should override `PREFERENCES.md`. Custom providers
@@ -292,13 +312,16 @@ export function resolveDefaultSessionModel(sessionProvider, basePath, availableM
292
312
  * priority — otherwise auto-mode tries to start the built-in provider from
293
313
  * PREFERENCES.md and fails with "Not logged in · Please run /login" (#4122).
294
314
  *
295
- * Reads models.json directly with a lightweight JSON parse to avoid
296
- * pulling in the full model-registry at this call site. Falls back to
297
- * `~/.pi/agent/models.json` for parity with `resolveModelsJsonPath()`.
298
- * Any read or parse error yields `false` (treat as not-custom) so a
299
- * malformed models.json never breaks the session bootstrap.
315
+ * Reads models.json directly with a lightweight JSON parse, then checks the
316
+ * live model registry for extension-registered providers that are not in the
317
+ * generated built-in provider catalog (`getProviders()`) and not one of the
318
+ * bundled external-CLI providers (`BUILTIN_EXTENSION_PROVIDERS`, e.g.
319
+ * `claude-code`), which register at runtime but are still built-in. Falls back
320
+ * to `~/.pi/agent/models.json` for parity with `resolveModelsJsonPath()`. Any
321
+ * read, parse, or registry error yields `false` (treat as not-custom) so
322
+ * bootstrap stays non-fatal.
300
323
  */
301
- export function isCustomProvider(provider) {
324
+ export function isCustomProvider(provider, registry) {
302
325
  if (!provider)
303
326
  return false;
304
327
  const candidates = [
@@ -319,7 +342,24 @@ export function isCustomProvider(provider) {
319
342
  // Ignore — malformed models.json must not break bootstrap.
320
343
  }
321
344
  }
322
- return false;
345
+ try {
346
+ const normalizedProvider = provider.trim().toLowerCase();
347
+ if (!normalizedProvider)
348
+ return false;
349
+ if (BUILTIN_EXTENSION_PROVIDERS.has(normalizedProvider))
350
+ return false;
351
+ const builtInProviders = new Set(getProviders().map((p) => p.toLowerCase()));
352
+ if (builtInProviders.has(normalizedProvider))
353
+ return false;
354
+ const registryModels = registry?.getAllWithDiscovered?.()
355
+ ?? registry?.getAll?.()
356
+ ?? registry?.getAvailable?.()
357
+ ?? [];
358
+ return registryModels.some((m) => m.provider.toLowerCase() === normalizedProvider);
359
+ }
360
+ catch {
361
+ return false;
362
+ }
323
363
  }
324
364
  /**
325
365
  * Determines the next fallback model to try when the current model fails.
@@ -61,6 +61,7 @@ export const KNOWN_PREFERENCE_KEYS = new Set([
61
61
  "git",
62
62
  "post_unit_hooks",
63
63
  "pre_dispatch_hooks",
64
+ "planning_subagent_registry",
64
65
  "planning_subagents",
65
66
  "dynamic_routing",
66
67
  "disabled_model_providers",
@@ -10,7 +10,7 @@ import { VALID_BRANCH_NAME } from "./git-service.js";
10
10
  import { normalizeStringArray } from "../shared/format-utils.js";
11
11
  import { getGateIdsForTurn } from "./gate-registry.js";
12
12
  import { KNOWN_PREFERENCE_KEYS, KNOWN_UNIT_LABELS, CONFIGURABLE_PLANNING_SUBAGENT_UNITS, GSD_MODEL_PHASE_KEYS, SKILL_ACTIONS, } from "./preferences-types.js";
13
- import { ALLOWED_PLANNING_DISPATCH_AGENTS, allowedPlanningDispatchAgentsList, } from "./planning-subagent-registry.js";
13
+ import { allowedPlanningDispatchAgentsList, isReadOnlyPlanningDispatchAgent, } from "./planning-subagent-registry.js";
14
14
  const VALID_TOKEN_PROFILES = new Set(["budget", "balanced", "quality", "burn-max"]);
15
15
  const VALID_THINKING_LEVELS = new Set([
16
16
  "off",
@@ -19,6 +19,7 @@ const VALID_THINKING_LEVELS = new Set([
19
19
  "medium",
20
20
  "high",
21
21
  "xhigh",
22
+ "max",
22
23
  ]);
23
24
  const KNOWN_MODEL_PHASE_KEYS = new Set(GSD_MODEL_PHASE_KEYS);
24
25
  const VALID_UOK_TURN_ACTIONS = new Set([
@@ -69,7 +70,7 @@ function sanitizeModelField(raw, label, errors, warnings) {
69
70
  }
70
71
  else {
71
72
  warnings?.push(`${label} thinking "${String(obj.thinking)}" is not a valid thinking level ` +
72
- `(off, minimal, low, medium, high, xhigh) — ignored`);
73
+ `(off, minimal, low, medium, high, xhigh, max) — ignored`);
73
74
  }
74
75
  }
75
76
  return sanitized;
@@ -457,7 +458,7 @@ export function validatePreferences(preferences) {
457
458
  const level = entry.thinking;
458
459
  if (level !== undefined && !VALID_THINKING_LEVELS.has(level)) {
459
460
  warnings.push(`models.${phase}.thinking "${String(level)}" is not a valid thinking level ` +
460
- `(off, minimal, low, medium, high, xhigh) — ignored`);
461
+ `(off, minimal, low, medium, high, xhigh, max) — ignored`);
461
462
  const { thinking: _ignored, ...rest } = entry;
462
463
  // If stripping the bad thinking leaves no usable model, drop the
463
464
  // phase entirely rather than storing a hollow `{}` / `{ provider }`
@@ -488,7 +489,7 @@ export function validatePreferences(preferences) {
488
489
  }
489
490
  if (!VALID_THINKING_LEVELS.has(level)) {
490
491
  warnings.push(`thinking.${phase} "${String(level)}" is not a valid thinking level ` +
491
- `(off, minimal, low, medium, high, xhigh) — ignored`);
492
+ `(off, minimal, low, medium, high, xhigh, max) — ignored`);
492
493
  continue;
493
494
  }
494
495
  validatedThinking[phase] = level;
@@ -728,6 +729,38 @@ export function validatePreferences(preferences) {
728
729
  validated.pre_dispatch_hooks = validPreHooks;
729
730
  }
730
731
  }
732
+ // ─── Planning Subagent Registry ──────────────────────────────────────────
733
+ if (preferences.planning_subagent_registry !== undefined) {
734
+ if (!preferences.planning_subagent_registry ||
735
+ typeof preferences.planning_subagent_registry !== "object" ||
736
+ Array.isArray(preferences.planning_subagent_registry)) {
737
+ errors.push("planning_subagent_registry must be an object keyed by agent id");
738
+ }
739
+ else {
740
+ const validRegistry = {};
741
+ for (const [rawAgentId, rawConfig] of Object.entries(preferences.planning_subagent_registry)) {
742
+ const agentId = rawAgentId.trim();
743
+ if (!agentId) {
744
+ errors.push("planning_subagent_registry includes an empty agent id");
745
+ continue;
746
+ }
747
+ if (!rawConfig || typeof rawConfig !== "object" || Array.isArray(rawConfig)) {
748
+ errors.push(`planning_subagent_registry.${agentId} must be an object with read_only_specialist: true`);
749
+ continue;
750
+ }
751
+ const readOnlySpecialist = rawConfig.read_only_specialist;
752
+ if (readOnlySpecialist !== true) {
753
+ errors.push(`planning_subagent_registry.${agentId}.read_only_specialist must be true to classify ` +
754
+ `"${agentId}" as a read-only planning specialist`);
755
+ continue;
756
+ }
757
+ validRegistry[agentId] = { read_only_specialist: true };
758
+ }
759
+ if (Object.keys(validRegistry).length > 0) {
760
+ validated.planning_subagent_registry = validRegistry;
761
+ }
762
+ }
763
+ }
731
764
  // ─── Planning Subagents ─────────────────────────────────────────────────
732
765
  if (preferences.planning_subagents !== undefined) {
733
766
  if (!preferences.planning_subagents ||
@@ -755,9 +788,11 @@ export function validatePreferences(preferences) {
755
788
  }
756
789
  const sanitizedAllowed = [];
757
790
  for (const agentId of allowed) {
758
- if (!ALLOWED_PLANNING_DISPATCH_AGENTS.has(agentId)) {
791
+ if (!isReadOnlyPlanningDispatchAgent(agentId, validated.planning_subagent_registry)) {
759
792
  errors.push(`planning_subagents.${unitType}.allowed includes "${agentId}", which is not classified as a ` +
760
- `read-only planning specialist. Allowed agents: ${allowedPlanningDispatchAgentsList()}`);
793
+ `read-only planning specialist. Allowed agents: ` +
794
+ `${allowedPlanningDispatchAgentsList(validated.planning_subagent_registry)}. ` +
795
+ `For custom planning agents, add planning_subagent_registry.${agentId}.read_only_specialist: true`);
761
796
  continue;
762
797
  }
763
798
  sanitizedAllowed.push(agentId);
@@ -635,6 +635,7 @@ function mergePreferences(base, override) {
635
635
  : undefined,
636
636
  post_unit_hooks: mergePostUnitHooks(base.post_unit_hooks, override.post_unit_hooks),
637
637
  pre_dispatch_hooks: mergePreDispatchHooks(base.pre_dispatch_hooks, override.pre_dispatch_hooks),
638
+ planning_subagent_registry: mergePlanningSubagentRegistry(base.planning_subagent_registry, override.planning_subagent_registry),
638
639
  planning_subagents: mergePlanningSubagents(base.planning_subagents, override.planning_subagents),
639
640
  dynamic_routing: (base.dynamic_routing || override.dynamic_routing)
640
641
  ? { ...(base.dynamic_routing ?? {}), ...(override.dynamic_routing ?? {}) }
@@ -769,6 +770,12 @@ function mergePlanningSubagents(base, override) {
769
770
  }
770
771
  return Object.keys(merged).length > 0 ? merged : undefined;
771
772
  }
773
+ function mergePlanningSubagentRegistry(base, override) {
774
+ if (!base && !override)
775
+ return undefined;
776
+ const merged = { ...(base ?? {}), ...(override ?? {}) };
777
+ return Object.keys(merged).length > 0 ? merged : undefined;
778
+ }
772
779
  // ─── System Prompt Rendering ──────────────────────────────────────────────────
773
780
  export function renderPreferencesForSystemPrompt(preferences, resolutions, options) {
774
781
  const validated = validatePreferences(preferences);
@@ -92,6 +92,9 @@ remote_questions:
92
92
  uat_dispatch:
93
93
  post_unit_hooks: []
94
94
  pre_dispatch_hooks: []
95
+ # planning_subagent_registry:
96
+ # my-custom-planner:
97
+ # read_only_specialist: true
95
98
  # planning_subagents:
96
99
  # plan-milestone:
97
100
  # allowed: [scout, planner, security]
@@ -6,7 +6,7 @@ import { isNonEmptyString, validateStringArray } from "../validation.js";
6
6
  import { getGateIdsForTurn } from "../gate-registry.js";
7
7
  import { transaction, getMilestone, getSlice, getSliceTasks, insertTask, upsertSlicePlanning, upsertTaskPlanning, insertGateRow, updateSliceStatus, setSliceSketchFlag, deleteTask, deleteArtifactByPath, } from "../gsd-db.js";
8
8
  import { invalidateStateCache } from "../state.js";
9
- import { renderPlanFromDb } from "../markdown-renderer.js";
9
+ import { renderPlanCheckboxes, renderPlanFromDb } from "../markdown-renderer.js";
10
10
  import { writeManifest } from "../workflow-manifest.js";
11
11
  import { appendEvent } from "../workflow-events.js";
12
12
  import { logWarning } from "../workflow-logger.js";
@@ -371,9 +371,13 @@ export async function handlePlanSlice(rawParams, basePath) {
371
371
  deleteArtifactByPath(artifactPath);
372
372
  }
373
373
  const sliceTasks = getSliceTasks(params.milestoneId, params.sliceId);
374
+ const hasClosedTasks = sliceTasks.some((task) => isClosedStatus(task.status));
374
375
  const renderResult = sliceTasks.length === 0
375
376
  ? { planPath: "", taskPlanPaths: [] }
376
377
  : await renderPlanFromDb(basePath, params.milestoneId, params.sliceId);
378
+ if (sliceTasks.length > 0 && hasClosedTasks) {
379
+ await renderPlanCheckboxes(basePath, params.milestoneId, params.sliceId);
380
+ }
377
381
  if (sliceTasks.length > 0) {
378
382
  setSliceSketchFlag(params.milestoneId, params.sliceId, false);
379
383
  }
@@ -11,13 +11,14 @@
11
11
  import { mkdirSync, writeFileSync } from "node:fs";
12
12
  import { join } from "node:path";
13
13
  /**
14
- * Write a T##-VERIFY.json artifact to the tasks directory.
14
+ * Write a T##-VERIFY.json artifact to the evidence directory.
15
15
  * Creates the directory with mkdirSync({ recursive: true }) if it doesn't exist.
16
+ * Flat-phase callers can pass sliceId to write S##-T##-VERIFY.json.
16
17
  *
17
18
  * stdout/stderr are excluded from the JSON — the full output lives in VerificationResult
18
19
  * in memory and is logged to stderr during the gate run.
19
20
  */
20
- export function writeVerificationJSON(result, tasksDir, taskId, unitId, retryAttempt, maxRetries) {
21
+ export function writeVerificationJSON(result, tasksDir, taskId, unitId, retryAttempt, maxRetries, sliceId) {
21
22
  mkdirSync(tasksDir, { recursive: true });
22
23
  const evidence = {
23
24
  schemaVersion: 1,
@@ -52,7 +53,8 @@ export function writeVerificationJSON(result, tasksDir, taskId, unitId, retryAtt
52
53
  fixAvailable: w.fixAvailable,
53
54
  }));
54
55
  }
55
- const filePath = join(tasksDir, `${taskId}-VERIFY.json`);
56
+ const fileName = sliceId ? `${sliceId}-${taskId}-VERIFY.json` : `${taskId}-VERIFY.json`;
57
+ const filePath = join(tasksDir, fileName);
56
58
  writeFileSync(filePath, JSON.stringify(evidence, null, 2) + "\n", "utf-8");
57
59
  }
58
60
  /**
@@ -21,7 +21,7 @@
21
21
  import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, statSync, unlinkSync, } from "node:fs";
22
22
  import { join } from "node:path";
23
23
  import { reconcileWorktreeDb } from "./gsd-db.js";
24
- import { resolveGsdPathContract } from "./paths.js";
24
+ import { dirIsContentBearingLegacyMilestone, resolveGsdPathContract } from "./paths.js";
25
25
  import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
26
26
  import { logError, logWarning } from "./workflow-logger.js";
27
27
  // ─── Private helpers ──────────────────────────────────────────────────────
@@ -129,6 +129,8 @@ function syncOtherMilestoneArtifacts(srcMilestonesDir, dstMilestonesDir, current
129
129
  if (milestoneEntry.name === currentMilestoneId)
130
130
  continue;
131
131
  const srcMilestoneDir = join(srcMilestonesDir, milestoneEntry.name);
132
+ if (!dirIsContentBearingLegacyMilestone(srcMilestoneDir))
133
+ continue;
132
134
  const dstMilestoneDir = join(dstMilestonesDir, milestoneEntry.name);
133
135
  // Additively project the entire milestone subtree (force:false), not just
134
136
  // top-level files. Prior completed milestones keep their per-slice and
@@ -219,25 +221,25 @@ export function _projectRootToWorktreeImpl(projectRoot, worktreePath_, milestone
219
221
  // Without this, worktree-local files (e.g. VALIDATION.md written
220
222
  // by validate-milestone) get clobbered by stale project root copies,
221
223
  // causing an infinite re-validation loop (#1886).
222
- safeCopyRecursive(join(prGsd, "milestones", milestoneId), join(wtGsd, "milestones", milestoneId), { force: false });
223
- // Additively project the full subtree of every OTHER milestone so
224
- // worktree-bound units can read prior-milestone context artifacts and so
225
- // completed milestones retain their per-slice/per-task SUMMARY.md and
226
- // UAT.md on the worktree filesystem (otherwise the stale-render detector
227
- // flags them as "complete in DB but missing on disk").
228
- syncOtherMilestoneArtifacts(join(prGsd, "milestones"), join(wtGsd, "milestones"), milestoneId);
229
- // Force-sync ASSESSMENT files that have a verdict from project root (#2821).
230
- // The additive-only copy above preserves worktree-local files, but
231
- // ASSESSMENT files are special: after run-uat writes a verdict and post-unit
232
- // syncs it to the project root, the worktree may retain a stale copy (e.g.
233
- // verdict:fail while the project root has verdict:pass from a retry). On
234
- // session resume the DB is rebuilt from disk, and if the stale ASSESSMENT
235
- // persists, checkNeedsRunUat finds no passing verdict → re-dispatches
236
- // run-uat indefinitely (stuck-loop ×9).
237
224
  const prMilestoneDir = join(prGsd, "milestones", milestoneId);
238
225
  const wtMilestoneDir = join(wtGsd, "milestones", milestoneId);
239
- forceOverwriteValidationWithVerdict(prMilestoneDir, wtMilestoneDir, milestoneId);
240
- forceOverwriteAssessmentsWithVerdict(prMilestoneDir, wtMilestoneDir);
226
+ if (dirIsContentBearingLegacyMilestone(prMilestoneDir)) {
227
+ safeCopyRecursive(prMilestoneDir, wtMilestoneDir, { force: false });
228
+ // Force-sync ASSESSMENT files that have a verdict from project root (#2821).
229
+ // The additive-only copy above preserves worktree-local files, but
230
+ // ASSESSMENT files are special: after run-uat writes a verdict and post-unit
231
+ // syncs it to the project root, the worktree may retain a stale copy (e.g.
232
+ // verdict:fail while the project root has verdict:pass from a retry). On
233
+ // session resume the DB is rebuilt from disk, and if the stale ASSESSMENT
234
+ // persists, checkNeedsRunUat finds no passing verdict → re-dispatches
235
+ // run-uat indefinitely (stuck-loop ×9).
236
+ forceOverwriteValidationWithVerdict(prMilestoneDir, wtMilestoneDir, milestoneId);
237
+ forceOverwriteAssessmentsWithVerdict(prMilestoneDir, wtMilestoneDir);
238
+ }
239
+ // Additively project the full subtree of every OTHER content-bearing legacy
240
+ // milestone so worktree-bound units can read prior-milestone context artifacts
241
+ // without recreating empty/meta-only milestones/ scaffolds in flat-phase projects.
242
+ syncOtherMilestoneArtifacts(join(prGsd, "milestones"), join(wtGsd, "milestones"), milestoneId);
241
243
  // Forward-sync completed-units.json from project root to worktree.
242
244
  // Project root is authoritative for completion state after crash recovery;
243
245
  // without this, the worktree re-dispatches already-completed units (#1886).
@@ -160,7 +160,7 @@ export function streamOllamaChat(model, context, options) {
160
160
  * - `"low" | "medium" | "high"` are passed through as strings; gpt-oss honors
161
161
  * them as strength levels, other thinking models treat any present value
162
162
  * as "thinking enabled".
163
- * - `"xhigh"` is collapsed to `"high"` (ollama caps strength at high).
163
+ * - `"xhigh"` and `"max"` are collapsed to `"high"` (ollama caps strength at high).
164
164
  */
165
165
  export function buildThinkParam(model, options) {
166
166
  if (!model.reasoning)
@@ -170,7 +170,7 @@ export function buildThinkParam(model, options) {
170
170
  return undefined;
171
171
  if (level === "minimal")
172
172
  return false;
173
- if (level === "xhigh")
173
+ if (level === "xhigh" || level === "max")
174
174
  return "high";
175
175
  return level;
176
176
  }
@@ -591,7 +591,7 @@ thinkingOverride) {
591
591
  }
592
592
  }
593
593
  }
594
- const ThinkingLevelSchema = StringEnum(["off", "minimal", "low", "medium", "high", "xhigh"], {
594
+ const ThinkingLevelSchema = StringEnum(["off", "minimal", "low", "medium", "high", "xhigh", "max"], {
595
595
  description: "Reasoning effort override for the subagent (forwarded as --thinking). " +
596
596
  "Independent of model choice; unsupported levels are clamped to the model at dispatch.",
597
597
  });
@@ -15,7 +15,7 @@ interface MinimalModel {
15
15
  interface MinimalModelRegistry {
16
16
  getAvailable(): MinimalModel[];
17
17
  }
18
- type ThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
18
+ type ThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max';
19
19
  interface MinimalSettingsManager {
20
20
  getDefaultProvider(): string | undefined;
21
21
  getDefaultModel(): string | undefined;