gsd-pi 2.60.0-dev.2580e65 → 2.60.0-dev.d9052f5

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 (196) hide show
  1. package/dist/resources/extensions/ask-user-questions.js +4 -7
  2. package/dist/resources/extensions/gsd/auto/phases.js +7 -15
  3. package/dist/resources/extensions/gsd/auto-dashboard.js +8 -21
  4. package/dist/resources/extensions/gsd/auto-dispatch.js +3 -6
  5. package/dist/resources/extensions/gsd/auto-model-selection.js +9 -58
  6. package/dist/resources/extensions/gsd/auto-post-unit.js +2 -3
  7. package/dist/resources/extensions/gsd/auto-prompts.js +20 -36
  8. package/dist/resources/extensions/gsd/auto-recovery.js +18 -37
  9. package/dist/resources/extensions/gsd/auto-start.js +5 -9
  10. package/dist/resources/extensions/gsd/auto-timers.js +5 -11
  11. package/dist/resources/extensions/gsd/auto-unit-closeout.js +3 -5
  12. package/dist/resources/extensions/gsd/auto-verification.js +2 -3
  13. package/dist/resources/extensions/gsd/auto-worktree.js +55 -120
  14. package/dist/resources/extensions/gsd/auto.js +17 -39
  15. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +3 -6
  16. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +2 -2
  17. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +10 -4
  18. package/dist/resources/extensions/gsd/bootstrap/journal-tools.js +1 -2
  19. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +0 -7
  20. package/dist/resources/extensions/gsd/bootstrap/system-context.js +10 -11
  21. package/dist/resources/extensions/gsd/commands/catalog.js +0 -2
  22. package/dist/resources/extensions/gsd/commands-codebase.js +21 -48
  23. package/dist/resources/extensions/gsd/commands-inspect.js +1 -2
  24. package/dist/resources/extensions/gsd/commands-maintenance.js +19 -32
  25. package/dist/resources/extensions/gsd/complexity-classifier.js +4 -8
  26. package/dist/resources/extensions/gsd/custom-verification.js +2 -3
  27. package/dist/resources/extensions/gsd/gsd-db.js +13 -33
  28. package/dist/resources/extensions/gsd/guided-flow.js +9 -19
  29. package/dist/resources/extensions/gsd/init-wizard.js +0 -12
  30. package/dist/resources/extensions/gsd/markdown-renderer.js +9 -11
  31. package/dist/resources/extensions/gsd/md-importer.js +4 -5
  32. package/dist/resources/extensions/gsd/milestone-actions.js +2 -3
  33. package/dist/resources/extensions/gsd/milestone-ids.js +1 -2
  34. package/dist/resources/extensions/gsd/model-router.js +121 -156
  35. package/dist/resources/extensions/gsd/parallel-merge.js +3 -5
  36. package/dist/resources/extensions/gsd/parallel-orchestrator.js +14 -26
  37. package/dist/resources/extensions/gsd/preferences-types.js +0 -1
  38. package/dist/resources/extensions/gsd/preferences-validation.js +0 -45
  39. package/dist/resources/extensions/gsd/preferences.js +3 -15
  40. package/dist/resources/extensions/gsd/prompt-loader.js +2 -3
  41. package/dist/resources/extensions/gsd/prompts/rethink.md +1 -1
  42. package/dist/resources/extensions/gsd/rule-registry.js +6 -7
  43. package/dist/resources/extensions/gsd/safe-fs.js +8 -6
  44. package/dist/resources/extensions/gsd/tools/complete-milestone.js +2 -3
  45. package/dist/resources/extensions/gsd/tools/complete-slice.js +2 -3
  46. package/dist/resources/extensions/gsd/tools/complete-task.js +2 -3
  47. package/dist/resources/extensions/gsd/tools/plan-milestone.js +2 -3
  48. package/dist/resources/extensions/gsd/tools/plan-slice.js +2 -3
  49. package/dist/resources/extensions/gsd/tools/plan-task.js +1 -2
  50. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +4 -4
  51. package/dist/resources/extensions/gsd/tools/reopen-slice.js +1 -2
  52. package/dist/resources/extensions/gsd/tools/reopen-task.js +1 -2
  53. package/dist/resources/extensions/gsd/tools/replan-slice.js +1 -2
  54. package/dist/resources/extensions/gsd/tools/validate-milestone.js +1 -2
  55. package/dist/resources/extensions/gsd/triage-resolution.js +4 -11
  56. package/dist/resources/extensions/gsd/workflow-events.js +1 -2
  57. package/dist/resources/extensions/gsd/workflow-logger.js +4 -37
  58. package/dist/resources/extensions/gsd/workflow-migration.js +12 -14
  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 +14 -26
  62. package/dist/resources/extensions/shared/interview-ui.js +1 -3
  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 +0 -5
  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 +1 -2
  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 +0 -16
  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 +0 -26
  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 +1 -6
  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/src/core/extensions/loader.ts +0 -6
  110. package/packages/pi-coding-agent/src/core/extensions/runner.ts +0 -19
  111. package/packages/pi-coding-agent/src/core/extensions/types.ts +0 -26
  112. package/packages/pi-coding-agent/src/core/lsp/config.ts +1 -7
  113. package/packages/pi-coding-agent/src/core/lsp/defaults.json +2 -2
  114. package/src/resources/extensions/ask-user-questions.ts +3 -7
  115. package/src/resources/extensions/gsd/auto/phases.ts +7 -17
  116. package/src/resources/extensions/gsd/auto-dashboard.ts +8 -22
  117. package/src/resources/extensions/gsd/auto-dispatch.ts +3 -7
  118. package/src/resources/extensions/gsd/auto-model-selection.ts +15 -77
  119. package/src/resources/extensions/gsd/auto-post-unit.ts +4 -4
  120. package/src/resources/extensions/gsd/auto-prompts.ts +20 -37
  121. package/src/resources/extensions/gsd/auto-recovery.ts +18 -38
  122. package/src/resources/extensions/gsd/auto-start.ts +9 -10
  123. package/src/resources/extensions/gsd/auto-timers.ts +5 -12
  124. package/src/resources/extensions/gsd/auto-unit-closeout.ts +2 -6
  125. package/src/resources/extensions/gsd/auto-verification.ts +6 -3
  126. package/src/resources/extensions/gsd/auto-worktree.ts +55 -121
  127. package/src/resources/extensions/gsd/auto.ts +17 -40
  128. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +3 -4
  129. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +2 -2
  130. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +16 -4
  131. package/src/resources/extensions/gsd/bootstrap/journal-tools.ts +1 -2
  132. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +0 -8
  133. package/src/resources/extensions/gsd/bootstrap/system-context.ts +10 -11
  134. package/src/resources/extensions/gsd/commands/catalog.ts +0 -2
  135. package/src/resources/extensions/gsd/commands-codebase.ts +20 -52
  136. package/src/resources/extensions/gsd/commands-inspect.ts +1 -2
  137. package/src/resources/extensions/gsd/commands-maintenance.ts +19 -28
  138. package/src/resources/extensions/gsd/complexity-classifier.ts +4 -9
  139. package/src/resources/extensions/gsd/custom-verification.ts +2 -3
  140. package/src/resources/extensions/gsd/gsd-db.ts +14 -12
  141. package/src/resources/extensions/gsd/guided-flow.ts +8 -9
  142. package/src/resources/extensions/gsd/init-wizard.ts +0 -12
  143. package/src/resources/extensions/gsd/markdown-renderer.ts +17 -11
  144. package/src/resources/extensions/gsd/md-importer.ts +4 -5
  145. package/src/resources/extensions/gsd/milestone-actions.ts +2 -3
  146. package/src/resources/extensions/gsd/milestone-ids.ts +1 -2
  147. package/src/resources/extensions/gsd/model-router.ts +173 -199
  148. package/src/resources/extensions/gsd/parallel-merge.ts +3 -5
  149. package/src/resources/extensions/gsd/parallel-orchestrator.ts +14 -18
  150. package/src/resources/extensions/gsd/preferences-types.ts +0 -13
  151. package/src/resources/extensions/gsd/preferences-validation.ts +0 -45
  152. package/src/resources/extensions/gsd/preferences.ts +3 -16
  153. package/src/resources/extensions/gsd/prompt-loader.ts +2 -3
  154. package/src/resources/extensions/gsd/prompts/rethink.md +1 -1
  155. package/src/resources/extensions/gsd/rule-registry.ts +6 -7
  156. package/src/resources/extensions/gsd/safe-fs.ts +5 -6
  157. package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +0 -63
  158. package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +2 -27
  159. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +4 -4
  160. package/src/resources/extensions/gsd/tests/model-router.test.ts +3 -403
  161. package/src/resources/extensions/gsd/tests/preferences.test.ts +0 -62
  162. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +0 -21
  163. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +6 -6
  164. package/src/resources/extensions/gsd/tools/complete-milestone.ts +6 -3
  165. package/src/resources/extensions/gsd/tools/complete-slice.ts +6 -3
  166. package/src/resources/extensions/gsd/tools/complete-task.ts +6 -3
  167. package/src/resources/extensions/gsd/tools/plan-milestone.ts +6 -3
  168. package/src/resources/extensions/gsd/tools/plan-slice.ts +6 -3
  169. package/src/resources/extensions/gsd/tools/plan-task.ts +3 -2
  170. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +6 -4
  171. package/src/resources/extensions/gsd/tools/reopen-slice.ts +3 -2
  172. package/src/resources/extensions/gsd/tools/reopen-task.ts +3 -2
  173. package/src/resources/extensions/gsd/tools/replan-slice.ts +3 -2
  174. package/src/resources/extensions/gsd/tools/validate-milestone.ts +3 -2
  175. package/src/resources/extensions/gsd/triage-resolution.ts +4 -11
  176. package/src/resources/extensions/gsd/types.ts +0 -1
  177. package/src/resources/extensions/gsd/workflow-events.ts +1 -2
  178. package/src/resources/extensions/gsd/workflow-logger.ts +5 -52
  179. package/src/resources/extensions/gsd/workflow-migration.ts +12 -14
  180. package/src/resources/extensions/gsd/workflow-projections.ts +2 -2
  181. package/src/resources/extensions/gsd/workflow-reconcile.ts +2 -2
  182. package/src/resources/extensions/gsd/worktree-manager.ts +14 -16
  183. package/src/resources/extensions/shared/interview-ui.ts +1 -3
  184. package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.d.ts +0 -2
  185. package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.d.ts.map +0 -1
  186. package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.js +0 -47
  187. package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.js.map +0 -1
  188. package/packages/pi-coding-agent/src/core/lsp/lsp-legacy-alias.test.ts +0 -70
  189. package/src/resources/extensions/gsd/tests/capability-router.test.ts +0 -347
  190. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +0 -1188
  191. package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +0 -841
  192. package/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts +0 -284
  193. package/src/resources/extensions/gsd/tests/workflow-logger-audit.test.ts +0 -120
  194. package/src/resources/extensions/shared/tests/interview-notes-loop.test.ts +0 -144
  195. /package/dist/web/standalone/.next/static/{ogyMN7M-3bGGuRY08L5HR → JVkoVYumy0cDhOQISEYdG}/_buildManifest.js +0 -0
  196. /package/dist/web/standalone/.next/static/{ogyMN7M-3bGGuRY08L5HR → JVkoVYumy0cDhOQISEYdG}/_ssgManifest.js +0 -0
