@opengsd/gsd-pi 1.1.1-dev.9f86580 → 1.1.1-dev.b2556262

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 (261) hide show
  1. package/dist/headless-recover.js +56 -1
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/browser-tools/index.js +39 -22
  4. package/dist/resources/extensions/browser-tools/state.js +12 -0
  5. package/dist/resources/extensions/browser-tools/tools/session.js +3 -2
  6. package/dist/resources/extensions/browser-tools/utils.js +3 -3
  7. package/dist/resources/extensions/gsd/auto/loop.js +4 -2
  8. package/dist/resources/extensions/gsd/auto/phases.js +43 -10
  9. package/dist/resources/extensions/gsd/auto/session.js +20 -1
  10. package/dist/resources/extensions/gsd/auto/workflow-kernel.js +1 -0
  11. package/dist/resources/extensions/gsd/auto-dispatch.js +72 -12
  12. package/dist/resources/extensions/gsd/auto-model-selection.js +128 -9
  13. package/dist/resources/extensions/gsd/auto-post-unit.js +19 -2
  14. package/dist/resources/extensions/gsd/auto-prompts.js +24 -19
  15. package/dist/resources/extensions/gsd/auto-recovery.js +4 -2
  16. package/dist/resources/extensions/gsd/auto-runtime-state.js +3 -0
  17. package/dist/resources/extensions/gsd/auto-start.js +1 -1
  18. package/dist/resources/extensions/gsd/auto.js +14 -11
  19. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -3
  20. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +172 -65
  21. package/dist/resources/extensions/gsd/closeout-wizard.js +32 -9
  22. package/dist/resources/extensions/gsd/commands/handlers/ops.js +2 -9
  23. package/dist/resources/extensions/gsd/commands-maintenance.js +93 -15
  24. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +2 -2
  25. package/dist/resources/extensions/gsd/db-writer.js +35 -0
  26. package/dist/resources/extensions/gsd/docs/preferences-reference.md +50 -1
  27. package/dist/resources/extensions/gsd/gsd-db.js +480 -172
  28. package/dist/resources/extensions/gsd/markdown-renderer.js +37 -53
  29. package/dist/resources/extensions/gsd/md-importer.js +38 -3
  30. package/dist/resources/extensions/gsd/migration-auto-check.js +126 -31
  31. package/dist/resources/extensions/gsd/parsers-legacy.js +23 -0
  32. package/dist/resources/extensions/gsd/planning-path-scope.js +22 -4
  33. package/dist/resources/extensions/gsd/pre-execution-checks.js +10 -2
  34. package/dist/resources/extensions/gsd/preferences-models.js +110 -43
  35. package/dist/resources/extensions/gsd/preferences-types.js +13 -0
  36. package/dist/resources/extensions/gsd/preferences-validation.js +68 -3
  37. package/dist/resources/extensions/gsd/preferences.js +4 -1
  38. package/dist/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
  39. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  40. package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  41. package/dist/resources/extensions/gsd/roadmap-slices.js +5 -1
  42. package/dist/resources/extensions/gsd/safety/content-validator.js +6 -4
  43. package/dist/resources/extensions/gsd/source-observations.js +306 -0
  44. package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +15 -8
  45. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +33 -5
  46. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-worker.js +34 -13
  47. package/dist/resources/extensions/gsd/state-reconciliation/index.js +39 -14
  48. package/dist/resources/extensions/gsd/state-reconciliation/spawn-gate.js +4 -4
  49. package/dist/resources/extensions/gsd/state.js +7 -3
  50. package/dist/resources/extensions/gsd/tool-contract.js +14 -0
  51. package/dist/resources/extensions/gsd/tool-presentation-plan.js +1 -9
  52. package/dist/resources/extensions/gsd/tools/complete-slice.js +7 -6
  53. package/dist/resources/extensions/gsd/tools/plan-slice.js +42 -11
  54. package/dist/resources/extensions/gsd/tools/plan-task.js +7 -1
  55. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +57 -429
  56. package/dist/resources/extensions/gsd/uat-policy.js +130 -0
  57. package/dist/resources/extensions/gsd/uat-run.js +414 -0
  58. package/dist/resources/extensions/gsd/unit-context-manifest.js +3 -4
  59. package/dist/resources/extensions/gsd/verdict-parser.js +3 -8
  60. package/dist/resources/extensions/gsd/workflow-manifest.js +132 -5
  61. package/dist/resources/extensions/gsd/workflow-projections.js +8 -0
  62. package/dist/resources/extensions/gsd/worktree-state-projection.js +18 -17
  63. package/dist/resources/extensions/subagent/agents.js +1 -0
  64. package/dist/resources/extensions/subagent/index.js +27 -12
  65. package/dist/resources/extensions/subagent/launch.js +7 -2
  66. package/dist/web/standalone/.next/BUILD_ID +1 -1
  67. package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
  68. package/dist/web/standalone/.next/build-manifest.json +2 -2
  69. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  70. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/index.html +1 -1
  87. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
  94. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  95. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  97. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  98. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  99. package/dist/web/standalone/node_modules/@gsd/native/dist/native.js +22 -0
  100. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  101. package/package.json +4 -4
  102. package/packages/cloud-mcp-gateway/package.json +2 -2
  103. package/packages/contracts/package.json +1 -1
  104. package/packages/daemon/package.json +4 -4
  105. package/packages/gsd-agent-core/package.json +5 -5
  106. package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  107. package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js +21 -23
  108. package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js.map +1 -1
  109. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +3 -0
  110. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  111. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +25 -0
  112. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  113. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts +1 -0
  114. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
  115. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +66 -12
  116. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
  117. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  118. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +18 -11
  119. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  120. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.d.ts.map +1 -1
  121. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js +16 -0
  122. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js.map +1 -1
  123. package/packages/gsd-agent-modes/package.json +7 -7
  124. package/packages/mcp-server/dist/workflow-tools.js +1 -1
  125. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  126. package/packages/mcp-server/package.json +3 -3
  127. package/packages/native/dist/native.js +22 -0
  128. package/packages/native/package.json +1 -1
  129. package/packages/pi-agent-core/package.json +1 -1
  130. package/packages/pi-ai/dist/image-models.generated.d.ts +30 -0
  131. package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
  132. package/packages/pi-ai/dist/image-models.generated.js +30 -0
  133. package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
  134. package/packages/pi-ai/dist/models.generated.d.ts +23 -17
  135. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  136. package/packages/pi-ai/dist/models.generated.js +25 -24
  137. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  138. package/packages/pi-ai/package.json +1 -1
  139. package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
  140. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  141. package/packages/pi-coding-agent/dist/theme/themes.js +1 -1
  142. package/packages/pi-coding-agent/dist/theme/themes.js.map +1 -1
  143. package/packages/pi-coding-agent/package.json +7 -7
  144. package/packages/pi-tui/dist/utils.d.ts +11 -0
  145. package/packages/pi-tui/dist/utils.d.ts.map +1 -1
  146. package/packages/pi-tui/dist/utils.js +119 -6
  147. package/packages/pi-tui/dist/utils.js.map +1 -1
  148. package/packages/pi-tui/package.json +2 -1
  149. package/packages/rpc-client/package.json +2 -2
  150. package/pkg/dist/theme/themes.js +1 -1
  151. package/pkg/dist/theme/themes.js.map +1 -1
  152. package/pkg/package.json +1 -1
  153. package/src/resources/extensions/browser-tools/index.ts +39 -22
  154. package/src/resources/extensions/browser-tools/state.ts +13 -0
  155. package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +57 -0
  156. package/src/resources/extensions/browser-tools/tools/session.ts +4 -2
  157. package/src/resources/extensions/browser-tools/utils.ts +3 -3
  158. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  159. package/src/resources/extensions/gsd/auto/loop.ts +4 -2
  160. package/src/resources/extensions/gsd/auto/phases.ts +42 -10
  161. package/src/resources/extensions/gsd/auto/session.ts +22 -1
  162. package/src/resources/extensions/gsd/auto/workflow-kernel.ts +1 -0
  163. package/src/resources/extensions/gsd/auto-dispatch.ts +85 -12
  164. package/src/resources/extensions/gsd/auto-model-selection.ts +164 -12
  165. package/src/resources/extensions/gsd/auto-post-unit.ts +20 -2
  166. package/src/resources/extensions/gsd/auto-prompts.ts +23 -20
  167. package/src/resources/extensions/gsd/auto-recovery.ts +22 -3
  168. package/src/resources/extensions/gsd/auto-runtime-state.ts +5 -0
  169. package/src/resources/extensions/gsd/auto-start.ts +1 -1
  170. package/src/resources/extensions/gsd/auto.ts +13 -10
  171. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -3
  172. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +225 -72
  173. package/src/resources/extensions/gsd/closeout-wizard.ts +47 -13
  174. package/src/resources/extensions/gsd/commands/handlers/ops.ts +2 -17
  175. package/src/resources/extensions/gsd/commands-maintenance.ts +124 -13
  176. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +2 -2
  177. package/src/resources/extensions/gsd/db-writer.ts +38 -0
  178. package/src/resources/extensions/gsd/docs/preferences-reference.md +50 -1
  179. package/src/resources/extensions/gsd/gsd-db.ts +564 -186
  180. package/src/resources/extensions/gsd/markdown-renderer.ts +44 -66
  181. package/src/resources/extensions/gsd/md-importer.ts +49 -2
  182. package/src/resources/extensions/gsd/migration-auto-check.ts +154 -34
  183. package/src/resources/extensions/gsd/parsers-legacy.ts +20 -0
  184. package/src/resources/extensions/gsd/planning-path-scope.ts +22 -4
  185. package/src/resources/extensions/gsd/pre-execution-checks.ts +9 -2
  186. package/src/resources/extensions/gsd/preferences-models.ts +112 -43
  187. package/src/resources/extensions/gsd/preferences-types.ts +39 -0
  188. package/src/resources/extensions/gsd/preferences-validation.ts +76 -2
  189. package/src/resources/extensions/gsd/preferences.ts +5 -0
  190. package/src/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
  191. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  192. package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  193. package/src/resources/extensions/gsd/roadmap-slices.ts +6 -1
  194. package/src/resources/extensions/gsd/safety/content-validator.ts +8 -5
  195. package/src/resources/extensions/gsd/source-observations.ts +402 -0
  196. package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +20 -8
  197. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +44 -5
  198. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-worker.ts +39 -11
  199. package/src/resources/extensions/gsd/state-reconciliation/index.ts +45 -15
  200. package/src/resources/extensions/gsd/state-reconciliation/spawn-gate.ts +4 -4
  201. package/src/resources/extensions/gsd/state.ts +7 -4
  202. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +15 -0
  203. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +299 -1
  204. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +32 -0
  205. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +75 -3
  206. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +22 -1
  207. package/src/resources/extensions/gsd/tests/before-provider-context-management.test.ts +145 -0
  208. package/src/resources/extensions/gsd/tests/closeout-wizard.test.ts +44 -0
  209. package/src/resources/extensions/gsd/tests/commands-dispatcher-unmerged-milestone.test.ts +26 -1
  210. package/src/resources/extensions/gsd/tests/content-validator.test.ts +74 -0
  211. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +16 -2
  212. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +1 -11
  213. package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +64 -0
  214. package/src/resources/extensions/gsd/tests/gate-storage.test.ts +15 -0
  215. package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +62 -1
  216. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +15 -0
  217. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +42 -0
  218. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +99 -0
  219. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +99 -2
  220. package/src/resources/extensions/gsd/tests/plan-task.test.ts +19 -0
  221. package/src/resources/extensions/gsd/tests/preferences.test.ts +14 -0
  222. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +1 -0
  223. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +9 -0
  224. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +101 -1
  225. package/src/resources/extensions/gsd/tests/repository-registry.test.ts +2 -2
  226. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +8 -0
  227. package/src/resources/extensions/gsd/tests/schema-v21-sequence.test.ts +5 -3
  228. package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +162 -18
  229. package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +8 -0
  230. package/src/resources/extensions/gsd/tests/source-observations.test.ts +275 -0
  231. package/src/resources/extensions/gsd/tests/stale-queued-milestone.test.ts +43 -0
  232. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +76 -21
  233. package/src/resources/extensions/gsd/tests/thinking-level-resolution.test.ts +203 -0
  234. package/src/resources/extensions/gsd/tests/uat-policy.test.ts +170 -0
  235. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +7 -1
  236. package/src/resources/extensions/gsd/tests/workflow-kernel.test.ts +7 -0
  237. package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +306 -1
  238. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +73 -6
  239. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +511 -1
  240. package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +44 -0
  241. package/src/resources/extensions/gsd/tool-contract.ts +28 -0
  242. package/src/resources/extensions/gsd/tool-presentation-plan.ts +1 -11
  243. package/src/resources/extensions/gsd/tools/complete-slice.ts +7 -6
  244. package/src/resources/extensions/gsd/tools/plan-slice.ts +54 -12
  245. package/src/resources/extensions/gsd/tools/plan-task.ts +8 -1
  246. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +66 -526
  247. package/src/resources/extensions/gsd/types.ts +1 -0
  248. package/src/resources/extensions/gsd/uat-policy.ts +191 -0
  249. package/src/resources/extensions/gsd/uat-run.ts +550 -0
  250. package/src/resources/extensions/gsd/unit-context-manifest.ts +3 -4
  251. package/src/resources/extensions/gsd/verdict-parser.ts +3 -10
  252. package/src/resources/extensions/gsd/workflow-manifest.ts +193 -7
  253. package/src/resources/extensions/gsd/workflow-projections.ts +9 -0
  254. package/src/resources/extensions/gsd/worktree-state-projection.ts +22 -22
  255. package/src/resources/extensions/shared/tests/format-utils.test.ts +8 -3
  256. package/src/resources/extensions/subagent/agents.ts +4 -0
  257. package/src/resources/extensions/subagent/index.ts +28 -3
  258. package/src/resources/extensions/subagent/launch.ts +8 -0
  259. package/src/resources/extensions/subagent/tests/model-override.test.ts +31 -0
  260. /package/dist/web/standalone/.next/static/{zzYMrKpPGfRQRxSFO32Jr → tJOKQbQRO-9MiFDO8DIDS}/_buildManifest.js +0 -0
  261. /package/dist/web/standalone/.next/static/{zzYMrKpPGfRQRxSFO32Jr → tJOKQbQRO-9MiFDO8DIDS}/_ssgManifest.js +0 -0
