@opengsd/gsd-core 1.4.3 → 1.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gsd-core",
3
3
  "displayName": "GSD Core",
4
- "version": "1.4.3",
4
+ "version": "1.4.5",
5
5
  "description": "GSD Core is a meta-prompting, context engineering, and spec-driven development system for AI coding agents.",
6
6
  "author": {
7
7
  "name": "open-gsd",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gsd-core",
3
- "version": "1.4.3",
3
+ "version": "1.4.5",
4
4
  "description": "GSD Core — a meta-prompting, context engineering, and spec-driven development system for AI coding agents. Loads gsd's operating context into every Gemini CLI session.",
5
5
  "contextFileName": "GEMINI.md"
6
6
  }
@@ -1283,6 +1283,7 @@ function _warnUnknownProfileOverrides(parsed, configLabel) {
1283
1283
  // between cases that intentionally exercise the warning path repeatedly.
1284
1284
  function _resetRuntimeWarningCacheForTests() {
1285
1285
  _warnedConfigKeys.clear();
1286
+ _modelPolicyUnmappableWarned.clear();
1286
1287
  }
1287
1288
  /**
1288
1289
  * #2517 — Resolve the runtime-aware tier entry for (runtime, tier).
@@ -1312,6 +1313,26 @@ function _resolveRuntimeTier(config, tier) {
1312
1313
  overrides: config['model_profile_overrides'],
1313
1314
  });
1314
1315
  }
1316
+ // Reverse of the Claude tier-default IDs, plus the Fable alias which Claude
1317
+ // Code's Agent tool accepts but which is not a GSD model-profile tier (#1133).
1318
+ const CLAUDE_POLICY_ID_TO_ALIAS = {
1319
+ ...Object.fromEntries(Object.entries(model_catalog_cjs_1.MODEL_ALIAS_MAP)
1320
+ .filter((e) => typeof e[1] === 'string')
1321
+ .map(([aliasName, id]) => [id, aliasName])),
1322
+ 'claude-fable-5': 'fable',
1323
+ };
1324
+ const CLAUDE_AGENT_ALIASES = new Set(['opus', 'sonnet', 'haiku', 'fable']);
1325
+ // Dedupe stderr warnings so repeated agent resolutions don't spam (#1133).
1326
+ const _modelPolicyUnmappableWarned = new Set();
1327
+ function warnModelPolicyUnmappable(agentType, policyModel, tier) {
1328
+ const key = `${agentType}::${policyModel}::${tier}`;
1329
+ if (_modelPolicyUnmappableWarned.has(key))
1330
+ return;
1331
+ _modelPolicyUnmappableWarned.add(key);
1332
+ // MUST go to stderr — resolve-model's JSON result is parsed from stdout.
1333
+ process.stderr.write(`gsd: warning — model_policy resolved "${policyModel}" for ${agentType}, ` +
1334
+ `but it has no Claude agent alias; using "${tier}" instead.\n`);
1335
+ }
1315
1336
  /**
1316
1337
  * #49 — Provider-neutral model policy preset resolution.
1317
1338
  */
@@ -1389,15 +1410,32 @@ function resolveModelInternal(cwd, agentType) {
1389
1410
  : (profile === 'inherit'
1390
1411
  ? 'inherit'
1391
1412
  : (agentModels ? (agentModels[profile] || agentModels['balanced']) : null));
1392
- // 2.5. model_policy preset (#49)
1413
+ // 2.5. model_policy preset (#49, #1133)
1393
1414
  const configRuntime = config['runtime'];
1394
- if (configRuntime && configRuntime !== 'claude' && tier && tier !== 'inherit') {
1415
+ if (tier && tier !== 'inherit') {
1416
+ const onClaude = !configRuntime || configRuntime === 'claude';
1417
+ const effectiveRuntime = configRuntime || 'claude';
1395
1418
  const mergedPolicy = config['model_policy']
1396
- ? { ...config['model_policy'], runtime: configRuntime }
1419
+ ? { ...config['model_policy'], runtime: effectiveRuntime }
1397
1420
  : null;
1398
1421
  const policyModel = resolveModelPolicy(mergedPolicy, tier);
1399
- if (policyModel)
1400
- return policyModel;
1422
+ if (policyModel) {
1423
+ // Non-Claude runtimes take full model IDs verbatim (unchanged behavior).
1424
+ if (!onClaude)
1425
+ return policyModel;
1426
+ // Claude Code's Agent tool takes tier aliases (opus/sonnet/haiku/fable),
1427
+ // not full model IDs — map the policy-resolved ID back to an alias (#1133).
1428
+ const aliasForId = CLAUDE_POLICY_ID_TO_ALIAS[policyModel];
1429
+ if (aliasForId)
1430
+ return aliasForId;
1431
+ // The policy value may already be a bare Claude agent alias (e.g. "fable").
1432
+ if (CLAUDE_AGENT_ALIASES.has(policyModel))
1433
+ return policyModel;
1434
+ // No Claude alias for this ID (e.g. a pinned minor version like
1435
+ // claude-opus-4-5). Warn once and fall through to the tier alias rather
1436
+ // than returning an ID Claude Code cannot spawn.
1437
+ warnModelPolicyUnmappable(agentType, policyModel, tier);
1438
+ }
1401
1439
  }
1402
1440
  // 3. Runtime-aware resolution (#2517)
1403
1441
  if (configRuntime && configRuntime !== 'claude' && tier && tier !== 'inherit') {
@@ -89,6 +89,11 @@
89
89
  "sonnet": { "low": { "model": "claude-haiku-4-5" }, "medium": { "model": "claude-sonnet-4-6" }, "high": { "model": "claude-opus-4-8" } },
90
90
  "haiku": { "low": { "model": "claude-haiku-4-5" }, "medium": { "model": "claude-haiku-4-5" }, "high": { "model": "claude-sonnet-4-6" } }
91
91
  },
92
+ "anthropic-fable": {
93
+ "opus": { "low": { "model": "claude-opus-4-5" }, "medium": { "model": "claude-opus-4-8" }, "high": { "model": "claude-fable-5" } },
94
+ "sonnet": { "low": { "model": "claude-haiku-4-5" }, "medium": { "model": "claude-sonnet-4-6" }, "high": { "model": "claude-fable-5" } },
95
+ "haiku": { "low": { "model": "claude-haiku-4-5" }, "medium": { "model": "claude-haiku-4-5" }, "high": { "model": "claude-sonnet-4-6" } }
96
+ },
92
97
  "openai": {
93
98
  "opus": { "low": { "model": "gpt-5.3-codex", "reasoning_effort": "medium" }, "medium": { "model": "gpt-5.5", "reasoning_effort": "high" }, "high": { "model": "gpt-5.5", "reasoning_effort": "xhigh" } },
94
99
  "sonnet": { "low": { "model": "gpt-5.4-mini", "reasoning_effort": "low" }, "medium": { "model": "gpt-5.3-codex", "reasoning_effort": "medium" }, "high": { "model": "gpt-5.5", "reasoning_effort": "medium" } },
@@ -84,7 +84,7 @@ Runtime Model Tiers:
84
84
  - `model_profile_overrides.<runtime>.haiku` (default: built-in for the runtime, or absent)
85
85
 
86
86
  Model Policy:
87
- - `model_policy.provider` (default: `null` — known values: anthropic, openai, google, qwen)
87
+ - `model_policy.provider` (default: `null` — known values: anthropic, anthropic-fable, openai, google, qwen)
88
88
  - `model_policy.budget` (default: `null` — known values: high, medium, low)
89
89
  - `model_policy.high` (default: `null` — model ID for the high-cost tier; used by generic provider path)
90
90
  - `model_policy.medium` (default: `null` — model ID for the medium-cost tier; used by generic provider path)
@@ -624,15 +624,16 @@ AskUserQuestion([
624
624
  multiSelect: false,
625
625
  options: [
626
626
  { label: "anthropic", description: "claude-opus-4-8 / claude-sonnet-4-6 / claude-haiku-4-5 (Anthropic / Claude)" },
627
+ { label: "anthropic-fable", description: "claude-fable-5 / claude-sonnet-4-6 / claude-haiku-4-5 (Anthropic / Claude Fable opt-in)" },
627
628
  { label: "openai", description: "gpt-5.5 / gpt-5.3-codex / gpt-5.4-mini (OpenAI / Codex)" },
628
- { label: "google", description: "gemini-3-pro / gemini-3-flash / gemini-2.5-flash-lite (Google Gemini)" },
629
- { label: "qwen", description: "qwen3-max-2026-01-23 / qwen3-coder-plus / qwen3-coder-next (Qwen)" }
629
+ { label: "Other known provider", description: "Type google or qwen; both still use the canonical tier mapping." }
630
630
  ]
631
631
  }
632
632
  ])
633
633
  ```
634
634
 
635
- After the user picks a provider, ask:
635
+ If the user selects "Other known provider", ask them to type `google` or `qwen`.
636
+ Use the typed value as the provider. After the user picks or types a provider, ask:
636
637
 
637
638
  ```text
638
639
  AskUserQuestion([
@@ -653,9 +654,12 @@ Canonical tier mappings by provider and budget:
653
654
 
654
655
  | Provider | Budget | high | medium | low |
655
656
  |-----------|--------|----------------------------|----------------------------|----------------------------|
656
- | anthropic | high | claude-opus-4-8 | claude-opus-4-8 | claude-opus-4-8 |
657
+ | anthropic | high | claude-opus-4-8 | claude-opus-4-8 | claude-sonnet-4-6 |
657
658
  | anthropic | medium | claude-opus-4-8 | claude-sonnet-4-6 | claude-haiku-4-5 |
658
659
  | anthropic | low | claude-haiku-4-5 | claude-haiku-4-5 | claude-haiku-4-5 |
660
+ | anthropic-fable | high | claude-fable-5 | claude-fable-5 | claude-sonnet-4-6 |
661
+ | anthropic-fable | medium | claude-opus-4-8 | claude-sonnet-4-6 | claude-haiku-4-5 |
662
+ | anthropic-fable | low | claude-haiku-4-5 | claude-haiku-4-5 | claude-haiku-4-5 |
659
663
  | openai | high | gpt-5.5 | gpt-5.5 | gpt-5.5 |
660
664
  | openai | medium | gpt-5.5 | gpt-5.3-codex | gpt-5.4-mini |
661
665
  | openai | low | gpt-5.4-mini | gpt-5.4-mini | gpt-5.4-mini |
@@ -668,6 +672,8 @@ Canonical tier mappings by provider and budget:
668
672
 
669
673
  Look up the selected (provider, budget) row and proceed to Step E to write those values.
670
674
 
675
+ > **claude runtime note:** On the default `claude` runtime, policy-resolved model IDs (e.g. `claude-fable-5`) are mapped to Claude Code agent aliases (`fable`, `opus`, `sonnet`, `haiku`); an ID with no corresponding alias emits a stderr warning and falls back to the configured tier alias.
676
+
671
677
  **Step D — Generic-provider path:**
672
678
 
673
679
  Prompt the user to enter each model ID as a free-text input. An empty input means "keep
@@ -713,7 +719,7 @@ Proceed to Step E.
713
719
 
714
720
  ```bash
715
721
  # Known-provider path — write all four keys atomically:
716
- gsd_run query config-set model_policy.provider "<provider>" # e.g., anthropic / openai / google / qwen
722
+ gsd_run query config-set model_policy.provider "<provider>" # e.g., anthropic / anthropic-fable / openai / google / qwen
717
723
  gsd_run query config-set model_policy.budget "<budget>" # high / medium / low
718
724
  gsd_run query config-set model_policy.high "<high-id>"
719
725
  gsd_run query config-set model_policy.medium "<medium-id>"
@@ -775,7 +781,7 @@ Display:
775
781
  | fast_mode.routing_tier_defaults.standard | {true/false} |
776
782
  | fast_mode.routing_tier_defaults.heavy | {true/false} |
777
783
  | fast_mode.agent_overrides.<agent-id> | {true/false} |
778
- | model_policy.provider | {anthropic/openai/google/qwen/custom/null} |
784
+ | model_policy.provider | {anthropic/anthropic-fable/openai/google/qwen/custom/null} |
779
785
  | model_policy.budget | {high/medium/low/null} |
780
786
  | model_policy.high | {model-id/null} |
781
787
  | model_policy.medium | {model-id/null} |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengsd/gsd-core",
3
- "version": "1.4.3",
3
+ "version": "1.4.5",
4
4
  "description": "GSD Core is a meta-prompting, context engineering, and spec-driven development system for AI coding agents.",
5
5
  "bin": {
6
6
  "gsd-core": "bin/install.js",