@@ -76,9 +76,6 @@ 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.
82
79
  export const MODEL_CAPABILITY_PROFILES = {
83
80
  "claude-opus-4-6": { coding: 95, debugging: 90, research: 85, reasoning: 95, speed: 30, longContext: 80, instruction: 90 },
84
81
  "claude-sonnet-4-6": { coding: 85, debugging: 80, research: 75, reasoning: 80, speed: 60, longContext: 75, instruction: 85 },
@@ -90,10 +87,7 @@ export const MODEL_CAPABILITY_PROFILES = {
90
87
  "deepseek-chat": { coding: 75, debugging: 65, research: 55, reasoning: 70, speed: 70, longContext: 55, instruction: 65 },
91
88
  "o3": { coding: 80, debugging: 85, research: 80, reasoning: 92, speed: 25, longContext: 70, instruction: 85 },
92
89
  };
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 = {
90
+ const BASE_REQUIREMENTS = {
97
91
  "execute-task": { coding: 0.9, instruction: 0.7, speed: 0.3 },
98
92
  "research-milestone": { research: 0.9, longContext: 0.7, reasoning: 0.5 },
99
93
  "research-slice": { research: 0.9, longContext: 0.7, reasoning: 0.5 },
@@ -106,28 +100,11 @@ export const BASE_REQUIREMENTS = {
106
100
  "discuss-milestone": { reasoning: 0.6, instruction: 0.7 },
107
101
  "complete-milestone": { instruction: 0.8, reasoning: 0.5 },
108
102
  };
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
- }
125
103
  /**
126
- * Compute dynamic task requirements from unit type and optional task metadata.
127
- * Returns a requirement vector refined by task-specific signals.
104
+ * Compute a task requirement vector from unit type and optional metadata.
128
105
  */
129
106
  export function computeTaskRequirements(unitType, metadata) {
130
- const base = BASE_REQUIREMENTS[unitType] ?? { reasoning: 0.5 };
107
+ const base = { ...(BASE_REQUIREMENTS[unitType] ?? { reasoning: 0.5 }) };
131
108
  if (unitType === "execute-task" && metadata) {
132
109
  if (metadata.tags?.some(t => /^(docs?|readme|comment|config|typo|rename)$/i.test(t))) {
133
110
  return { ...base, instruction: 0.9, coding: 0.3, speed: 0.7 };
@@ -145,108 +122,32 @@ export function computeTaskRequirements(unitType, metadata) {
145
122
  return base;
146
123
  }
147
124
  /**
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.
151
- */
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];
193
- }
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.
125
+ * Score a model against a task requirement vector.
126
+ * Returns weighted average in range 0–100. Returns 50 for empty requirements.
218
127
  */
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
- }
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;
227
135
  }
228
- return result;
136
+ return weightSum > 0 ? weightedSum / weightSum : 50;
229
137
  }