@@ -19,6 +19,9 @@ import type {
19
19
  GSDPreferences,
20
20
  GSDModelConfigV2,
21
21
  GSDPhaseModelConfig,
22
+ GSDThinkingLevel,
23
+ GSDModelPhaseKey,
24
+ GSDThinkingConfig,
22
25
  ResolvedModelConfig,
23
26
  AutoSupervisorConfig,
24
27
  } from "./preferences-types.js";
@@ -37,82 +40,103 @@ export function resolveModelForUnit(unitType: string): string | undefined {
37
40
  }
38
41
 
39
42
  /**
40
- * Resolve model and fallbacks for a given auto-mode unit type.
41
- * Returns the primary model and ordered fallbacks, or undefined if not configured.
43
+ * Ordered phase-bucket chain a unit type resolves against, most-specific
44
+ * first. The first chain entry with a configured value wins; later entries
45
+ * are siblings the unit falls back to (e.g. `discuss → planning`).
42
46
  *
43
- * Supports both legacy string format and extended object format:
44
- * - Legacy: `planning: claude-opus-4-6`
45
- * - Extended: `planning: { model: claude-opus-4-6, fallbacks: [glm-5, minimax-m2.5] }`
47
+ * Single source of truth for the unit-type phase mapping, shared by model
48
+ * resolution (`resolveModelWithFallbacksForUnit`) and thinking resolution
49
+ * (`resolveThinkingLevelForUnit`) so the two never drift (ADR-026).
46
50
  */
