gsd-pi 2.68.1-dev.362687a → 2.68.1-dev.abc8f2b
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/gsd/auto-model-selection.js +27 -1
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +7 -0
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +1 -5
- package/dist/resources/extensions/gsd/guided-flow.js +25 -70
- package/dist/resources/extensions/gsd/model-router.js +85 -2
- package/dist/resources/extensions/gsd/prompts/discuss.md +2 -0
- package/dist/resources/extensions/gsd/templates/context.md +34 -2
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
- 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 +1 -1
- 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 +13 -13
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/pi-ai/dist/index.d.ts +3 -0
- package/packages/pi-ai/dist/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/index.js +2 -0
- package/packages/pi-ai/dist/index.js.map +1 -1
- package/packages/pi-ai/dist/providers/amazon-bedrock.js +2 -2
- package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.js +2 -2
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-shared.js +2 -2
- package/packages/pi-ai/dist/providers/google-shared.js.map +1 -1
- package/packages/pi-ai/dist/providers/mistral.js +2 -2
- package/packages/pi-ai/dist/providers/mistral.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +2 -2
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses-shared.js +2 -2
- package/packages/pi-ai/dist/providers/openai-responses-shared.js.map +1 -1
- package/packages/pi-ai/dist/providers/provider-capabilities.d.ts +59 -0
- package/packages/pi-ai/dist/providers/provider-capabilities.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/provider-capabilities.js +173 -0
- package/packages/pi-ai/dist/providers/provider-capabilities.js.map +1 -0
- package/packages/pi-ai/dist/providers/provider-capabilities.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/provider-capabilities.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/provider-capabilities.test.js +132 -0
- package/packages/pi-ai/dist/providers/provider-capabilities.test.js.map +1 -0
- package/packages/pi-ai/dist/providers/transform-messages-report.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/transform-messages-report.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/transform-messages-report.test.js +172 -0
- package/packages/pi-ai/dist/providers/transform-messages-report.test.js.map +1 -0
- package/packages/pi-ai/dist/providers/transform-messages.d.ts +34 -1
- package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.js +73 -2
- package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
- package/packages/pi-ai/src/index.ts +3 -0
- package/packages/pi-ai/src/providers/amazon-bedrock.ts +2 -2
- package/packages/pi-ai/src/providers/anthropic-shared.ts +2 -2
- package/packages/pi-ai/src/providers/google-shared.ts +2 -2
- package/packages/pi-ai/src/providers/mistral.ts +2 -2
- package/packages/pi-ai/src/providers/openai-completions.ts +2 -2
- package/packages/pi-ai/src/providers/openai-responses-shared.ts +2 -2
- package/packages/pi-ai/src/providers/provider-capabilities.test.ts +174 -0
- package/packages/pi-ai/src/providers/provider-capabilities.ts +215 -0
- package/packages/pi-ai/src/providers/transform-messages-report.test.ts +189 -0
- package/packages/pi-ai/src/providers/transform-messages.ts +94 -1
- package/packages/pi-coding-agent/dist/core/extensions/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/index.js.map +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 +10 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +2 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js +15 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +41 -0
- 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/tools/index.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.js +1 -0
- package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/tool-compatibility-registry.d.ts +27 -0
- package/packages/pi-coding-agent/dist/core/tools/tool-compatibility-registry.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/tool-compatibility-registry.js +69 -0
- package/packages/pi-coding-agent/dist/core/tools/tool-compatibility-registry.js.map +1 -0
- package/packages/pi-coding-agent/dist/index.d.ts +2 -2
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +3 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/src/core/extensions/index.ts +4 -0
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +11 -1
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +18 -0
- package/packages/pi-coding-agent/src/core/extensions/types.ts +45 -0
- package/packages/pi-coding-agent/src/core/tools/index.ts +7 -0
- package/packages/pi-coding-agent/src/core/tools/tool-compatibility-registry.ts +83 -0
- package/packages/pi-coding-agent/src/index.ts +9 -0
- package/src/resources/extensions/gsd/auto-model-selection.ts +36 -4
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +8 -0
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +1 -5
- package/src/resources/extensions/gsd/guided-flow.ts +22 -84
- package/src/resources/extensions/gsd/model-router.ts +117 -10
- package/src/resources/extensions/gsd/preferences-types.ts +3 -1
- package/src/resources/extensions/gsd/prompts/discuss.md +2 -0
- package/src/resources/extensions/gsd/templates/context.md +34 -2
- package/src/resources/extensions/gsd/tests/capability-router.test.ts +31 -7
- package/src/resources/extensions/gsd/tests/model-router.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/tool-compatibility.test.ts +199 -0
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +13 -16
- package/dist/resources/extensions/gsd/prompt-validation.js +0 -67
- package/dist/resources/extensions/gsd/prompts/discuss-prepared.md +0 -424
- package/dist/resources/extensions/gsd/templates/context-enhanced.md +0 -138
- package/src/resources/extensions/gsd/prompt-validation.ts +0 -88
- package/src/resources/extensions/gsd/prompts/discuss-prepared.md +0 -424
- package/src/resources/extensions/gsd/templates/context-enhanced.md +0 -138
- package/src/resources/extensions/gsd/tests/adversarial-review-fixes.test.ts +0 -223
- package/src/resources/extensions/gsd/tests/integration/test-isolation.ts +0 -53
- package/src/resources/extensions/gsd/tests/integration-prepared-discussion.test.ts +0 -525
- package/src/resources/extensions/gsd/tests/preparation.test.ts +0 -1211
- package/src/resources/extensions/gsd/tests/prompt-builder.test.ts +0 -669
- /package/dist/web/standalone/.next/static/{VkiZZ5UjK7EfSjrWWd5RC → 3HMOXcBoys84RYd2F8a79}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{VkiZZ5UjK7EfSjrWWd5RC → 3HMOXcBoys84RYd2F8a79}/_ssgManifest.js +0 -0
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
import type { ComplexityTier, ClassificationResult, TaskMetadata } from "./complexity-classifier.js";
|
|
6
6
|
import { tierOrdinal } from "./complexity-classifier.js";
|
|
7
7
|
import type { ResolvedModelConfig } from "./preferences.js";
|
|
8
|
+
import { getProviderCapabilities, type ProviderCapabilities } from "@gsd/pi-ai";
|
|
9
|
+
import { getToolCompatibility, getAllToolCompatibility } from "@gsd/pi-coding-agent";
|
|
10
|
+
import type { ToolCompatibility } from "@gsd/pi-coding-agent";
|
|
8
11
|
|
|
9
12
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
10
13
|
|
|
@@ -37,6 +40,8 @@ export interface RoutingDecision {
|
|
|
37
40
|
selectionMethod: "tier-only" | "capability-scored";
|
|
38
41
|
/** Capability scores per eligible model (capability-scored path only) */
|
|
39
42
|
capabilityScores?: Record<string, number>;
|
|
43
|
+
/** Tools filtered out due to provider incompatibility (ADR-005) */
|
|
44
|
+
filteredTools?: string[];
|
|
40
45
|
/** Task requirement vector used for scoring */
|
|
41
46
|
taskRequirements?: Partial<Record<string, number>>;
|
|
42
47
|
}
|
|
@@ -58,7 +63,7 @@ export interface ModelCapabilities {
|
|
|
58
63
|
// Maps known model IDs to their capability tier. Used when tier_models is not
|
|
59
64
|
// explicitly configured to pick the best available model for each tier.
|
|
60
65
|
|
|
61
|
-
const MODEL_CAPABILITY_TIER: Record<string, ComplexityTier> = {
|
|
66
|
+
export const MODEL_CAPABILITY_TIER: Record<string, ComplexityTier> = {
|
|
62
67
|
// Light-tier models (cheapest)
|
|
63
68
|
"claude-haiku-4-5": "light",
|
|
64
69
|
"claude-3-5-haiku-latest": "light",
|
|
@@ -139,15 +144,49 @@ const MODEL_COST_PER_1K_INPUT: Record<string, number> = {
|
|
|
139
144
|
// model selection within an eligible tier set.
|
|
140
145
|
|
|
141
146
|
export const MODEL_CAPABILITY_PROFILES: Record<string, ModelCapabilities> = {
|
|
142
|
-
|
|
143
|
-
"claude-
|
|
144
|
-
"claude-
|
|
145
|
-
"
|
|
146
|
-
"
|
|
147
|
-
"
|
|
148
|
-
"
|
|
149
|
-
"
|
|
150
|
-
"
|
|
147
|
+
// ── Anthropic ──────────────────────────────────────────────────────────────
|
|
148
|
+
"claude-opus-4-6": { coding: 95, debugging: 90, research: 85, reasoning: 95, speed: 30, longContext: 80, instruction: 90 },
|
|
149
|
+
"claude-sonnet-4-6": { coding: 85, debugging: 80, research: 75, reasoning: 80, speed: 60, longContext: 75, instruction: 85 },
|
|
150
|
+
"claude-sonnet-4-5-20250514": { coding: 85, debugging: 80, research: 75, reasoning: 80, speed: 60, longContext: 75, instruction: 85 },
|
|
151
|
+
"claude-3-5-sonnet-latest": { coding: 82, debugging: 78, research: 72, reasoning: 78, speed: 62, longContext: 70, instruction: 82 },
|
|
152
|
+
"claude-haiku-4-5": { coding: 60, debugging: 50, research: 45, reasoning: 50, speed: 95, longContext: 50, instruction: 75 },
|
|
153
|
+
"claude-3-5-haiku-latest": { coding: 60, debugging: 50, research: 45, reasoning: 50, speed: 95, longContext: 50, instruction: 75 },
|
|
154
|
+
"claude-3-haiku-20240307": { coding: 50, debugging: 40, research: 35, reasoning: 40, speed: 95, longContext: 40, instruction: 65 },
|
|
155
|
+
"claude-3-opus-latest": { coding: 90, debugging: 85, research: 82, reasoning: 90, speed: 35, longContext: 75, instruction: 88 },
|
|
156
|
+
|
|
157
|
+
// ── OpenAI GPT ─────────────────────────────────────────────────────────────
|
|
158
|
+
"gpt-4o": { coding: 80, debugging: 75, research: 70, reasoning: 75, speed: 65, longContext: 70, instruction: 80 },
|
|
159
|
+
"gpt-4o-mini": { coding: 55, debugging: 45, research: 40, reasoning: 45, speed: 90, longContext: 45, instruction: 70 },
|
|
160
|
+
"gpt-4-turbo": { coding: 78, debugging: 72, research: 68, reasoning: 72, speed: 50, longContext: 65, instruction: 78 },
|
|
161
|
+
"gpt-4.1": { coding: 82, debugging: 78, research: 72, reasoning: 78, speed: 62, longContext: 72, instruction: 82 },
|
|
162
|
+
"gpt-4.1-mini": { coding: 58, debugging: 48, research: 42, reasoning: 48, speed: 88, longContext: 48, instruction: 72 },
|
|
163
|
+
"gpt-4.1-nano": { coding: 40, debugging: 30, research: 25, reasoning: 30, speed: 95, longContext: 30, instruction: 60 },
|
|
164
|
+
"gpt-5": { coding: 92, debugging: 88, research: 85, reasoning: 92, speed: 40, longContext: 85, instruction: 90 },
|
|
165
|
+
"gpt-5-mini": { coding: 62, debugging: 52, research: 48, reasoning: 52, speed: 88, longContext: 52, instruction: 74 },
|
|
166
|
+
"gpt-5-nano": { coding: 42, debugging: 32, research: 28, reasoning: 32, speed: 95, longContext: 32, instruction: 62 },
|
|
167
|
+
"gpt-5-pro": { coding: 94, debugging: 90, research: 88, reasoning: 94, speed: 35, longContext: 88, instruction: 92 },
|
|
168
|
+
"gpt-5.1": { coding: 93, debugging: 89, research: 86, reasoning: 93, speed: 42, longContext: 86, instruction: 91 },
|
|
169
|
+
"gpt-5.1-codex-max": { coding: 90, debugging: 85, research: 70, reasoning: 85, speed: 55, longContext: 75, instruction: 85 },
|
|
170
|
+
"gpt-5.1-codex-mini": { coding: 65, debugging: 55, research: 40, reasoning: 50, speed: 88, longContext: 48, instruction: 72 },
|
|
171
|
+
"gpt-5.2": { coding: 93, debugging: 90, research: 87, reasoning: 93, speed: 42, longContext: 87, instruction: 91 },
|
|
172
|
+
"gpt-5.2-codex": { coding: 93, debugging: 90, research: 72, reasoning: 88, speed: 50, longContext: 78, instruction: 88 },
|
|
173
|
+
"gpt-5.3-codex": { coding: 94, debugging: 91, research: 74, reasoning: 89, speed: 50, longContext: 80, instruction: 89 },
|
|
174
|
+
"gpt-5.3-codex-spark": { coding: 68, debugging: 58, research: 42, reasoning: 52, speed: 90, longContext: 50, instruction: 74 },
|
|
175
|
+
"gpt-5.4": { coding: 95, debugging: 92, research: 88, reasoning: 94, speed: 42, longContext: 88, instruction: 92 },
|
|
176
|
+
|
|
177
|
+
// ── OpenAI o-series (reasoning-first) ──────────────────────────────────────
|
|
178
|
+
"o1": { coding: 78, debugging: 82, research: 78, reasoning: 90, speed: 20, longContext: 65, instruction: 82 },
|
|
179
|
+
"o3": { coding: 80, debugging: 85, research: 80, reasoning: 92, speed: 25, longContext: 70, instruction: 85 },
|
|
180
|
+
"o4-mini": { coding: 75, debugging: 80, research: 72, reasoning: 88, speed: 60, longContext: 65, instruction: 80 },
|
|
181
|
+
"o4-mini-deep-research": { coding: 75, debugging: 80, research: 85, reasoning: 88, speed: 30, longContext: 80, instruction: 80 },
|
|
182
|
+
|
|
183
|
+
// ── Google ─────────────────────────────────────────────────────────────────
|
|
184
|
+
"gemini-2.5-pro": { coding: 75, debugging: 70, research: 85, reasoning: 75, speed: 55, longContext: 90, instruction: 75 },
|
|
185
|
+
"gemini-2.0-flash": { coding: 50, debugging: 40, research: 50, reasoning: 40, speed: 95, longContext: 60, instruction: 65 },
|
|
186
|
+
"gemini-flash-2.0": { coding: 50, debugging: 40, research: 50, reasoning: 40, speed: 95, longContext: 60, instruction: 65 },
|
|
187
|
+
|
|
188
|
+
// ── DeepSeek ───────────────────────────────────────────────────────────────
|
|
189
|
+
"deepseek-chat": { coding: 75, debugging: 65, research: 55, reasoning: 70, speed: 70, longContext: 55, instruction: 65 },
|
|
151
190
|
};
|
|
152
191
|
|
|
153
192
|
// ─── Base Task Requirements Data Table ───────────────────────────────────────
|
|
@@ -502,3 +541,71 @@ function getModelCost(modelId: string): number {
|
|
|
502
541
|
// Unknown cost — assume expensive to avoid routing to unknown cheap models
|
|
503
542
|
return 999;
|
|
504
543
|
}
|
|
544
|
+
|
|
545
|
+
// ─── Tool Compatibility Filter (ADR-005 Phase 3) ───────────────────────────
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Check if a tool is compatible with a provider's capabilities.
|
|
549
|
+
* Returns true if the tool can be used with the provider.
|
|
550
|
+
*/
|
|
551
|
+
export function isToolCompatibleWithProvider(
|
|
552
|
+
toolName: string,
|
|
553
|
+
providerCaps: ProviderCapabilities,
|
|
554
|
+
): boolean {
|
|
555
|
+
const compat = getToolCompatibility(toolName);
|
|
556
|
+
if (!compat) return true; // no metadata = always compatible
|
|
557
|
+
|
|
558
|
+
// Hard filter: provider doesn't support image tool results
|
|
559
|
+
if (compat.producesImages && !providerCaps.imageToolResults) return false;
|
|
560
|
+
|
|
561
|
+
// Hard filter: tool uses schema features provider doesn't support
|
|
562
|
+
if (compat.schemaFeatures?.some(f => providerCaps.unsupportedSchemaFeatures.includes(f))) {
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
return true;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Filter a list of tool names to only those compatible with a provider.
|
|
571
|
+
* Used by the routing pipeline to adjust tool sets when switching providers.
|
|
572
|
+
*/
|
|
573
|
+
export function filterToolsForProvider(
|
|
574
|
+
toolNames: string[],
|
|
575
|
+
providerApi: string,
|
|
576
|
+
): { compatible: string[]; filtered: string[] } {
|
|
577
|
+
const providerCaps = getProviderCapabilities(providerApi);
|
|
578
|
+
|
|
579
|
+
// Provider doesn't support tool calling at all
|
|
580
|
+
if (!providerCaps.toolCalling) {
|
|
581
|
+
return { compatible: [], filtered: toolNames };
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const compatible: string[] = [];
|
|
585
|
+
const filtered: string[] = [];
|
|
586
|
+
|
|
587
|
+
for (const name of toolNames) {
|
|
588
|
+
if (isToolCompatibleWithProvider(name, providerCaps)) {
|
|
589
|
+
compatible.push(name);
|
|
590
|
+
} else {
|
|
591
|
+
filtered.push(name);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return { compatible, filtered };
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Adjust the active tool set for a selected model's provider capabilities.
|
|
600
|
+
* Returns tool names that should be active — removes incompatible tools.
|
|
601
|
+
*
|
|
602
|
+
* This is a hard filter only — it removes tools that would fail at the
|
|
603
|
+
* provider level. It does NOT remove tools based on soft heuristics.
|
|
604
|
+
*/
|
|
605
|
+
export function adjustToolSet(
|
|
606
|
+
activeToolNames: string[],
|
|
607
|
+
selectedModelApi: string,
|
|
608
|
+
): { toolNames: string[]; removedTools: string[] } {
|
|
609
|
+
const { compatible, filtered } = filterToolsForProvider(activeToolNames, selectedModelApi);
|
|
610
|
+
return { toolNames: compatible, removedTools: filtered };
|
|
611
|
+
}
|
|
@@ -20,7 +20,7 @@ import type {
|
|
|
20
20
|
ReactiveExecutionConfig,
|
|
21
21
|
GateEvaluationConfig,
|
|
22
22
|
} from "./types.js";
|
|
23
|
-
import type { DynamicRoutingConfig } from "./model-router.js";
|
|
23
|
+
import type { DynamicRoutingConfig, ModelCapabilities } from "./model-router.js";
|
|
24
24
|
|
|
25
25
|
export interface ContextManagementConfig {
|
|
26
26
|
observation_masking?: boolean; // default: true
|
|
@@ -255,6 +255,8 @@ export interface GSDPreferences {
|
|
|
255
255
|
post_unit_hooks?: PostUnitHookConfig[];
|
|
256
256
|
pre_dispatch_hooks?: PreDispatchHookConfig[];
|
|
257
257
|
dynamic_routing?: DynamicRoutingConfig;
|
|
258
|
+
/** Per-model capability overrides. Deep-merged with built-in profiles for capability-aware routing (ADR-004). */
|
|
259
|
+
modelOverrides?: Record<string, { capabilities?: Partial<ModelCapabilities> }>;
|
|
258
260
|
context_management?: ContextManagementConfig;
|
|
259
261
|
token_profile?: TokenProfile;
|
|
260
262
|
phases?: PhaseSkipPreferences;
|
|
@@ -28,6 +28,8 @@ After reflection is confirmed, decide the approach based on the actual scope —
|
|
|
28
28
|
|
|
29
29
|
**Anti-reduction rule:** If the user describes a big vision, plan the big vision. Do not ask "what's the minimum viable version?" or try to reduce scope unless the user explicitly asks for an MVP or minimal version. When something is complex or risky, phase it into a later milestone — do not cut it. The user's ambition is the target, and your job is to sequence it intelligently, not shrink it.
|
|
30
30
|
|
|
31
|
+
{{preparationContext}}
|
|
32
|
+
|
|
31
33
|
## Mandatory Investigation Before First Question Round
|
|
32
34
|
|
|
33
35
|
Before asking your first question, do a mandatory investigation pass. This is not optional.
|
|
@@ -38,6 +38,28 @@ To call this milestone complete, we must prove:
|
|
|
38
38
|
- {{one real end-to-end scenario}}
|
|
39
39
|
- {{what cannot be simulated if this milestone is to be considered truly done}}
|
|
40
40
|
|
|
41
|
+
## Architectural Decisions
|
|
42
|
+
|
|
43
|
+
### {{decisionTitle}}
|
|
44
|
+
|
|
45
|
+
**Decision:** {{decisionStatement}}
|
|
46
|
+
|
|
47
|
+
**Rationale:** {{rationale}}
|
|
48
|
+
|
|
49
|
+
**Alternatives Considered:**
|
|
50
|
+
- {{alternative}} — {{whyNotChosen}}
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
> Add additional decisions as separate `### Decision Title` blocks following the same structure above.
|
|
55
|
+
> See `.gsd/DECISIONS.md` for the full append-only register of all project decisions.
|
|
56
|
+
|
|
57
|
+
## Error Handling Strategy
|
|
58
|
+
|
|
59
|
+
{{errorHandlingStrategy}}
|
|
60
|
+
|
|
61
|
+
> Describe the approach for handling failures, edge cases, and error propagation. Include retry policies, fallback behaviors, and user-facing error messages where relevant.
|
|
62
|
+
|
|
41
63
|
## Risks and Unknowns
|
|
42
64
|
|
|
43
65
|
- {{riskOrUnknown}} — {{whyItMatters}}
|
|
@@ -47,8 +69,6 @@ To call this milestone complete, we must prove:
|
|
|
47
69
|
- `{{fileOrModule}}` — {{howItRelates}}
|
|
48
70
|
- `{{fileOrModule}}` — {{howItRelates}}
|
|
49
71
|
|
|
50
|
-
> See `.gsd/DECISIONS.md` for all architectural and pattern decisions — it is an append-only register; read it during planning, append to it during execution.
|
|
51
|
-
|
|
52
72
|
## Relevant Requirements
|
|
53
73
|
|
|
54
74
|
- {{requirementId}} — {{howThisMilestoneAdvancesIt}}
|
|
@@ -71,6 +91,18 @@ To call this milestone complete, we must prove:
|
|
|
71
91
|
|
|
72
92
|
- {{systemOrService}} — {{howThisMilestoneInteractsWithIt}}
|
|
73
93
|
|
|
94
|
+
## Testing Requirements
|
|
95
|
+
|
|
96
|
+
{{testingRequirements}}
|
|
97
|
+
|
|
98
|
+
> Specify test types (unit, integration, e2e), coverage expectations, and specific test scenarios that must pass.
|
|
99
|
+
|
|
100
|
+
## Acceptance Criteria
|
|
101
|
+
|
|
102
|
+
{{acceptanceCriteria}}
|
|
103
|
+
|
|
104
|
+
> Per-slice acceptance criteria gathered during discussion. Each slice should have clear, testable criteria.
|
|
105
|
+
|
|
74
106
|
## Open Questions
|
|
75
107
|
|
|
76
108
|
- {{question}} — {{currentThinking}}
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
getEligibleModels,
|
|
12
12
|
resolveModelForComplexity,
|
|
13
13
|
MODEL_CAPABILITY_PROFILES,
|
|
14
|
+
MODEL_CAPABILITY_TIER,
|
|
14
15
|
BASE_REQUIREMENTS,
|
|
15
16
|
defaultRoutingConfig,
|
|
16
17
|
} from "../model-router.js";
|
|
@@ -125,13 +126,9 @@ describe("computeTaskRequirements", () => {
|
|
|
125
126
|
// ─── MODEL_CAPABILITY_PROFILES ───────────────────────────────────────────────
|
|
126
127
|
|
|
127
128
|
describe("MODEL_CAPABILITY_PROFILES", () => {
|
|
128
|
-
test("contains all
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
"gpt-4o", "gpt-4o-mini", "gemini-2.5-pro", "gemini-2.0-flash",
|
|
132
|
-
"deepseek-chat", "o3",
|
|
133
|
-
];
|
|
134
|
-
for (const model of required) {
|
|
129
|
+
test("contains profiles for all tier-mapped models", () => {
|
|
130
|
+
const tierModels = Object.keys(MODEL_CAPABILITY_TIER);
|
|
131
|
+
for (const model of tierModels) {
|
|
135
132
|
assert.ok(MODEL_CAPABILITY_PROFILES[model], `Missing profile for ${model}`);
|
|
136
133
|
}
|
|
137
134
|
});
|
|
@@ -345,3 +342,30 @@ describe("RoutingDecision.selectionMethod", () => {
|
|
|
345
342
|
assert.equal(result.selectionMethod, "tier-only");
|
|
346
343
|
});
|
|
347
344
|
});
|
|
345
|
+
|
|
346
|
+
// ─── ADR-004: Profile Completeness Lint ─────────────────────────────────────
|
|
347
|
+
// Every model in MODEL_CAPABILITY_TIER must have an entry in
|
|
348
|
+
// MODEL_CAPABILITY_PROFILES. This prevents profile staleness as new models
|
|
349
|
+
// are added to the tier map without corresponding capability data.
|
|
350
|
+
|
|
351
|
+
describe("profile completeness (ADR-004 lint)", () => {
|
|
352
|
+
test("every model in MODEL_CAPABILITY_TIER has a MODEL_CAPABILITY_PROFILES entry", () => {
|
|
353
|
+
const tierModels = Object.keys(MODEL_CAPABILITY_TIER);
|
|
354
|
+
const missing = tierModels.filter(id => !MODEL_CAPABILITY_PROFILES[id]);
|
|
355
|
+
assert.equal(
|
|
356
|
+
missing.length,
|
|
357
|
+
0,
|
|
358
|
+
`Models in MODEL_CAPABILITY_TIER but missing from MODEL_CAPABILITY_PROFILES:\n ${missing.join("\n ")}\n\nAdd capability profiles for these models in model-router.ts.`,
|
|
359
|
+
);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
test("MODEL_CAPABILITY_PROFILES does not contain models absent from MODEL_CAPABILITY_TIER", () => {
|
|
363
|
+
const profileModels = Object.keys(MODEL_CAPABILITY_PROFILES);
|
|
364
|
+
const orphaned = profileModels.filter(id => !MODEL_CAPABILITY_TIER[id]);
|
|
365
|
+
assert.equal(
|
|
366
|
+
orphaned.length,
|
|
367
|
+
0,
|
|
368
|
+
`Models in MODEL_CAPABILITY_PROFILES but not in MODEL_CAPABILITY_TIER:\n ${orphaned.join("\n ")}\n\nEither add these to MODEL_CAPABILITY_TIER or remove stale profiles.`,
|
|
369
|
+
);
|
|
370
|
+
});
|
|
371
|
+
});
|
|
@@ -287,9 +287,9 @@ test("resolveModelForComplexity falls back to tier-only when capability_routing
|
|
|
287
287
|
assert.ok(!result.selectionMethod || result.selectionMethod === "tier-only");
|
|
288
288
|
});
|
|
289
289
|
|
|
290
|
-
test("MODEL_CAPABILITY_PROFILES has entries for
|
|
290
|
+
test("MODEL_CAPABILITY_PROFILES has entries for all tier-mapped models", () => {
|
|
291
291
|
const profiledModels = Object.keys(MODEL_CAPABILITY_PROFILES);
|
|
292
|
-
assert.ok(profiledModels.length >=
|
|
292
|
+
assert.ok(profiledModels.length >= 30, `Expected ≥30 profiles, got ${profiledModels.length}`);
|
|
293
293
|
assert.ok(MODEL_CAPABILITY_PROFILES["claude-opus-4-6"]);
|
|
294
294
|
assert.ok(MODEL_CAPABILITY_PROFILES["claude-haiku-4-5"]);
|
|
295
295
|
});
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// GSD-2 — Tool Compatibility + Model Router Tool Filtering Tests (ADR-005 Phases 2-3)
|
|
2
|
+
import { describe, test, beforeEach } from "node:test";
|
|
3
|
+
import assert from "node:assert/strict";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
registerToolCompatibility,
|
|
7
|
+
getToolCompatibility,
|
|
8
|
+
getAllToolCompatibility,
|
|
9
|
+
registerMcpToolCompatibility,
|
|
10
|
+
resetToolCompatibilityRegistry,
|
|
11
|
+
} from "@gsd/pi-coding-agent";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
isToolCompatibleWithProvider,
|
|
15
|
+
filterToolsForProvider,
|
|
16
|
+
adjustToolSet,
|
|
17
|
+
} from "../model-router.js";
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
getProviderCapabilities,
|
|
21
|
+
} from "@gsd/pi-ai";
|
|
22
|
+
|
|
23
|
+
// ─── Tool Compatibility Registry ────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
describe("tool compatibility registry", () => {
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
resetToolCompatibilityRegistry();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("built-in tools are pre-registered", () => {
|
|
31
|
+
const builtins = ["bash", "read", "write", "edit", "grep", "find", "ls", "lsp"];
|
|
32
|
+
for (const name of builtins) {
|
|
33
|
+
const compat = getToolCompatibility(name);
|
|
34
|
+
assert.ok(compat !== undefined, `${name} should be pre-registered`);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("unknown tool returns undefined", () => {
|
|
39
|
+
assert.equal(getToolCompatibility("nonexistent_tool_xyz"), undefined);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("registerToolCompatibility stores and retrieves metadata", () => {
|
|
43
|
+
registerToolCompatibility("screenshot_tool", {
|
|
44
|
+
producesImages: true,
|
|
45
|
+
minCapabilityTier: "standard",
|
|
46
|
+
});
|
|
47
|
+
const compat = getToolCompatibility("screenshot_tool");
|
|
48
|
+
assert.ok(compat);
|
|
49
|
+
assert.equal(compat.producesImages, true);
|
|
50
|
+
assert.equal(compat.minCapabilityTier, "standard");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("registerMcpToolCompatibility sets default schema features", () => {
|
|
54
|
+
registerMcpToolCompatibility("mcp__test__tool");
|
|
55
|
+
const compat = getToolCompatibility("mcp__test__tool");
|
|
56
|
+
assert.ok(compat);
|
|
57
|
+
assert.ok(compat.schemaFeatures?.includes("patternProperties"));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("registerMcpToolCompatibility allows overrides", () => {
|
|
61
|
+
registerMcpToolCompatibility("mcp__test__override", { producesImages: true });
|
|
62
|
+
const compat = getToolCompatibility("mcp__test__override");
|
|
63
|
+
assert.ok(compat);
|
|
64
|
+
assert.equal(compat.producesImages, true);
|
|
65
|
+
assert.ok(compat.schemaFeatures?.includes("patternProperties"));
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("getAllToolCompatibility returns all entries", () => {
|
|
69
|
+
const all = getAllToolCompatibility();
|
|
70
|
+
assert.ok(all.size >= 10); // at least built-in tools
|
|
71
|
+
assert.ok(all.has("bash"));
|
|
72
|
+
assert.ok(all.has("read"));
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("resetToolCompatibilityRegistry clears custom entries but keeps builtins", () => {
|
|
76
|
+
registerToolCompatibility("custom_tool", { producesImages: true });
|
|
77
|
+
assert.ok(getToolCompatibility("custom_tool"));
|
|
78
|
+
resetToolCompatibilityRegistry();
|
|
79
|
+
assert.equal(getToolCompatibility("custom_tool"), undefined);
|
|
80
|
+
assert.ok(getToolCompatibility("bash")); // built-in preserved
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// ─── isToolCompatibleWithProvider ───────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
describe("isToolCompatibleWithProvider", () => {
|
|
87
|
+
beforeEach(() => {
|
|
88
|
+
resetToolCompatibilityRegistry();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("tool without compatibility metadata is always compatible", () => {
|
|
92
|
+
const caps = getProviderCapabilities("anthropic-messages");
|
|
93
|
+
assert.equal(isToolCompatibleWithProvider("unknown_tool", caps), true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("built-in tools are compatible with all providers", () => {
|
|
97
|
+
const providers = ["anthropic-messages", "openai-responses", "google-generative-ai", "mistral-conversations"];
|
|
98
|
+
const tools = ["bash", "read", "write", "edit"];
|
|
99
|
+
for (const api of providers) {
|
|
100
|
+
const caps = getProviderCapabilities(api);
|
|
101
|
+
for (const tool of tools) {
|
|
102
|
+
assert.equal(
|
|
103
|
+
isToolCompatibleWithProvider(tool, caps),
|
|
104
|
+
true,
|
|
105
|
+
`${tool} should be compatible with ${api}`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("image-producing tool filtered for providers without image support", () => {
|
|
112
|
+
registerToolCompatibility("screenshot", { producesImages: true });
|
|
113
|
+
const openaiCaps = getProviderCapabilities("openai-responses");
|
|
114
|
+
assert.equal(isToolCompatibleWithProvider("screenshot", openaiCaps), false);
|
|
115
|
+
|
|
116
|
+
const anthropicCaps = getProviderCapabilities("anthropic-messages");
|
|
117
|
+
assert.equal(isToolCompatibleWithProvider("screenshot", anthropicCaps), true);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("tool with unsupported schema features filtered for Google", () => {
|
|
121
|
+
registerToolCompatibility("complex_schema_tool", {
|
|
122
|
+
schemaFeatures: ["patternProperties"],
|
|
123
|
+
});
|
|
124
|
+
const googleCaps = getProviderCapabilities("google-generative-ai");
|
|
125
|
+
assert.equal(isToolCompatibleWithProvider("complex_schema_tool", googleCaps), false);
|
|
126
|
+
|
|
127
|
+
const anthropicCaps = getProviderCapabilities("anthropic-messages");
|
|
128
|
+
assert.equal(isToolCompatibleWithProvider("complex_schema_tool", anthropicCaps), true);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// ─── filterToolsForProvider ─────────────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
describe("filterToolsForProvider", () => {
|
|
135
|
+
beforeEach(() => {
|
|
136
|
+
resetToolCompatibilityRegistry();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("all built-in tools pass for any provider", () => {
|
|
140
|
+
const toolNames = ["bash", "read", "write", "edit", "grep", "find", "ls"];
|
|
141
|
+
const { compatible, filtered } = filterToolsForProvider(toolNames, "mistral-conversations");
|
|
142
|
+
assert.deepEqual(compatible, toolNames);
|
|
143
|
+
assert.deepEqual(filtered, []);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("image tool filtered for OpenAI Responses", () => {
|
|
147
|
+
registerToolCompatibility("browser_screenshot", { producesImages: true });
|
|
148
|
+
const toolNames = ["bash", "read", "browser_screenshot"];
|
|
149
|
+
const { compatible, filtered } = filterToolsForProvider(toolNames, "openai-responses");
|
|
150
|
+
assert.deepEqual(compatible, ["bash", "read"]);
|
|
151
|
+
assert.deepEqual(filtered, ["browser_screenshot"]);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("MCP tool with patternProperties filtered for Google", () => {
|
|
155
|
+
registerMcpToolCompatibility("mcp__repowise__search");
|
|
156
|
+
const toolNames = ["bash", "read", "mcp__repowise__search"];
|
|
157
|
+
const { compatible, filtered } = filterToolsForProvider(toolNames, "google-generative-ai");
|
|
158
|
+
assert.deepEqual(compatible, ["bash", "read"]);
|
|
159
|
+
assert.deepEqual(filtered, ["mcp__repowise__search"]);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("unknown provider passes all tools (permissive default)", () => {
|
|
163
|
+
registerToolCompatibility("image_tool", { producesImages: true });
|
|
164
|
+
registerMcpToolCompatibility("mcp_tool");
|
|
165
|
+
const toolNames = ["bash", "image_tool", "mcp_tool"];
|
|
166
|
+
const { compatible, filtered } = filterToolsForProvider(toolNames, "unknown-provider-xyz");
|
|
167
|
+
assert.deepEqual(compatible, toolNames);
|
|
168
|
+
assert.deepEqual(filtered, []);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// ─── adjustToolSet ──────────────────────────────────────────────────────────
|
|
173
|
+
|
|
174
|
+
describe("adjustToolSet", () => {
|
|
175
|
+
beforeEach(() => {
|
|
176
|
+
resetToolCompatibilityRegistry();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("returns all tools for Anthropic (most permissive)", () => {
|
|
180
|
+
registerToolCompatibility("screenshot", { producesImages: true });
|
|
181
|
+
const toolNames = ["bash", "read", "screenshot"];
|
|
182
|
+
const { toolNames: result, removedTools } = adjustToolSet(toolNames, "anthropic-messages");
|
|
183
|
+
assert.deepEqual(result, toolNames);
|
|
184
|
+
assert.deepEqual(removedTools, []);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("removes incompatible tools and reports them", () => {
|
|
188
|
+
registerToolCompatibility("screenshot", { producesImages: true });
|
|
189
|
+
registerMcpToolCompatibility("mcp_complex");
|
|
190
|
+
const toolNames = ["bash", "read", "screenshot", "mcp_complex"];
|
|
191
|
+
const { toolNames: result, removedTools } = adjustToolSet(toolNames, "google-generative-ai");
|
|
192
|
+
// Google supports images but not patternProperties
|
|
193
|
+
assert.ok(result.includes("bash"));
|
|
194
|
+
assert.ok(result.includes("read"));
|
|
195
|
+
assert.ok(result.includes("screenshot")); // Google supports images
|
|
196
|
+
assert.ok(!result.includes("mcp_complex")); // patternProperties not supported
|
|
197
|
+
assert.deepEqual(removedTools, ["mcp_complex"]);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
@@ -230,16 +230,13 @@ import {
|
|
|
230
230
|
// ─── Scenario 19: isGateQuestionId recognizes all gate patterns ──
|
|
231
231
|
|
|
232
232
|
test('write-gate: isGateQuestionId recognizes all gate patterns', () => {
|
|
233
|
-
assert.strictEqual(isGateQuestionId('layer1_scope_gate'), true);
|
|
234
|
-
assert.strictEqual(isGateQuestionId('layer2_architecture_gate'), true);
|
|
235
|
-
assert.strictEqual(isGateQuestionId('layer3_error_gate'), true);
|
|
236
|
-
assert.strictEqual(isGateQuestionId('layer4_quality_gate'), true);
|
|
237
233
|
assert.strictEqual(isGateQuestionId('depth_verification'), true);
|
|
238
234
|
assert.strictEqual(isGateQuestionId('depth_verification_M002'), true);
|
|
239
|
-
assert.strictEqual(isGateQuestionId('
|
|
235
|
+
assert.strictEqual(isGateQuestionId('depth_verification_confirm'), true);
|
|
240
236
|
// Non-gate question IDs
|
|
241
237
|
assert.strictEqual(isGateQuestionId('project_intent'), false);
|
|
242
238
|
assert.strictEqual(isGateQuestionId('feature_priority'), false);
|
|
239
|
+
assert.strictEqual(isGateQuestionId('layer1_scope_gate'), false);
|
|
243
240
|
assert.strictEqual(isGateQuestionId(''), false);
|
|
244
241
|
});
|
|
245
242
|
|
|
@@ -249,14 +246,14 @@ test('write-gate: pending gate lifecycle (set, get, clear)', () => {
|
|
|
249
246
|
clearDiscussionFlowState();
|
|
250
247
|
assert.strictEqual(getPendingGate(), null, 'starts null');
|
|
251
248
|
|
|
252
|
-
setPendingGate('
|
|
253
|
-
assert.strictEqual(getPendingGate(), '
|
|
249
|
+
setPendingGate('depth_verification');
|
|
250
|
+
assert.strictEqual(getPendingGate(), 'depth_verification', 'set correctly');
|
|
254
251
|
|
|
255
252
|
clearPendingGate();
|
|
256
253
|
assert.strictEqual(getPendingGate(), null, 'cleared correctly');
|
|
257
254
|
|
|
258
255
|
// clearDiscussionFlowState also clears pending gate
|
|
259
|
-
setPendingGate('
|
|
256
|
+
setPendingGate('depth_verification_M002');
|
|
260
257
|
clearDiscussionFlowState();
|
|
261
258
|
assert.strictEqual(getPendingGate(), null, 'clearDiscussionFlowState clears pending gate');
|
|
262
259
|
});
|
|
@@ -265,12 +262,12 @@ test('write-gate: pending gate lifecycle (set, get, clear)', () => {
|
|
|
265
262
|
|
|
266
263
|
test('write-gate: shouldBlockPendingGate blocks write/edit during pending gate', () => {
|
|
267
264
|
clearDiscussionFlowState();
|
|
268
|
-
setPendingGate('
|
|
265
|
+
setPendingGate('depth_verification');
|
|
269
266
|
|
|
270
267
|
// write should be blocked during discussion
|
|
271
268
|
const writeResult = shouldBlockPendingGate('write', 'M001', false);
|
|
272
269
|
assert.strictEqual(writeResult.block, true, 'write should be blocked');
|
|
273
|
-
assert.ok(writeResult.reason!.includes('
|
|
270
|
+
assert.ok(writeResult.reason!.includes('depth_verification'), 'reason mentions the gate');
|
|
274
271
|
|
|
275
272
|
// edit should be blocked
|
|
276
273
|
const editResult = shouldBlockPendingGate('edit', 'M001', false);
|
|
@@ -287,7 +284,7 @@ test('write-gate: shouldBlockPendingGate blocks write/edit during pending gate',
|
|
|
287
284
|
|
|
288
285
|
test('write-gate: shouldBlockPendingGate allows read-only and ask_user_questions during pending gate', () => {
|
|
289
286
|
clearDiscussionFlowState();
|
|
290
|
-
setPendingGate('
|
|
287
|
+
setPendingGate('depth_verification');
|
|
291
288
|
|
|
292
289
|
// ask_user_questions is always safe (model needs to re-ask)
|
|
293
290
|
assert.strictEqual(shouldBlockPendingGate('ask_user_questions', 'M001').block, false);
|
|
@@ -304,7 +301,7 @@ test('write-gate: shouldBlockPendingGate allows read-only and ask_user_questions
|
|
|
304
301
|
|
|
305
302
|
test('write-gate: shouldBlockPendingGate blocks outside discussion when a gate is pending', () => {
|
|
306
303
|
clearDiscussionFlowState();
|
|
307
|
-
setPendingGate('
|
|
304
|
+
setPendingGate('depth_verification');
|
|
308
305
|
|
|
309
306
|
// No milestoneId and no queue phase — still block because the gate is pending
|
|
310
307
|
const result = shouldBlockPendingGate('write', null, false);
|
|
@@ -330,7 +327,7 @@ test('write-gate: shouldBlockPendingGate blocks in queue mode when gate is pendi
|
|
|
330
327
|
|
|
331
328
|
test('write-gate: shouldBlockPendingGateBash allows read-only commands during pending gate', () => {
|
|
332
329
|
clearDiscussionFlowState();
|
|
333
|
-
setPendingGate('
|
|
330
|
+
setPendingGate('depth_verification');
|
|
334
331
|
|
|
335
332
|
assert.strictEqual(shouldBlockPendingGateBash('cat file.txt', 'M001').block, false);
|
|
336
333
|
assert.strictEqual(shouldBlockPendingGateBash('git log --oneline', 'M001').block, false);
|
|
@@ -344,11 +341,11 @@ test('write-gate: shouldBlockPendingGateBash allows read-only commands during pe
|
|
|
344
341
|
|
|
345
342
|
test('write-gate: shouldBlockPendingGateBash blocks mutating commands during pending gate', () => {
|
|
346
343
|
clearDiscussionFlowState();
|
|
347
|
-
setPendingGate('
|
|
344
|
+
setPendingGate('depth_verification');
|
|
348
345
|
|
|
349
346
|
const result = shouldBlockPendingGateBash('npm run build', 'M001');
|
|
350
347
|
assert.strictEqual(result.block, true, 'mutating bash should be blocked');
|
|
351
|
-
assert.ok(result.reason!.includes('
|
|
348
|
+
assert.ok(result.reason!.includes('depth_verification'));
|
|
352
349
|
|
|
353
350
|
clearDiscussionFlowState();
|
|
354
351
|
});
|
|
@@ -365,7 +362,7 @@ test('write-gate: no pending gate means no blocking', () => {
|
|
|
365
362
|
// ─── Scenario 28: resetWriteGateState clears pending gate ──
|
|
366
363
|
|
|
367
364
|
test('write-gate: resetWriteGateState clears pending gate', () => {
|
|
368
|
-
setPendingGate('
|
|
365
|
+
setPendingGate('depth_verification');
|
|
369
366
|
resetWriteGateState();
|
|
370
367
|
assert.strictEqual(getPendingGate(), null);
|
|
371
368
|
});
|