138
+ // ─── Public API ──────────────────────────────────────────────────────────────
230
139
  /**
231
140
  * Resolve the model to use for a given complexity tier.
232
141
  *
233
142
  * Downgrade-only: the returned model is always equal to or cheaper than
234
143
  * the user's configured primary model. Never upgrades beyond configuration.
235
144
  *
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)
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)
248
149
  */
249
- export function resolveModelForComplexity(classification, phaseConfig, routingConfig, availableModelIds, unitType, taskMetadata, capabilityOverrides) {
150
+ export function resolveModelForComplexity(classification, phaseConfig, routingConfig, availableModelIds, unitType, metadata) {
250
151
  // If no phase config or routing disabled, pass through
251
152
  if (!phaseConfig || !routingConfig.enabled) {
252
153
  return {
@@ -255,7 +156,6 @@ export function resolveModelForComplexity(classification, phaseConfig, routingCo
255
156
  tier: classification.tier,
256
157
  wasDowngraded: false,
257
158
  reason: "dynamic routing disabled or no phase config",
258
- selectionMethod: "tier-only",
259
159
  };
260
160
  }
261
161
  const configuredPrimary = phaseConfig.primary;
@@ -273,7 +173,6 @@ export function resolveModelForComplexity(classification, phaseConfig, routingCo
273
173
  tier: requestedTier,
274
174
  wasDowngraded: false,
275
175
  reason: `configured model "${configuredPrimary}" is not in the known tier map — honoring explicit config`,
276
- selectionMethod: "tier-only",
277
176
  };
278
177
  }
279
178
  // Downgrade-only: if requested tier >= configured tier, no change
@@ -284,55 +183,47 @@ export function resolveModelForComplexity(classification, phaseConfig, routingCo
284
183
  tier: requestedTier,
285
184
  wasDowngraded: false,
286
185
  reason: `tier ${requestedTier} >= configured ${configuredTier}`,
287
- selectionMethod: "tier-only",
288
186
  };
289
187
  }
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
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) {
294
205
  return {
295
206
  modelId: configuredPrimary,
296
207
  fallbacks: phaseConfig.fallbacks,
297
208
  tier: requestedTier,
298
209
  wasDowngraded: false,
299
210
  reason: `no ${requestedTier}-tier model available`,
300
- selectionMethod: "tier-only",
211
+ selectionMethod,
301
212
  };
302
213
  }
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);
214
+ const fallbacks = [
215
+ ...phaseConfig.fallbacks.filter(f => f !== targetModelId),
216
+ configuredPrimary,
217
+ ].filter(f => f !== targetModelId);
329
218
  return {
330
219
  modelId: targetModelId,
331
220
  fallbacks,
332
221
  tier: requestedTier,
333
222
  wasDowngraded: true,
334
223
  reason: classification.reason,
335
- selectionMethod: "tier-only",
224
+ selectionMethod,
225
+ capabilityScores,
226
+ taskRequirements,
336
227
  };