47
- export function resolveModelWithFallbacksForUnit(unitType: string): ResolvedModelConfig | undefined {
48
- const prefs = loadEffectiveGSDPreferences(undefined, { availableModelIds: [] });
49
- const models = prefs?.preferences?.models;
50
- if (!models) return undefined;
51
- const m = models as GSDModelConfigV2;
52
-
53
- let phaseConfig: string | GSDPhaseModelConfig | undefined;
51
+ export function phaseChainForUnit(unitType: string): GSDModelPhaseKey[] | undefined {
54
52
  switch (unitType) {
55
53
  case "research-milestone":
56
54
  case "research-slice":
57
- phaseConfig = m.research;
58
- break;
55
+ // Deep-mode project research orchestrator. Reads PROJECT.md / REQUIREMENTS.md
56
+ // and fans out research subagents. Routes to the research bucket.
57
+ case "research-project":
58
+ return ["research"];
59
59
  case "plan-milestone":
60
60
  case "plan-slice":
61
61
  case "refine-slice":
62
62
  case "replan-slice":
63
- phaseConfig = m.planning;
64
- break;
63
+ return ["planning"];
64
+ // Deep-mode project-level discussion units route to the same model bucket
65
+ // as milestone-level discussion (interactive interview style). Workflow
66
+ // preferences and research-decision are tiny ask_user_questions style units
67
+ // that share the discuss bucket because they are conversational. All fall
68
+ // back to planning when no `discuss` bucket is set.
65
69
  case "discuss-milestone":
66
70
  case "discuss-slice":
67
- // Deep-mode project-level discussion units route to the same model
68
- // bucket as milestone-level discussion (interactive interview style).
69
71
  case "discuss-project":
70
72
  case "discuss-requirements":
71
- // Workflow preferences and research-decision are tiny ask_user_questions
72
- // style units; they share the discuss bucket because they are
73
- // conversational rather than research/execution. Falling back to planning
74
- // when no `discuss` bucket is set keeps parity with the milestone units.
75
73
  case "workflow-preferences":
76
74
  case "research-decision":
77
- phaseConfig = m.discuss ?? m.planning;
78
- break;
79
- // Deep-mode project research orchestrator. Reads PROJECT.md / REQUIREMENTS.md
80
- // and fans out research subagents. Routes to the research bucket so it
81
- // gets the research-tier model when one is configured.
82
- case "research-project":
83
- phaseConfig = m.research;
84
- break;
75
+ return ["discuss", "planning"];
85
76
  case "execute-task":
86
77
  case "reactive-execute":
87
- phaseConfig = m.execution;
88
- break;
78
+ return ["execution"];
89
79
  case "execute-task-simple":
90
- phaseConfig = m.execution_simple ?? m.execution;
91
- break;
80
+ return ["execution_simple", "execution"];
92
81
  case "complete-slice":
93
82
  case "complete-milestone":
94
83
  case "worktree-merge":
95
- phaseConfig = m.completion;
96
- break;
84
+ return ["completion"];
97
85
  case "run-uat":
98
- phaseConfig = m.uat ?? m.completion;
99
- break;
86
+ return ["uat", "completion"];
100
87
  case "reassess-roadmap":
101
88
  case "rewrite-docs":
102
89
  case "gate-evaluate":
103
90
  case "validate-milestone":
104
- phaseConfig = m.validation ?? m.planning;
105
- break;
91
+ return ["validation", "planning"];
106
92
  default:
107
93
  // Subagent unit types (e.g., "subagent", "subagent/scout")
108
94
  if (unitType === "subagent" || unitType.startsWith("subagent/")) {
109
- phaseConfig = m.subagent;
110
- break;
95
+ return ["subagent"];
111
96
  }
112
97
  return undefined;
113
98
  }
99
+ }
114
100
 
115
- if (!phaseConfig) return undefined;
101
+ /**
102
+ * Find the phase bucket whose `models` entry wins the chain for a unit, plus
103
+ * that entry. Returns undefined when no phase in the chain is configured.
104
+ */
105
+ function resolveWinningPhase(
106
+ models: GSDModelConfigV2 | undefined,
107
+ chain: GSDModelPhaseKey[],
108
+ ): { phase: GSDModelPhaseKey; config: string | GSDPhaseModelConfig } | undefined {
109
+ if (!models) return undefined;
110
+ for (const key of chain) {
111
+ const config = models[key];
112
+ // Falsy check (not `!= null`) so an empty-string model is treated as
113
+ // unconfigured and the chain falls through — matches the pre-refactor
114
+ // switch, which bailed via `if (!phaseConfig)`.
115
+ if (!config) continue;
116
+ // An object entry only "wins" if it provides a usable model. A model-less
117
+ // object (e.g. `{ provider: x }`, or `{}` left after stripping an invalid
118
+ // `thinking`) must not shadow sibling fallback or yield `{ primary: undefined }`.
119
+ if (typeof config === "object" && !config.model) continue;
120
+ return { phase: key, config };
121
+ }
122
+ return undefined;
123
+ }
124
+
125
+ /**
126
+ * Resolve model and fallbacks for a given auto-mode unit type.
127
+ * Returns the primary model and ordered fallbacks, or undefined if not configured.
128
+ *
129
+ * Supports both legacy string format and extended object format:
130
+ * - Legacy: `planning: claude-opus-4-6`
131
+ * - Extended: `planning: { model: claude-opus-4-6, fallbacks: [glm-5, minimax-m2.5] }`
132
+ */
133
+ export function resolveModelWithFallbacksForUnit(unitType: string): ResolvedModelConfig | undefined {
134
+ const prefs = loadEffectiveGSDPreferences(undefined, { availableModelIds: [] });
135
+ const chain = phaseChainForUnit(unitType);
136
+ if (!chain) return undefined;
137
+ const winner = resolveWinningPhase(prefs?.preferences?.models as GSDModelConfigV2 | undefined, chain);
138
+ if (!winner) return undefined;
139
+ const phaseConfig = winner.config;
116
140
 
117
141
  // Normalize: string -> { model, fallbacks: [] }
118
142
  if (typeof phaseConfig === "string") {
@@ -131,6 +155,51 @@ export function resolveModelWithFallbacksForUnit(unitType: string): ResolvedMode
131
155
  };
132
156
  }
133
157
 
158
+ /**
159
+ * Resolve the explicitly configured reasoning effort for a unit type (ADR-026).
160
+ *
161
+ * Thinking travels with the model. The chain is walked most-specific-first up to
162
+ * and including the phase whose model won; at each level inline
163
+ * `models.<phase>.thinking` is preferred, then the same phase's `thinking` block
164
+ * entry. This means:
165
+ * - a more-specific block key (`thinking.execution_simple`) surfaces even when
166
+ * the model only resolves on a less-specific sibling (`models.execution`);
167
+ * - inline thinking is honored even on a model-less `models.<phase>` entry
168
+ * (e.g. `{ thinking: "high" }` with no `model`);
169
+ * - a unit that claimed its own model bucket never borrows a *less*-specific
170
+ * sibling's thinking (the walk stops at the winning phase).
171
+ * When no model is configured anywhere in the chain, the walk spans the full
172
+ * chain so inline thinking and the `thinking` block both resolve on their own
173
+ * sibling chain.
174
+ *
175
+ * Returns undefined when nothing explicit is configured — the dispatch path
176
+ * then falls back to the session/default level and applies the code-writing
177
+ * floor. Session level, defaults, the floor, and capability clamping are NOT
178
+ * applied here.
179
+ */
180
+ export function resolveThinkingLevelForUnit(unitType: string): GSDThinkingLevel | undefined {
181
+ const prefs = loadEffectiveGSDPreferences(undefined, { availableModelIds: [] })?.preferences;
182
+ if (!prefs) return undefined;
183
+ const chain = phaseChainForUnit(unitType);
184
+ if (!chain) return undefined;
185
+
186
+ const models = prefs.models as GSDModelConfigV2 | undefined;
187
+ const block = prefs.thinking as GSDThinkingConfig | undefined;
188
+
189
+ // Walk most-specific-first, up to and including the winning model phase (or
190
+ // the full chain when no model is configured), checking inline then block.
191
+ const winner = resolveWinningPhase(models, chain);
192
+ const limit = winner ? chain.indexOf(winner.phase) + 1 : chain.length;
193
+ for (let i = 0; i < limit; i++) {
194
+ const key = chain[i];
195
+ const entry = models?.[key];
196
+ if (typeof entry === "object" && entry?.thinking) return entry.thinking; // inline (incl. model-less)
197
+ const blockLevel = block?.[key];
198
+ if (blockLevel) return blockLevel; // block
199
+ }
200
+ return undefined;
201
+ }
202
+
134
203
  /**
135
204
  * Resolve the default session model from GSD preferences.
136
205
  *
@@ -99,6 +99,7 @@ export const KNOWN_PREFERENCE_KEYS = new Set<string>([
99
99
  "skill_rules",
100
100
  "custom_instructions",
101
101
  "models",
102
+ "thinking",
102
103
  "skill_discovery",
103
104
  "skill_staleness_days",
104
105
  "auto_supervisor",
@@ -198,6 +199,28 @@ export interface GSDSkillRule {
198
199
  avoid?: string[];
199
200
  }
200
201
 
202
+ /**
203
+ * Reasoning effort levels. Mirrors `ThinkingLevel` in `@gsd/pi-agent-core`;
204
+ * declared locally so preference parsing/validation does not import the agent
205
+ * core package. `xhigh` is only honored by models whose `thinkingLevelMap`
206
+ * advertises it — unsupported levels are clamped at dispatch (ADR-026).
207
+ */
208
+ export type GSDThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
209
+
210
+ /** The nine model-routing phase buckets. */
211
+ export const GSD_MODEL_PHASE_KEYS = [
212
+ "research",
213
+ "planning",
214
+ "discuss",
215
+ "execution",
216
+ "execution_simple",
217
+ "completion",
218
+ "validation",
219
+ "subagent",
220
+ "uat",
221
+ ] as const;
222
+ export type GSDModelPhaseKey = (typeof GSD_MODEL_PHASE_KEYS)[number];
223
+
201
224
  /**
202
225
  * Model configuration for a single phase.
203
226
  * Supports primary model with optional fallbacks for resilience.
@@ -209,8 +232,22 @@ export interface GSDPhaseModelConfig {
209
232
  provider?: string;
210
233
  /** Fallback models to try in order if primary fails (e.g., rate limits, credits exhausted) */
211
234
  fallbacks?: string[];
235
+ /**
236
+ * Reasoning effort for this phase (ADR-026). Travels with the model: an
237
+ * explicit value here bypasses the `execute-task` thinking floor and is
238
+ * capability-clamped to the resolved model at dispatch. Takes precedence
239
+ * over a same-phase entry in the separate `thinking` block.
240
+ */
241
+ thinking?: GSDThinkingLevel;
212
242
  }
