gsd-pi 2.59.0-dev.d77b3dd → 2.60.0-dev.2580e65

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 (198) hide show
  1. package/dist/resources/extensions/ask-user-questions.js +7 -4
  2. package/dist/resources/extensions/gsd/auto/phases.js +15 -7
  3. package/dist/resources/extensions/gsd/auto-dashboard.js +21 -8
  4. package/dist/resources/extensions/gsd/auto-dispatch.js +6 -3
  5. package/dist/resources/extensions/gsd/auto-model-selection.js +58 -9
  6. package/dist/resources/extensions/gsd/auto-post-unit.js +3 -2
  7. package/dist/resources/extensions/gsd/auto-prompts.js +36 -20
  8. package/dist/resources/extensions/gsd/auto-recovery.js +37 -18
  9. package/dist/resources/extensions/gsd/auto-start.js +9 -5
  10. package/dist/resources/extensions/gsd/auto-timers.js +11 -5
  11. package/dist/resources/extensions/gsd/auto-unit-closeout.js +5 -3
  12. package/dist/resources/extensions/gsd/auto-verification.js +3 -2
  13. package/dist/resources/extensions/gsd/auto-worktree.js +120 -55
  14. package/dist/resources/extensions/gsd/auto.js +39 -17
  15. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +6 -3
  16. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +2 -2
  17. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +4 -10
  18. package/dist/resources/extensions/gsd/bootstrap/journal-tools.js +2 -1
  19. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +7 -0
  20. package/dist/resources/extensions/gsd/bootstrap/system-context.js +11 -10
  21. package/dist/resources/extensions/gsd/commands/catalog.js +2 -0
  22. package/dist/resources/extensions/gsd/commands-codebase.js +48 -21
  23. package/dist/resources/extensions/gsd/commands-inspect.js +2 -1
  24. package/dist/resources/extensions/gsd/commands-maintenance.js +32 -19
  25. package/dist/resources/extensions/gsd/complexity-classifier.js +8 -4
  26. package/dist/resources/extensions/gsd/custom-verification.js +3 -2
  27. package/dist/resources/extensions/gsd/gsd-db.js +33 -13
  28. package/dist/resources/extensions/gsd/guided-flow.js +19 -9
  29. package/dist/resources/extensions/gsd/init-wizard.js +12 -0
  30. package/dist/resources/extensions/gsd/markdown-renderer.js +11 -9
  31. package/dist/resources/extensions/gsd/md-importer.js +5 -4
  32. package/dist/resources/extensions/gsd/milestone-actions.js +3 -2
  33. package/dist/resources/extensions/gsd/milestone-ids.js +2 -1
  34. package/dist/resources/extensions/gsd/model-router.js +156 -121
  35. package/dist/resources/extensions/gsd/parallel-merge.js +5 -3
  36. package/dist/resources/extensions/gsd/parallel-orchestrator.js +26 -14
  37. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  38. package/dist/resources/extensions/gsd/preferences-validation.js +45 -0
  39. package/dist/resources/extensions/gsd/preferences.js +15 -3
  40. package/dist/resources/extensions/gsd/prompt-loader.js +3 -2
  41. package/dist/resources/extensions/gsd/prompts/rethink.md +1 -1
  42. package/dist/resources/extensions/gsd/rule-registry.js +7 -6
  43. package/dist/resources/extensions/gsd/safe-fs.js +6 -8
  44. package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -2
  45. package/dist/resources/extensions/gsd/tools/complete-slice.js +3 -2
  46. package/dist/resources/extensions/gsd/tools/complete-task.js +3 -2
  47. package/dist/resources/extensions/gsd/tools/plan-milestone.js +3 -2
  48. package/dist/resources/extensions/gsd/tools/plan-slice.js +3 -2
  49. package/dist/resources/extensions/gsd/tools/plan-task.js +2 -1
  50. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +4 -4
  51. package/dist/resources/extensions/gsd/tools/reopen-slice.js +2 -1
  52. package/dist/resources/extensions/gsd/tools/reopen-task.js +2 -1
  53. package/dist/resources/extensions/gsd/tools/replan-slice.js +2 -1
  54. package/dist/resources/extensions/gsd/tools/validate-milestone.js +2 -1
  55. package/dist/resources/extensions/gsd/triage-resolution.js +11 -4
  56. package/dist/resources/extensions/gsd/workflow-events.js +2 -1
  57. package/dist/resources/extensions/gsd/workflow-logger.js +37 -4
  58. package/dist/resources/extensions/gsd/workflow-migration.js +14 -12
  59. package/dist/resources/extensions/gsd/workflow-projections.js +2 -2
  60. package/dist/resources/extensions/gsd/workflow-reconcile.js +2 -2
  61. package/dist/resources/extensions/gsd/worktree-manager.js +26 -14
  62. package/dist/resources/extensions/shared/interview-ui.js +3 -1
  63. package/dist/web/standalone/.next/BUILD_ID +1 -1
  64. package/dist/web/standalone/.next/app-path-routes-manifest.json +19 -19
  65. package/dist/web/standalone/.next/build-manifest.json +2 -2
  66. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  67. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  68. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/index.html +1 -1
  84. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app-paths-manifest.json +19 -19
  91. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  92. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  93. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  94. package/package.json +1 -1
  95. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  96. package/packages/pi-coding-agent/dist/core/extensions/loader.js +5 -0
  97. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  98. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +2 -1
  99. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  100. package/packages/pi-coding-agent/dist/core/extensions/runner.js +16 -0
  101. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  102. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +26 -0
  103. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  104. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  105. package/packages/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -1
  106. package/packages/pi-coding-agent/dist/core/lsp/config.js +6 -1
  107. package/packages/pi-coding-agent/dist/core/lsp/config.js.map +1 -1
  108. package/packages/pi-coding-agent/dist/core/lsp/defaults.json +2 -2
  109. package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.d.ts +2 -0
  110. package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.d.ts.map +1 -0
  111. package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.js +47 -0
  112. package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.js.map +1 -0
  113. package/packages/pi-coding-agent/package.json +1 -1
  114. package/packages/pi-coding-agent/src/core/extensions/loader.ts +6 -0
  115. package/packages/pi-coding-agent/src/core/extensions/runner.ts +19 -0
  116. package/packages/pi-coding-agent/src/core/extensions/types.ts +26 -0
  117. package/packages/pi-coding-agent/src/core/lsp/config.ts +7 -1
  118. package/packages/pi-coding-agent/src/core/lsp/defaults.json +2 -2
  119. package/packages/pi-coding-agent/src/core/lsp/lsp-legacy-alias.test.ts +70 -0
  120. package/pkg/package.json +1 -1
  121. package/src/resources/extensions/ask-user-questions.ts +7 -3
  122. package/src/resources/extensions/gsd/auto/phases.ts +17 -7
  123. package/src/resources/extensions/gsd/auto-dashboard.ts +22 -8
  124. package/src/resources/extensions/gsd/auto-dispatch.ts +7 -3
  125. package/src/resources/extensions/gsd/auto-model-selection.ts +77 -15
  126. package/src/resources/extensions/gsd/auto-post-unit.ts +4 -4
  127. package/src/resources/extensions/gsd/auto-prompts.ts +37 -20
  128. package/src/resources/extensions/gsd/auto-recovery.ts +38 -18
  129. package/src/resources/extensions/gsd/auto-start.ts +10 -9
  130. package/src/resources/extensions/gsd/auto-timers.ts +12 -5
  131. package/src/resources/extensions/gsd/auto-unit-closeout.ts +6 -2
  132. package/src/resources/extensions/gsd/auto-verification.ts +3 -6
  133. package/src/resources/extensions/gsd/auto-worktree.ts +121 -55
  134. package/src/resources/extensions/gsd/auto.ts +40 -17
  135. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +4 -3
  136. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +2 -2
  137. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +4 -16
  138. package/src/resources/extensions/gsd/bootstrap/journal-tools.ts +2 -1
  139. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +8 -0
  140. package/src/resources/extensions/gsd/bootstrap/system-context.ts +11 -10
  141. package/src/resources/extensions/gsd/commands/catalog.ts +2 -0
  142. package/src/resources/extensions/gsd/commands-codebase.ts +52 -20
  143. package/src/resources/extensions/gsd/commands-inspect.ts +2 -1
  144. package/src/resources/extensions/gsd/commands-maintenance.ts +28 -19
  145. package/src/resources/extensions/gsd/complexity-classifier.ts +9 -4
  146. package/src/resources/extensions/gsd/custom-verification.ts +3 -2
  147. package/src/resources/extensions/gsd/gsd-db.ts +12 -14
  148. package/src/resources/extensions/gsd/guided-flow.ts +9 -8
  149. package/src/resources/extensions/gsd/init-wizard.ts +12 -0
  150. package/src/resources/extensions/gsd/markdown-renderer.ts +11 -17
  151. package/src/resources/extensions/gsd/md-importer.ts +5 -4
  152. package/src/resources/extensions/gsd/milestone-actions.ts +3 -2
  153. package/src/resources/extensions/gsd/milestone-ids.ts +2 -1
  154. package/src/resources/extensions/gsd/model-router.ts +199 -173
  155. package/src/resources/extensions/gsd/parallel-merge.ts +5 -3
  156. package/src/resources/extensions/gsd/parallel-orchestrator.ts +18 -14
  157. package/src/resources/extensions/gsd/preferences-types.ts +13 -0
  158. package/src/resources/extensions/gsd/preferences-validation.ts +45 -0
  159. package/src/resources/extensions/gsd/preferences.ts +16 -3
  160. package/src/resources/extensions/gsd/prompt-loader.ts +3 -2
  161. package/src/resources/extensions/gsd/prompts/rethink.md +1 -1
  162. package/src/resources/extensions/gsd/rule-registry.ts +7 -6
  163. package/src/resources/extensions/gsd/safe-fs.ts +6 -5
  164. package/src/resources/extensions/gsd/tests/capability-router.test.ts +347 -0
  165. package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +63 -0
  166. package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +27 -2
  167. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +4 -4
  168. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +1188 -0
  169. package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +841 -0
  170. package/src/resources/extensions/gsd/tests/model-router.test.ts +403 -3
  171. package/src/resources/extensions/gsd/tests/preferences.test.ts +62 -0
  172. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +21 -0
  173. package/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts +284 -0
  174. package/src/resources/extensions/gsd/tests/workflow-logger-audit.test.ts +120 -0
  175. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +6 -6
  176. package/src/resources/extensions/gsd/tools/complete-milestone.ts +3 -6
  177. package/src/resources/extensions/gsd/tools/complete-slice.ts +3 -6
  178. package/src/resources/extensions/gsd/tools/complete-task.ts +3 -6
  179. package/src/resources/extensions/gsd/tools/plan-milestone.ts +3 -6
  180. package/src/resources/extensions/gsd/tools/plan-slice.ts +3 -6
  181. package/src/resources/extensions/gsd/tools/plan-task.ts +2 -3
  182. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +4 -6
  183. package/src/resources/extensions/gsd/tools/reopen-slice.ts +2 -3
  184. package/src/resources/extensions/gsd/tools/reopen-task.ts +2 -3
  185. package/src/resources/extensions/gsd/tools/replan-slice.ts +2 -3
  186. package/src/resources/extensions/gsd/tools/validate-milestone.ts +2 -3
  187. package/src/resources/extensions/gsd/triage-resolution.ts +11 -4
  188. package/src/resources/extensions/gsd/types.ts +1 -0
  189. package/src/resources/extensions/gsd/workflow-events.ts +2 -1
  190. package/src/resources/extensions/gsd/workflow-logger.ts +52 -5
  191. package/src/resources/extensions/gsd/workflow-migration.ts +14 -12
  192. package/src/resources/extensions/gsd/workflow-projections.ts +2 -2
  193. package/src/resources/extensions/gsd/workflow-reconcile.ts +2 -2
  194. package/src/resources/extensions/gsd/worktree-manager.ts +16 -14
  195. package/src/resources/extensions/shared/interview-ui.ts +3 -1
  196. package/src/resources/extensions/shared/tests/interview-notes-loop.test.ts +144 -0
  197. /package/dist/web/standalone/.next/static/{t_cBZAENjaOJIRST3dw08 → ogyMN7M-3bGGuRY08L5HR}/_buildManifest.js +0 -0
  198. /package/dist/web/standalone/.next/static/{t_cBZAENjaOJIRST3dw08 → ogyMN7M-3bGGuRY08L5HR}/_ssgManifest.js +0 -0