337
228
  }
338
229
  /**
@@ -352,7 +243,7 @@ export function escalateTier(currentTier) {
352
243
  export function defaultRoutingConfig() {
353
244
  return {
354
245
  enabled: true,
355
- capability_routing: true,
246
+ capability_routing: false,
356
247
  escalate_on_failure: true,
357
248
  budget_pressure: true,
358
249
  cross_provider: true,
@@ -371,8 +262,8 @@ function getModelTier(modelId) {
371
262
  if (bareId.includes(knownId) || knownId.includes(bareId))
372
263
  return tier;
373
264
  }
374
- // Unknown models are assumed standard (per D-15: avoids silently ignoring user config)
375
- return "standard";
265
+ // Unknown models are assumed heavy (safest assumption)
266
+ return "heavy";
376
267
  }
377
268
  /** Check if a model ID has a known capability tier mapping. (#2192) */
378
269
  function isKnownModel(modelId) {
@@ -385,6 +276,80 @@ function isKnownModel(modelId) {
385
276
  }
386
277
  return false;
387
278
  }
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
+ }
388
353
  function getModelCost(modelId) {
389
354
  const bareId = modelId.includes("/") ? modelId.split("/").pop() : modelId;
390
355
  if (MODEL_COST_PER_1K_INPUT[bareId] !== undefined) {
@@ -13,7 +13,6 @@ 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";
17
16
  // ─── Merge Queue ───────────────────────────────────────────────────────────
18
17
  /**
19
18
  * Check whether a milestone is complete by querying its worktree SQLite DB.
@@ -28,8 +27,7 @@ export function isMilestoneCompleteInWorktreeDb(basePath, mid) {
28
27
  const result = spawnSync("sqlite3", [dbPath, `SELECT status FROM milestones WHERE id='${mid}' LIMIT 1`], { timeout: 3000, encoding: "utf-8" });
29
28
  return (result.stdout || "").trim() === "complete";
30
29
  }
31
- catch (e) {
32
- logWarning("parallel", `spawnSync milestone completion check failed for ${mid}: ${e.message}`);
30
+ catch {
33
31
  return false;
34
32
  }
35
33
  }
@@ -47,8 +45,8 @@ function discoverDbCompletedMilestones(basePath) {
47
45
  }
48
46
  }
49
47
  }
50
- catch (e) {
51
- logWarning("parallel", `readdirSync for completed set failed: ${e.message}`);
48
+ catch {
49
+ // worktrees dir may not exist
52
50
  }
53
51
  return completed;
54
52
  }
@@ -19,7 +19,6 @@ 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";
23
22
  // ─── Module State ──────────────────────────────────────────────────────────
24
23
  let state = null;
25
24
  // ─── Persistence ──────────────────────────────────────────────────────────
@@ -62,9 +61,7 @@ export function persistState(basePath) {
62
61
  writeFileSync(tmp, JSON.stringify(persisted, null, 2), "utf-8");
63
62
  renameSync(tmp, dest);
64
63
  }
65
- catch (e) {
66
- logWarning("parallel", `persist parallel state failed: ${e.message}`);
67
- }
64
+ catch { /* non-fatal */ }
68
65
  }