213
243
 
244
+ /**
245
+ * Separate per-phase reasoning-effort block (ADR-026). An alternative to
246
+ * inline `models.<phase>.thinking` that lets thinking be pinned without
247
+ * pinning a model. Same nine phase keys as `models`.
248
+ */
249
+ export type GSDThinkingConfig = Partial<Record<GSDModelPhaseKey, GSDThinkingLevel>>;
250
+
214
251
  /**
215
252
  * Legacy model config -- simple string per phase.
216
253
  * Kept for backward compatibility; will be migrated to GSDModelConfigV2 on load.
@@ -372,6 +409,8 @@ export interface GSDPreferences {
372
409
  skill_rules?: GSDSkillRule[];
373
410
  custom_instructions?: string[];
374
411
  models?: GSDModelConfig | GSDModelConfigV2;
412
+ /** Per-phase reasoning effort, separate from `models` (ADR-026). */
413
+ thinking?: GSDThinkingConfig;
375
414
  skill_discovery?: SkillDiscoveryMode;
376
415
  skill_staleness_days?: number; // Skills unused for N days get deprioritized (#599). 0 = disabled. Default: 60.
377
416
  auto_supervisor?: AutoSupervisorConfig;
@@ -12,18 +12,30 @@ import type { DynamicRoutingConfig } from "./model-router.js";
12
12
  import { isAbsolute } from "node:path";
