Files
msd-core/get-shit-done/bin/lib/config-schema.cjs
Tom Boucher 96ce608ee6 fix(config): add resolve_model_ids to VALID_CONFIG_KEYS; accept workflow._auto_chain_active via RUNTIME_STATE_KEYS
Fixes #3162

`resolve_model_ids` is a documented top-level config key (CONFIGURATION.md)
read by core.cjs and session-runner.ts, but was missing from the CJS and SDK
VALID_CONFIG_KEYS allowlists — causing config-set to reject it with
"Unknown config key".

`workflow._auto_chain_active` is internal runtime state intentionally excluded
from VALID_CONFIG_KEYS by #2530, but plan-phase, execute-phase, discuss-phase,
transition, and new-project workflows all write it via `config-set`. Without
a valid write path these calls emit spurious errors (silenced with `|| true`
but noisy in logs). A new RUNTIME_STATE_KEYS set in config-schema.cjs holds
keys that isValidConfigKey() accepts without exposing them as user-settable
options — preserving the #2530 intent while fixing the runtime error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 14:56:34 -04:00

122 lines
5.6 KiB
JavaScript

'use strict';
/**
* Single source of truth for valid config key paths.
*
* Imported by:
* - config.cjs (isValidConfigKey validator)
* - tests/config-schema-docs-parity.test.cjs (CI drift guard)
*
* Adding a key here without documenting it in docs/CONFIGURATION.md will
* fail the parity test. Adding a key to docs/CONFIGURATION.md without
* adding it here will cause config-set to reject it at runtime.
*/
/** Exact-match config key paths accepted by config-set. */
const VALID_CONFIG_KEYS = new Set([
'mode', 'granularity', 'parallelization', 'commit_docs', 'model_profile',
'search_gitignored', 'brave_search', 'firecrawl', 'exa_search',
'workflow.research', 'workflow.plan_check', 'workflow.verifier',
'workflow.nyquist_validation', 'workflow.ai_integration_phase', 'workflow.ui_phase', 'workflow.ui_safety_gate',
'workflow.auto_advance', 'workflow.node_repair', 'workflow.node_repair_budget',
'workflow.tdd_mode',
'workflow.text_mode',
'workflow.research_before_questions',
'workflow.discuss_mode',
'workflow.skip_discuss',
'workflow.auto_prune_state',
'workflow.use_worktrees',
'workflow.worktree_skip_hooks',
'workflow.code_review',
'workflow.code_review_depth',
'workflow.code_review_command',
'workflow.pattern_mapper',
'workflow.plan_bounce',
'workflow.plan_bounce_script',
'workflow.plan_bounce_passes',
'workflow.plan_chunked',
'workflow.plan_review_convergence',
'workflow.post_planning_gaps',
'workflow.security_enforcement',
'workflow.security_asvs_level',
'workflow.security_block_on',
'workflow.drift_threshold',
'workflow.drift_action',
'git.branching_strategy', 'git.base_branch', 'git.phase_branch_template', 'git.milestone_branch_template', 'git.quick_branch_template',
'planning.commit_docs', 'planning.search_gitignored', 'planning.sub_repos',
'review.ollama_host', 'review.lm_studio_host', 'review.llama_cpp_host',
'workflow.cross_ai_execution', 'workflow.cross_ai_command', 'workflow.cross_ai_timeout',
'workflow.subagent_timeout',
'workflow.inline_plan_threshold',
'hooks.context_warnings',
'hooks.workflow_guard',
'workflow.context_coverage_gate',
'statusline.show_last_command',
'workflow.ui_review',
'workflow.max_discuss_passes',
'features.thinking_partner',
'context',
'features.global_learnings',
'learnings.max_inject',
'project_code', 'phase_naming',
'manager.flags.discuss', 'manager.flags.plan', 'manager.flags.execute',
'response_language',
'context_window',
'intel.enabled',
'graphify.enabled',
'graphify.build_timeout',
'claude_md_path',
'claude_md_assembly.mode',
// #2517 — runtime-aware model profiles
'runtime',
// #3162 — documented top-level key: controls model ID resolution for non-Claude runtimes
'resolve_model_ids',
]);
/**
* Internal runtime-state keys — accepted by config-set (workflows write them) but not
* exposed as user-settable options. Excluded from VALID_CONFIG_KEYS so they stay out of
* the public docs-parity check and the "Valid keys:" error message.
* See: #3162 (workflow._auto_chain_active written by plan/execute/discuss workflows)
*/
const RUNTIME_STATE_KEYS = new Set([
'workflow._auto_chain_active',
]);
/**
* Dynamic-pattern validators — keys matching these regexes are also accepted.
* Each entry has a `test` function and a human-readable `description`.
*/
const DYNAMIC_KEY_PATTERNS = [
{ topLevel: 'agent_skills', test: (k) => /^agent_skills\.[a-zA-Z0-9_-]+$/.test(k), description: 'agent_skills.<agent-type>' },
{ topLevel: 'review', test: (k) => /^review\.models\.[a-zA-Z0-9_-]+$/.test(k), description: 'review.models.<cli-name>' },
{ topLevel: 'features', test: (k) => /^features\.[a-zA-Z0-9_]+$/.test(k), description: 'features.<feature_name>' },
{ topLevel: 'claude_md_assembly', test: (k) => /^claude_md_assembly\.blocks\.[a-zA-Z0-9_]+$/.test(k), description: 'claude_md_assembly.blocks.<section>' },
// #2517 — runtime-aware model profile overrides: model_profile_overrides.<runtime>.<tier>
// <runtime> is a free string (so users can map non-built-in runtimes); <tier> is enum-restricted.
{ topLevel: 'model_profile_overrides', test: (k) => /^model_profile_overrides\.[a-zA-Z0-9_-]+\.(opus|sonnet|haiku)$/.test(k),
description: 'model_profile_overrides.<runtime>.<opus|sonnet|haiku>' },
// #3023 — per-phase-type model map: models.<phase_type> = <tier>
// Six named slots (planning/discuss/research/execution/verification/completion);
// unknown phase-types are rejected. Per-agent model_overrides still take
// precedence over phase-type at resolve time.
{ topLevel: 'models', test: (k) => /^models\.(planning|discuss|research|execution|verification|completion)$/.test(k),
description: 'models.<planning|discuss|research|execution|verification|completion>' },
// #3024 — dynamic routing block. Three top-level scalar settings
// plus a tier_models sub-block keyed by light/standard/heavy.
{ topLevel: 'dynamic_routing',
test: (k) => /^dynamic_routing\.(enabled|escalate_on_failure|max_escalations|tier_models\.(light|standard|heavy))$/.test(k),
description: 'dynamic_routing.<enabled|escalate_on_failure|max_escalations|tier_models.<light|standard|heavy>>' },
];
/**
* Returns true if keyPath is a valid config key (exact, dynamic pattern, or runtime state).
*/
function isValidConfigKey(keyPath) {
if (VALID_CONFIG_KEYS.has(keyPath)) return true;
if (RUNTIME_STATE_KEYS.has(keyPath)) return true;
return DYNAMIC_KEY_PATTERNS.some((p) => p.test(keyPath));
}
module.exports = { VALID_CONFIG_KEYS, RUNTIME_STATE_KEYS, DYNAMIC_KEY_PATTERNS, isValidConfigKey };