69
66
  /**
70
67
  * Remove the persisted state file.
@@ -75,9 +72,7 @@ function removeStateFile(basePath) {
75
72
  if (existsSync(p))
76
73
  unlinkSync(p);
77
74
  }
78
- catch (e) {
79
- logWarning("parallel", `clear parallel state file failed: ${e.message}`);
80
- }
75
+ catch { /* non-fatal */ }
81
76
  }
82
77
  function isPidAlive(pid) {
83
78
  if (!Number.isInteger(pid) || pid <= 0)
@@ -86,8 +81,7 @@ function isPidAlive(pid) {
86
81
  process.kill(pid, 0);
87
82
  return true;
88
83
  }
89
- catch (e) {
90
- logWarning("parallel", `pid alive check failed for pid ${pid}: ${e.message}`);
84
+ catch {
91
85
  return false;
92
86
  }
93
87
  }
@@ -118,8 +112,7 @@ export function restoreState(basePath) {
118
112
  }
119
113
  return persisted;
120
114
  }
121
- catch (e) {
122
- logWarning("parallel", `readParallelState JSON parse failed: ${e.message}`);
115
+ catch {
123
116
  return null;
124
117
  }
125
118
  }
@@ -133,8 +126,8 @@ function appendWorkerLog(basePath, milestoneId, chunk) {
133
126
  mkdirSync(dir, { recursive: true });
134
127
  appendFileSync(workerLogPath(basePath, milestoneId), chunk, "utf-8");
135
128
  }
