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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Maps complexity tiers to models, enforcing downgrade-only semantics.
|
|
3
3
|
// The user's configured model is always the ceiling.
|
|
4
4
|
|
|
5
|
-
import type { ComplexityTier, ClassificationResult
|
|
5
|
+
import type { ComplexityTier, ClassificationResult } from "./complexity-classifier.js";
|
|
6
6
|
import { tierOrdinal } from "./complexity-classifier.js";
|
|
7
7
|
import type { ResolvedModelConfig } from "./preferences.js";
|
|
8
8
|
|
|
@@ -33,27 +33,14 @@ export interface RoutingDecision {
|
|
|
33
33
|
wasDowngraded: boolean;
|
|
34
34
|
/** Human-readable reason for this decision */
|
|
35
35
|
reason: string;
|
|
36
|
-
/** How the model was selected */
|
|
37
|
-
selectionMethod
|
|
38
|
-
/** Capability scores per
|
|
36
|
+
/** How the model was selected. */
|
|
37
|
+
selectionMethod?: "tier-only" | "capability-scored";
|
|
38
|
+
/** Capability scores per model (when capability-scored). */
|
|
39
39
|
capabilityScores?: Record<string, number>;
|
|
40
|
-
/** Task requirement vector
|
|
40
|
+
/** Task requirement vector (when capability-scored). */
|
|
41
41
|
taskRequirements?: Partial<Record<string, number>>;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
// ─── Capability Profiles ─────────────────────────────────────────────────────
|
|
45
|
-
|
|
46
|
-
/** Seven-dimension capability profile for a model. All values in 0–100 range. */
|
|
47
|
-
export interface ModelCapabilities {
|
|
48
|
-
coding: number;
|
|
49
|
-
debugging: number;
|
|
50
|
-
research: number;
|
|
51
|
-
reasoning: number;
|
|
52
|
-
speed: number;
|
|
53
|
-
longContext: number;
|
|
54
|
-
instruction: number;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
44
|
// ─── Known Model Tiers ───────────────────────────────────────────────────────
|
|
58
45
|
// Maps known model IDs to their capability tier. Used when tier_models is not
|
|
59
46
|
// explicitly configured to pick the best available model for each tier.
|
|
@@ -134,27 +121,33 @@ const MODEL_COST_PER_1K_INPUT: Record<string, number> = {
|
|
|
134
121
|
"deepseek-chat": 0.00014,
|
|
135
122
|
};
|
|
136
123
|
|
|
137
|
-
// ─── Capability Profiles
|
|
138
|
-
//
|
|
139
|
-
//
|
|
124
|
+
// ─── Capability Profiles (ADR-004 Phase 2) ──────────────────────────────────
|
|
125
|
+
// 7-dimension profiles, 0–100 normalized. Models without a profile
|
|
126
|
+
// score 50 uniformly — capability scoring is a no-op for them.
|
|
127
|
+
|
|
128
|
+
export interface ModelCapabilities {
|
|
129
|
+
coding: number;
|
|
130
|
+
debugging: number;
|
|
131
|
+
research: number;
|
|
132
|
+
reasoning: number;
|
|
133
|
+
speed: number;
|
|
134
|
+
longContext: number;
|
|
135
|
+
instruction: number;
|
|
136
|
+
}
|
|
140
137
|
|
|
141
138
|
export const MODEL_CAPABILITY_PROFILES: Record<string, ModelCapabilities> = {
|
|
142
|
-
"claude-opus-4-6":
|
|
143
|
-
"claude-sonnet-4-6":
|
|
144
|
-
"claude-haiku-4-5":
|
|
145
|
-
"gpt-4o":
|
|
146
|
-
"gpt-4o-mini":
|
|
147
|
-
"gemini-2.5-pro":
|
|
148
|
-
"gemini-2.0-flash":
|
|
149
|
-
"deepseek-chat":
|
|
150
|
-
"o3":
|
|
139
|
+
"claude-opus-4-6": { coding: 95, debugging: 90, research: 85, reasoning: 95, speed: 30, longContext: 80, instruction: 90 },
|
|
140
|
+
"claude-sonnet-4-6": { coding: 85, debugging: 80, research: 75, reasoning: 80, speed: 60, longContext: 75, instruction: 85 },
|
|
141
|
+
"claude-haiku-4-5": { coding: 60, debugging: 50, research: 45, reasoning: 50, speed: 95, longContext: 50, instruction: 75 },
|
|
142
|
+
"gpt-4o": { coding: 80, debugging: 75, research: 70, reasoning: 75, speed: 65, longContext: 70, instruction: 80 },
|
|
143
|
+
"gpt-4o-mini": { coding: 55, debugging: 45, research: 40, reasoning: 45, speed: 90, longContext: 45, instruction: 70 },
|
|
144
|
+
"gemini-2.5-pro": { coding: 75, debugging: 70, research: 85, reasoning: 75, speed: 55, longContext: 90, instruction: 75 },
|
|
145
|
+
"gemini-2.0-flash": { coding: 50, debugging: 40, research: 50, reasoning: 40, speed: 95, longContext: 60, instruction: 65 },
|
|
146
|
+
"deepseek-chat": { coding: 75, debugging: 65, research: 55, reasoning: 70, speed: 70, longContext: 55, instruction: 65 },
|
|
147
|
+
"o3": { coding: 80, debugging: 85, research: 80, reasoning: 92, speed: 25, longContext: 70, instruction: 85 },
|
|
151
148
|
};
|
|
152
149
|
|
|
153
|
-
|
|
154
|
-
// Per-unit-type base requirement vectors. Weights indicate how important each
|
|
155
|
-
// capability dimension is for this unit type.
|
|
156
|
-
|
|
157
|
-
export const BASE_REQUIREMENTS: Record<string, Partial<Record<keyof ModelCapabilities, number>>> = {
|
|
150
|
+
const BASE_REQUIREMENTS: Record<string, Partial<Record<keyof ModelCapabilities, number>>> = {
|
|
158
151
|
"execute-task": { coding: 0.9, instruction: 0.7, speed: 0.3 },
|
|
159
152
|
"research-milestone": { research: 0.9, longContext: 0.7, reasoning: 0.5 },
|
|
160
153
|
"research-slice": { research: 0.9, longContext: 0.7, reasoning: 0.5 },
|
|
@@ -168,36 +161,15 @@ export const BASE_REQUIREMENTS: Record<string, Partial<Record<keyof ModelCapabil
|
|
|
168
161
|
"complete-milestone": { instruction: 0.8, reasoning: 0.5 },
|
|
169
162
|
};
|
|
170
163
|
|
|
171
|
-
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
172
|
-
|
|
173
164
|
/**
|
|
174
|
-
*
|
|
175
|
-
* Returns a weighted average of capability dimensions (0–100).
|
|
176
|
-
* Returns 50 if requirements are empty (neutral score).
|
|
177
|
-
*/
|
|
178
|
-
export function scoreModel(
|
|
179
|
-
model: ModelCapabilities,
|
|
180
|
-
requirements: Partial<Record<keyof ModelCapabilities, number>>,
|
|
181
|
-
): number {
|
|
182
|
-
let weightedSum = 0;
|
|
183
|
-
let weightSum = 0;
|
|
184
|
-
for (const [dim, weight] of Object.entries(requirements)) {
|
|
185
|
-
const capability = model[dim as keyof ModelCapabilities] ?? 50;
|
|
186
|
-
weightedSum += weight * capability;
|
|
187
|
-
weightSum += weight;
|
|
188
|
-
}
|
|
189
|
-
return weightSum > 0 ? weightedSum / weightSum : 50;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Compute dynamic task requirements from unit type and optional task metadata.
|
|
194
|
-
* Returns a requirement vector refined by task-specific signals.
|
|
165
|
+
* Compute a task requirement vector from unit type and optional metadata.
|
|
195
166
|
*/
|
|
196
167
|
export function computeTaskRequirements(
|
|
197
168
|
unitType: string,
|
|
198
|
-
metadata?:
|
|
169
|
+
metadata?: { tags?: string[]; complexityKeywords?: string[]; fileCount?: number; estimatedLines?: number },
|
|
199
170
|
): Partial<Record<keyof ModelCapabilities, number>> {
|
|
200
|
-
const base = BASE_REQUIREMENTS[unitType] ?? { reasoning: 0.5 };
|
|
171
|
+
const base = { ...(BASE_REQUIREMENTS[unitType] ?? { reasoning: 0.5 }) };
|
|
172
|
+
|
|
201
173
|
if (unitType === "execute-task" && metadata) {
|
|
202
174
|
if (metadata.tags?.some(t => /^(docs?|readme|comment|config|typo|rename)$/i.test(t))) {
|
|
203
175
|
return { ...base, instruction: 0.9, coding: 0.3, speed: 0.7 };
|
|
@@ -212,101 +184,29 @@ export function computeTaskRequirements(
|
|
|
212
184
|
return { ...base, coding: 0.9, reasoning: 0.7 };
|
|
213
185
|
}
|
|
214
186
|
}
|
|
187
|
+
|
|
215
188
|
return base;
|
|
216
189
|
}
|
|
217
190
|
|
|
218
191
|
/**
|
|
219
|
-
* Score
|
|
220
|
-
*
|
|
221
|
-
* lexicographic tie-break by model ID.
|
|
192
|
+
* Score a model against a task requirement vector.
|
|
193
|
+
* Returns weighted average in range 0–100. Returns 50 for empty requirements.
|
|
222
194
|
*/
|
|
223
|
-
export function
|
|
224
|
-
|
|
195
|
+
export function scoreModel(
|
|
196
|
+
capabilities: ModelCapabilities,
|
|
225
197
|
requirements: Partial<Record<keyof ModelCapabilities, number>>,
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
: { coding: 50, debugging: 50, research: 50, reasoning: 50, speed: 50, longContext: 50, instruction: 50 };
|
|
234
|
-
return { modelId, score: scoreModel(profile, requirements) };
|
|
235
|
-
});
|
|
236
|
-
scored.sort((a, b) => {
|
|
237
|
-
const scoreDiff = b.score - a.score;
|
|
238
|
-
if (Math.abs(scoreDiff) > 2) return scoreDiff;
|
|
239
|
-
const costA = MODEL_COST_PER_1K_INPUT[a.modelId] ?? Infinity;
|
|
240
|
-
const costB = MODEL_COST_PER_1K_INPUT[b.modelId] ?? Infinity;
|
|
241
|
-
if (costA !== costB) return costA - costB;
|
|
242
|
-
return a.modelId.localeCompare(b.modelId);
|
|
243
|
-
});
|
|
244
|
-
return scored;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Return all models eligible for a given tier, sorted cheapest first.
|
|
249
|
-
* If routingConfig.tier_models[tier] is set and available, returns only that
|
|
250
|
-
* model. Otherwise filters availableModelIds by tier from MODEL_CAPABILITY_TIER.
|
|
251
|
-
*/
|
|
252
|
-
export function getEligibleModels(
|
|
253
|
-
tier: ComplexityTier,
|
|
254
|
-
availableModelIds: string[],
|
|
255
|
-
routingConfig: DynamicRoutingConfig,
|
|
256
|
-
): string[] {
|
|
257
|
-
// 1. Check explicit tier_models config
|
|
258
|
-
const explicitModel = routingConfig.tier_models?.[tier];
|
|
259
|
-
if (explicitModel) {
|
|
260
|
-
// Exact match
|
|
261
|
-
if (availableModelIds.includes(explicitModel)) return [explicitModel];
|
|
262
|
-
// Provider-prefix-stripped match
|
|
263
|
-
const match = availableModelIds.find(id => {
|
|
264
|
-
const bareAvail = id.includes("/") ? id.split("/").pop()! : id;
|
|
265
|
-
const bareExplicit = explicitModel.includes("/") ? explicitModel.split("/").pop()! : explicitModel;
|
|
266
|
-
return bareAvail === bareExplicit;
|
|
267
|
-
});
|
|
268
|
-
if (match) return [match];
|
|
198
|
+
): number {
|
|
199
|
+
let weightedSum = 0;
|
|
200
|
+
let weightSum = 0;
|
|
201
|
+
for (const [dim, weight] of Object.entries(requirements)) {
|
|
202
|
+
const capability = capabilities[dim as keyof ModelCapabilities] ?? 50;
|
|
203
|
+
weightedSum += weight * capability;
|
|
204
|
+
weightSum += weight;
|
|
269
205
|
}
|
|
270
|
-
|
|
271
|
-
// 2. Auto-detect: filter by tier, sort cheapest first
|
|
272
|
-
return availableModelIds
|
|
273
|
-
.filter(id => getModelTier(id) === tier)
|
|
274
|
-
.sort((a, b) => {
|
|
275
|
-
const costA = getModelCost(a);
|
|
276
|
-
const costB = getModelCost(b);
|
|
277
|
-
return costA - costB;
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Build a fallback chain for a selected model: [selectedModel, ...configuredFallbacks, configuredPrimary]
|
|
283
|
-
* Deduplicates entries while preserving order.
|
|
284
|
-
*/
|
|
285
|
-
function buildFallbackChain(selectedModelId: string, phaseConfig: ResolvedModelConfig): string[] {
|
|
286
|
-
return [
|
|
287
|
-
...phaseConfig.fallbacks.filter(f => f !== selectedModelId),
|
|
288
|
-
phaseConfig.primary,
|
|
289
|
-
].filter(f => f !== selectedModelId);
|
|
206
|
+
return weightSum > 0 ? weightedSum / weightSum : 50;
|
|
290
207
|
}
|
|
291
208
|
|
|
292
|
-
|
|
293
|
-
* Load capability overrides from user preferences' modelOverrides section.
|
|
294
|
-
* Returns a map of model ID → partial capability overrides to deep-merge with built-in profiles.
|
|
295
|
-
*
|
|
296
|
-
* Per D-17: partial capability overrides via models.json modelOverrides, deep-merged with defaults.
|
|
297
|
-
*/
|
|
298
|
-
export function loadCapabilityOverrides(
|
|
299
|
-
prefs: { modelOverrides?: Record<string, { capabilities?: Partial<ModelCapabilities> }> },
|
|
300
|
-
): Record<string, Partial<ModelCapabilities>> {
|
|
301
|
-
const result: Record<string, Partial<ModelCapabilities>> = {};
|
|
302
|
-
if (!prefs.modelOverrides) return result;
|
|
303
|
-
for (const [modelId, overrideEntry] of Object.entries(prefs.modelOverrides)) {
|
|
304
|
-
if (overrideEntry.capabilities) {
|
|
305
|
-
result[modelId] = overrideEntry.capabilities;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
return result;
|
|
309
|
-
}
|
|
209
|
+
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
310
210
|
|
|
311
211
|
/**
|
|
312
212
|
* Resolve the model to use for a given complexity tier.
|
|
@@ -314,18 +214,10 @@ export function loadCapabilityOverrides(
|
|
|
314
214
|
* Downgrade-only: the returned model is always equal to or cheaper than
|
|
315
215
|
* the user's configured primary model. Never upgrades beyond configuration.
|
|
316
216
|
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
* @param classification The complexity classification result
|
|
323
|
-
* @param phaseConfig The user's configured model for this phase (ceiling)
|
|
324
|
-
* @param routingConfig Dynamic routing configuration
|
|
325
|
-
* @param availableModelIds List of available model IDs (from registry)
|
|
326
|
-
* @param unitType The unit type for capability requirement computation (optional)
|
|
327
|
-
* @param taskMetadata Task metadata for refined requirement vectors (optional)
|
|
328
|
-
* @param capabilityOverrides User-provided capability overrides (deep-merged with built-in profiles, optional)
|
|
217
|
+
* @param classification The complexity classification result
|
|
218
|
+
* @param phaseConfig The user's configured model for this phase (ceiling)
|
|
219
|
+
* @param routingConfig Dynamic routing configuration
|
|
220
|
+
* @param availableModelIds List of available model IDs (from registry)
|
|
329
221
|
*/
|
|
330
222
|
export function resolveModelForComplexity(
|
|
331
223
|
classification: ClassificationResult,
|
|
@@ -333,8 +225,7 @@ export function resolveModelForComplexity(
|
|
|
333
225
|
routingConfig: DynamicRoutingConfig,
|
|
334
226
|
availableModelIds: string[],
|
|
335
227
|
unitType?: string,
|
|
336
|
-
|
|
337
|
-
capabilityOverrides?: Record<string, Partial<ModelCapabilities>>,
|
|
228
|
+
metadata?: { tags?: string[]; complexityKeywords?: string[]; fileCount?: number; estimatedLines?: number },
|
|
338
229
|
): RoutingDecision {
|
|
339
230
|
// If no phase config or routing disabled, pass through
|
|
340
231
|
if (!phaseConfig || !routingConfig.enabled) {
|
|
@@ -344,7 +235,6 @@ export function resolveModelForComplexity(
|
|
|
344
235
|
tier: classification.tier,
|
|
345
236
|
wasDowngraded: false,
|
|
346
237
|
reason: "dynamic routing disabled or no phase config",
|
|
347
|
-
selectionMethod: "tier-only",
|
|
348
238
|
};
|
|
349
239
|
}
|
|
350
240
|
|
|
@@ -364,7 +254,6 @@ export function resolveModelForComplexity(
|
|
|
364
254
|
tier: requestedTier,
|
|
365
255
|
wasDowngraded: false,
|
|
366
256
|
reason: `configured model "${configuredPrimary}" is not in the known tier map — honoring explicit config`,
|
|
367
|
-
selectionMethod: "tier-only",
|
|
368
257
|
};
|
|
369
258
|
}
|
|
370
259
|
|
|
@@ -376,52 +265,48 @@ export function resolveModelForComplexity(
|
|
|
376
265
|
tier: requestedTier,
|
|
377
266
|
wasDowngraded: false,
|
|
378
267
|
reason: `tier ${requestedTier} >= configured ${configuredTier}`,
|
|
379
|
-
selectionMethod: "tier-only",
|
|
380
268
|
};
|
|
381
269
|
}
|
|
382
270
|
|
|
383
|
-
//
|
|
384
|
-
const
|
|
271
|
+
// Find the best model for the requested tier
|
|
272
|
+
const useCapabilityScoring = routingConfig.capability_routing && unitType;
|
|
273
|
+
|
|
274
|
+
let targetModelId: string | null;
|
|
275
|
+
let capabilityScores: Record<string, number> | undefined;
|
|
276
|
+
let taskRequirements: Partial<Record<string, number>> | undefined;
|
|
277
|
+
let selectionMethod: "tier-only" | "capability-scored" = "tier-only";
|
|
278
|
+
|
|
279
|
+
if (useCapabilityScoring) {
|
|
280
|
+
const result = findModelForTierWithCapability(
|
|
281
|
+
requestedTier, routingConfig, availableModelIds,
|
|
282
|
+
routingConfig.cross_provider !== false, unitType, metadata,
|
|
283
|
+
);
|
|
284
|
+
targetModelId = result.modelId;
|
|
285
|
+
capabilityScores = Object.keys(result.scores).length > 0 ? result.scores : undefined;
|
|
286
|
+
taskRequirements = Object.keys(result.requirements).length > 0 ? result.requirements : undefined;
|
|
287
|
+
selectionMethod = capabilityScores ? "capability-scored" : "tier-only";
|
|
288
|
+
} else {
|
|
289
|
+
targetModelId = findModelForTier(
|
|
290
|
+
requestedTier, routingConfig, availableModelIds,
|
|
291
|
+
routingConfig.cross_provider !== false,
|
|
292
|
+
);
|
|
293
|
+
}
|
|
385
294
|
|
|
386
|
-
if (
|
|
387
|
-
// No suitable model found — use configured primary
|
|
295
|
+
if (!targetModelId) {
|
|
388
296
|
return {
|
|
389
297
|
modelId: configuredPrimary,
|
|
390
298
|
fallbacks: phaseConfig.fallbacks,
|
|
391
299
|
tier: requestedTier,
|
|
392
300
|
wasDowngraded: false,
|
|
393
301
|
reason: `no ${requestedTier}-tier model available`,
|
|
394
|
-
selectionMethod
|
|
302
|
+
selectionMethod,
|
|
395
303
|
};
|
|
396
304
|
}
|
|
397
305
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
const winner = scored[0];
|
|
403
|
-
if (winner) {
|
|
404
|
-
const capScores: Record<string, number> = {};
|
|
405
|
-
for (const s of scored) capScores[s.modelId] = s.score;
|
|
406
|
-
const fallbacks = buildFallbackChain(winner.modelId, phaseConfig);
|
|
407
|
-
return {
|
|
408
|
-
modelId: winner.modelId,
|
|
409
|
-
fallbacks,
|
|
410
|
-
tier: requestedTier,
|
|
411
|
-
wasDowngraded: true,
|
|
412
|
-
reason: `capability-scored: ${winner.modelId} (${winner.score.toFixed(1)}) for ${unitType}`,
|
|
413
|
-
capabilityScores: capScores,
|
|
414
|
-
taskRequirements: requirements,
|
|
415
|
-
selectionMethod: "capability-scored",
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// STEP 3: Fallback — use first eligible model (cheapest in tier, or single eligible)
|
|
421
|
-
const targetModelId = eligible[0];
|
|
422
|
-
|
|
423
|
-
// Build fallback chain: [downgraded_model, ...configured_fallbacks, configured_primary]
|
|
424
|
-
const fallbacks = buildFallbackChain(targetModelId, phaseConfig);
|
|
306
|
+
const fallbacks = [
|
|
307
|
+
...phaseConfig.fallbacks.filter(f => f !== targetModelId),
|
|
308
|
+
configuredPrimary,
|
|
309
|
+
].filter(f => f !== targetModelId);
|
|
425
310
|
|
|
426
311
|
return {
|
|
427
312
|
modelId: targetModelId,
|
|
@@ -429,7 +314,9 @@ export function resolveModelForComplexity(
|
|
|
429
314
|
tier: requestedTier,
|
|
430
315
|
wasDowngraded: true,
|
|
431
316
|
reason: classification.reason,
|
|
432
|
-
selectionMethod
|
|
317
|
+
selectionMethod,
|
|
318
|
+
capabilityScores,
|
|
319
|
+
taskRequirements,
|
|
433
320
|
};
|
|
434
321
|
}
|
|
435
322
|
|
|
@@ -451,7 +338,7 @@ export function escalateTier(currentTier: ComplexityTier): ComplexityTier | null
|
|
|
451
338
|
export function defaultRoutingConfig(): DynamicRoutingConfig {
|
|
452
339
|
return {
|
|
453
340
|
enabled: true,
|
|
454
|
-
capability_routing:
|
|
341
|
+
capability_routing: false,
|
|
455
342
|
escalate_on_failure: true,
|
|
456
343
|
budget_pressure: true,
|
|
457
344
|
cross_provider: true,
|
|
@@ -473,8 +360,8 @@ function getModelTier(modelId: string): ComplexityTier {
|
|
|
473
360
|
if (bareId.includes(knownId) || knownId.includes(bareId)) return tier;
|
|
474
361
|
}
|
|
475
362
|
|
|
476
|
-
// Unknown models are assumed
|
|
477
|
-
return "
|
|
363
|
+
// Unknown models are assumed heavy (safest assumption)
|
|
364
|
+
return "heavy";
|
|
478
365
|
}
|
|
479
366
|
|
|
480
367
|
/** Check if a model ID has a known capability tier mapping. (#2192) */
|
|
@@ -487,6 +374,93 @@ function isKnownModel(modelId: string): boolean {
|
|
|
487
374
|
return false;
|
|
488
375
|
}
|
|
489
376
|
|
|
377
|
+
function findModelForTier(
|
|
378
|
+
tier: ComplexityTier,
|
|
379
|
+
config: DynamicRoutingConfig,
|
|
380
|
+
availableModelIds: string[],
|
|
381
|
+
crossProvider: boolean,
|
|
382
|
+
): string | null {
|
|
383
|
+
// 1. Check explicit tier_models config
|
|
384
|
+
const explicitModel = config.tier_models?.[tier];
|
|
385
|
+
if (explicitModel && availableModelIds.includes(explicitModel)) {
|
|
386
|
+
return explicitModel;
|
|
387
|
+
}
|
|
388
|
+
// Also check with provider prefix stripped
|
|
389
|
+
if (explicitModel) {
|
|
390
|
+
const match = availableModelIds.find(id => {
|
|
391
|
+
const bareAvail = id.includes("/") ? id.split("/").pop()! : id;
|
|
392
|
+
const bareExplicit = explicitModel.includes("/") ? explicitModel.split("/").pop()! : explicitModel;
|
|
393
|
+
return bareAvail === bareExplicit;
|
|
394
|
+
});
|
|
395
|
+
if (match) return match;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// 2. Auto-detect: find the cheapest available model in the requested tier
|
|
399
|
+
const candidates = availableModelIds
|
|
400
|
+
.filter(id => {
|
|
401
|
+
const modelTier = getModelTier(id);
|
|
402
|
+
return modelTier === tier;
|
|
403
|
+
})
|
|
404
|
+
.sort((a, b) => {
|
|
405
|
+
if (!crossProvider) return 0;
|
|
406
|
+
const costA = getModelCost(a);
|
|
407
|
+
const costB = getModelCost(b);
|
|
408
|
+
return costA - costB;
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
return candidates[0] ?? null;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function findModelForTierWithCapability(
|
|
415
|
+
tier: ComplexityTier,
|
|
416
|
+
config: DynamicRoutingConfig,
|
|
417
|
+
availableModelIds: string[],
|
|
418
|
+
crossProvider: boolean,
|
|
419
|
+
unitType: string,
|
|
420
|
+
metadata?: { tags?: string[]; complexityKeywords?: string[]; fileCount?: number; estimatedLines?: number },
|
|
421
|
+
): { modelId: string | null; scores: Record<string, number>; requirements: Partial<Record<string, number>> } {
|
|
422
|
+
const explicitModel = config.tier_models?.[tier];
|
|
423
|
+
if (explicitModel) {
|
|
424
|
+
const match = availableModelIds.find(id => {
|
|
425
|
+
const bareAvail = id.includes("/") ? id.split("/").pop()! : id;
|
|
426
|
+
const bareExplicit = explicitModel.includes("/") ? explicitModel.split("/").pop()! : explicitModel;
|
|
427
|
+
return bareAvail === bareExplicit || id === explicitModel;
|
|
428
|
+
});
|
|
429
|
+
if (match) return { modelId: match, scores: {}, requirements: {} };
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const requirements = computeTaskRequirements(unitType, metadata);
|
|
433
|
+
const candidates = availableModelIds.filter(id => getModelTier(id) === tier);
|
|
434
|
+
if (candidates.length === 0) return { modelId: null, scores: {}, requirements };
|
|
435
|
+
|
|
436
|
+
const scores: Record<string, number> = {};
|
|
437
|
+
for (const id of candidates) {
|
|
438
|
+
const bareId = id.includes("/") ? id.split("/").pop()! : id;
|
|
439
|
+
const profile = getModelProfile(bareId);
|
|
440
|
+
scores[id] = scoreModel(profile, requirements);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
candidates.sort((a, b) => {
|
|
444
|
+
const scoreDiff = scores[b] - scores[a];
|
|
445
|
+
if (Math.abs(scoreDiff) > 2) return scoreDiff;
|
|
446
|
+
if (crossProvider) {
|
|
447
|
+
const costDiff = getModelCost(a) - getModelCost(b);
|
|
448
|
+
if (costDiff !== 0) return costDiff;
|
|
449
|
+
}
|
|
450
|
+
return a.localeCompare(b);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
return { modelId: candidates[0], scores, requirements };
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function getModelProfile(bareId: string): ModelCapabilities {
|
|
457
|
+
if (MODEL_CAPABILITY_PROFILES[bareId]) return MODEL_CAPABILITY_PROFILES[bareId];
|
|
458
|
+
for (const [knownId, profile] of Object.entries(MODEL_CAPABILITY_PROFILES)) {
|
|
459
|
+
if (bareId.includes(knownId) || knownId.includes(bareId)) return profile;
|
|
460
|
+
}
|
|
461
|
+
return { coding: 50, debugging: 50, research: 50, reasoning: 50, speed: 50, longContext: 50, instruction: 50 };
|
|
462
|
+
}
|
|
463
|
+
|
|
490
464
|
function getModelCost(modelId: string): number {
|
|
491
465
|
const bareId = modelId.includes("/") ? modelId.split("/").pop()! : modelId;
|
|
492
466
|
|
|
@@ -15,7 +15,6 @@ import { MergeConflictError } from "./git-service.js";
|
|
|
15
15
|
import { removeSessionStatus } from "./session-status-io.js";
|
|
16
16
|
import type { WorkerInfo } from "./parallel-orchestrator.js";
|
|
17
17
|
import { getErrorMessage } from "./error-utils.js";
|
|
18
|
-
import { logWarning } from "./workflow-logger.js";
|
|
19
18
|
|
|
20
19
|
// ─── Types ─────────────────────────────────────────────────────────────────
|
|
21
20
|
|
|
@@ -48,8 +47,7 @@ export function isMilestoneCompleteInWorktreeDb(basePath: string, mid: string):
|
|
|
48
47
|
{ timeout: 3000, encoding: "utf-8" },
|
|
49
48
|
);
|
|
50
49
|
return (result.stdout || "").trim() === "complete";
|
|
51
|
-
} catch
|
|
52
|
-
logWarning("parallel", `spawnSync milestone completion check failed for ${mid}: ${(e as Error).message}`);
|
|
50
|
+
} catch {
|
|
53
51
|
return false;
|
|
54
52
|
}
|
|
55
53
|
}
|
|
@@ -67,8 +65,8 @@ function discoverDbCompletedMilestones(basePath: string): Set<string> {
|
|
|
67
65
|
completed.add(entry);
|
|
68
66
|
}
|
|
69
67
|
}
|
|
70
|
-
} catch
|
|
71
|
-
|
|
68
|
+
} catch {
|
|
69
|
+
// worktrees dir may not exist
|
|
72
70
|
}
|
|
73
71
|
return completed;
|
|
74
72
|
}
|
|
@@ -41,7 +41,6 @@ import {
|
|
|
41
41
|
type ParallelCandidates,
|
|
42
42
|
} from "./parallel-eligibility.js";
|
|
43
43
|
import { getErrorMessage } from "./error-utils.js";
|
|
44
|
-
import { logWarning } from "./workflow-logger.js";
|
|
45
44
|
|
|
46
45
|
// ─── Types ─────────────────────────────────────────────────────────────────
|
|
47
46
|
|
|
@@ -127,7 +126,7 @@ export function persistState(basePath: string): void {
|
|
|
127
126
|
const tmp = dest + TMP_SUFFIX;
|
|
128
127
|
writeFileSync(tmp, JSON.stringify(persisted, null, 2), "utf-8");
|
|
129
128
|
renameSync(tmp, dest);
|
|
130
|
-
} catch
|
|
129
|
+
} catch { /* non-fatal */ }
|
|
131
130
|
}
|
|
132
131
|
|
|
133
132
|
/**
|
|
@@ -137,7 +136,7 @@ function removeStateFile(basePath: string): void {
|
|
|
137
136
|
try {
|
|
138
137
|
const p = stateFilePath(basePath);
|
|
139
138
|
if (existsSync(p)) unlinkSync(p);
|
|
140
|
-
} catch
|
|
139
|
+
} catch { /* non-fatal */ }
|
|
141
140
|
}
|
|
142
141
|
|
|
143
142
|
function isPidAlive(pid: number): boolean {
|
|
@@ -145,8 +144,7 @@ function isPidAlive(pid: number): boolean {
|
|
|
145
144
|
try {
|
|
146
145
|
process.kill(pid, 0);
|
|
147
146
|
return true;
|
|
148
|
-
} catch
|
|
149
|
-
logWarning("parallel", `pid alive check failed for pid ${pid}: ${(e as Error).message}`);
|
|
147
|
+
} catch {
|
|
150
148
|
return false;
|
|
151
149
|
}
|
|
152
150
|
}
|
|
@@ -178,8 +176,7 @@ export function restoreState(basePath: string): PersistedState | null {
|
|
|
178
176
|
}
|
|
179
177
|
|
|
180
178
|
return persisted;
|
|
181
|
-
} catch
|
|
182
|
-
logWarning("parallel", `readParallelState JSON parse failed: ${(e as Error).message}`);
|
|
179
|
+
} catch {
|
|
183
180
|
return null;
|
|
184
181
|
}
|
|
185
182
|
}
|
|
@@ -193,8 +190,8 @@ function appendWorkerLog(basePath: string, milestoneId: string, chunk: string):
|
|
|
193
190
|
const dir = join(gsdRoot(basePath), "parallel");
|
|
194
191
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
195
192
|
appendFileSync(workerLogPath(basePath, milestoneId), chunk, "utf-8");
|
|
196
|
-
} catch
|
|
197
|
-
|
|
193
|
+
} catch {
|
|
194
|
+
// Non-fatal — diagnostics should never break orchestration.
|
|
198
195
|
}
|
|
199
196
|
}
|
|
200
197
|
|
|
@@ -433,8 +430,9 @@ export async function startParallel(
|
|
|
433
430
|
let wtPath: string;
|
|
434
431
|
try {
|
|
435
432
|
wtPath = createMilestoneWorktree(basePath, mid);
|
|
436
|
-
} catch
|
|
437
|
-
|
|
433
|
+
} catch {
|
|
434
|
+
// Worktree creation may fail in test environments or when git
|
|
435
|
+
// is not available. Fall back to a placeholder path.
|
|
438
436
|
wtPath = worktreePath(basePath, mid);
|
|
439
437
|
}
|
|
440
438
|
|
|
@@ -566,8 +564,7 @@ export function spawnWorker(
|
|
|
566
564
|
stdio: ["ignore", "pipe", "pipe"],
|
|
567
565
|
detached: false,
|
|
568
566
|
});
|
|
569
|
-
} catch
|
|
570
|
-
logWarning("parallel", `spawnSync worker failed for ${milestoneId}: ${(e as Error).message}`);
|
|
567
|
+
} catch {
|
|
571
568
|
return false;
|
|
572
569
|
}
|
|
573
570
|
|
|
@@ -697,8 +694,7 @@ function resolveGsdBin(): string | null {
|
|
|
697
694
|
let thisDir: string;
|
|
698
695
|
try {
|
|
699
696
|
thisDir = dirname(fileURLToPath(import.meta.url));
|
|
700
|
-
} catch
|
|
701
|
-
logWarning("parallel", `dirname(fileURLToPath) failed: ${(e as Error).message}`);
|
|
697
|
+
} catch {
|
|
702
698
|
thisDir = process.cwd();
|
|
703
699
|
}
|
|
704
700
|
const candidates = [
|
|
@@ -726,7 +722,7 @@ function processWorkerLine(basePath: string, milestoneId: string, line: string):
|
|
|
726
722
|
try {
|
|
727
723
|
event = JSON.parse(line);
|
|
728
724
|
} catch {
|
|
729
|
-
return; //
|
|
725
|
+
return; // Not valid JSON — skip (stderr leakage, debug output, etc.)
|
|
730
726
|
}
|
|
731
727
|
|
|
732
728
|
const type = String(event.type ?? "");
|
|
@@ -821,7 +817,7 @@ export async function stopParallel(
|
|
|
821
817
|
} else if (worker.pid !== process.pid) {
|
|
822
818
|
process.kill(worker.pid, "SIGTERM");
|
|
823
819
|
}
|
|
824
|
-
} catch
|
|
820
|
+
} catch { /* process may already be dead */ }
|
|
825
821
|
}
|
|
826
822
|
|
|
827
823
|
// Wait for the headless process to cascade SIGTERM to its RPC child.
|
|
@@ -837,7 +833,7 @@ export async function stopParallel(
|
|
|
837
833
|
} else if (worker.pid !== process.pid) {
|
|
838
834
|
process.kill(worker.pid, "SIGKILL");
|
|
839
835
|
}
|
|
840
|
-
} catch
|
|
836
|
+
} catch { /* process may already be dead */ }
|
|
841
837
|
await waitForWorkerExit(worker, 250);
|
|
842
838
|
}
|
|
843
839
|
|