13
13
  import { VALID_BRANCH_NAME } from "./git-service.js";
14
14
  import { normalizeStringArray } from "../shared/format-utils.js";
15
+ import { getGateIdsForTurn } from "./gate-registry.js";
15
16
 
16
17
  import {
17
18
  KNOWN_PREFERENCE_KEYS,
18
19
  KNOWN_UNIT_LABELS,
20
+ GSD_MODEL_PHASE_KEYS,
19
21
 
20
22
  SKILL_ACTIONS,
21
23
  type WorkflowMode,
22
24
  type GSDPreferences,
23
25
  type GSDSkillRule,
26
+ type GSDThinkingLevel,
24
27
  } from "./preferences-types.js";
25
28
 
26
29
  const VALID_TOKEN_PROFILES = new Set<TokenProfile>(["budget", "balanced", "quality", "burn-max"]);
30
+ const VALID_THINKING_LEVELS = new Set<GSDThinkingLevel>([
31
+ "off",
32
+ "minimal",
33
+ "low",
34
+ "medium",
35
+ "high",
36
+ "xhigh",
37
+ ]);
38
+ const KNOWN_MODEL_PHASE_KEYS = new Set<string>(GSD_MODEL_PHASE_KEYS);
27
39
  const VALID_UOK_TURN_ACTIONS = new Set<"commit" | "snapshot" | "status-only">([
28
40
  "commit",
29
41
  "snapshot",
@@ -37,6 +49,7 @@ const VALID_POST_UNIT_HOOK_ON_BLOCK_ACTIONS = new Set([
37
49
  "queue-slice",
38
50
  "pause",
39
51
  ]);
52
+ const VALID_GATE_EVALUATE_SLICE_GATES = new Set<string>(getGateIdsForTurn("gate-evaluate"));
40
53
 
41
54
  export function validatePreferences(preferences: GSDPreferences): {
42
55
  preferences: GSDPreferences;
@@ -390,12 +403,66 @@ export function validatePreferences(preferences: GSDPreferences): {
390
403
  // ─── Models ─────────────────────────────────────────────────────────
391
404
  if (preferences.models !== undefined) {
392
405
  if (preferences.models && typeof preferences.models === "object") {
393
- validated.models = preferences.models;
406
+ // Static check for inline per-phase thinking (ADR-026). The resolved
407
+ // model isn't known until dispatch, so capability is clamped there; here
408
+ // we warn on illegal level strings AND strip them, so a typo can't reach
409
+ // resolveThinkingLevelForUnit and be treated as explicit configuration.
410
+ const sanitizedModels: Record<string, unknown> = {};
411
+ for (const [phase, entry] of Object.entries(preferences.models as Record<string, unknown>)) {
412
+ if (entry && typeof entry === "object" && "thinking" in entry) {
413
+ const level = (entry as { thinking?: unknown }).thinking;
414
+ if (level !== undefined && !VALID_THINKING_LEVELS.has(level as GSDThinkingLevel)) {
415
+ warnings.push(
416
+ `models.${phase}.thinking "${String(level)}" is not a valid thinking level ` +
417
+ `(off, minimal, low, medium, high, xhigh) — ignored`,
418
+ );
419
+ const { thinking: _ignored, ...rest } = entry as Record<string, unknown>;
420
+ // If stripping the bad thinking leaves no usable model, drop the
421
+ // phase entirely rather than storing a hollow `{}` / `{ provider }`
422
+ // entry that resolveWinningPhase would otherwise treat as configured.
423
+ if (rest.model) {
424
+ sanitizedModels[phase] = rest;
425
+ }
426
+ continue;
427
+ }
428
+ }
429
+ sanitizedModels[phase] = entry;
430
+ }
431
+ validated.models = sanitizedModels as GSDPreferences["models"];
394
432
  } else {
395
433
  errors.push("models must be an object");
396
434
  }
397
435
  }
398
436
 
437
+ // ─── Thinking (separate per-phase block, ADR-026) ───────────────────
438
+ if (preferences.thinking !== undefined) {
439
+ if (preferences.thinking && typeof preferences.thinking === "object" && !Array.isArray(preferences.thinking)) {
440
+ const validatedThinking: Record<string, GSDThinkingLevel> = {};
441
+ for (const [phase, level] of Object.entries(preferences.thinking as Record<string, unknown>)) {
442
+ if (!KNOWN_MODEL_PHASE_KEYS.has(phase)) {
443
+ warnings.push(
444
+ `unknown thinking phase "${phase}" — must be one of: ` +
445
+ `${[...KNOWN_MODEL_PHASE_KEYS].join(", ")} — ignored`,
446
+ );
447
+ continue;
448
+ }
449
+ if (!VALID_THINKING_LEVELS.has(level as GSDThinkingLevel)) {
450
+ warnings.push(
451
+ `thinking.${phase} "${String(level)}" is not a valid thinking level ` +
452
+ `(off, minimal, low, medium, high, xhigh) — ignored`,
453
+ );
454
+ continue;
455
+ }
456
+ validatedThinking[phase] = level as GSDThinkingLevel;
457
+ }
458
+ if (Object.keys(validatedThinking).length > 0) {
459
+ validated.thinking = validatedThinking;
460
+ }
461
+ } else {
462
+ errors.push("thinking must be an object");
463
+ }
464
+ }
465
+
399
466
  // ─── Auto Supervisor ────────────────────────────────────────────────
400
467
  if (preferences.auto_supervisor !== undefined) {
401
468
  if (preferences.auto_supervisor && typeof preferences.auto_supervisor === "object") {
@@ -907,7 +974,14 @@ export function validatePreferences(preferences: GSDPreferences): {
907
974
  }
908
975
  if (ge.slice_gates !== undefined) {
909
976
  if (Array.isArray(ge.slice_gates) && ge.slice_gates.every((g: unknown) => typeof g === "string")) {
910
- validGe.slice_gates = ge.slice_gates;
977
+ const invalid = ge.slice_gates.filter((g) => !VALID_GATE_EVALUATE_SLICE_GATES.has(g));
978
+ if (invalid.length === 0) {
979
+ validGe.slice_gates = ge.slice_gates;
980
+ } else {
981
+ errors.push(
982
+ `gate_evaluation.slice_gates must contain only gate-evaluate slice gates: ${[...VALID_GATE_EVALUATE_SLICE_GATES].join(", ")}`,
983
+ );
984
+ }
911
985
  } else {
912
986
  errors.push("gate_evaluation.slice_gates must be an array of strings");
913
987
  }
@@ -86,6 +86,8 @@ export function resolveSkillStalenessDays(basePath?: string): number {
86
86
  export {
87
87
  resolveModelForUnit,
88
88
  resolveModelWithFallbacksForUnit,
89
+ resolveThinkingLevelForUnit,
90
+ phaseChainForUnit,
89
91
  getNextFallbackModel,
90
92
  isTransientNetworkError,
91
93
  validateModelId,
@@ -437,6 +439,9 @@ function mergePreferences(base: GSDPreferences, override: GSDPreferences): GSDPr
437
439
  skill_rules: [...(base.skill_rules ?? []), ...(override.skill_rules ?? [])],
438
440
  custom_instructions: mergeStringLists(base.custom_instructions, override.custom_instructions),
439
441
  models: { ...(base.models ?? {}), ...(override.models ?? {}) },
442
+ thinking: (base.thinking || override.thinking)
443
+ ? { ...(base.thinking ?? {}), ...(override.thinking ?? {}) }
444
+ : undefined,
440
445
  skill_discovery: override.skill_discovery ?? base.skill_discovery,
441
446
  skill_staleness_days: override.skill_staleness_days ?? base.skill_staleness_days,
442
447
  auto_supervisor: { ...(base.auto_supervisor ?? {}), ...(override.auto_supervisor ?? {}) },
@@ -38,7 +38,7 @@ You are evaluating **quality gates in parallel** for this slice. Each gate is an
38
38
  3. **Verify each gate wrote its result** by checking that `gsd_save_gate_result` was called for each gate ID.
39
39
  - Call it **directly** — do **not** use `ToolSearch` (it is not available in GSD).
40
40
  - Inside Claude Code use the active MCP-scoped workflow name for `gsd_save_gate_result`; otherwise use `gsd_save_gate_result`.
41
- - Always pass all required fields (camelCase): `milestoneId`, `sliceId`, `gateId`, `verdict`, `rationale`. Never call with an empty `{}` object.
41
+ - Always pass all required fields (camelCase): `milestoneId`, `sliceId`, `gateId`, `verdict`, `rationale`, and `findings` (empty string if none). Never call with an empty `{}` object.
42
42
  4. **Report the batch outcome** — which gates passed, which flagged concerns, and which were omitted as not applicable.
43
43
 
44
44
  Gate agents may return `verdict: "omitted"` if the gate question is not applicable to this slice (e.g., no auth surface for Q3, no existing requirements touched for Q4). This is expected for simple slices.
@@ -44,7 +44,7 @@ If slice research is inlined, trust its architectural findings, but verify every
44
44
  6. Include Threat Surface (Q3), Requirement Impact (Q4), proof level, observability, integration closure, Failure Modes (Q5), Load Profile (Q6), and Negative Tests (Q7) only where applicable.
45
45
  7. Right-size tasks. Simple slices can be one task; split only when context, ownership, or verification boundaries justify it.
46
46
  8. Task `verify` commands must be safe, simple commands. Do not use shell pipes, redirects, semicolons, backticks, command substitution, output trimming, or grep regex alternation with `|`. If multiple checks are needed, create a small test file and run it with `node --test` or a package test script, or use separate simple commands joined only with `&&`. For absence checks, verify a pattern does not exist with `! grep -q 'pattern' file` or `! rg -q 'pattern' file`; do not use `grep -c` or `rg -c` to assert zero matches because count commands exit 1 when they find zero matches, and the verification gate treats that as failure.
47
- 9. Each task needs the exact `gsd_plan_slice.tasks[]` shape: `taskId`, `title`, `description`, `estimate`, `files`, `verify`, `inputs`, `expectedOutput`, and optional `observabilityImpact`. `description` should contain the Why / Do / Done-when narrative. `files`, `inputs`, and `expectedOutput` must be JSON arrays of strings, even when there is only one path (for example, `"inputs": ["src/index.ts"]`, never `"inputs": "src/index.ts"`). Use paths relative to `{{workingDirectory}}`; do not put absolute paths to the original checkout or any directory outside `{{workingDirectory}}` in `files`, `inputs`, `expectedOutput`, or verification commands. **`expectedOutput` must only list files the task actually creates or overwrites on disk.** Do NOT include files the task merely reads, verifies, or tests — those belong only in `inputs`. If a task is a pure verification or test task that produces no new files, `expectedOutput` may be `[]` or limited to test-result artifacts (e.g. a log or assertion output). A file that does not yet exist on disk and is needed as an `input` must be produced by an earlier task's `expectedOutput` — if no prior task creates it, add a task before this one that does.
47
+ 9. Each task needs the exact `gsd_plan_slice.tasks[]` shape: `taskId`, `title`, `description`, `estimate`, `files`, `verify`, `inputs`, `expectedOutput`, and optional `observabilityImpact`. `description` should contain the Why / Do / Done-when narrative. `files`, `inputs`, and `expectedOutput` must be JSON arrays of strings, even when there is only one path (for example, `"inputs": ["src/index.ts"]`, never `"inputs": "src/index.ts"`). Use paths relative to `{{workingDirectory}}`; do not put absolute paths to the original checkout or any directory outside `{{workingDirectory}}` in `files`, `inputs`, `expectedOutput`, or verification commands. **`expectedOutput` must only list files the task actually creates or overwrites on disk.** Do NOT include files the task merely reads, verifies, tests, or describes — those belong in `inputs`, `verify`, `description`, or slice success criteria. If a task is a pure verification or test task that produces no new files, `expectedOutput` must be `[]`; if it writes a test-result log or assertion output file, list only that concrete file path. A file that does not yet exist on disk and is needed as an `input` must be produced by an earlier task's `expectedOutput` — if no prior task creates it, add a task before this one that does.
48
48
  10. Persist with `gsd_plan_slice` using `milestoneId`, `sliceId`, `goal`, optional `successCriteria`/`proofLevel`/`integrationClosure`/`observabilityImpact`, and `tasks`. `gsd_plan_slice` handles task persistence transactionally and renders `{{outputPath}}` plus task plans; do not call `gsd_plan_task`. The DB-backed tool is the canonical write path. Do **not** rely on direct `PLAN.md` writes as the source of truth.
49
49
  11. Self-audit before finishing: goal/demo closure, requirement coverage, deliverable coverage audit (cross-check every file listed in CONTEXT.md `## Scope` / `### In Scope` against task `files` or `expectedOutput`), locked decisions, concrete paths, dependency order, wiring, scope size, proof truthfulness, feature completeness, and quality gates. Quality gates: non-trivial slices/tasks include specific Q3-Q7 coverage where applicable.
50
50
  12. If planning creates structural decisions, call `gsd_decision_save` for each; the tool persists the decision and regenerates `.gsd/DECISIONS.md`.
@@ -64,7 +64,7 @@ Then:
64
64
  2. {{skillActivation}} Record the installed skills you expect executors to use in each task plan's `skills_used` frontmatter.
65
65
  3. Define slice-level verification: the objective stopping condition. Plan real test files with real assertions; for simple slices, executable commands are fine.
66
66
  4. For non-trivial slices, plan observability / proof level / integration closure, threat surface, and requirement impact. Omit entirely for simple slices.
67
- 5. Decompose the slice into tasks that fit one context window each. Every task passed to `gsd_plan_slice` must use the exact keys `taskId`, `title`, `description`, `estimate`, `files`, `verify`, `inputs`, `expectedOutput`, and optional `observabilityImpact`. Put Why / Do / Done-when detail in `description`. `files`, `inputs`, and `expectedOutput` must be JSON arrays of strings, even for one path (for example, `"expectedOutput": ["src/index.ts"]`, never `"expectedOutput": "src/index.ts"`).
67
+ 5. Decompose the slice into tasks that fit one context window each. Every task passed to `gsd_plan_slice` must use the exact keys `taskId`, `title`, `description`, `estimate`, `files`, `verify`, `inputs`, `expectedOutput`, and optional `observabilityImpact`. Put Why / Do / Done-when detail in `description`. `files`, `inputs`, and `expectedOutput` must be JSON arrays of strings, even for one path (for example, `"expectedOutput": ["src/index.ts"]`, never `"expectedOutput": "src/index.ts"`). `expectedOutput` is path-only: list only files the task creates or overwrites, and use `[]` for pure verification tasks.
68
68
  6. **Persist planning state through `gsd_plan_slice`.** Call it with the full payload. The tool writes to the DB and renders `{{outputPath}}` and `{{slicePath}}/tasks/T##-PLAN.md` automatically. Do NOT rely on direct `PLAN.md` writes.
69
69
  7. **Self-audit the plan.** If every task were completed exactly as written, the slice goal/demo should be true. Every must-have maps to a task. Inputs and Expected Output are backtick-wrapped file paths.
70
70
  8. If refinement produced structural decisions that diverge from the sketch, call `gsd_decision_save` for each; the tool persists the decision and regenerates `.gsd/DECISIONS.md`.
@@ -193,7 +193,12 @@ export function parseRoadmapSlices(content: string): RoadmapSliceEntry[] {
193
193
  ? expandDependencies(depsMatch[1]!.split(",").map(s => s.trim()))
194
194
  : [];
195
195
 
196
- currentSlice = { id, title, risk, depends, done, demo: "" };
196
+ // ADR-011: the renderer writes a `[sketch]` badge for sketch slices.
197
+ // Parse it back so the is_sketch flag survives a markdown → DB re-import
198
+ // (e.g. /gsd recover); otherwise the flag was silently lost.
199
+ const isSketch = /\[sketch\]/i.test(rest);
200
+
201
+ currentSlice = { id, title, risk, depends, done, demo: "", isSketch };
197
202
  continue;
198
203
  }
199
204
 
@@ -11,7 +11,7 @@ import { logWarning } from "../workflow-logger.js";
11
11
  // ─── Types ──────────────────────────────────────────────────────────────────
12
12
 
13
13
  export interface ContentViolation {
14
- severity: "warning";
14
+ severity: "error" | "warning";
15
15
  reason: string;
16
16
  }
17
17
 
@@ -46,6 +46,9 @@ export function validateContent(
46
46
 
47
47
  type ContentValidatorFn = (content: string) => ContentViolation[];
48
48
 
49
+ const TASK_MARKER_RE = /^\s*(?:-\s+\[[ xX]\]\s+\*\*T\d+:|#{2,4}\s+T\d+\b)/gm;
50
+ const SLICE_MARKER_RE = /^\s*(?:-\s+\[[ xX]\]\s+\*\*S\d+:|#{2,4}\s+S\d+\b)/gm;
51
+
49
52
  const VALIDATORS: Record<string, ContentValidatorFn> = {
50
53
  "plan-slice": validatePlanSlice,
51
54
  "plan-milestone": validatePlanMilestone,
@@ -55,10 +58,10 @@ function validatePlanSlice(content: string): ContentViolation[] {
55
58
  const violations: ContentViolation[] = [];
56
59
 
57
60
  // Must have at least 1 task entry — single-task slices are valid (#3649)
58
- const taskCount = (content.match(/- \[[ x]\] \*\*T\d+/g) || []).length;
61
+ const taskCount = (content.match(TASK_MARKER_RE) || []).length;
59
62
  if (taskCount < 1) {
60
63
  violations.push({
61
- severity: "warning",
64
+ severity: "error",
62
65
  reason: `Slice plan has ${taskCount} task(s) — expected at least 1`,
63
66
  });
64
67
  }
@@ -86,10 +89,10 @@ function validatePlanMilestone(content: string): ContentViolation[] {
86
89
  const violations: ContentViolation[] = [];
87
90
 
88
91
  // Must have at least 1 slice entry
89
- const sliceCount = (content.match(/##\s+S\d+/g) || []).length;
92
+ const sliceCount = (content.match(SLICE_MARKER_RE) || []).length;
90
93
  if (sliceCount < 1) {
91
94
  violations.push({
92
- severity: "warning",
95
+ severity: "error",
93
96
  reason: `Milestone roadmap has ${sliceCount} slice(s) — expected at least 1`,
94
97
  });
95
98
  }