136
- catch (e) {
137
- logWarning("parallel", `appendFileSync worker log failed for ${milestoneId}: ${e.message}`);
129
+ catch {
130
+ // Non-fatal diagnostics should never break orchestration.
138
131
  }
139
132
  }
140
133
  function restoreRuntimeState(basePath) {
@@ -338,8 +331,9 @@ export async function startParallel(basePath, milestoneIds, prefs) {
338
331
  try {
339
332
  wtPath = createMilestoneWorktree(basePath, mid);
340
333
  }
341
- catch (e) {
342
- logWarning("parallel", `createMilestoneWorktree fallback for ${mid}: ${e.message}`);
334
+ catch {
335
+ // Worktree creation may fail in test environments or when git
336
+ // is not available. Fall back to a placeholder path.
343
337
  wtPath = worktreePath(basePath, mid);
344
338
  }
345
339
  const worker = {
@@ -457,8 +451,7 @@ export function spawnWorker(basePath, milestoneId) {
457
451
  detached: false,
458
452
  });
459
453
  }
460
- catch (e) {
461
- logWarning("parallel", `spawnSync worker failed for ${milestoneId}: ${e.message}`);
454
+ catch {
462
455
  return false;
463
456
  }
464
457
  // Handle spawn errors (e.g., ENOENT when binary doesn't exist)
@@ -579,8 +572,7 @@ function resolveGsdBin() {
579
572
  try {
580
573
  thisDir = dirname(fileURLToPath(import.meta.url));
581
574
  }
582
- catch (e) {
583
- logWarning("parallel", `dirname(fileURLToPath) failed: ${e.message}`);
575
+ catch {
584
576
  thisDir = process.cwd();
585
577
  }
586
578
  const candidates = [
@@ -607,7 +599,7 @@ function processWorkerLine(basePath, milestoneId, line) {
607
599
  event = JSON.parse(line);
608
600
  }
609
601
  catch {
610
- return; // Non-NDJSON lines (progress text, tool output) are expected — silent drop
602
+ return; // Not valid JSON — skip (stderr leakage, debug output, etc.)
611
603
  }
612
604
  const type = String(event.type ?? "");
613
605
  // message_end carries usage data with cost
@@ -692,9 +684,7 @@ export async function stopParallel(basePath, milestoneId) {
692
684
  process.kill(worker.pid, "SIGTERM");
693
685
  }
694
686
  }
695
- catch (e) {
696
- logWarning("parallel", `process.kill SIGTERM failed for pid ${worker.pid}: ${e.message}`);
697
- }
687
+ catch { /* process may already be dead */ }
698
688
  }
699
689
  // Wait for the headless process to cascade SIGTERM to its RPC child.
700
690
  // The headless signal handler calls client.stop() which sends SIGTERM
@@ -711,9 +701,7 @@ export async function stopParallel(basePath, milestoneId) {
711
701
  process.kill(worker.pid, "SIGKILL");
712
702
  }
713
703
  }
714
- catch (e) {
715
- logWarning("parallel", `process.kill SIGKILL failed for pid ${worker.pid}: ${e.message}`);
716
- }
704
+ catch { /* process may already be dead */ }
717
705
  await waitForWorkerExit(worker, 250);
718
706
  }
719
707
  // Remove stream listeners before releasing the process handle
@@ -73,7 +73,6 @@ export const KNOWN_PREFERENCE_KEYS = new Set([
73
73
  "stale_commit_threshold_minutes",
74
74
  "context_management",
75
75
  "experimental",
76
- "codebase",
77
76
  ]);
78
77
  /** Canonical list of all dispatch unit types. */
79
78
  export const KNOWN_UNIT_TYPES = [
@@ -894,50 +894,5 @@ 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
- }
942
897
  return { preferences: validated, errors, warnings };
943
898
  }
@@ -15,7 +15,6 @@ 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";
19
18
  import { resolveProfileDefaults as _resolveProfileDefaults } from "./preferences-models.js";
20
19
  import { KNOWN_PREFERENCE_KEYS, MODE_DEFAULTS, } from "./preferences-types.js";
21
20
  import { validatePreferences } from "./preferences-validation.js";
@@ -163,7 +162,7 @@ function parseFrontmatterBlock(frontmatter) {
163
162
  return parsed;
164
163
  }
165
164
  catch (e) {
166
- logWarning("guided", `YAML parse error in frontmatter block: ${e.message}`);
165
+ console.error("[parseFrontmatterBlock] YAML parse error:", e);
167
166
  return {};
168
167
  }
169
168
  }
