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.
- package/dist/resources/extensions/ask-user-questions.js +4 -7
- package/dist/resources/extensions/gsd/auto/phases.js +7 -15
- package/dist/resources/extensions/gsd/auto-dashboard.js +8 -21
- package/dist/resources/extensions/gsd/auto-dispatch.js +3 -6
- package/dist/resources/extensions/gsd/auto-model-selection.js +9 -58
- package/dist/resources/extensions/gsd/auto-post-unit.js +2 -3
- package/dist/resources/extensions/gsd/auto-prompts.js +20 -36
- package/dist/resources/extensions/gsd/auto-recovery.js +18 -37
- package/dist/resources/extensions/gsd/auto-start.js +5 -9
- package/dist/resources/extensions/gsd/auto-timers.js +5 -11
- package/dist/resources/extensions/gsd/auto-unit-closeout.js +3 -5
- package/dist/resources/extensions/gsd/auto-verification.js +2 -3
- package/dist/resources/extensions/gsd/auto-worktree.js +55 -120
- package/dist/resources/extensions/gsd/auto.js +17 -39
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +3 -6
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +2 -2
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +10 -4
- package/dist/resources/extensions/gsd/bootstrap/journal-tools.js +1 -2
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +0 -7
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +10 -11
- package/dist/resources/extensions/gsd/commands/catalog.js +0 -2
- package/dist/resources/extensions/gsd/commands-codebase.js +21 -48
- package/dist/resources/extensions/gsd/commands-inspect.js +1 -2
- package/dist/resources/extensions/gsd/commands-maintenance.js +19 -32
- package/dist/resources/extensions/gsd/complexity-classifier.js +4 -8
- package/dist/resources/extensions/gsd/custom-verification.js +2 -3
- package/dist/resources/extensions/gsd/gsd-db.js +13 -33
- package/dist/resources/extensions/gsd/guided-flow.js +9 -19
- package/dist/resources/extensions/gsd/init-wizard.js +0 -12
- package/dist/resources/extensions/gsd/markdown-renderer.js +9 -11
- package/dist/resources/extensions/gsd/md-importer.js +4 -5
- package/dist/resources/extensions/gsd/milestone-actions.js +2 -3
- package/dist/resources/extensions/gsd/milestone-ids.js +1 -2
- package/dist/resources/extensions/gsd/model-router.js +121 -156
- package/dist/resources/extensions/gsd/parallel-merge.js +3 -5
- package/dist/resources/extensions/gsd/parallel-orchestrator.js +14 -26
- package/dist/resources/extensions/gsd/preferences-types.js +0 -1
- package/dist/resources/extensions/gsd/preferences-validation.js +0 -45
- package/dist/resources/extensions/gsd/preferences.js +3 -15
- package/dist/resources/extensions/gsd/prompt-loader.js +2 -3
- package/dist/resources/extensions/gsd/prompts/rethink.md +1 -1
- package/dist/resources/extensions/gsd/rule-registry.js +6 -7
- package/dist/resources/extensions/gsd/safe-fs.js +8 -6
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +2 -3
- package/dist/resources/extensions/gsd/tools/complete-slice.js +2 -3
- package/dist/resources/extensions/gsd/tools/complete-task.js +2 -3
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +2 -3
- package/dist/resources/extensions/gsd/tools/plan-slice.js +2 -3
- package/dist/resources/extensions/gsd/tools/plan-task.js +1 -2
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +4 -4
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +1 -2
- package/dist/resources/extensions/gsd/tools/reopen-task.js +1 -2
- package/dist/resources/extensions/gsd/tools/replan-slice.js +1 -2
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +1 -2
- package/dist/resources/extensions/gsd/triage-resolution.js +4 -11
- package/dist/resources/extensions/gsd/workflow-events.js +1 -2
- package/dist/resources/extensions/gsd/workflow-logger.js +4 -37
- package/dist/resources/extensions/gsd/workflow-migration.js +12 -14
- package/dist/resources/extensions/gsd/workflow-projections.js +2 -2
- package/dist/resources/extensions/gsd/workflow-reconcile.js +2 -2
- package/dist/resources/extensions/gsd/worktree-manager.js +14 -26
- package/dist/resources/extensions/shared/interview-ui.js +1 -3
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +19 -19
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +19 -19
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +0 -5
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +1 -2
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js +0 -16
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +0 -26
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/config.js +1 -6
- package/packages/pi-coding-agent/dist/core/lsp/config.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/defaults.json +2 -2
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +0 -6
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +0 -19
- package/packages/pi-coding-agent/src/core/extensions/types.ts +0 -26
- package/packages/pi-coding-agent/src/core/lsp/config.ts +1 -7
- package/packages/pi-coding-agent/src/core/lsp/defaults.json +2 -2
- package/src/resources/extensions/ask-user-questions.ts +3 -7
- package/src/resources/extensions/gsd/auto/phases.ts +7 -17
- package/src/resources/extensions/gsd/auto-dashboard.ts +8 -22
- package/src/resources/extensions/gsd/auto-dispatch.ts +3 -7
- package/src/resources/extensions/gsd/auto-model-selection.ts +15 -77
- package/src/resources/extensions/gsd/auto-post-unit.ts +4 -4
- package/src/resources/extensions/gsd/auto-prompts.ts +20 -37
- package/src/resources/extensions/gsd/auto-recovery.ts +18 -38
- package/src/resources/extensions/gsd/auto-start.ts +9 -10
- package/src/resources/extensions/gsd/auto-timers.ts +5 -12
- package/src/resources/extensions/gsd/auto-unit-closeout.ts +2 -6
- package/src/resources/extensions/gsd/auto-verification.ts +6 -3
- package/src/resources/extensions/gsd/auto-worktree.ts +55 -121
- package/src/resources/extensions/gsd/auto.ts +17 -40
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +3 -4
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +2 -2
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +16 -4
- package/src/resources/extensions/gsd/bootstrap/journal-tools.ts +1 -2
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +0 -8
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +10 -11
- package/src/resources/extensions/gsd/commands/catalog.ts +0 -2
- package/src/resources/extensions/gsd/commands-codebase.ts +20 -52
- package/src/resources/extensions/gsd/commands-inspect.ts +1 -2
- package/src/resources/extensions/gsd/commands-maintenance.ts +19 -28
- package/src/resources/extensions/gsd/complexity-classifier.ts +4 -9
- package/src/resources/extensions/gsd/custom-verification.ts +2 -3
- package/src/resources/extensions/gsd/gsd-db.ts +14 -12
- package/src/resources/extensions/gsd/guided-flow.ts +8 -9
- package/src/resources/extensions/gsd/init-wizard.ts +0 -12
- package/src/resources/extensions/gsd/markdown-renderer.ts +17 -11
- package/src/resources/extensions/gsd/md-importer.ts +4 -5
- package/src/resources/extensions/gsd/milestone-actions.ts +2 -3
- package/src/resources/extensions/gsd/milestone-ids.ts +1 -2
- package/src/resources/extensions/gsd/model-router.ts +173 -199
- package/src/resources/extensions/gsd/parallel-merge.ts +3 -5
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +14 -18
- package/src/resources/extensions/gsd/preferences-types.ts +0 -13
- package/src/resources/extensions/gsd/preferences-validation.ts +0 -45
- package/src/resources/extensions/gsd/preferences.ts +3 -16
- package/src/resources/extensions/gsd/prompt-loader.ts +2 -3
- package/src/resources/extensions/gsd/prompts/rethink.md +1 -1
- package/src/resources/extensions/gsd/rule-registry.ts +6 -7
- package/src/resources/extensions/gsd/safe-fs.ts +5 -6
- package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +0 -63
- package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +2 -27
- package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/model-router.test.ts +3 -403
- package/src/resources/extensions/gsd/tests/preferences.test.ts +0 -62
- package/src/resources/extensions/gsd/tests/remote-questions.test.ts +0 -21
- package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +6 -6
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +6 -3
- package/src/resources/extensions/gsd/tools/complete-slice.ts +6 -3
- package/src/resources/extensions/gsd/tools/complete-task.ts +6 -3
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +6 -3
- package/src/resources/extensions/gsd/tools/plan-slice.ts +6 -3
- package/src/resources/extensions/gsd/tools/plan-task.ts +3 -2
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +6 -4
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +3 -2
- package/src/resources/extensions/gsd/tools/reopen-task.ts +3 -2
- package/src/resources/extensions/gsd/tools/replan-slice.ts +3 -2
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +3 -2
- package/src/resources/extensions/gsd/triage-resolution.ts +4 -11
- package/src/resources/extensions/gsd/types.ts +0 -1
- package/src/resources/extensions/gsd/workflow-events.ts +1 -2
- package/src/resources/extensions/gsd/workflow-logger.ts +5 -52
- package/src/resources/extensions/gsd/workflow-migration.ts +12 -14
- package/src/resources/extensions/gsd/workflow-projections.ts +2 -2
- package/src/resources/extensions/gsd/workflow-reconcile.ts +2 -2
- package/src/resources/extensions/gsd/worktree-manager.ts +14 -16
- package/src/resources/extensions/shared/interview-ui.ts +1 -3
- package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.d.ts +0 -2
- package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.d.ts.map +0 -1
- package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.js +0 -47
- package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.js.map +0 -1
- package/packages/pi-coding-agent/src/core/lsp/lsp-legacy-alias.test.ts +0 -70
- package/src/resources/extensions/gsd/tests/capability-router.test.ts +0 -347
- package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +0 -1188
- package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +0 -841
- package/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts +0 -284
- package/src/resources/extensions/gsd/tests/workflow-logger-audit.test.ts +0 -120
- package/src/resources/extensions/shared/tests/interview-notes-loop.test.ts +0 -144
- /package/dist/web/standalone/.next/static/{ogyMN7M-3bGGuRY08L5HR → JVkoVYumy0cDhOQISEYdG}/_buildManifest.js +0 -0
- /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
|
-
|
|
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
|
|
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
|
|
149
|
-
*
|
|
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
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
|
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
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
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,
|
|
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
|
-
//
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
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
|
|
211
|
+
selectionMethod,
|
|
301
212
|
};
|
|
302
213
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
|
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:
|
|
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
|
|
375
|
-
return "
|
|
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
|
|
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
|
|
51
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
137
|
-
|
|
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
|
|
342
|
-
|
|
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
|
|
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
|
|
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; //
|
|
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
|
|
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
|
|
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
|
|
@@ -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
|
-
|
|
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
|
|
222
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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({
|
|
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
|
|