@@ -76,6 +76,9 @@ const MODEL_COST_PER_1K_INPUT = {
76
76
  "gemini-2.5-pro": 0.00125,
77
77
  "deepseek-chat": 0.00014,
78
78
  };
79
+ // ─── Capability Profiles Data Table ──────────────────────────────────────────
80
+ // Per-model capability profiles (0–100 scale). Used for capability-aware
81
+ // model selection within an eligible tier set.
79
82
  export const MODEL_CAPABILITY_PROFILES = {
80
83
  "claude-opus-4-6": { coding: 95, debugging: 90, research: 85, reasoning: 95, speed: 30, longContext: 80, instruction: 90 },
81
84
  "claude-sonnet-4-6": { coding: 85, debugging: 80, research: 75, reasoning: 80, speed: 60, longContext: 75, instruction: 85 },
@@ -87,7 +90,10 @@ export const MODEL_CAPABILITY_PROFILES = {
87
90
  "deepseek-chat": { coding: 75, debugging: 65, research: 55, reasoning: 70, speed: 70, longContext: 55, instruction: 65 },
88
91
  "o3": { coding: 80, debugging: 85, research: 80, reasoning: 92, speed: 25, longContext: 70, instruction: 85 },
89
92
  };
90
- const BASE_REQUIREMENTS = {
93
+ // ─── Base Task Requirements Data Table ───────────────────────────────────────
94
+ // Per-unit-type base requirement vectors. Weights indicate how important each
95
+ // capability dimension is for this unit type.
96
+ export const BASE_REQUIREMENTS = {
91
97
  "execute-task": { coding: 0.9, instruction: 0.7, speed: 0.3 },
92
98
  "research-milestone": { research: 0.9, longContext: 0.7, reasoning: 0.5 },
93
99
  "research-slice": { research: 0.9, longContext: 0.7, reasoning: 0.5 },
@@ -100,11 +106,28 @@ const BASE_REQUIREMENTS = {
100
106
  "discuss-milestone": { reasoning: 0.6, instruction: 0.7 },
101
107
  "complete-milestone": { instruction: 0.8, reasoning: 0.5 },
102
108
  };
109
+ // ─── Public API ──────────────────────────────────────────────────────────────
110
+ /**
111
+ * Score a model's suitability for a task given a requirement vector.
112
+ * Returns a weighted average of capability dimensions (0–100).
113
+ * Returns 50 if requirements are empty (neutral score).
114
+ */
115
+ export function scoreModel(model, requirements) {
116
+ let weightedSum = 0;
117
+ let weightSum = 0;
118
+ for (const [dim, weight] of Object.entries(requirements)) {
119
+ const capability = model[dim] ?? 50;
120
+ weightedSum += weight * capability;
121
+ weightSum += weight;
122
+ }
123
+ return weightSum > 0 ? weightedSum / weightSum : 50;
124
+ }
103
125
  /**
104
- * Compute a task requirement vector from unit type and optional metadata.
126
+ * Compute dynamic task requirements from unit type and optional task metadata.
127
+ * Returns a requirement vector refined by task-specific signals.
105
128
  */
106
129
  export function computeTaskRequirements(unitType, metadata) {
107
- const base = { ...(BASE_REQUIREMENTS[unitType] ?? { reasoning: 0.5 }) };
130
+ const base = BASE_REQUIREMENTS[unitType] ?? { reasoning: 0.5 };
108
131
  if (unitType === "execute-task" && metadata) {
109
132
  if (metadata.tags?.some(t => /^(docs?|readme|comment|config|typo|rename)$/i.test(t))) {
110
133
  return { ...base, instruction: 0.9, coding: 0.3, speed: 0.7 };
@@ -122,32 +145,108 @@ export function computeTaskRequirements(unitType, metadata) {
122
145
  return base;
123
146
  }
124
147
  /**
125
- * Score a model against a task requirement vector.
126
- * Returns weighted average in range 0–100. Returns 50 for empty requirements.
148
+ * Score all eligible models against a requirement vector and return them
149
+ * sorted by score descending. Within 2 points: prefer cheaper; equal cost:
150
+ * lexicographic tie-break by model ID.
127
151
  */
128
- export function scoreModel(capabilities, requirements) {
129
- let weightedSum = 0;
130
- let weightSum = 0;
131
- for (const [dim, weight] of Object.entries(requirements)) {
132
- const capability = capabilities[dim] ?? 50;
133
- weightedSum += weight * capability;
134
- weightSum += weight;
152
+ export function scoreEligibleModels(eligibleModelIds, requirements, capabilityOverrides) {
153
+ const scored = eligibleModelIds.map(modelId => {
154
+ const builtin = MODEL_CAPABILITY_PROFILES[modelId];
155
+ const override = capabilityOverrides?.[modelId];
156
+ const profile = builtin
157
+ ? override ? { ...builtin, ...override } : builtin
158
+ : { coding: 50, debugging: 50, research: 50, reasoning: 50, speed: 50, longContext: 50, instruction: 50 };
159
+ return { modelId, score: scoreModel(profile, requirements) };
160
+ });
161
+ scored.sort((a, b) => {
162
+ const scoreDiff = b.score - a.score;
163
+ if (Math.abs(scoreDiff) > 2)
164
+ return scoreDiff;
165
+ const costA = MODEL_COST_PER_1K_INPUT[a.modelId] ?? Infinity;
166
+ const costB = MODEL_COST_PER_1K_INPUT[b.modelId] ?? Infinity;
167
+ if (costA !== costB)
168
+ return costA - costB;
169
+ return a.modelId.localeCompare(b.modelId);
170
+ });
171
+ return scored;
172
+ }
173
+ /**
174
+ * Return all models eligible for a given tier, sorted cheapest first.
175
+ * If routingConfig.tier_models[tier] is set and available, returns only that
176
+ * model. Otherwise filters availableModelIds by tier from MODEL_CAPABILITY_TIER.
177
+ */
178
+ export function getEligibleModels(tier, availableModelIds, routingConfig) {
179
+ // 1. Check explicit tier_models config
180
+ const explicitModel = routingConfig.tier_models?.[tier];
181
+ if (explicitModel) {
182
+ // Exact match
183
+ if (availableModelIds.includes(explicitModel))
184
+ return [explicitModel];
185
+ // Provider-prefix-stripped match
186
+ const match = availableModelIds.find(id => {
187
+ const bareAvail = id.includes("/") ? id.split("/").pop() : id;
188
+ const bareExplicit = explicitModel.includes("/") ? explicitModel.split("/").pop() : explicitModel;
189
+ return bareAvail === bareExplicit;
190
+ });
191
+ if (match)
192
+ return [match];
135
193
  }
136
- return weightSum > 0 ? weightedSum / weightSum : 50;
194
+ // 2. Auto-detect: filter by tier, sort cheapest first
195
+ return availableModelIds
196
+ .filter(id => getModelTier(id) === tier)
197
+ .sort((a, b) => {
198
+ const costA = getModelCost(a);
199
+ const costB = getModelCost(b);
200
+ return costA - costB;
201
+ });
202
+ }
203
+ /**
204
+ * Build a fallback chain for a selected model: [selectedModel, ...configuredFallbacks, configuredPrimary]
205
+ * Deduplicates entries while preserving order.
206
+ */
207
+ function buildFallbackChain(selectedModelId, phaseConfig) {
208
+ return [
209
+ ...phaseConfig.fallbacks.filter(f => f !== selectedModelId),
210
+ phaseConfig.primary,
211
+ ].filter(f => f !== selectedModelId);
212
+ }
213
+ /**
214
+ * Load capability overrides from user preferences' modelOverrides section.
215
+ * Returns a map of model ID → partial capability overrides to deep-merge with built-in profiles.
216
+ *
217
+ * Per D-17: partial capability overrides via models.json modelOverrides, deep-merged with defaults.
218
+ */
219
+ export function loadCapabilityOverrides(prefs) {
220
+ const result = {};
221
+ if (!prefs.modelOverrides)
222
+ return result;
223
+ for (const [modelId, overrideEntry] of Object.entries(prefs.modelOverrides)) {
224
+ if (overrideEntry.capabilities) {
225
+ result[modelId] = overrideEntry.capabilities;
226
+ }
227
+ }
228
+ return result;
137
229
  }
138
- // ─── Public API ──────────────────────────────────────────────────────────────
139
230
  /**
140
231
  * Resolve the model to use for a given complexity tier.
141
232
  *
142
233
  * Downgrade-only: the returned model is always equal to or cheaper than
143
234
  * the user's configured primary model. Never upgrades beyond configuration.
144
235
  *
145
- * @param classification The complexity classification result
146
- * @param phaseConfig The user's configured model for this phase (ceiling)
147
- * @param routingConfig Dynamic routing configuration
148
- * @param availableModelIds List of available model IDs (from registry)
236
+ * STEP 1: Filter to eligible models for the requested tier.
237
+ * STEP 2: Capability scoring ranks eligible models by task-capability match
238
+ * when capability_routing is enabled and multiple eligible models exist.
239
+ * STEP 3: Fallback chain assembly.
240
+ *
241
+ * @param classification The complexity classification result
242
+ * @param phaseConfig The user's configured model for this phase (ceiling)
243
+ * @param routingConfig Dynamic routing configuration
244
+ * @param availableModelIds List of available model IDs (from registry)
245
+ * @param unitType The unit type for capability requirement computation (optional)
246
+ * @param taskMetadata Task metadata for refined requirement vectors (optional)
247
+ * @param capabilityOverrides User-provided capability overrides (deep-merged with built-in profiles, optional)
149
248
  */
150
- export function resolveModelForComplexity(classification, phaseConfig, routingConfig, availableModelIds, unitType, metadata) {
249
+ export function resolveModelForComplexity(classification, phaseConfig, routingConfig, availableModelIds, unitType, taskMetadata, capabilityOverrides) {
151
250
  // If no phase config or routing disabled, pass through
152
251
  if (!phaseConfig || !routingConfig.enabled) {
153
252
  return {
@@ -156,6 +255,7 @@ export function resolveModelForComplexity(classification, phaseConfig, routingCo
156
255
  tier: classification.tier,
157
256
  wasDowngraded: false,
158
257
  reason: "dynamic routing disabled or no phase config",
258
+ selectionMethod: "tier-only",
159
259
  };
160
260
  }
161
261
  const configuredPrimary = phaseConfig.primary;
@@ -173,6 +273,7 @@ export function resolveModelForComplexity(classification, phaseConfig, routingCo
173
273
  tier: requestedTier,
174
274
  wasDowngraded: false,
175
275
  reason: `configured model "${configuredPrimary}" is not in the known tier map — honoring explicit config`,
276
+ selectionMethod: "tier-only",
176
277
  };
177
278
  }
178
279
  // Downgrade-only: if requested tier >= configured tier, no change
@@ -183,47 +284,55 @@ export function resolveModelForComplexity(classification, phaseConfig, routingCo
183
284
  tier: requestedTier,
184
285
  wasDowngraded: false,
185
286
  reason: `tier ${requestedTier} >= configured ${configuredTier}`,
287
+ selectionMethod: "tier-only",
186
288
  };
187
289
  }
188
- // Find the best model for the requested tier
189
- const useCapabilityScoring = routingConfig.capability_routing && unitType;
190
- let targetModelId;
191
- let capabilityScores;
192
- let taskRequirements;
193
- let selectionMethod = "tier-only";
194
- if (useCapabilityScoring) {
195
- const result = findModelForTierWithCapability(requestedTier, routingConfig, availableModelIds, routingConfig.cross_provider !== false, unitType, metadata);
196
- targetModelId = result.modelId;
197
- capabilityScores = Object.keys(result.scores).length > 0 ? result.scores : undefined;
198
- taskRequirements = Object.keys(result.requirements).length > 0 ? result.requirements : undefined;
199
- selectionMethod = capabilityScores ? "capability-scored" : "tier-only";
200
- }
201
- else {
202
- targetModelId = findModelForTier(requestedTier, routingConfig, availableModelIds, routingConfig.cross_provider !== false);
203
- }
204
- if (!targetModelId) {
290
+ // STEP 1: Get all eligible models for the requested tier
291
+ const eligible = getEligibleModels(requestedTier, availableModelIds, routingConfig);
292
+ if (eligible.length === 0) {
293
+ // No suitable model found — use configured primary
205
294
  return {
206
295
  modelId: configuredPrimary,
207
296
  fallbacks: phaseConfig.fallbacks,
208
297
  tier: requestedTier,
209
298
  wasDowngraded: false,
210
299
  reason: `no ${requestedTier}-tier model available`,
211
- selectionMethod,
300
+ selectionMethod: "tier-only",
212
301
  };
213
302
  }
214
- const fallbacks = [
215
- ...phaseConfig.fallbacks.filter(f => f !== targetModelId),
216
- configuredPrimary,
217
- ].filter(f => f !== targetModelId);
303
+ // STEP 2: Capability scoring (when enabled and multiple eligible models exist)
304
+ if (routingConfig.capability_routing !== false && eligible.length > 1 && unitType) {
305
+ const requirements = computeTaskRequirements(unitType, taskMetadata);
306
+ const scored = scoreEligibleModels(eligible, requirements, capabilityOverrides);
307
+ const winner = scored[0];
308
+ if (winner) {
309
+ const capScores = {};
310
+ for (const s of scored)
311
+ capScores[s.modelId] = s.score;
312
+ const fallbacks = buildFallbackChain(winner.modelId, phaseConfig);
313
+ return {
314
+ modelId: winner.modelId,
315
+ fallbacks,
316
+ tier: requestedTier,
317
+ wasDowngraded: true,
318
+ reason: `capability-scored: ${winner.modelId} (${winner.score.toFixed(1)}) for ${unitType}`,
319
+ capabilityScores: capScores,
320
+ taskRequirements: requirements,
321
+ selectionMethod: "capability-scored",
322
+ };
323
+ }
324
+ }
325
+ // STEP 3: Fallback — use first eligible model (cheapest in tier, or single eligible)
326
+ const targetModelId = eligible[0];
327
+ // Build fallback chain: [downgraded_model, ...configured_fallbacks, configured_primary]
328
+ const fallbacks = buildFallbackChain(targetModelId, phaseConfig);
218
329
  return {
219
330
  modelId: targetModelId,
220
331
  fallbacks,
221
332
  tier: requestedTier,
222
333
  wasDowngraded: true,
223
334
  reason: classification.reason,
224
- selectionMethod,
225
- capabilityScores,
226
- taskRequirements,
335
+ selectionMethod: "tier-only",
227
336
  };
228
337
  }
229
338
  /**
@@ -243,7 +352,7 @@ export function escalateTier(currentTier) {
243
352
  export function defaultRoutingConfig() {
244
353
  return {
245
354
  enabled: true,
246
- capability_routing: false,
355
+ capability_routing: true,
247
356
  escalate_on_failure: true,
248
357
  budget_pressure: true,
249
358
  cross_provider: true,
@@ -262,8 +371,8 @@ function getModelTier(modelId) {
262
371
  if (bareId.includes(knownId) || knownId.includes(bareId))
263
372
  return tier;
264
373
  }
265
- // Unknown models are assumed heavy (safest assumption)
266
- return "heavy";
374
+ // Unknown models are assumed standard (per D-15: avoids silently ignoring user config)
375
+ return "standard";
267
376
  }
268
377
  /** Check if a model ID has a known capability tier mapping. (#2192) */
269
378
  function isKnownModel(modelId) {
@@ -276,80 +385,6 @@ function isKnownModel(modelId) {
276
385
  }
277
386
  return false;
278
387
  }
279
- function findModelForTier(tier, config, availableModelIds, crossProvider) {
280
- // 1. Check explicit tier_models config
281
- const explicitModel = config.tier_models?.[tier];
282
- if (explicitModel && availableModelIds.includes(explicitModel)) {
283
- return explicitModel;
284
- }
285
- // Also check with provider prefix stripped
286
- if (explicitModel) {
287
- const match = availableModelIds.find(id => {
288
- const bareAvail = id.includes("/") ? id.split("/").pop() : id;
289
- const bareExplicit = explicitModel.includes("/") ? explicitModel.split("/").pop() : explicitModel;
290
- return bareAvail === bareExplicit;
291
- });
292
- if (match)
293
- return match;
294
- }
295
- // 2. Auto-detect: find the cheapest available model in the requested tier
296
- const candidates = availableModelIds
297
- .filter(id => {
298
- const modelTier = getModelTier(id);
299
- return modelTier === tier;
300
- })
301
- .sort((a, b) => {
302
- if (!crossProvider)
303
- return 0;
304
- const costA = getModelCost(a);
305
- const costB = getModelCost(b);
306
- return costA - costB;
307
- });
308
- return candidates[0] ?? null;
309
- }
310
- function findModelForTierWithCapability(tier, config, availableModelIds, crossProvider, unitType, metadata) {
311
- const explicitModel = config.tier_models?.[tier];
312
- if (explicitModel) {
313
- const match = availableModelIds.find(id => {
314
- const bareAvail = id.includes("/") ? id.split("/").pop() : id;
315
- const bareExplicit = explicitModel.includes("/") ? explicitModel.split("/").pop() : explicitModel;
316
- return bareAvail === bareExplicit || id === explicitModel;
317
- });
318
- if (match)
319
- return { modelId: match, scores: {}, requirements: {} };
320
- }
321
- const requirements = computeTaskRequirements(unitType, metadata);
322
- const candidates = availableModelIds.filter(id => getModelTier(id) === tier);
323
- if (candidates.length === 0)
324
- return { modelId: null, scores: {}, requirements };
325
- const scores = {};
326
- for (const id of candidates) {
327
- const bareId = id.includes("/") ? id.split("/").pop() : id;
328
- const profile = getModelProfile(bareId);
329
- scores[id] = scoreModel(profile, requirements);
330
- }
331
- candidates.sort((a, b) => {
332
- const scoreDiff = scores[b] - scores[a];
333
- if (Math.abs(scoreDiff) > 2)
334
- return scoreDiff;
335
- if (crossProvider) {
336
- const costDiff = getModelCost(a) - getModelCost(b);
337
- if (costDiff !== 0)
338
- return costDiff;
339
- }
340
- return a.localeCompare(b);
341
- });
342
- return { modelId: candidates[0], scores, requirements };
343
- }
344
- function getModelProfile(bareId) {
345
- if (MODEL_CAPABILITY_PROFILES[bareId])
346
- return MODEL_CAPABILITY_PROFILES[bareId];
347
- for (const [knownId, profile] of Object.entries(MODEL_CAPABILITY_PROFILES)) {
348
- if (bareId.includes(knownId) || knownId.includes(bareId))
349
- return profile;
350
- }
351
- return { coding: 50, debugging: 50, research: 50, reasoning: 50, speed: 50, longContext: 50, instruction: 50 };
352
- }
353
388
  function getModelCost(modelId) {
354
389
  const bareId = modelId.includes("/") ? modelId.split("/").pop() : modelId;
355
390
  if (MODEL_COST_PER_1K_INPUT[bareId] !== undefined) {
@@ -13,6 +13,7 @@ import { mergeMilestoneToMain } from "./auto-worktree.js";
13
13
  import { MergeConflictError } from "./git-service.js";
14
14
  import { removeSessionStatus } from "./session-status-io.js";
15
15
  import { getErrorMessage } from "./error-utils.js";
16
+ import { logWarning } from "./workflow-logger.js";
16
17
  // ─── Merge Queue ───────────────────────────────────────────────────────────
17
18
  /**
18
19
  * Check whether a milestone is complete by querying its worktree SQLite DB.
@@ -27,7 +28,8 @@ export function isMilestoneCompleteInWorktreeDb(basePath, mid) {
27
28
  const result = spawnSync("sqlite3", [dbPath, `SELECT status FROM milestones WHERE id='${mid}' LIMIT 1`], { timeout: 3000, encoding: "utf-8" });
28
29
  return (result.stdout || "").trim() === "complete";
29
30
  }
30
- catch {
31
+ catch (e) {
32
+ logWarning("parallel", `spawnSync milestone completion check failed for ${mid}: ${e.message}`);
31
33
  return false;
32
34
  }
33
35
  }
@@ -45,8 +47,8 @@ function discoverDbCompletedMilestones(basePath) {
45
47
  }
46
48
  }
47
49
  }
48
- catch {
49
- // worktrees dir may not exist
50
+ catch (e) {
51
+ logWarning("parallel", `readdirSync for completed set failed: ${e.message}`);
50
52
  }
51
53
  return completed;
52
54
  }
@@ -19,6 +19,7 @@ import { resolveParallelConfig } from "./preferences.js";
19
19
  import { writeSessionStatus, readAllSessionStatuses, readSessionStatus, removeSessionStatus, sendSignal, cleanupStaleSessions, } from "./session-status-io.js";
20
20
  import { analyzeParallelEligibility, } from "./parallel-eligibility.js";
21
21
  import { getErrorMessage } from "./error-utils.js";
22
+ import { logWarning } from "./workflow-logger.js";
22
23
  // ─── Module State ──────────────────────────────────────────────────────────
23
24
  let state = null;
24
25
  // ─── Persistence ──────────────────────────────────────────────────────────
@@ -61,7 +62,9 @@ export function persistState(basePath) {
61
62
  writeFileSync(tmp, JSON.stringify(persisted, null, 2), "utf-8");
62
63
  renameSync(tmp, dest);
63
64
  }
64
- catch { /* non-fatal */ }
65
+ catch (e) {
66
+ logWarning("parallel", `persist parallel state failed: ${e.message}`);
67
+ }
65
68
  }
66
69
  /**
67
70
  * Remove the persisted state file.
@@ -72,7 +75,9 @@ function removeStateFile(basePath) {
72
75
  if (existsSync(p))
73
76
  unlinkSync(p);
74
77
  }
75
- catch { /* non-fatal */ }
78
+ catch (e) {
79
+ logWarning("parallel", `clear parallel state file failed: ${e.message}`);
80
+ }
76
81
  }
77
82
  function isPidAlive(pid) {
78
83
  if (!Number.isInteger(pid) || pid <= 0)
@@ -81,7 +86,8 @@ function isPidAlive(pid) {
81
86
  process.kill(pid, 0);
82
87
  return true;
83
88
  }
84
- catch {
89
+ catch (e) {
90
+ logWarning("parallel", `pid alive check failed for pid ${pid}: ${e.message}`);
85
91
  return false;
86
92
  }
87
93
  }
@@ -112,7 +118,8 @@ export function restoreState(basePath) {
112
118
  }
113
119
  return persisted;
114
120
  }
115
- catch {
121
+ catch (e) {
122
+ logWarning("parallel", `readParallelState JSON parse failed: ${e.message}`);
116
123
  return null;
117
124
  }
118
125
  }
@@ -126,8 +133,8 @@ function appendWorkerLog(basePath, milestoneId, chunk) {
126
133
  mkdirSync(dir, { recursive: true });
127
134
  appendFileSync(workerLogPath(basePath, milestoneId), chunk, "utf-8");
128
135
  }
129
- catch {
130
- // Non-fatal diagnostics should never break orchestration.
136
+ catch (e) {
137
+ logWarning("parallel", `appendFileSync worker log failed for ${milestoneId}: ${e.message}`);
131
138
  }
132
139
  }
133
140
  function restoreRuntimeState(basePath) {
@@ -331,9 +338,8 @@ export async function startParallel(basePath, milestoneIds, prefs) {
331
338
  try {
332
339
  wtPath = createMilestoneWorktree(basePath, mid);
333
340
  }
334
- catch {
335
- // Worktree creation may fail in test environments or when git
336
- // is not available. Fall back to a placeholder path.
341
+ catch (e) {
342
+ logWarning("parallel", `createMilestoneWorktree fallback for ${mid}: ${e.message}`);
337
343
  wtPath = worktreePath(basePath, mid);
338
344
  }
339
345
  const worker = {
@@ -451,7 +457,8 @@ export function spawnWorker(basePath, milestoneId) {
451
457
  detached: false,
452
458
  });
453
459
  }
454
- catch {
460
+ catch (e) {
461
+ logWarning("parallel", `spawnSync worker failed for ${milestoneId}: ${e.message}`);
455
462
  return false;
456
463
  }
457
464
  // Handle spawn errors (e.g., ENOENT when binary doesn't exist)
@@ -572,7 +579,8 @@ function resolveGsdBin() {
572
579
  try {
573
580
  thisDir = dirname(fileURLToPath(import.meta.url));
574
581
  }
575
- catch {
582
+ catch (e) {
583
+ logWarning("parallel", `dirname(fileURLToPath) failed: ${e.message}`);
576
584
  thisDir = process.cwd();
577
585
  }
578
586
  const candidates = [
@@ -599,7 +607,7 @@ function processWorkerLine(basePath, milestoneId, line) {
599
607
  event = JSON.parse(line);
600
608
  }
601
609
  catch {
602
- return; // Not valid JSON — skip (stderr leakage, debug output, etc.)
610
+ return; // Non-NDJSON lines (progress text, tool output) are expected — silent drop
603
611
  }
604
612
  const type = String(event.type ?? "");
605
613
  // message_end carries usage data with cost
@@ -684,7 +692,9 @@ export async function stopParallel(basePath, milestoneId) {
684
692
  process.kill(worker.pid, "SIGTERM");
685
693
  }
686
694
  }
687
- catch { /* process may already be dead */ }
695
+ catch (e) {
696
+ logWarning("parallel", `process.kill SIGTERM failed for pid ${worker.pid}: ${e.message}`);
697
+ }
688
698
  }
689
699
  // Wait for the headless process to cascade SIGTERM to its RPC child.
690
700
  // The headless signal handler calls client.stop() which sends SIGTERM
@@ -701,7 +711,9 @@ export async function stopParallel(basePath, milestoneId) {
701
711
  process.kill(worker.pid, "SIGKILL");
702
712
  }
703
713
  }
704
- catch { /* process may already be dead */ }
714
+ catch (e) {
715
+ logWarning("parallel", `process.kill SIGKILL failed for pid ${worker.pid}: ${e.message}`);
716
+ }
705
717
  await waitForWorkerExit(worker, 250);
706
718
  }
707
719
  // Remove stream listeners before releasing the process handle
@@ -73,6 +73,7 @@ export const KNOWN_PREFERENCE_KEYS = new Set([
73
73
  "stale_commit_threshold_minutes",
74
74
  "context_management",
75
75
  "experimental",
76
+ "codebase",
76
77
  ]);
77
78
  /** Canonical list of all dispatch unit types. */
78
79
  export const KNOWN_UNIT_TYPES = [
@@ -894,5 +894,50 @@ export function validatePreferences(preferences) {
894
894
  errors.push("experimental must be an object");
895
895
  }
896
896
  }
897
+ // ─── Codebase Map ──────────────────────────────────────────────────
898
+ if (preferences.codebase !== undefined) {
899
+ if (typeof preferences.codebase === "object" && preferences.codebase !== null) {
900
+ const cb = preferences.codebase;
901
+ const validCb = {};
902
+ if (cb.exclude_patterns !== undefined) {
903
+ if (Array.isArray(cb.exclude_patterns) && cb.exclude_patterns.every((p) => typeof p === "string")) {
904
+ validCb.exclude_patterns = cb.exclude_patterns;
905
+ }
906
+ else {
907
+ errors.push("codebase.exclude_patterns must be an array of strings");
908
+ }
909
+ }
910
+ if (cb.max_files !== undefined) {
911
+ const mf = typeof cb.max_files === "number" ? cb.max_files : Number(cb.max_files);
912
+ if (Number.isFinite(mf) && mf >= 1) {
913
+ validCb.max_files = Math.floor(mf);
914
+ }
915
+ else {
916
+ errors.push("codebase.max_files must be a positive integer");
917
+ }
918
+ }
919
+ if (cb.collapse_threshold !== undefined) {
920
+ const ct = typeof cb.collapse_threshold === "number" ? cb.collapse_threshold : Number(cb.collapse_threshold);
921
+ if (Number.isFinite(ct) && ct >= 1) {
922
+ validCb.collapse_threshold = Math.floor(ct);
923
+ }
924
+ else {
925
+ errors.push("codebase.collapse_threshold must be a positive integer");
926
+ }
927
+ }
928
+ const knownCbKeys = new Set(["exclude_patterns", "max_files", "collapse_threshold"]);
929
+ for (const key of Object.keys(cb)) {
930
+ if (!knownCbKeys.has(key)) {
931
+ warnings.push(`unknown codebase key "${key}" — ignored`);
932
+ }
933
+ }
934
+ if (Object.keys(validCb).length > 0) {
935
+ validated.codebase = validCb;
936
+ }
937
+ }
938
+ else {
939
+ errors.push("codebase must be an object");
940
+ }
941
+ }
897
942
  return { preferences: validated, errors, warnings };
898
943
  }
@@ -15,6 +15,7 @@ import { join } from "node:path";
15
15
  import { gsdRoot } from "./paths.js";
16
16
  import { parse as parseYaml } from "yaml";
17
17
  import { normalizeStringArray } from "../shared/format-utils.js";
18
+ import { logWarning } from "./workflow-logger.js";
18
19
  import { resolveProfileDefaults as _resolveProfileDefaults } from "./preferences-models.js";
19
20
  import { KNOWN_PREFERENCE_KEYS, MODE_DEFAULTS, } from "./preferences-types.js";
20
21
  import { validatePreferences } from "./preferences-validation.js";
@@ -162,7 +163,7 @@ function parseFrontmatterBlock(frontmatter) {
162
163
  return parsed;
163
164
  }
164
165
  catch (e) {
165
- console.error("[parseFrontmatterBlock] YAML parse error:", e);
166
+ logWarning("guided", `YAML parse error in frontmatter block: ${e.message}`);
166
167
  return {};
167
168
  }
168
169
  }
@@ -217,8 +218,8 @@ function parseHeadingListFormat(content) {
217
218
  }
218
219
  typed[targetSection] = value;
219
220
  }
220
- catch {
221
- /* malformed section skip */
221
+ catch (e) {
222
+ logWarning("guided", `preferences section parse failed: ${e.message}`);
222
223
  }
223
224
  }
224
225
  return typed;
@@ -289,6 +290,17 @@ function mergePreferences(base, override) {
289
290
  service_tier: override.service_tier ?? base.service_tier,
290
291
  forensics_dedup: override.forensics_dedup ?? base.forensics_dedup,
291
292
  show_token_cost: override.show_token_cost ?? base.show_token_cost,
293
+ codebase: (base.codebase || override.codebase)
294
+ ? {
295
+ ...(base.codebase ?? {}),
296
+ ...(override.codebase ?? {}),
297
+ // Merge exclude_patterns arrays rather than overriding
298
+ exclude_patterns: [
299
+ ...((base.codebase?.exclude_patterns) ?? []),
300
+ ...((override.codebase?.exclude_patterns) ?? []),
301
+ ].filter(Boolean),
302
+ }
303
+ : undefined,
292
304
  };
293
305
  }
294
306
  function mergeStringLists(base, override) {
@@ -21,6 +21,7 @@ import { GSDError, GSD_PARSE_ERROR } from "./errors.js";
21
21
  import { join, dirname } from "node:path";
22
22
  import { fileURLToPath } from "node:url";
23
23
  import { homedir } from "node:os";
24
+ import { logWarning } from "./workflow-logger.js";
24
25
  /**
25
26
  * Resolve the GSD extension directory.
26
27
  *
@@ -69,7 +70,7 @@ function warmCache() {
69
70
  // prompts/ may not exist in test environments — lazy loading still works.
70
71
  // Emit a diagnostic when running outside tests so wrong-path bugs are visible.
71
72
  if (!process.env.VITEST && !process.env.NODE_TEST) {
72
- process.stderr.write(`[gsd:prompt-loader] warmCache: prompts dir not found: ${promptsDir}\n`);
73
+ logWarning("prompt", `warmCache: prompts dir not found: ${promptsDir}`);
73
74
  }
74
75
  }
75
76
  try {
@@ -85,7 +86,7 @@ function warmCache() {
85
86
  catch {
86
87
  // templates/ may not exist in test environments — lazy loading still works.
87
88
  if (!process.env.VITEST && !process.env.NODE_TEST) {
88
- process.stderr.write(`[gsd:prompt-loader] warmCache: templates dir not found: ${templatesDir}\n`);
89
+ logWarning("prompt", `warmCache: templates dir not found: ${templatesDir}`);
89
90
  }
90
91
  }
91
92
  }
@@ -48,7 +48,7 @@ Remove the `{ID}-PARKED.md` file from the milestone directory to reactivate it.
48
48
  ### Skip a slice
49
49
  Mark a slice as skipped so auto-mode advances past it without executing. Use the `gsd_skip_slice` tool:
50
50
  ```
51
- gsd_skip_slice({ milestone_id: "M003", slice_id: "S02", reason: "Descoped — feature moved to M005" })
51
+ gsd_skip_slice({ milestoneId: "M003", sliceId: "S02", reason: "Descoped — feature moved to M005" })
52
52
  ```
53
53
  Skipped slices are treated as closed by the state machine (like "complete" but distinct). Use when a slice is no longer needed or has been superseded. The slice data is preserved for reference.
54
54