@@ -218,8 +217,8 @@ function parseHeadingListFormat(content) {
218
217
  }
219
218
  typed[targetSection] = value;
220
219
  }
221
- catch (e) {
222
- logWarning("guided", `preferences section parse failed: ${e.message}`);
220
+ catch {
221
+ /* malformed section skip */
223
222
  }
224
223
  }
225
224
  return typed;
@@ -290,17 +289,6 @@ function mergePreferences(base, override) {
290
289
  service_tier: override.service_tier ?? base.service_tier,
291
290
  forensics_dedup: override.forensics_dedup ?? base.forensics_dedup,
292
291
  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,
304
292
  };
305
293
  }
306
294
  function mergeStringLists(base, override) {
@@ -21,7 +21,6 @@ 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";
25
24
  /**
26
25
  * Resolve the GSD extension directory.
27
26
  *
@@ -70,7 +69,7 @@ function warmCache() {
70
69
  // prompts/ may not exist in test environments — lazy loading still works.
71
70
  // Emit a diagnostic when running outside tests so wrong-path bugs are visible.
72
71
  if (!process.env.VITEST && !process.env.NODE_TEST) {
73
- logWarning("prompt", `warmCache: prompts dir not found: ${promptsDir}`);
72
+ process.stderr.write(`[gsd:prompt-loader] warmCache: prompts dir not found: ${promptsDir}\n`);
74
73
  }
75
74
  }
76
75
  try {
@@ -86,7 +85,7 @@ function warmCache() {
86
85
  catch {
87
86
  // templates/ may not exist in test environments — lazy loading still works.
88
87
  if (!process.env.VITEST && !process.env.NODE_TEST) {
89
- logWarning("prompt", `warmCache: templates dir not found: ${templatesDir}`);
88
+ process.stderr.write(`[gsd:prompt-loader] warmCache: templates dir not found: ${templatesDir}\n`);
90
89
  }
91
90
  }
92
91
  }
@@ -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({ milestoneId: "M003", sliceId: "S02", reason: "Descoped — feature moved to M005" })
51
+ gsd_skip_slice({ milestone_id: "M003", slice_id: "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