diff --git a/.changeset/rapid-jays-bark.md b/.changeset/rapid-jays-bark.md new file mode 100644 index 000000000..fe1667cde --- /dev/null +++ b/.changeset/rapid-jays-bark.md @@ -0,0 +1,5 @@ +--- +type: Added +pr: 2458 +--- +**Config-gated provider escalation when a run hits a quota or rate limit** — an executor killed by a provider throttle stopped the phase and waited for a manual restart; escalating a tier did not help because the same throttled provider was still in play. Set `dynamic_routing.provider_escalation` to an ordered list of fallback model IDs and GSD now switches provider on a quota-exceeded failure, logs the swap (`sonnet → gpt-5`), honors the provider's `Retry-After`, caps the walk at `max_escalations`, and names every model tried once the list is spent. Opt-in — unset, quota failures keep today's manual recovery prompt. (#2296) diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 26acb1614..e2ac83149 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -160,7 +160,8 @@ GSD stores project settings in `.planning/config.json`. Created during `/gsd-new | `dynamic_routing.enabled` | boolean | `true`, `false` | `false` | Master switch for [dynamic routing with failure-tier escalation](#dynamic-routing-with-failure-tier-escalation-dynamic_routing--added-in-v140). When `true`, agents resolve to `tier_models[default_tier]` and escalate one tier up on orchestrator-detected soft failure. Added in v1.40 ([#3024](https://github.com/open-gsd/gsd-core/pull/3031)) | | `dynamic_routing.tier_models.` | enum | `opus`, `sonnet`, `haiku` | (none) | Tier alias for `light`, `standard`, or `heavy`. Used when `dynamic_routing.enabled: true`. Added in v1.40 | | `dynamic_routing.escalate_on_failure` | boolean | `true`, `false` | `true` | When `false`, escalation is disabled even if `enabled: true` — every attempt uses the default tier. Added in v1.40 | -| `dynamic_routing.max_escalations` | integer | `0`, `1`, `2`, … | `1` | Hard cap on retries per agent invocation. Beyond the cap the resolver returns the cap-tier model. Added in v1.40 | +| `dynamic_routing.max_escalations` | integer | `0`, `1`, `2`, … | `1` | Hard cap on retries per agent invocation. Beyond the cap the resolver returns the cap-tier model. Also caps `provider_escalation`. Added in v1.40 | +| `dynamic_routing.provider_escalation` | string[] | ordered model IDs | (none) | Opt-in fallback providers tried when a run dies on a quota / rate limit — see [provider escalation](#provider-escalation-on-quota-exceeded--added-in-v143). Added in v1.43 ([#2296](https://github.com/open-gsd/gsd-core/issues/2296)) | | `project_code` | string | any short string | (none) | Prefix for phase directory names (e.g., `"ABC"` produces `ABC-01-setup/`). Added in v1.31 | | `phase_id_convention` | enum | `"milestone-prefixed"`, `null` | `null` | Phase ID naming convention. `null` = legacy numeric IDs (`Phase 1`, `Phase 2`). `"milestone-prefixed"` = globally unique IDs that encode the enclosing milestone (`Phase 1-01`, `Phase 1-02`). Run `gsd-tools roadmap upgrade --convention milestone-prefixed` to migrate an existing ROADMAP.md. | | `response_language` | string | language code | (none) | Language for agent responses (e.g., `"pt"`, `"ko"`, `"ja"`). Propagates to all spawned agents for cross-phase language consistency. Added in v1.32 | @@ -1242,7 +1243,40 @@ The `dynamic_routing` block is **disabled by default** — `enabled: false` (or | `dynamic_routing.tier_models.standard` | enum | (none) | Tier alias for standard. Typically `sonnet`. | | `dynamic_routing.tier_models.heavy` | enum | (none) | Tier alias for heavy. Typically `opus`. | | `dynamic_routing.escalate_on_failure` | boolean | `true` | When false, escalation is disabled (every attempt uses the default tier). | -| `dynamic_routing.max_escalations` | integer | `1` | Hard cap on retries per agent invocation. Prevents runaway loops. | +| `dynamic_routing.max_escalations` | integer | `1` | Hard cap on retries per agent invocation. Prevents runaway loops. Also caps the provider ladder below. | +| `dynamic_routing.provider_escalation` | string[] | (none) | Ordered fallback model IDs tried when a run dies on a provider **quota / rate limit**. Added in v1.43 ([#2296](https://github.com/open-gsd/gsd-core/issues/2296)) | + +#### Provider escalation on quota-exceeded — added in v1.43 + +The tier ladder above escalates *within one provider*. That does not help when the +provider itself is what ran out: a heavier tier on the same throttled account is still +throttled. `provider_escalation` is a separate, opt-in ladder for exactly that case. + +```json +{ + "dynamic_routing": { + "enabled": true, + "tier_models": { "light": "haiku", "standard": "sonnet", "heavy": "opus" }, + "provider_escalation": ["gpt-5", "nvidia/llama-3.3"], + "max_escalations": 2 + } +} +``` + +When an executor dies and `gsd-tools agent classify-failure` classifies the error body as +`quota-exceeded`, `execute-phase` re-resolves the model from this list instead of waiting +for a quota reset, logs the switch (`sonnet → gpt-5`), and honors any `Retry-After` the +provider sent. The ladder is capped at `min(max_escalations, provider_escalation.length)`; +once spent, GSD reports every model it tried and falls back to the manual recovery prompt +rather than silently retrying the last one. + +- **Opt-in.** With no `provider_escalation` configured, quota failures keep today's manual + wait-for-reset prompt exactly as before. +- **Quota only.** Other failure classes (`classify-handoff-bug`, `unknown-failure`) never + consult this ladder — they keep the tier ladder. +- **`escalate_on_failure: false`** disables this ladder too. +- Entries are opaque model IDs passed to the runtime. Blank and non-string entries are + dropped; the surviving order is preserved. #### When to use which diff --git a/docs/INVENTORY-MANIFEST.json b/docs/INVENTORY-MANIFEST.json index e210e8a81..2bc680831 100644 --- a/docs/INVENTORY-MANIFEST.json +++ b/docs/INVENTORY-MANIFEST.json @@ -229,6 +229,7 @@ "execute-mvp-tdd.md", "execute-phase-between-wave-reset.md", "execute-phase-context-guard.md", + "execute-phase-quota-recovery.md", "execute-phase-requirement-revert.md", "execute-phase-response-language.md", "execute-phase-wave-guard.md", diff --git a/docs/INVENTORY.md b/docs/INVENTORY.md index bfba70a18..fdc1830a8 100644 --- a/docs/INVENTORY.md +++ b/docs/INVENTORY.md @@ -317,6 +317,7 @@ Full roster at `gsd-core/references/*.md`. References are shared knowledge docum | `execute-phase-context-guard.md` | Context exhaustion guard step for `execute-phase` wave loop — `workflow.context_guard_mode` dispatch table (warn/auto/off) and POOR-tier pause-work trigger (#1452). | | `execute-phase-requirement-revert.md` | Gap-report step for `execute-phase` — reverts this phase's own shared requirement IDs out of `Complete` in REQUIREMENTS.md before rendering a `gaps_found` report, scoped to `PHASE_REQ_IDS` so other phases' rows are untouched (#2388). | | `execute-phase-response-language.md` | Response-language directive for `execute-phase` orchestrator output (questions, narration, report-template prose); extracted to keep the workflow under the frozen pre-phase-6 byte ceiling (#2402). | +| `execute-phase-quota-recovery.md` | Step 7.1 detail for `execute-phase` — `quota-exceeded` recovery: the opt-in `dynamic_routing.provider_escalation` ladder (swap provider, honor `Retry-After`, fail loudly when spent) and the default manual wait-for-reset prompt (#2296). | | `continuation-format.md` | Session continuation/resume format. | | `domain-probes.md` | Domain-specific probing questions for discuss-phase. | | `edge-probe.md` | Spec-phase edge-completeness probe — 8-category edge taxonomy, shape classification, and the `requirements → checks → verifier` resolution model (Step 5.5). | diff --git a/docs/how-to/configure-model-profiles.md b/docs/how-to/configure-model-profiles.md index 8d575d2f5..99639d141 100644 --- a/docs/how-to/configure-model-profiles.md +++ b/docs/how-to/configure-model-profiles.md @@ -152,6 +152,35 @@ Every attempt uses `tier_models[default_tier]` regardless of outcome — useful `dynamic_routing` is **disabled by default**. Omitting the block or setting `enabled: false` preserves static resolution. +### Keep going when a provider throttles you + +The tier ladder above escalates within one provider. When the provider itself is the thing +that ran out of quota, a heavier tier on the same account is still throttled. Add +`provider_escalation` — an ordered list of fallback model IDs — to keep the phase moving +instead of stopping for a manual restart: + +```json +{ + "dynamic_routing": { + "enabled": true, + "tier_models": { "light": "haiku", "standard": "sonnet", "heavy": "opus" }, + "provider_escalation": ["gpt-5", "nvidia/llama-3.3"], + "max_escalations": 2 + } +} +``` + +When an executor dies on a rate limit, GSD classifies the error body, switches to the next +model in the list, logs the swap (`sonnet → gpt-5`), and waits out any `Retry-After` the +provider sent. The walk is capped at `min(max_escalations, provider_escalation.length)`. +Once the list is spent, GSD names every model it tried and hands you the normal recovery +prompt — it never silently retries the exhausted one. + +This is most useful on providers without a guaranteed SLA (Nvidia NIM, OpenRouter, and +other third-party OpenCode models), where a throttle mid-phase is routine. It only fires on +quota / rate-limit failures; other failures keep the tier ladder. Leaving +`provider_escalation` unset preserves the manual wait-for-reset behaviour exactly. + --- ## Using GSD on non-Anthropic runtimes diff --git a/gsd-core/bin/gsd-tools.cjs b/gsd-core/bin/gsd-tools.cjs index 767e7c221..1e205439a 100755 --- a/gsd-core/bin/gsd-tools.cjs +++ b/gsd-core/bin/gsd-tools.cjs @@ -295,7 +295,7 @@ const { routePhasesCommand } = require('./lib/phases-command-router.cjs'); const { routeValidateCommand } = require('./lib/validate-command-router.cjs'); const { routeRoadmapCommand } = require('./lib/roadmap-command-router.cjs'); const { routeCapabilityCommand } = require('./lib/capability-command-router.cjs'); -const { routeAgentCommand } = require('./lib/agent-command-router.cjs'); +const { routeAgentCommand, AGENT_FAILURE_CLASSES } = require('./lib/agent-command-router.cjs'); const smartEntryMod = require('./lib/smart-entry.cjs'); const { routeCheckCommand } = require('./lib/check-command-router.cjs'); const { routeTaskCommand } = require('./lib/task-command-router.cjs'); @@ -609,7 +609,20 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load let effortOverride; let fastModeOverride; let attempt; + let failureClass; const positionals = []; + // #2296: the valid classes come from the classifier's own frozen enum, so + // this validator can never drift from what `agent classify-failure` emits. + const validFailureClasses = Object.values(AGENT_FAILURE_CLASSES); + const setFailureClass = (v) => { + if (!validFailureClasses.includes(v)) { + error( + `--failure-class must be one of: ${validFailureClasses.join(', ')}`, + ERROR_REASON.USAGE, + ); + } + failureClass = v; + }; for (let i = 0; i < execArgs.length; i++) { const a = execArgs[i]; if (a.startsWith('--effort=')) { @@ -628,6 +641,10 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load attempt = n; continue; } + if (a.startsWith('--failure-class=')) { + setFailureClass(a.slice('--failure-class='.length)); + continue; + } if (a === '--effort') { const val = execArgs[i + 1]; if (val === undefined || val.startsWith('--')) error('Missing value for --effort', ERROR_REASON.USAGE); @@ -651,6 +668,13 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load i++; continue; } + if (a === '--failure-class') { + const val = execArgs[i + 1]; + if (val === undefined || val.startsWith('--')) error('Missing value for --failure-class', ERROR_REASON.USAGE); + setFailureClass(val); + i++; + continue; + } if (a === '--raw') continue; if (a.startsWith('-')) error(`Unknown flag for resolve-execution: ${a}`, ERROR_REASON.USAGE); positionals.push(a); @@ -662,6 +686,7 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load effortOverride, fastModeOverride, attempt, + failureClass, }); } diff --git a/gsd-core/bin/shared/config-schema.manifest.json b/gsd-core/bin/shared/config-schema.manifest.json index f0d43d5b9..1676e2afd 100644 --- a/gsd-core/bin/shared/config-schema.manifest.json +++ b/gsd-core/bin/shared/config-schema.manifest.json @@ -149,8 +149,8 @@ }, { "topLevel": "dynamic_routing", - "source": "^dynamic_routing\\.(enabled|escalate_on_failure|max_escalations|tier_models\\.(light|standard|heavy))$", - "description": "dynamic_routing.>" + "source": "^dynamic_routing\\.(enabled|escalate_on_failure|max_escalations|provider_escalation|tier_models\\.(light|standard|heavy))$", + "description": "dynamic_routing.>" }, { "topLevel": "model_overrides", diff --git a/gsd-core/references/execute-phase-quota-recovery.md b/gsd-core/references/execute-phase-quota-recovery.md new file mode 100644 index 000000000..3c5fb9315 --- /dev/null +++ b/gsd-core/references/execute-phase-quota-recovery.md @@ -0,0 +1,55 @@ +**Step 7.1 detail — `class == "quota-exceeded"` recovery.** + +Do not offer "retry now". Run the step-5 spot-check first; if SUMMARY.md is missing but +commits exist, route to safe-resume (`state.verify-against-disk`) instead of an immediate +redispatch. + +**7.1a — provider escalation (#2296, opt-in).** A heavier tier on the same throttled +provider is still throttled, so when `dynamic_routing.provider_escalation` is configured +GSD swaps PROVIDER rather than waiting for a reset. `QUOTA_ATTEMPT` starts at 1 on the +first quota failure of this phase and increments on each subsequent one. + +```bash +ESC_JSON=$(gsd_run query resolve-execution gsd-executor --attempt "${QUOTA_ATTEMPT:-1}" --failure-class quota-exceeded) +ESCALATED=$(echo "$ESC_JSON" | jq -r '.escalation.escalated') +EXHAUSTED=$(echo "$ESC_JSON" | jq -r '.escalation.exhausted') +ESC_FROM=$(echo "$ESC_JSON" | jq -r '.escalation.from') +ESC_TO=$(echo "$ESC_JSON" | jq -r '.escalation.to') +ESC_TRIED=$(echo "$ESC_JSON" | jq -r '.escalation.attempted | join(" -> ")') +``` + +- **`ESCALATED == "true"`** — log the switch, honor the provider's own backoff + (`sleep "$RETRY_AFTER"` when `RETRY_AFTER` is set), then re-dispatch the failed plan with + `executor_model` overridden to `$ESC_TO` and `QUOTA_ATTEMPT` incremented. Do not prompt — + this is the configured, opt-in path. + + ```text + ⚡ Provider quota hit — escalating model: {ESC_FROM} → {ESC_TO} + Runtime sentinel: {SENTINEL} + {RETRY_HINT} + ``` + +- **`EXHAUSTED == "true"`** — the ladder is spent. Fail loudly naming every model tried, + then fall through to the manual options below. Never silently retry the last one. + + ```text + ⛔ Provider escalation exhausted — tried: {ESC_TRIED} + ``` + +- **`ESCALATED == "false"` and not exhausted** — escalation is not configured for this + project; use the manual path below. This is the default. + +**7.1b — manual recovery (default when escalation is not configured).** + +```text +⚠ Plan {plan_id} terminated by provider quota / rate limit + Runtime sentinel: {SENTINEL} + {RETRY_HINT} + Partial commits on worktree branch: {N} + SUMMARY.md present: {yes|no} + 1. Wait for quota reset, then resume (recommended) +2. Switch to a different runtime / model and resume +3. Abort phase and report partial state +``` + +Re-run `/gsd:execute-phase` after the quota resets for Option 1. diff --git a/gsd-core/workflows/execute-phase.md b/gsd-core/workflows/execute-phase.md index 4ab92545b..7e6d69714 100644 --- a/gsd-core/workflows/execute-phase.md +++ b/gsd-core/workflows/execute-phase.md @@ -1024,24 +1024,14 @@ increases monotonically across waves. `{status}` is `complete` (success), if [ -n "$RETRY_AFTER" ]; then RETRY_HINT=" Provider hinted retry-after: ${RETRY_AFTER}s"; else RETRY_HINT=""; fi ``` One classifier branch handles sentinels across Claude/Copilot/Codex/Gemini. Reference: `docs/research/provider-rate-limit-signals.md`. - **Step 7.1 — `class == "quota-exceeded"`:** - Do not offer "retry now". Run step-5 spot-check first; if SUMMARY.md is missing but commits exist, route to safe-resume (`state.verify-against-disk`) instead of immediate redispatch. - ```text - ⚠ Plan {plan_id} terminated by provider quota / rate limit - Runtime sentinel: {SENTINEL} - {RETRY_HINT} - Partial commits on worktree branch: {N} - SUMMARY.md present: {yes|no} - 1. Wait for quota reset, then resume (recommended) - 2. Switch to a different runtime / model and resume - 3. Abort phase and report partial state - ``` - Re-run `/gsd:execute-phase` after quota reset for Option 1. + **Step 7.1 — `class == "quota-exceeded"`:** follow the quota-recovery fragment below. **Step 7.2 — `class == "classify-handoff-bug"`:** If error contains `classifyHandoffIfNeeded is not defined`, treat as Claude runtime bug. Run the same step-5 spot-checks; PASS => treat as success, FAIL => fall through. **Step 7.3 — `class == "unknown-failure"`:** Report failed plan and ask Continue/Stop; continuing may cascade into dependent plan failures. +@~/.claude/gsd-core/references/execute-phase-quota-recovery.md + @~/.claude/gsd-core/references/execute-phase-between-wave-reset.md 8. **Execute checkpoint plans between waves** — see ``. diff --git a/src/agent-command-router.cts b/src/agent-command-router.cts index 71b89a7dc..c4d6197c3 100644 --- a/src/agent-command-router.cts +++ b/src/agent-command-router.cts @@ -36,6 +36,21 @@ interface RouteAgentCommandOptions { // ─── Constants ──────────────────────────────────────────────────────────────── +/** + * #2296 — The runtime enum of failure classes `classifyAgentFailure` can emit. + * + * `AgentFailureResult`'s class strings are TypeScript types, which erase at + * runtime. Any second surface that needs to validate a class (the + * `resolve-execution --failure-class` flag) would otherwise have to re-declare + * the literals, giving two lists that can silently diverge. This frozen enum is + * the single runtime source both surfaces consume. + */ +const AGENT_FAILURE_CLASSES = Object.freeze({ + QUOTA_EXCEEDED: 'quota-exceeded', + CLASSIFY_HANDOFF_BUG: 'classify-handoff-bug', + UNKNOWN_FAILURE: 'unknown-failure', +} as const); + const QUOTA_SENTINELS: string[] = [ '429', 'usage_limit_reached', @@ -65,26 +80,26 @@ function classifyAgentFailure(body: unknown): AgentFailureResult { // eslint-disable-next-line @typescript-eslint/no-base-to-string const normalized = String(body ?? '').toLowerCase(); if (normalized.trim() === '') { - return { class: 'unknown-failure' }; + return { class: AGENT_FAILURE_CLASSES.UNKNOWN_FAILURE }; } for (const sentinel of QUOTA_SENTINELS) { if (normalized.includes(sentinel)) { const retryAfterSeconds = parseRetryAfter(body); return retryAfterSeconds === undefined - ? { class: 'quota-exceeded', sentinel } - : { class: 'quota-exceeded', sentinel, retryAfterSeconds }; + ? { class: AGENT_FAILURE_CLASSES.QUOTA_EXCEEDED, sentinel } + : { class: AGENT_FAILURE_CLASSES.QUOTA_EXCEEDED, sentinel, retryAfterSeconds }; } } if (normalized.includes(CLASSIFY_HANDOFF_SENTINEL)) { return { - class: 'classify-handoff-bug', + class: AGENT_FAILURE_CLASSES.CLASSIFY_HANDOFF_BUG, sentinel: CLASSIFY_HANDOFF_SENTINEL, }; } - return { class: 'unknown-failure' }; + return { class: AGENT_FAILURE_CLASSES.UNKNOWN_FAILURE }; } function routeAgentCommand({ args, raw }: RouteAgentCommandOptions): void { @@ -98,6 +113,7 @@ function routeAgentCommand({ args, raw }: RouteAgentCommandOptions): void { } export = { + AGENT_FAILURE_CLASSES, classifyAgentFailure, routeAgentCommand, }; diff --git a/src/commands.cts b/src/commands.cts index 006d405f0..4b5f48e50 100644 --- a/src/commands.cts +++ b/src/commands.cts @@ -30,7 +30,10 @@ import roadmapParserMod = require('./roadmap-parser.cjs'); const { extractCurrentMilestone, stripShippedMilestones: _stripShippedMilestones, getMilestoneInfo, getMilestonePhaseFilter, getRoadmapPhaseInternal } = roadmapParserMod; // eslint-disable-next-line @typescript-eslint/no-require-imports import modelResolverMod = require('./model-resolver.cjs'); -const { resolveModelInternal, resolveModelForTier, resolveEffortInternal, resolveFastModeInternal, resolveEffortForTier, resolveGranularityInternal, assertValidGranularityOverride } = modelResolverMod; +const { resolveModelInternal, resolveModelForTier, resolveProviderEscalation, resolveEffortInternal, resolveFastModeInternal, resolveEffortForTier, resolveGranularityInternal, assertValidGranularityOverride } = modelResolverMod; +// eslint-disable-next-line @typescript-eslint/no-require-imports +import agentCommandRouterMod = require('./agent-command-router.cjs'); +const { AGENT_FAILURE_CLASSES } = agentCommandRouterMod; import { renderEffortForRuntime, RUNTIMES_WITH_FAST_MODE } from './model-catalog.cjs'; // eslint-disable-next-line @typescript-eslint/no-require-imports import planningWorkspace = require('./planning-workspace.cjs'); @@ -482,9 +485,10 @@ function cmdResolveGranularity(cwd: string, phaseType: string | undefined, raw: * { model, profile, effort, effort_rendered, effort_param, effort_propagation, * fast_mode, fast_mode_supported, [unknown_agent] } * - * Flags: --effort , --fast-mode , --attempt + * Flags: --effort , --fast-mode , --attempt , + * --failure-class (#2296) */ -function cmdResolveExecution(cwd: string, agentType: string | undefined, raw: boolean, opts?: { effortOverride?: string; fastModeOverride?: boolean; attempt?: number }): void { +function cmdResolveExecution(cwd: string, agentType: string | undefined, raw: boolean, opts?: { effortOverride?: string; fastModeOverride?: boolean; attempt?: number; failureClass?: string }): void { if (!agentType) { error('agent-type required'); } @@ -499,10 +503,23 @@ function cmdResolveExecution(cwd: string, agentType: string | undefined, raw: bo // including dynamic_routing-enabled users who don't pass --attempt), and only an // explicit attempt routes through the tier ladder. resolveModelForTier itself // still falls back to resolveModelInternal when dynamic_routing is off. - const model = (opts.attempt !== undefined && opts.attempt !== null) + let model = (opts.attempt !== undefined && opts.attempt !== null) ? resolveModelForTier(cwd, agentType!, opts.attempt) : resolveModelInternal(cwd, agentType!); + // #2296: when the caller reports WHY the previous attempt failed, consult the + // provider-escalation ladder. Only a quota/rate-limit class warrants it — a + // heavier tier on the same throttled provider is still throttled, so this + // ladder swaps providers instead. Gated on an explicit --failure-class so the + // JSON contract is byte-identical for every existing caller. + let escalation: Record | undefined; + if (opts.failureClass !== undefined) { + const applicable = opts.failureClass === AGENT_FAILURE_CLASSES.QUOTA_EXCEEDED; + const resolved = resolveProviderEscalation(cwd, agentType!, opts.attempt, applicable); + if (resolved.escalated) model = resolved.to; + escalation = { class: opts.failureClass, ...resolved }; + } + const effortOpts: Record = {}; if (typeof opts.effortOverride === 'string') effortOpts['override'] = opts.effortOverride; @@ -532,6 +549,7 @@ function cmdResolveExecution(cwd: string, agentType: string | undefined, raw: bo fast_mode_supported: fastModeSupported, }; if (!agentModels) result['unknown_agent'] = true; + if (escalation) result['escalation'] = escalation; output(result, raw, effort); } diff --git a/src/model-resolver.cts b/src/model-resolver.cts index e252ea52c..c47e5f526 100644 --- a/src/model-resolver.cts +++ b/src/model-resolver.cts @@ -505,6 +505,101 @@ function resolveModelForTier(cwd: string, agentType: string, attempt?: number): return alias; } +/** + * #2296 — Outcome of consulting the provider-escalation ladder for one attempt. + * + * `from`/`to` describe the PROVIDER ladder only; they are equal whenever the + * ladder was not consulted or had nothing to offer. + */ +interface ProviderEscalationResult { + from: string; + to: string; + escalated: boolean; + exhausted: boolean; + attempted: string[]; + index: number; +} + +/** + * Keep only usable model ids: non-empty strings. A malformed config can put + * anything in here (nulls, numbers, blank strings), and a blank model id would + * resolve to an unusable agent invocation rather than failing visibly. Invalid + * entries are dropped and the surviving order is preserved, so the ladder stays + * predictable (ADR 227 — validate shape, not just type). + */ +function sanitizeProviderEscalation(raw: unknown): string[] { + if (!Array.isArray(raw)) return []; + return raw.filter((entry): entry is string => typeof entry === 'string' && entry.trim().length > 0); +} + +/** + * #2296 — Resolve the model for one attempt of the PROVIDER escalation ladder. + * + * The tier ladder (`resolveModelForTier`) escalates within one provider's + * `tier_models`, which does not help when that provider is the thing that is + * throttled. This walks `dynamic_routing.provider_escalation` instead: an + * ordered list of alternative model ids, capped by + * `min(max_escalations, list length)`. + * + * `applicable` is the caller's policy decision (only a quota-exceeded + * classification should consult this ladder). It is a parameter rather than a + * class check here so this module keeps depending only on leaf modules, per + * CONTEXT.md's Model Resolution module contract. + * + * Attempt 0 — and every non-applicable call — stays on the source model. + * `exhausted` reports that the ladder is spent so the caller can fail loudly + * naming every model it tried. + */ +function resolveProviderEscalation( + cwd: string, + agentType: string, + attempt: number | undefined, + applicable: boolean, +): ProviderEscalationResult { + // The model that would be used with no provider escalation at all. + const from = resolveModelForTier(cwd, agentType, 0); + const stay = (exhausted = false): ProviderEscalationResult => ({ + from, + to: from, + escalated: false, + exhausted, + attempted: [from], + index: 0, + }); + + if (!applicable) return stay(); + + const config = loadConfig(cwd); + const dr = config['dynamic_routing'] as Record | null | undefined; + if (!dr || typeof dr !== 'object' || dr['enabled'] !== true) return stay(); + if (dr['escalate_on_failure'] === false) return stay(); + + const list = sanitizeProviderEscalation(dr['provider_escalation']); + if (list.length === 0) return stay(); + + // Same default and same validity rule as the tier ladder above — a negative or + // non-integer max_escalations is invalid config, not a request for zero. + const maxEscalations = Number.isInteger(dr['max_escalations']) && (dr['max_escalations'] as number) >= 0 + ? (dr['max_escalations'] as number) + : 1; + const cap = Math.min(maxEscalations, list.length); + // An explicit cap of 0 means the ladder exists but is spent before it starts. + if (cap === 0) return stay(true); + + const attemptN = Number.isInteger(attempt) && (attempt as number) > 0 ? (attempt as number) : 0; + if (attemptN === 0) return stay(); + + const index = Math.min(attemptN, cap); + return { + from, + to: list[index - 1], + escalated: true, + exhausted: attemptN > cap, + attempted: [from, ...list.slice(0, index)], + index, + }; +} + // ─── #443 — Unified effort + fast_mode resolvers ───────────────────────────── const VALID_EFFORTS = ['minimal', 'low', 'medium', 'high', 'xhigh', 'max']; @@ -691,6 +786,7 @@ export = { resolveGranularityInternal, assertValidGranularityOverride, resolveModelForTier, + resolveProviderEscalation, VALID_EFFORTS, EFFORT_SET, nextEffort, diff --git a/tests/feat-2296-provider-escalation.test.cjs b/tests/feat-2296-provider-escalation.test.cjs new file mode 100644 index 000000000..8b2a1c77e --- /dev/null +++ b/tests/feat-2296-provider-escalation.test.cjs @@ -0,0 +1,412 @@ +/** + * GSD Tools Tests — config-gated provider escalation on quota-exceeded (#2296) + * + * #2068 wired `resolveModelForTier` into `cmdResolveExecution`, so `--attempt` + * already escalates the model up the TIER ladder (light -> standard -> heavy) + * within one provider's `tier_models`. That does not help when the failure is a + * provider quota/rate-limit: a heavier tier on the same throttled account is + * still throttled. + * + * #2296 layers a PROVIDER escalation ladder onto the existing reactive + * classification seam (`EXEC.CLASSIFY` / `agent classify-failure`, #3095): + * when `resolve-execution` is told the failure class was `quota-exceeded` and + * `dynamic_routing.provider_escalation` is configured, the model resolves from + * that ordered list instead of the tier ladder, capped by `max_escalations`, + * reporting `from -> to` so the switch is visible, and reporting `exhausted` + * once the list is spent so the caller can fail loudly. + * + * Baselines confirmed by running the built CLI against a temp project before + * writing these assertions (gsd-executor default routing tier is "standard"): + * attempt 0, no --failure-class -> sonnet + * attempt 1, no --failure-class -> opus (tier ladder, unchanged by #2296) + * --failure-class -> "Unknown flag" before this change + * config-set dynamic_routing.provider_escalation -> "Unknown config key" + */ + +const { test, describe, beforeEach, afterEach } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('fs'); +const path = require('path'); +const fc = require('fast-check'); +const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs'); + +const TIER_MODELS = { light: 'haiku', standard: 'sonnet', heavy: 'opus' }; + +function writeConfig(tmpDir, config) { + fs.writeFileSync(path.join(tmpDir, '.planning', 'config.json'), JSON.stringify(config)); +} + +function routingConfig(extra) { + return { dynamic_routing: { enabled: true, tier_models: TIER_MODELS, ...extra } }; +} + +/** Run resolve-execution and parse the JSON contract. Asserts success. */ +function resolve(tmpDir, args) { + const result = runGsdTools(`resolve-execution ${args}`, tmpDir); + assert.ok(result.success, `resolve-execution ${args} failed: ${result.error}`); + return JSON.parse(result.output); +} + +/** Run resolve-execution expecting a non-zero exit. Returns the raw result. */ +function resolveExpectFailure(tmpDir, args) { + const result = runGsdTools(`resolve-execution ${args}`, tmpDir); + assert.ok(!result.success, `resolve-execution ${args} unexpectedly succeeded`); + return result; +} + +describe('resolve-execution: provider escalation on quota-exceeded (#2296)', () => { + let tmpDir; + + beforeEach(() => { + tmpDir = createTempProject(); + }); + + afterEach(() => { + cleanup(tmpDir); + }); + + // ─── Back-compat: the default contract must not move ─────────────────────── + // + // Hyrum's Law guard. Every existing consumer of `resolve-execution` reads this + // JSON. Adding provider escalation must be invisible unless --failure-class is + // explicitly passed, exactly as #2068 gated the model on an explicit --attempt. + describe('back-compat — no --failure-class means no behavior change', () => { + beforeEach(() => { + writeConfig(tmpDir, routingConfig({ provider_escalation: ['gpt-5', 'llama-3.3'] })); + }); + + test('omitting --failure-class emits no `escalation` key at all', () => { + const parsed = resolve(tmpDir, 'gsd-executor --attempt 1'); + assert.ok( + !Object.hasOwn(parsed, 'escalation'), + 'the escalation block must be absent unless --failure-class is passed', + ); + }); + + test('omitting --failure-class keeps the TIER ladder (attempt 1 -> opus), ignoring provider_escalation', () => { + const parsed = resolve(tmpDir, 'gsd-executor --attempt 1'); + assert.strictEqual(parsed.model, 'opus'); + }); + + test('a configured provider_escalation does not leak into the no-attempt classic path', () => { + const parsed = resolve(tmpDir, 'gsd-executor'); + assert.strictEqual(parsed.model, 'sonnet'); + assert.ok(!Object.hasOwn(parsed, 'escalation')); + }); + }); + + // ─── Core behavior: quota-exceeded walks the provider list ───────────────── + // + // provider_escalation has 2 entries and max_escalations is 2, so cap == 2 and + // the limit-1 / limit / limit+1 boundaries are all observable. + describe('quota-exceeded — walks provider_escalation (cap == 2)', () => { + beforeEach(() => { + writeConfig(tmpDir, routingConfig({ + provider_escalation: ['gpt-5', 'llama-3.3'], + max_escalations: 2, + })); + }); + + test('attempt 0 stays on the source model and does not escalate', () => { + const parsed = resolve(tmpDir, 'gsd-executor --attempt 0 --failure-class quota-exceeded'); + assert.strictEqual(parsed.model, 'sonnet'); + assert.strictEqual(parsed.escalation.escalated, false); + assert.strictEqual(parsed.escalation.exhausted, false); + assert.strictEqual(parsed.escalation.index, 0); + }); + + // Boundary: cap - 1 + test('attempt 1 (cap-1) escalates to the FIRST provider and reports from -> to', () => { + const parsed = resolve(tmpDir, 'gsd-executor --attempt 1 --failure-class quota-exceeded'); + assert.strictEqual(parsed.model, 'gpt-5'); + assert.strictEqual(parsed.escalation.escalated, true); + assert.strictEqual(parsed.escalation.exhausted, false); + assert.strictEqual(parsed.escalation.from, 'sonnet'); + assert.strictEqual(parsed.escalation.to, 'gpt-5'); + assert.strictEqual(parsed.escalation.index, 1); + assert.deepStrictEqual(parsed.escalation.attempted, ['sonnet', 'gpt-5']); + }); + + // Boundary: exactly cap + test('attempt 2 (== cap) escalates to the SECOND provider, still not exhausted', () => { + const parsed = resolve(tmpDir, 'gsd-executor --attempt 2 --failure-class quota-exceeded'); + assert.strictEqual(parsed.model, 'llama-3.3'); + assert.strictEqual(parsed.escalation.escalated, true); + assert.strictEqual(parsed.escalation.exhausted, false); + assert.strictEqual(parsed.escalation.index, 2); + assert.deepStrictEqual(parsed.escalation.attempted, ['sonnet', 'gpt-5', 'llama-3.3']); + }); + + // Boundary: cap + 1 — the ladder is spent; the caller must be able to fail loudly. + test('attempt 3 (cap+1) reports exhausted:true and names every model attempted', () => { + const parsed = resolve(tmpDir, 'gsd-executor --attempt 3 --failure-class quota-exceeded'); + assert.strictEqual(parsed.escalation.exhausted, true); + assert.strictEqual(parsed.escalation.index, 2, 'index must pin at the cap, not run past it'); + assert.strictEqual(parsed.model, 'llama-3.3', 'model pins at the last provider once exhausted'); + assert.deepStrictEqual( + parsed.escalation.attempted, + ['sonnet', 'gpt-5', 'llama-3.3'], + 'attempted must name every model tried so the caller can report them', + ); + }); + + test('the echoed class is the class that was passed in', () => { + const parsed = resolve(tmpDir, 'gsd-executor --attempt 1 --failure-class quota-exceeded'); + assert.strictEqual(parsed.escalation.class, 'quota-exceeded'); + }); + }); + + // ─── The cap is min(max_escalations, list length) ────────────────────────── + describe('cap is the smaller of max_escalations and the list length', () => { + test('a list SHORTER than max_escalations exhausts at the list length', () => { + writeConfig(tmpDir, routingConfig({ provider_escalation: ['only-one'], max_escalations: 5 })); + const parsed = resolve(tmpDir, 'gsd-executor --attempt 2 --failure-class quota-exceeded'); + assert.strictEqual(parsed.model, 'only-one'); + assert.strictEqual(parsed.escalation.exhausted, true); + assert.strictEqual(parsed.escalation.index, 1); + }); + + test('max_escalations SMALLER than the list caps before the list ends', () => { + writeConfig(tmpDir, routingConfig({ + provider_escalation: ['first', 'second', 'third'], + max_escalations: 1, + })); + const parsed = resolve(tmpDir, 'gsd-executor --attempt 2 --failure-class quota-exceeded'); + assert.strictEqual(parsed.model, 'first', 'must not reach "second" past the cap'); + assert.strictEqual(parsed.escalation.exhausted, true); + assert.strictEqual(parsed.escalation.index, 1); + }); + + test('max_escalations: 0 disables escalation entirely', () => { + writeConfig(tmpDir, routingConfig({ provider_escalation: ['gpt-5'], max_escalations: 0 })); + const parsed = resolve(tmpDir, 'gsd-executor --attempt 1 --failure-class quota-exceeded'); + assert.strictEqual(parsed.model, 'sonnet'); + assert.strictEqual(parsed.escalation.escalated, false); + assert.strictEqual(parsed.escalation.exhausted, true, 'a zero cap is immediately spent'); + }); + + test('escalate_on_failure:false is a kill switch for provider escalation too', () => { + writeConfig(tmpDir, routingConfig({ + provider_escalation: ['gpt-5'], + escalate_on_failure: false, + })); + const parsed = resolve(tmpDir, 'gsd-executor --attempt 1 --failure-class quota-exceeded'); + assert.strictEqual(parsed.model, 'sonnet'); + assert.strictEqual(parsed.escalation.escalated, false); + }); + }); + + // ─── Opt-in gating: nothing happens unless configured ────────────────────── + describe('opt-in gating', () => { + test('quota-exceeded with NO provider_escalation configured falls back to the tier ladder', () => { + writeConfig(tmpDir, routingConfig({})); + const parsed = resolve(tmpDir, 'gsd-executor --attempt 1 --failure-class quota-exceeded'); + assert.strictEqual(parsed.model, 'opus', 'unconfigured means the existing tier ladder still applies'); + assert.strictEqual(parsed.escalation.escalated, false); + }); + + test('dynamic_routing disabled ignores provider_escalation', () => { + writeConfig(tmpDir, { + dynamic_routing: { enabled: false, tier_models: TIER_MODELS, provider_escalation: ['gpt-5'] }, + }); + const parsed = resolve(tmpDir, 'gsd-executor --attempt 1 --failure-class quota-exceeded'); + assert.strictEqual(parsed.model, 'sonnet'); + assert.strictEqual(parsed.escalation.escalated, false); + }); + + test('a NON-quota failure class never touches the provider list', () => { + writeConfig(tmpDir, routingConfig({ provider_escalation: ['gpt-5'] })); + const parsed = resolve(tmpDir, 'gsd-executor --attempt 1 --failure-class unknown-failure'); + assert.strictEqual(parsed.model, 'opus', 'non-quota failures keep the tier ladder'); + assert.strictEqual(parsed.escalation.escalated, false); + assert.strictEqual(parsed.escalation.class, 'unknown-failure'); + }); + + test('classify-handoff-bug does not trigger provider escalation', () => { + writeConfig(tmpDir, routingConfig({ provider_escalation: ['gpt-5'] })); + const parsed = resolve(tmpDir, 'gsd-executor --attempt 1 --failure-class classify-handoff-bug'); + assert.strictEqual(parsed.escalation.escalated, false); + }); + }); + + // ─── Hostile / malformed config (QA matrix: malformed, hostile, wrong type) ─ + describe('malformed provider_escalation is rejected without crashing', () => { + const cases = [ + ['a scalar where an array is expected', 'gpt-5'], + ['an object where an array is expected', { 0: 'gpt-5' }], + ['an empty array', []], + ['null', null], + ['an array of only invalid entries', [null, 42, '', ' ', {}]], + ]; + + for (const [label, value] of cases) { + test(`${label} falls back to the tier ladder and never throws`, () => { + writeConfig(tmpDir, routingConfig({ provider_escalation: value })); + const parsed = resolve(tmpDir, 'gsd-executor --attempt 1 --failure-class quota-exceeded'); + assert.strictEqual(parsed.model, 'opus'); + assert.strictEqual(parsed.escalation.escalated, false); + }); + } + + test('invalid entries are dropped and the surviving entries keep their order', () => { + writeConfig(tmpDir, routingConfig({ + provider_escalation: [null, 'good-one', '', 42, ' ', 'good-two'], + max_escalations: 5, + })); + const first = resolve(tmpDir, 'gsd-executor --attempt 1 --failure-class quota-exceeded'); + assert.strictEqual(first.model, 'good-one'); + const second = resolve(tmpDir, 'gsd-executor --attempt 2 --failure-class quota-exceeded'); + assert.strictEqual(second.model, 'good-two'); + assert.strictEqual(second.escalation.exhausted, false); + }); + + test('a prototype-polluting key in the list is not treated as an entry', () => { + writeConfig(tmpDir, routingConfig({ provider_escalation: ['__proto__', 'constructor'] })); + const parsed = resolve(tmpDir, 'gsd-executor --attempt 1 --failure-class quota-exceeded'); + // These are ordinary strings, not lookups — they must be passed through as + // opaque model ids and must not mutate any prototype. + assert.strictEqual(parsed.model, '__proto__'); + assert.strictEqual({}.polluted, undefined); + }); + + // A negative max_escalations is invalid config, not a request for zero. The + // tier ladder in resolveModelForTier already falls back to the documented + // default of 1 for any non-integer/negative value; the provider ladder must + // apply the SAME rule for the SAME key, or one config value would mean two + // different things inside one dynamic_routing block. + test('a negative max_escalations falls back to the default cap of 1, never inverts', () => { + writeConfig(tmpDir, routingConfig({ + provider_escalation: ['gpt-5', 'llama-3.3'], + max_escalations: -3, + })); + const first = resolve(tmpDir, 'gsd-executor --attempt 1 --failure-class quota-exceeded'); + assert.strictEqual(first.model, 'gpt-5'); + assert.strictEqual(first.escalation.index, 1); + + const past = resolve(tmpDir, 'gsd-executor --attempt 2 --failure-class quota-exceeded'); + assert.strictEqual(past.escalation.index, 1, 'cap stays 1, never negative and never wider'); + assert.strictEqual(past.escalation.exhausted, true); + }); + + test('a non-integer max_escalations also falls back to the default cap of 1', () => { + writeConfig(tmpDir, routingConfig({ + provider_escalation: ['gpt-5', 'llama-3.3'], + max_escalations: 2.7, + })); + const parsed = resolve(tmpDir, 'gsd-executor --attempt 2 --failure-class quota-exceeded'); + assert.strictEqual(parsed.escalation.index, 1); + assert.strictEqual(parsed.escalation.exhausted, true); + }); + }); + + // ─── CLI contract: --failure-class negative matrix ───────────────────────── + describe('--failure-class CLI negative matrix', () => { + beforeEach(() => { + writeConfig(tmpDir, routingConfig({ provider_escalation: ['gpt-5'] })); + }); + + test('an unknown class value is a usage error, not a silent no-op', () => { + const result = resolveExpectFailure(tmpDir, 'gsd-executor --attempt 1 --failure-class bogus-class'); + assert.ok(!/\bat .*:\d+:\d+/.test(result.error || ''), 'must not leak a stack trace'); + }); + + test('an empty --failure-class value is a usage error', () => { + resolveExpectFailure(tmpDir, 'gsd-executor --attempt 1 --failure-class='); + }); + + test('a missing value for --failure-class is a usage error', () => { + resolveExpectFailure(tmpDir, 'gsd-executor --failure-class'); + }); + + test('a flag-shaped value for --failure-class is a usage error', () => { + resolveExpectFailure(tmpDir, 'gsd-executor --failure-class --attempt'); + }); + + test('the --failure-class=value form is accepted', () => { + const parsed = resolve(tmpDir, 'gsd-executor --attempt 1 --failure-class=quota-exceeded'); + assert.strictEqual(parsed.model, 'gpt-5'); + }); + + test('shell metacharacters in the class value are rejected, never interpolated', () => { + const result = resolveExpectFailure( + tmpDir, + 'gsd-executor --attempt 1 --failure-class "quota-exceeded; touch pwned"', + ); + assert.ok(!fs.existsSync(path.join(tmpDir, 'pwned')), 'no shell interpolation may occur'); + assert.ok(!/\bat .*:\d+:\d+/.test(result.error || ''), 'must not leak a stack trace'); + }); + + test('--failure-class without --attempt does not escalate', () => { + const parsed = resolve(tmpDir, 'gsd-executor --failure-class quota-exceeded'); + assert.strictEqual(parsed.model, 'sonnet', 'no attempt counter means no escalation step'); + assert.strictEqual(parsed.escalation.escalated, false); + }); + }); + + // ─── Config-key registration (behavioral, per CONTRIBUTING.md) ───────────── + describe('dynamic_routing.provider_escalation is a registered config key', () => { + // argv-array form: the string form of runGsdTools strips inner quotes, + // which would mangle the JSON value into `[gpt-5,llama-3.3]`. + test('config-set accepts the key and persists the value', () => { + const result = runGsdTools( + ['config-set', 'dynamic_routing.provider_escalation', '["gpt-5","llama-3.3"]'], + tmpDir, + ); + assert.ok(result.success, `config-set should accept the key: ${result.error}`); + const config = JSON.parse(fs.readFileSync(path.join(tmpDir, '.planning', 'config.json'), 'utf-8')); + assert.deepStrictEqual(config.dynamic_routing?.provider_escalation, ['gpt-5', 'llama-3.3']); + }); + + test('a sibling typo under dynamic_routing is still rejected', () => { + const result = runGsdTools( + ['config-set', 'dynamic_routing.provider_escalations', '["x"]'], + tmpDir, + ); + assert.ok(!result.success, 'the regex must not have been widened into a catch-all'); + }); + }); + + // ─── Property: the resolved model is always drawn from the declared ladder ── + // + // This is the budget-limit invariant. Whatever the attempt counter and list, + // the resolver may only ever return the source model or an entry from the + // configured list, and may never walk past the cap. Deterministic seed, bounded + // runs, replay data printed on failure. + describe('property — the resolver never invents a model or walks past the cap', () => { + test('resolved model is always source-or-listed, and index <= cap', () => { + fc.assert( + fc.property( + fc.array(fc.string({ minLength: 1, maxLength: 12 }).filter((s) => s.trim().length > 0), { + minLength: 1, + maxLength: 4, + }), + fc.integer({ min: 0, max: 6 }), + fc.integer({ min: 0, max: 6 }), + (list, maxEscalations, attempt) => { + const dir = createTempProject(); + try { + writeConfig(dir, routingConfig({ + provider_escalation: list, + max_escalations: maxEscalations, + })); + const parsed = resolve(dir, `gsd-executor --attempt ${attempt} --failure-class quota-exceeded`); + const cap = Math.min(maxEscalations, list.length); + const allowed = new Set(['sonnet', ...list.slice(0, cap)]); + assert.ok( + allowed.has(parsed.model), + `resolved "${parsed.model}" outside the declared ladder ${JSON.stringify([...allowed])}`, + ); + assert.ok( + parsed.escalation.index <= cap, + `index ${parsed.escalation.index} walked past cap ${cap}`, + ); + } finally { + cleanup(dir); + } + }, + ), + { numRuns: 25, seed: 2296, verbose: true }, + ); + }); + }); +}); diff --git a/tests/fixtures/golden-install-parity/antigravity.json b/tests/fixtures/golden-install-parity/antigravity.json index ef5946a57..f463693b0 100644 --- a/tests/fixtures/golden-install-parity/antigravity.json +++ b/tests/fixtures/golden-install-parity/antigravity.json @@ -39,10 +39,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "ea841e2865248e74", - "gsd-core/bin/gsd-tools.cjs": "8c5bf9788c3967be", + "gsd-core/bin/gsd-tools.cjs": "df767e8f2f10fd3b", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "0109a5a9866a24e0", + "gsd-core/bin/shared/config-schema.manifest.json": "2f60b9eaa6cf6bc1", "gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "8bc541aabc2e143c", @@ -88,6 +88,7 @@ "gsd-core/references/execute-mvp-tdd.md": "a98a270a7ab126bc", "gsd-core/references/execute-phase-between-wave-reset.md": "3ad96ca0f7fee37e", "gsd-core/references/execute-phase-context-guard.md": "a5a1058d35806a8e", + "gsd-core/references/execute-phase-quota-recovery.md": "713cf7681f57e7ce", "gsd-core/references/execute-phase-requirement-revert.md": "48ade76866c57ce2", "gsd-core/references/execute-phase-response-language.md": "c240dc476dd15df5", "gsd-core/references/execute-phase-wave-guard.md": "826cf9315e592a1d", @@ -244,7 +245,7 @@ "gsd-core/workflows/docs-update.md": "16acb903dbe2bcbe", "gsd-core/workflows/edit-phase.md": "fc932e82ba1f585a", "gsd-core/workflows/eval-review.md": "9e8b169c05098b57", - "gsd-core/workflows/execute-phase.md": "e96e1fa1b78b8a21", + "gsd-core/workflows/execute-phase.md": "37ac5461a298c883", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "55d0706e80a2554a", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "13aa54f8279960ae", diff --git a/tests/fixtures/golden-install-parity/augment.json b/tests/fixtures/golden-install-parity/augment.json index 943f1349f..12b39860e 100644 --- a/tests/fixtures/golden-install-parity/augment.json +++ b/tests/fixtures/golden-install-parity/augment.json @@ -110,10 +110,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62", - "gsd-core/bin/gsd-tools.cjs": "d119a8ff93c9c0a3", + "gsd-core/bin/gsd-tools.cjs": "d44831d1e2f9c1c1", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "0109a5a9866a24e0", + "gsd-core/bin/shared/config-schema.manifest.json": "2f60b9eaa6cf6bc1", "gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -159,6 +159,7 @@ "gsd-core/references/execute-mvp-tdd.md": "a98a270a7ab126bc", "gsd-core/references/execute-phase-between-wave-reset.md": "3ad96ca0f7fee37e", "gsd-core/references/execute-phase-context-guard.md": "982006c1f3364242", + "gsd-core/references/execute-phase-quota-recovery.md": "c4ca0ee3875b6e02", "gsd-core/references/execute-phase-requirement-revert.md": "48ade76866c57ce2", "gsd-core/references/execute-phase-response-language.md": "c240dc476dd15df5", "gsd-core/references/execute-phase-wave-guard.md": "de9ac22cead4cfd8", @@ -315,7 +316,7 @@ "gsd-core/workflows/docs-update.md": "b5cdf1c35043cf7b", "gsd-core/workflows/edit-phase.md": "966a3eadd1bebc04", "gsd-core/workflows/eval-review.md": "32d3d7e80ab2bfc7", - "gsd-core/workflows/execute-phase.md": "1325f146afc9404a", + "gsd-core/workflows/execute-phase.md": "470e39f0acc3c121", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "4e265392b3f2ba0e", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "7b2d9a4a4013fd6a", diff --git a/tests/fixtures/golden-install-parity/claude-local.json b/tests/fixtures/golden-install-parity/claude-local.json index f33b6b287..c193e136d 100644 --- a/tests/fixtures/golden-install-parity/claude-local.json +++ b/tests/fixtures/golden-install-parity/claude-local.json @@ -109,10 +109,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62", - "gsd-core/bin/gsd-tools.cjs": "d119a8ff93c9c0a3", + "gsd-core/bin/gsd-tools.cjs": "d44831d1e2f9c1c1", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "0109a5a9866a24e0", + "gsd-core/bin/shared/config-schema.manifest.json": "2f60b9eaa6cf6bc1", "gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -158,6 +158,7 @@ "gsd-core/references/execute-mvp-tdd.md": "a98a270a7ab126bc", "gsd-core/references/execute-phase-between-wave-reset.md": "3ad96ca0f7fee37e", "gsd-core/references/execute-phase-context-guard.md": "a5a1058d35806a8e", + "gsd-core/references/execute-phase-quota-recovery.md": "713cf7681f57e7ce", "gsd-core/references/execute-phase-requirement-revert.md": "48ade76866c57ce2", "gsd-core/references/execute-phase-response-language.md": "c240dc476dd15df5", "gsd-core/references/execute-phase-wave-guard.md": "de9ac22cead4cfd8", @@ -314,7 +315,7 @@ "gsd-core/workflows/docs-update.md": "99908189254c0131", "gsd-core/workflows/edit-phase.md": "dbbb6191f5a8b65e", "gsd-core/workflows/eval-review.md": "11620e0dc4a003dd", - "gsd-core/workflows/execute-phase.md": "4e272ba9df401dc5", + "gsd-core/workflows/execute-phase.md": "eb81251ee1b55fb0", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "6d38bfd540030da4", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "a7f9b9a45303382f", diff --git a/tests/fixtures/golden-install-parity/claude.json b/tests/fixtures/golden-install-parity/claude.json index 3cac955ca..7ee2ce23a 100644 --- a/tests/fixtures/golden-install-parity/claude.json +++ b/tests/fixtures/golden-install-parity/claude.json @@ -38,10 +38,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62", - "gsd-core/bin/gsd-tools.cjs": "d119a8ff93c9c0a3", + "gsd-core/bin/gsd-tools.cjs": "d44831d1e2f9c1c1", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "0109a5a9866a24e0", + "gsd-core/bin/shared/config-schema.manifest.json": "2f60b9eaa6cf6bc1", "gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -87,6 +87,7 @@ "gsd-core/references/execute-mvp-tdd.md": "a98a270a7ab126bc", "gsd-core/references/execute-phase-between-wave-reset.md": "3ad96ca0f7fee37e", "gsd-core/references/execute-phase-context-guard.md": "a5a1058d35806a8e", + "gsd-core/references/execute-phase-quota-recovery.md": "713cf7681f57e7ce", "gsd-core/references/execute-phase-requirement-revert.md": "48ade76866c57ce2", "gsd-core/references/execute-phase-response-language.md": "c240dc476dd15df5", "gsd-core/references/execute-phase-wave-guard.md": "de9ac22cead4cfd8", @@ -243,7 +244,7 @@ "gsd-core/workflows/docs-update.md": "e94d48c979be0211", "gsd-core/workflows/edit-phase.md": "8323bfe10faa0c0a", "gsd-core/workflows/eval-review.md": "7c2f5ff8cc4d06e5", - "gsd-core/workflows/execute-phase.md": "95ab6bf45302a773", + "gsd-core/workflows/execute-phase.md": "6d122c77d70c2e4e", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "facb0e816d87a0c7", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "7b2d9a4a4013fd6a", diff --git a/tests/fixtures/golden-install-parity/cline.json b/tests/fixtures/golden-install-parity/cline.json index 90d6918fd..60928cf1e 100644 --- a/tests/fixtures/golden-install-parity/cline.json +++ b/tests/fixtures/golden-install-parity/cline.json @@ -42,10 +42,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "476aa24e8c4f03cf", - "gsd-core/bin/gsd-tools.cjs": "36d17f5566bdec9e", + "gsd-core/bin/gsd-tools.cjs": "f737db8e3aa1ce61", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "0109a5a9866a24e0", + "gsd-core/bin/shared/config-schema.manifest.json": "2f60b9eaa6cf6bc1", "gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -91,6 +91,7 @@ "gsd-core/references/execute-mvp-tdd.md": "a98a270a7ab126bc", "gsd-core/references/execute-phase-between-wave-reset.md": "e9582246dbc617e0", "gsd-core/references/execute-phase-context-guard.md": "982006c1f3364242", + "gsd-core/references/execute-phase-quota-recovery.md": "c4ca0ee3875b6e02", "gsd-core/references/execute-phase-requirement-revert.md": "48ade76866c57ce2", "gsd-core/references/execute-phase-response-language.md": "c240dc476dd15df5", "gsd-core/references/execute-phase-wave-guard.md": "742bb890af014c87", @@ -247,7 +248,7 @@ "gsd-core/workflows/docs-update.md": "4bcb3d9630d6bb10", "gsd-core/workflows/edit-phase.md": "9c9fadc047c61d74", "gsd-core/workflows/eval-review.md": "17fc9b4c4f7e1c74", - "gsd-core/workflows/execute-phase.md": "1c703e5f1855b1e2", + "gsd-core/workflows/execute-phase.md": "84798552f7f61eb9", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "67ebc93f51968cb6", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "35612890c1173577", diff --git a/tests/fixtures/golden-install-parity/codebuddy.json b/tests/fixtures/golden-install-parity/codebuddy.json index 6167e8acc..0827b5852 100644 --- a/tests/fixtures/golden-install-parity/codebuddy.json +++ b/tests/fixtures/golden-install-parity/codebuddy.json @@ -110,10 +110,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62", - "gsd-core/bin/gsd-tools.cjs": "d119a8ff93c9c0a3", + "gsd-core/bin/gsd-tools.cjs": "d44831d1e2f9c1c1", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "0109a5a9866a24e0", + "gsd-core/bin/shared/config-schema.manifest.json": "2f60b9eaa6cf6bc1", "gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -159,6 +159,7 @@ "gsd-core/references/execute-mvp-tdd.md": "a98a270a7ab126bc", "gsd-core/references/execute-phase-between-wave-reset.md": "3ad96ca0f7fee37e", "gsd-core/references/execute-phase-context-guard.md": "982006c1f3364242", + "gsd-core/references/execute-phase-quota-recovery.md": "c4ca0ee3875b6e02", "gsd-core/references/execute-phase-requirement-revert.md": "48ade76866c57ce2", "gsd-core/references/execute-phase-response-language.md": "c240dc476dd15df5", "gsd-core/references/execute-phase-wave-guard.md": "de9ac22cead4cfd8", @@ -315,7 +316,7 @@ "gsd-core/workflows/docs-update.md": "b5cdf1c35043cf7b", "gsd-core/workflows/edit-phase.md": "966a3eadd1bebc04", "gsd-core/workflows/eval-review.md": "32d3d7e80ab2bfc7", - "gsd-core/workflows/execute-phase.md": "b090500cb2b6fe9f", + "gsd-core/workflows/execute-phase.md": "a0bdd2da561df3d5", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "4e265392b3f2ba0e", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "7b2d9a4a4013fd6a", diff --git a/tests/fixtures/golden-install-parity/codex.json b/tests/fixtures/golden-install-parity/codex.json index 2371e78c2..1f1b1c656 100644 --- a/tests/fixtures/golden-install-parity/codex.json +++ b/tests/fixtures/golden-install-parity/codex.json @@ -145,10 +145,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62", - "gsd-core/bin/gsd-tools.cjs": "d119a8ff93c9c0a3", + "gsd-core/bin/gsd-tools.cjs": "d44831d1e2f9c1c1", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "0109a5a9866a24e0", + "gsd-core/bin/shared/config-schema.manifest.json": "2f60b9eaa6cf6bc1", "gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -194,6 +194,7 @@ "gsd-core/references/execute-mvp-tdd.md": "a98a270a7ab126bc", "gsd-core/references/execute-phase-between-wave-reset.md": "3ad96ca0f7fee37e", "gsd-core/references/execute-phase-context-guard.md": "a483199612e216f1", + "gsd-core/references/execute-phase-quota-recovery.md": "885cbb5843f4f066", "gsd-core/references/execute-phase-requirement-revert.md": "48ade76866c57ce2", "gsd-core/references/execute-phase-response-language.md": "c240dc476dd15df5", "gsd-core/references/execute-phase-wave-guard.md": "95e55087876a0d96", @@ -350,7 +351,7 @@ "gsd-core/workflows/docs-update.md": "70ad463cdad599c9", "gsd-core/workflows/edit-phase.md": "e592a4d85ce5380f", "gsd-core/workflows/eval-review.md": "c89a63285ead961e", - "gsd-core/workflows/execute-phase.md": "994554be349d76bd", + "gsd-core/workflows/execute-phase.md": "05df9b5d596213a7", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "f4cacd27d37bac65", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "7b2d9a4a4013fd6a", diff --git a/tests/fixtures/golden-install-parity/copilot.json b/tests/fixtures/golden-install-parity/copilot.json index bc4eee629..fc0eda5c8 100644 --- a/tests/fixtures/golden-install-parity/copilot.json +++ b/tests/fixtures/golden-install-parity/copilot.json @@ -40,10 +40,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "ea841e2865248e74", - "gsd-core/bin/gsd-tools.cjs": "8c5bf9788c3967be", + "gsd-core/bin/gsd-tools.cjs": "df767e8f2f10fd3b", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "0109a5a9866a24e0", + "gsd-core/bin/shared/config-schema.manifest.json": "2f60b9eaa6cf6bc1", "gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "10226e9512dd44bf", @@ -89,6 +89,7 @@ "gsd-core/references/execute-mvp-tdd.md": "a98a270a7ab126bc", "gsd-core/references/execute-phase-between-wave-reset.md": "3ad96ca0f7fee37e", "gsd-core/references/execute-phase-context-guard.md": "a5a1058d35806a8e", + "gsd-core/references/execute-phase-quota-recovery.md": "713cf7681f57e7ce", "gsd-core/references/execute-phase-requirement-revert.md": "48ade76866c57ce2", "gsd-core/references/execute-phase-response-language.md": "c240dc476dd15df5", "gsd-core/references/execute-phase-wave-guard.md": "1eb1e1e552ebd17b", @@ -245,7 +246,7 @@ "gsd-core/workflows/docs-update.md": "fddba4a71268a33f", "gsd-core/workflows/edit-phase.md": "8667c28b22b1599f", "gsd-core/workflows/eval-review.md": "1ae2d3d3fbf72893", - "gsd-core/workflows/execute-phase.md": "0afe5c2ba3b22f15", + "gsd-core/workflows/execute-phase.md": "2b444eb7efcc17cd", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "63b712920f21a40f", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "25bebed74e645109", diff --git a/tests/fixtures/golden-install-parity/cursor.json b/tests/fixtures/golden-install-parity/cursor.json index da136ca51..17073cced 100644 --- a/tests/fixtures/golden-install-parity/cursor.json +++ b/tests/fixtures/golden-install-parity/cursor.json @@ -110,10 +110,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "2525f1ae8b086828", - "gsd-core/bin/gsd-tools.cjs": "40e8e83311b87500", + "gsd-core/bin/gsd-tools.cjs": "1c1255108c81fde6", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "0109a5a9866a24e0", + "gsd-core/bin/shared/config-schema.manifest.json": "2f60b9eaa6cf6bc1", "gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -159,6 +159,7 @@ "gsd-core/references/execute-mvp-tdd.md": "a98a270a7ab126bc", "gsd-core/references/execute-phase-between-wave-reset.md": "44e31bdae72ed5d5", "gsd-core/references/execute-phase-context-guard.md": "a5a1058d35806a8e", + "gsd-core/references/execute-phase-quota-recovery.md": "713cf7681f57e7ce", "gsd-core/references/execute-phase-requirement-revert.md": "48ade76866c57ce2", "gsd-core/references/execute-phase-response-language.md": "c240dc476dd15df5", "gsd-core/references/execute-phase-wave-guard.md": "b0e27ed2d8405974", @@ -315,7 +316,7 @@ "gsd-core/workflows/docs-update.md": "817776e1a5f87b39", "gsd-core/workflows/edit-phase.md": "8323bfe10faa0c0a", "gsd-core/workflows/eval-review.md": "9389c58f7384972d", - "gsd-core/workflows/execute-phase.md": "6dbd3d9281ba270d", + "gsd-core/workflows/execute-phase.md": "fadfa28157f409ef", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "facb0e816d87a0c7", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "7b2d9a4a4013fd6a", diff --git a/tests/fixtures/golden-install-parity/hermes.json b/tests/fixtures/golden-install-parity/hermes.json index 7821946d2..6414dd920 100644 --- a/tests/fixtures/golden-install-parity/hermes.json +++ b/tests/fixtures/golden-install-parity/hermes.json @@ -39,10 +39,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "3a3409215044af9f", - "gsd-core/bin/gsd-tools.cjs": "c7e11161f0296129", + "gsd-core/bin/gsd-tools.cjs": "38cc609e9d4cbc3c", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "0109a5a9866a24e0", + "gsd-core/bin/shared/config-schema.manifest.json": "2f60b9eaa6cf6bc1", "gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -88,6 +88,7 @@ "gsd-core/references/execute-mvp-tdd.md": "a98a270a7ab126bc", "gsd-core/references/execute-phase-between-wave-reset.md": "4d6ea8e85e88b1fe", "gsd-core/references/execute-phase-context-guard.md": "a5a1058d35806a8e", + "gsd-core/references/execute-phase-quota-recovery.md": "713cf7681f57e7ce", "gsd-core/references/execute-phase-requirement-revert.md": "48ade76866c57ce2", "gsd-core/references/execute-phase-response-language.md": "c240dc476dd15df5", "gsd-core/references/execute-phase-wave-guard.md": "b13d8860a76b7a41", @@ -244,7 +245,7 @@ "gsd-core/workflows/docs-update.md": "de614d940b6f64ff", "gsd-core/workflows/edit-phase.md": "7f27003f20e88fb8", "gsd-core/workflows/eval-review.md": "e7cd5dfcf18e2458", - "gsd-core/workflows/execute-phase.md": "1b9e1284cf788e43", + "gsd-core/workflows/execute-phase.md": "b0398bcbf9893a1f", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "14a27cc0828f59d3", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "26ee34c543926402", "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "362123fdf99e9980", diff --git a/tests/fixtures/golden-install-parity/kilo.json b/tests/fixtures/golden-install-parity/kilo.json index f39925c70..9fe52959f 100644 --- a/tests/fixtures/golden-install-parity/kilo.json +++ b/tests/fixtures/golden-install-parity/kilo.json @@ -110,10 +110,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62", - "gsd-core/bin/gsd-tools.cjs": "d119a8ff93c9c0a3", + "gsd-core/bin/gsd-tools.cjs": "d44831d1e2f9c1c1", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "0109a5a9866a24e0", + "gsd-core/bin/shared/config-schema.manifest.json": "2f60b9eaa6cf6bc1", "gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -159,6 +159,7 @@ "gsd-core/references/execute-mvp-tdd.md": "a98a270a7ab126bc", "gsd-core/references/execute-phase-between-wave-reset.md": "3ad96ca0f7fee37e", "gsd-core/references/execute-phase-context-guard.md": "a5a1058d35806a8e", + "gsd-core/references/execute-phase-quota-recovery.md": "713cf7681f57e7ce", "gsd-core/references/execute-phase-requirement-revert.md": "48ade76866c57ce2", "gsd-core/references/execute-phase-response-language.md": "c240dc476dd15df5", "gsd-core/references/execute-phase-wave-guard.md": "de9ac22cead4cfd8", @@ -315,7 +316,7 @@ "gsd-core/workflows/docs-update.md": "6bca7d8fda0d1d54", "gsd-core/workflows/edit-phase.md": "8323bfe10faa0c0a", "gsd-core/workflows/eval-review.md": "35be91cd22a9bc11", - "gsd-core/workflows/execute-phase.md": "49e2f1740bf7c6b1", + "gsd-core/workflows/execute-phase.md": "6e759310d721e841", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "facb0e816d87a0c7", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "7b2d9a4a4013fd6a", diff --git a/tests/fixtures/golden-install-parity/kimi.json b/tests/fixtures/golden-install-parity/kimi.json index 9dc1c859b..58fcd9b05 100644 --- a/tests/fixtures/golden-install-parity/kimi.json +++ b/tests/fixtures/golden-install-parity/kimi.json @@ -103,10 +103,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62", - "gsd-core/bin/gsd-tools.cjs": "d119a8ff93c9c0a3", + "gsd-core/bin/gsd-tools.cjs": "d44831d1e2f9c1c1", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "0109a5a9866a24e0", + "gsd-core/bin/shared/config-schema.manifest.json": "2f60b9eaa6cf6bc1", "gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -152,6 +152,7 @@ "gsd-core/references/execute-mvp-tdd.md": "a98a270a7ab126bc", "gsd-core/references/execute-phase-between-wave-reset.md": "3ad96ca0f7fee37e", "gsd-core/references/execute-phase-context-guard.md": "982006c1f3364242", + "gsd-core/references/execute-phase-quota-recovery.md": "c4ca0ee3875b6e02", "gsd-core/references/execute-phase-requirement-revert.md": "48ade76866c57ce2", "gsd-core/references/execute-phase-response-language.md": "c240dc476dd15df5", "gsd-core/references/execute-phase-wave-guard.md": "de9ac22cead4cfd8", @@ -308,7 +309,7 @@ "gsd-core/workflows/docs-update.md": "b5cdf1c35043cf7b", "gsd-core/workflows/edit-phase.md": "966a3eadd1bebc04", "gsd-core/workflows/eval-review.md": "32d3d7e80ab2bfc7", - "gsd-core/workflows/execute-phase.md": "e9c9c9691bbc24e2", + "gsd-core/workflows/execute-phase.md": "8117e54ce638397b", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "4e265392b3f2ba0e", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "7b2d9a4a4013fd6a", diff --git a/tests/fixtures/golden-install-parity/opencode.json b/tests/fixtures/golden-install-parity/opencode.json index 1c60c975e..07a89c6b1 100644 --- a/tests/fixtures/golden-install-parity/opencode.json +++ b/tests/fixtures/golden-install-parity/opencode.json @@ -110,10 +110,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62", - "gsd-core/bin/gsd-tools.cjs": "d119a8ff93c9c0a3", + "gsd-core/bin/gsd-tools.cjs": "d44831d1e2f9c1c1", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "0109a5a9866a24e0", + "gsd-core/bin/shared/config-schema.manifest.json": "2f60b9eaa6cf6bc1", "gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -159,6 +159,7 @@ "gsd-core/references/execute-mvp-tdd.md": "a98a270a7ab126bc", "gsd-core/references/execute-phase-between-wave-reset.md": "3ad96ca0f7fee37e", "gsd-core/references/execute-phase-context-guard.md": "a5a1058d35806a8e", + "gsd-core/references/execute-phase-quota-recovery.md": "713cf7681f57e7ce", "gsd-core/references/execute-phase-requirement-revert.md": "48ade76866c57ce2", "gsd-core/references/execute-phase-response-language.md": "c240dc476dd15df5", "gsd-core/references/execute-phase-wave-guard.md": "de9ac22cead4cfd8", @@ -315,7 +316,7 @@ "gsd-core/workflows/docs-update.md": "4c7ea896438d9d45", "gsd-core/workflows/edit-phase.md": "1876c855fb0a0a39", "gsd-core/workflows/eval-review.md": "1973f08d0eb34159", - "gsd-core/workflows/execute-phase.md": "6148ab536d9210cc", + "gsd-core/workflows/execute-phase.md": "eb0ab7bc861ecd92", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "1804215577f1ad35", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "c4cba01539f0368a", diff --git a/tests/fixtures/golden-install-parity/pi.json b/tests/fixtures/golden-install-parity/pi.json index 4ac2d2402..bfed41f4c 100644 --- a/tests/fixtures/golden-install-parity/pi.json +++ b/tests/fixtures/golden-install-parity/pi.json @@ -6,10 +6,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62", - "gsd-core/bin/gsd-tools.cjs": "d119a8ff93c9c0a3", + "gsd-core/bin/gsd-tools.cjs": "d44831d1e2f9c1c1", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "0109a5a9866a24e0", + "gsd-core/bin/shared/config-schema.manifest.json": "2f60b9eaa6cf6bc1", "gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -55,6 +55,7 @@ "gsd-core/references/execute-mvp-tdd.md": "a98a270a7ab126bc", "gsd-core/references/execute-phase-between-wave-reset.md": "3ad96ca0f7fee37e", "gsd-core/references/execute-phase-context-guard.md": "982006c1f3364242", + "gsd-core/references/execute-phase-quota-recovery.md": "c4ca0ee3875b6e02", "gsd-core/references/execute-phase-requirement-revert.md": "48ade76866c57ce2", "gsd-core/references/execute-phase-response-language.md": "c240dc476dd15df5", "gsd-core/references/execute-phase-wave-guard.md": "de9ac22cead4cfd8", @@ -211,7 +212,7 @@ "gsd-core/workflows/docs-update.md": "b5cdf1c35043cf7b", "gsd-core/workflows/edit-phase.md": "966a3eadd1bebc04", "gsd-core/workflows/eval-review.md": "32d3d7e80ab2bfc7", - "gsd-core/workflows/execute-phase.md": "043ed0d7fb5452dc", + "gsd-core/workflows/execute-phase.md": "ba18fc959cf66a35", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "4e265392b3f2ba0e", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "7b2d9a4a4013fd6a", diff --git a/tests/fixtures/golden-install-parity/qwen.json b/tests/fixtures/golden-install-parity/qwen.json index daf9e0fd5..6c8e63b3d 100644 --- a/tests/fixtures/golden-install-parity/qwen.json +++ b/tests/fixtures/golden-install-parity/qwen.json @@ -39,10 +39,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "6e98d76e955e35a2", - "gsd-core/bin/gsd-tools.cjs": "c67b43694423939a", + "gsd-core/bin/gsd-tools.cjs": "982a6ff24abb8e59", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "0109a5a9866a24e0", + "gsd-core/bin/shared/config-schema.manifest.json": "2f60b9eaa6cf6bc1", "gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -88,6 +88,7 @@ "gsd-core/references/execute-mvp-tdd.md": "a98a270a7ab126bc", "gsd-core/references/execute-phase-between-wave-reset.md": "8c2e5a596401dd33", "gsd-core/references/execute-phase-context-guard.md": "a5a1058d35806a8e", + "gsd-core/references/execute-phase-quota-recovery.md": "713cf7681f57e7ce", "gsd-core/references/execute-phase-requirement-revert.md": "48ade76866c57ce2", "gsd-core/references/execute-phase-response-language.md": "c240dc476dd15df5", "gsd-core/references/execute-phase-wave-guard.md": "41d8123374d3838b", @@ -244,7 +245,7 @@ "gsd-core/workflows/docs-update.md": "db6e70dddaf019ad", "gsd-core/workflows/edit-phase.md": "0fb5e0123cfc6f36", "gsd-core/workflows/eval-review.md": "88431952699d9fa6", - "gsd-core/workflows/execute-phase.md": "782d4ca8c8439afd", + "gsd-core/workflows/execute-phase.md": "44e3fcc6a74c33b5", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "36af8d91e4ae8b9c", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "0b04cc2dcab61107", diff --git a/tests/fixtures/golden-install-parity/trae.json b/tests/fixtures/golden-install-parity/trae.json index 6ec94a7f2..0934ac518 100644 --- a/tests/fixtures/golden-install-parity/trae.json +++ b/tests/fixtures/golden-install-parity/trae.json @@ -39,10 +39,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "de4627dff103d527", - "gsd-core/bin/gsd-tools.cjs": "44e114ddf0f32e08", + "gsd-core/bin/gsd-tools.cjs": "cafd8c05ee085279", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "0109a5a9866a24e0", + "gsd-core/bin/shared/config-schema.manifest.json": "2f60b9eaa6cf6bc1", "gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -88,6 +88,7 @@ "gsd-core/references/execute-mvp-tdd.md": "a98a270a7ab126bc", "gsd-core/references/execute-phase-between-wave-reset.md": "7a1aa0ad7963f809", "gsd-core/references/execute-phase-context-guard.md": "a5a1058d35806a8e", + "gsd-core/references/execute-phase-quota-recovery.md": "713cf7681f57e7ce", "gsd-core/references/execute-phase-requirement-revert.md": "48ade76866c57ce2", "gsd-core/references/execute-phase-response-language.md": "c240dc476dd15df5", "gsd-core/references/execute-phase-wave-guard.md": "973a3a6a5db7e449", @@ -244,7 +245,7 @@ "gsd-core/workflows/docs-update.md": "141185e54648df02", "gsd-core/workflows/edit-phase.md": "7facd0faa33c8cad", "gsd-core/workflows/eval-review.md": "90a29a4fa0cdd947", - "gsd-core/workflows/execute-phase.md": "67c7f93f3cef43f1", + "gsd-core/workflows/execute-phase.md": "e62fe4c3357b083c", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "c985a30317a1aa6b", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "7d30384e9bf82664", diff --git a/tests/fixtures/golden-install-parity/windsurf.json b/tests/fixtures/golden-install-parity/windsurf.json index c5754f353..0d143570d 100644 --- a/tests/fixtures/golden-install-parity/windsurf.json +++ b/tests/fixtures/golden-install-parity/windsurf.json @@ -39,10 +39,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "5636ca0b726871b2", - "gsd-core/bin/gsd-tools.cjs": "3224906065bfb20c", + "gsd-core/bin/gsd-tools.cjs": "abce82dc226344f2", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "0109a5a9866a24e0", + "gsd-core/bin/shared/config-schema.manifest.json": "2f60b9eaa6cf6bc1", "gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -88,6 +88,7 @@ "gsd-core/references/execute-mvp-tdd.md": "a98a270a7ab126bc", "gsd-core/references/execute-phase-between-wave-reset.md": "1587345770b19c7f", "gsd-core/references/execute-phase-context-guard.md": "a5a1058d35806a8e", + "gsd-core/references/execute-phase-quota-recovery.md": "713cf7681f57e7ce", "gsd-core/references/execute-phase-requirement-revert.md": "48ade76866c57ce2", "gsd-core/references/execute-phase-response-language.md": "c240dc476dd15df5", "gsd-core/references/execute-phase-wave-guard.md": "96485eccd4606e54", @@ -244,7 +245,7 @@ "gsd-core/workflows/docs-update.md": "a7b78ce2f5404160", "gsd-core/workflows/edit-phase.md": "c0ae7d0063f3e789", "gsd-core/workflows/eval-review.md": "713de00ea5326988", - "gsd-core/workflows/execute-phase.md": "a0b5283f992532a2", + "gsd-core/workflows/execute-phase.md": "a52dc6640b7ede7b", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "47ae5482f8e64100", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "fb4497767cdf73a3", diff --git a/tests/fixtures/golden-install-parity/zcode.json b/tests/fixtures/golden-install-parity/zcode.json index f14ef2a8c..dd529949f 100644 --- a/tests/fixtures/golden-install-parity/zcode.json +++ b/tests/fixtures/golden-install-parity/zcode.json @@ -110,10 +110,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62", - "gsd-core/bin/gsd-tools.cjs": "d119a8ff93c9c0a3", + "gsd-core/bin/gsd-tools.cjs": "d44831d1e2f9c1c1", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "0109a5a9866a24e0", + "gsd-core/bin/shared/config-schema.manifest.json": "2f60b9eaa6cf6bc1", "gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -159,6 +159,7 @@ "gsd-core/references/execute-mvp-tdd.md": "a98a270a7ab126bc", "gsd-core/references/execute-phase-between-wave-reset.md": "3ad96ca0f7fee37e", "gsd-core/references/execute-phase-context-guard.md": "982006c1f3364242", + "gsd-core/references/execute-phase-quota-recovery.md": "c4ca0ee3875b6e02", "gsd-core/references/execute-phase-requirement-revert.md": "48ade76866c57ce2", "gsd-core/references/execute-phase-response-language.md": "c240dc476dd15df5", "gsd-core/references/execute-phase-wave-guard.md": "de9ac22cead4cfd8", @@ -315,7 +316,7 @@ "gsd-core/workflows/docs-update.md": "b5cdf1c35043cf7b", "gsd-core/workflows/edit-phase.md": "966a3eadd1bebc04", "gsd-core/workflows/eval-review.md": "32d3d7e80ab2bfc7", - "gsd-core/workflows/execute-phase.md": "3054948793be8131", + "gsd-core/workflows/execute-phase.md": "8ce56aef5c42f73a", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "4e265392b3f2ba0e", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "7b2d9a4a4013fd6a", diff --git a/tests/fixtures/install-tree/antigravity.json b/tests/fixtures/install-tree/antigravity.json index 8d348ba88..c0f62dcc6 100644 --- a/tests/fixtures/install-tree/antigravity.json +++ b/tests/fixtures/install-tree/antigravity.json @@ -88,6 +88,7 @@ "gsd-core/references/execute-mvp-tdd.md", "gsd-core/references/execute-phase-between-wave-reset.md", "gsd-core/references/execute-phase-context-guard.md", + "gsd-core/references/execute-phase-quota-recovery.md", "gsd-core/references/execute-phase-requirement-revert.md", "gsd-core/references/execute-phase-response-language.md", "gsd-core/references/execute-phase-wave-guard.md", diff --git a/tests/fixtures/install-tree/augment.json b/tests/fixtures/install-tree/augment.json index 70c7816a7..18cfdf1cc 100644 --- a/tests/fixtures/install-tree/augment.json +++ b/tests/fixtures/install-tree/augment.json @@ -159,6 +159,7 @@ "gsd-core/references/execute-mvp-tdd.md", "gsd-core/references/execute-phase-between-wave-reset.md", "gsd-core/references/execute-phase-context-guard.md", + "gsd-core/references/execute-phase-quota-recovery.md", "gsd-core/references/execute-phase-requirement-revert.md", "gsd-core/references/execute-phase-response-language.md", "gsd-core/references/execute-phase-wave-guard.md", diff --git a/tests/fixtures/install-tree/claude-local.json b/tests/fixtures/install-tree/claude-local.json index 8e8863071..b3f02d070 100644 --- a/tests/fixtures/install-tree/claude-local.json +++ b/tests/fixtures/install-tree/claude-local.json @@ -158,6 +158,7 @@ "gsd-core/references/execute-mvp-tdd.md", "gsd-core/references/execute-phase-between-wave-reset.md", "gsd-core/references/execute-phase-context-guard.md", + "gsd-core/references/execute-phase-quota-recovery.md", "gsd-core/references/execute-phase-requirement-revert.md", "gsd-core/references/execute-phase-response-language.md", "gsd-core/references/execute-phase-wave-guard.md", diff --git a/tests/fixtures/install-tree/claude.json b/tests/fixtures/install-tree/claude.json index 0908d0b40..805730b4d 100644 --- a/tests/fixtures/install-tree/claude.json +++ b/tests/fixtures/install-tree/claude.json @@ -87,6 +87,7 @@ "gsd-core/references/execute-mvp-tdd.md", "gsd-core/references/execute-phase-between-wave-reset.md", "gsd-core/references/execute-phase-context-guard.md", + "gsd-core/references/execute-phase-quota-recovery.md", "gsd-core/references/execute-phase-requirement-revert.md", "gsd-core/references/execute-phase-response-language.md", "gsd-core/references/execute-phase-wave-guard.md", diff --git a/tests/fixtures/install-tree/cline.json b/tests/fixtures/install-tree/cline.json index 9deced98e..e277a598a 100644 --- a/tests/fixtures/install-tree/cline.json +++ b/tests/fixtures/install-tree/cline.json @@ -91,6 +91,7 @@ "gsd-core/references/execute-mvp-tdd.md", "gsd-core/references/execute-phase-between-wave-reset.md", "gsd-core/references/execute-phase-context-guard.md", + "gsd-core/references/execute-phase-quota-recovery.md", "gsd-core/references/execute-phase-requirement-revert.md", "gsd-core/references/execute-phase-response-language.md", "gsd-core/references/execute-phase-wave-guard.md", diff --git a/tests/fixtures/install-tree/codebuddy.json b/tests/fixtures/install-tree/codebuddy.json index e4edd73fb..ca90afbf8 100644 --- a/tests/fixtures/install-tree/codebuddy.json +++ b/tests/fixtures/install-tree/codebuddy.json @@ -159,6 +159,7 @@ "gsd-core/references/execute-mvp-tdd.md", "gsd-core/references/execute-phase-between-wave-reset.md", "gsd-core/references/execute-phase-context-guard.md", + "gsd-core/references/execute-phase-quota-recovery.md", "gsd-core/references/execute-phase-requirement-revert.md", "gsd-core/references/execute-phase-response-language.md", "gsd-core/references/execute-phase-wave-guard.md", diff --git a/tests/fixtures/install-tree/codex.json b/tests/fixtures/install-tree/codex.json index 7d83520ca..d0dfafc0f 100644 --- a/tests/fixtures/install-tree/codex.json +++ b/tests/fixtures/install-tree/codex.json @@ -194,6 +194,7 @@ "gsd-core/references/execute-mvp-tdd.md", "gsd-core/references/execute-phase-between-wave-reset.md", "gsd-core/references/execute-phase-context-guard.md", + "gsd-core/references/execute-phase-quota-recovery.md", "gsd-core/references/execute-phase-requirement-revert.md", "gsd-core/references/execute-phase-response-language.md", "gsd-core/references/execute-phase-wave-guard.md", diff --git a/tests/fixtures/install-tree/copilot.json b/tests/fixtures/install-tree/copilot.json index ffaeacd68..5afd405b0 100644 --- a/tests/fixtures/install-tree/copilot.json +++ b/tests/fixtures/install-tree/copilot.json @@ -89,6 +89,7 @@ "gsd-core/references/execute-mvp-tdd.md", "gsd-core/references/execute-phase-between-wave-reset.md", "gsd-core/references/execute-phase-context-guard.md", + "gsd-core/references/execute-phase-quota-recovery.md", "gsd-core/references/execute-phase-requirement-revert.md", "gsd-core/references/execute-phase-response-language.md", "gsd-core/references/execute-phase-wave-guard.md", diff --git a/tests/fixtures/install-tree/cursor.json b/tests/fixtures/install-tree/cursor.json index 6c043ee73..85c826896 100644 --- a/tests/fixtures/install-tree/cursor.json +++ b/tests/fixtures/install-tree/cursor.json @@ -159,6 +159,7 @@ "gsd-core/references/execute-mvp-tdd.md", "gsd-core/references/execute-phase-between-wave-reset.md", "gsd-core/references/execute-phase-context-guard.md", + "gsd-core/references/execute-phase-quota-recovery.md", "gsd-core/references/execute-phase-requirement-revert.md", "gsd-core/references/execute-phase-response-language.md", "gsd-core/references/execute-phase-wave-guard.md", diff --git a/tests/fixtures/install-tree/hermes.json b/tests/fixtures/install-tree/hermes.json index 3efad48ef..1c8278566 100644 --- a/tests/fixtures/install-tree/hermes.json +++ b/tests/fixtures/install-tree/hermes.json @@ -88,6 +88,7 @@ "gsd-core/references/execute-mvp-tdd.md", "gsd-core/references/execute-phase-between-wave-reset.md", "gsd-core/references/execute-phase-context-guard.md", + "gsd-core/references/execute-phase-quota-recovery.md", "gsd-core/references/execute-phase-requirement-revert.md", "gsd-core/references/execute-phase-response-language.md", "gsd-core/references/execute-phase-wave-guard.md", diff --git a/tests/fixtures/install-tree/kilo.json b/tests/fixtures/install-tree/kilo.json index c3f5b25bc..db7924f7a 100644 --- a/tests/fixtures/install-tree/kilo.json +++ b/tests/fixtures/install-tree/kilo.json @@ -159,6 +159,7 @@ "gsd-core/references/execute-mvp-tdd.md", "gsd-core/references/execute-phase-between-wave-reset.md", "gsd-core/references/execute-phase-context-guard.md", + "gsd-core/references/execute-phase-quota-recovery.md", "gsd-core/references/execute-phase-requirement-revert.md", "gsd-core/references/execute-phase-response-language.md", "gsd-core/references/execute-phase-wave-guard.md", diff --git a/tests/fixtures/install-tree/kimi.json b/tests/fixtures/install-tree/kimi.json index b3b75587c..8bb37054c 100644 --- a/tests/fixtures/install-tree/kimi.json +++ b/tests/fixtures/install-tree/kimi.json @@ -152,6 +152,7 @@ "gsd-core/references/execute-mvp-tdd.md", "gsd-core/references/execute-phase-between-wave-reset.md", "gsd-core/references/execute-phase-context-guard.md", + "gsd-core/references/execute-phase-quota-recovery.md", "gsd-core/references/execute-phase-requirement-revert.md", "gsd-core/references/execute-phase-response-language.md", "gsd-core/references/execute-phase-wave-guard.md", diff --git a/tests/fixtures/install-tree/opencode.json b/tests/fixtures/install-tree/opencode.json index d2579a897..bd837e2a4 100644 --- a/tests/fixtures/install-tree/opencode.json +++ b/tests/fixtures/install-tree/opencode.json @@ -159,6 +159,7 @@ "gsd-core/references/execute-mvp-tdd.md", "gsd-core/references/execute-phase-between-wave-reset.md", "gsd-core/references/execute-phase-context-guard.md", + "gsd-core/references/execute-phase-quota-recovery.md", "gsd-core/references/execute-phase-requirement-revert.md", "gsd-core/references/execute-phase-response-language.md", "gsd-core/references/execute-phase-wave-guard.md", diff --git a/tests/fixtures/install-tree/pi.json b/tests/fixtures/install-tree/pi.json index 88a08ac6f..05793d57c 100644 --- a/tests/fixtures/install-tree/pi.json +++ b/tests/fixtures/install-tree/pi.json @@ -55,6 +55,7 @@ "gsd-core/references/execute-mvp-tdd.md", "gsd-core/references/execute-phase-between-wave-reset.md", "gsd-core/references/execute-phase-context-guard.md", + "gsd-core/references/execute-phase-quota-recovery.md", "gsd-core/references/execute-phase-requirement-revert.md", "gsd-core/references/execute-phase-response-language.md", "gsd-core/references/execute-phase-wave-guard.md", diff --git a/tests/fixtures/install-tree/qwen.json b/tests/fixtures/install-tree/qwen.json index 3f3f4c39d..a5e781d2d 100644 --- a/tests/fixtures/install-tree/qwen.json +++ b/tests/fixtures/install-tree/qwen.json @@ -88,6 +88,7 @@ "gsd-core/references/execute-mvp-tdd.md", "gsd-core/references/execute-phase-between-wave-reset.md", "gsd-core/references/execute-phase-context-guard.md", + "gsd-core/references/execute-phase-quota-recovery.md", "gsd-core/references/execute-phase-requirement-revert.md", "gsd-core/references/execute-phase-response-language.md", "gsd-core/references/execute-phase-wave-guard.md", diff --git a/tests/fixtures/install-tree/trae.json b/tests/fixtures/install-tree/trae.json index bff4e3421..c5f8d8753 100644 --- a/tests/fixtures/install-tree/trae.json +++ b/tests/fixtures/install-tree/trae.json @@ -88,6 +88,7 @@ "gsd-core/references/execute-mvp-tdd.md", "gsd-core/references/execute-phase-between-wave-reset.md", "gsd-core/references/execute-phase-context-guard.md", + "gsd-core/references/execute-phase-quota-recovery.md", "gsd-core/references/execute-phase-requirement-revert.md", "gsd-core/references/execute-phase-response-language.md", "gsd-core/references/execute-phase-wave-guard.md", diff --git a/tests/fixtures/install-tree/windsurf.json b/tests/fixtures/install-tree/windsurf.json index 58c449def..77d953a18 100644 --- a/tests/fixtures/install-tree/windsurf.json +++ b/tests/fixtures/install-tree/windsurf.json @@ -88,6 +88,7 @@ "gsd-core/references/execute-mvp-tdd.md", "gsd-core/references/execute-phase-between-wave-reset.md", "gsd-core/references/execute-phase-context-guard.md", + "gsd-core/references/execute-phase-quota-recovery.md", "gsd-core/references/execute-phase-requirement-revert.md", "gsd-core/references/execute-phase-response-language.md", "gsd-core/references/execute-phase-wave-guard.md", diff --git a/tests/fixtures/install-tree/zcode.json b/tests/fixtures/install-tree/zcode.json index 60946a81b..7d28a680c 100644 --- a/tests/fixtures/install-tree/zcode.json +++ b/tests/fixtures/install-tree/zcode.json @@ -159,6 +159,7 @@ "gsd-core/references/execute-mvp-tdd.md", "gsd-core/references/execute-phase-between-wave-reset.md", "gsd-core/references/execute-phase-context-guard.md", + "gsd-core/references/execute-phase-quota-recovery.md", "gsd-core/references/execute-phase-requirement-revert.md", "gsd-core/references/execute-phase-response-language.md", "gsd-core/references/execute-phase-wave-guard.md", diff --git a/tests/run-with-timeout.test.cjs b/tests/run-with-timeout.test.cjs index 00994d609..0cc820493 100644 --- a/tests/run-with-timeout.test.cjs +++ b/tests/run-with-timeout.test.cjs @@ -163,18 +163,27 @@ describe('#2351 run-with-timeout — kill semantics (POSIX process groups)', () // `kill(pid, 0)`: a SIGKILL'd orphan lingers as a zombie until reaped, and a // container's PID 1 reaps slowly, so `kill(pid,0)` reads a dead child as // "alive". A reaped child stops ticking; a genuine orphan keeps ticking. + // + // The child writes its FIRST heartbeat synchronously at startup, before + // arming the interval, and the kill window is 3s rather than 1s. Both are + // load-independence requirements, not cosmetics: with the first write + // deferred to the interval's initial 100ms tick inside a 1s window, a loaded + // CI container can group-kill before that tick ever lands, and the existence + // check below then fails for a reason that has nothing to do with reaping — + // the behavior under test is the FREEZE assertion further down, which is + // unaffected by writing one extra sample at t=0. const dir = createTempDir('rwt-c1'); t.after(() => cleanup(dir)); const parentFile = path.join(dir, 'parent.js'); const hbFile = path.join(dir, 'heartbeat'); fs.writeFileSync(parentFile, [ 'const cp = require("child_process");', - "const childCode = 'const fs=require(\"fs\");const hb=process.argv[1];process.on(\"SIGTERM\",()=>{});setInterval(()=>fs.writeFileSync(hb,String(Date.now())),100);';", + "const childCode = 'const fs=require(\"fs\");const hb=process.argv[1];const tick=()=>fs.writeFileSync(hb,String(Date.now()));process.on(\"SIGTERM\",()=>{});tick();setInterval(tick,100);';", 'cp.spawn(process.execPath, ["-e", childCode, process.argv[2]], { stdio: "ignore" });', 'process.on("SIGTERM", () => process.exit(0));', 'setInterval(() => {}, 1000);', ].join('\n')); - const r = runVerb(['1', '--', NODE, parentFile, hbFile], { timeout: 20000 }); + const r = runVerb(['3', '--', NODE, parentFile, hbFile], { timeout: 20000 }); assert.equal(r.status, 124, 'must report a timeout (124), not hang'); assert.ok(fs.existsSync(hbFile), 'child heartbeat should exist'); await sleep(300); // let any in-flight write settle after the SIGKILL diff --git a/tests/workflow-size-baseline.json b/tests/workflow-size-baseline.json index 52dffc296..5381e3a57 100644 --- a/tests/workflow-size-baseline.json +++ b/tests/workflow-size-baseline.json @@ -24,7 +24,7 @@ "docs-update.md": 55908, "edit-phase.md": 12927, "eval-review.md": 10332, - "execute-phase.md": 93292, + "execute-phase.md": 92782, "execute-plan.md": 34548, "explore.md": 10541, "extract-learnings.md": 12893,