fix(#2653): eliminate SDK↔CJS config-schema drift (#2670)

The SDK's config-set kept its own hand-maintained allowlist (28-key
drift vs. get-shit-done/bin/lib/config-schema.cjs), so documented
keys accepted by the CJS config-set — planning.sub_repos,
workflow.code_review_command, workflow.security_*, review.models.*,
model_profile_overrides.*, etc. — were rejected with
"Unknown config key" when routed through the SDK.

Changes:
- New sdk/src/query/config-schema.ts mirrors the CJS schema exactly
  (exact-match keys + dynamic regex sources).
- config-mutation.ts imports VALID_CONFIG_KEYS / DYNAMIC_KEY_PATTERNS
  from the shared module instead of rolling its own set and regex
  branches.
- Drop hand-coded agent_skills.* / features.* regex branches —
  now schema-driven so claude_md_assembly.blocks.*, review.models.*,
  and model_profile_overrides.<runtime>.<tier> are also accepted.
- Add tests/config-schema-sdk-parity.test.cjs (node:test) as the
  CI drift guard: asserts CJS VALID_CONFIG_KEYS set-equals the
  literal set parsed from config-schema.ts, and that every CJS
  dynamic pattern source has an identical counterpart in the SDK.
  Parallel to the CJS↔docs parity added in #2479.
- Vitest #2653 specs iterate every CJS key through the SDK
  validator, spot-check each dynamic pattern, and lock in
  planning.sub_repos.
- While here: add workflow.context_coverage_gate to the CJS schema
  (already in docs and SDK; CJS previously rejected it) and sync
  the missing curated typo-suggestions (review.model, sub_repos,
  plan_checker, workflow.review_command) into the SDK.

Fixes #2653.
This commit is contained in:
Tom Boucher
2026-04-24 18:05:16 -04:00
committed by GitHub
parent e973ff4cb6
commit 387c8a1f9c
6 changed files with 266 additions and 45 deletions

View File

@@ -86,6 +86,42 @@ describe('isValidConfigKey', () => {
expect(r2.valid).toBe(false);
expect(r2.suggestion).toBe('workflow.nyquist_validation');
});
// #2653 — SDK/CJS config-schema drift regression.
// Every key accepted by the CJS config-set must also be accepted by
// the SDK config-set. We exercise every entry in the shared schema
// so drift fails this test the moment it is introduced.
it('#2653 — accepts every key in shared VALID_CONFIG_KEYS', async () => {
const { isValidConfigKey } = await import('./config-mutation.js');
const { VALID_CONFIG_KEYS } = await import('./config-schema.js');
const rejected: string[] = [];
for (const key of VALID_CONFIG_KEYS) {
const { valid } = isValidConfigKey(key);
if (!valid) rejected.push(key);
}
expect(rejected).toEqual([]);
});
it('#2653 — accepts sample dynamic keys from every DYNAMIC_KEY_PATTERN', async () => {
const { isValidConfigKey } = await import('./config-mutation.js');
const samples = [
'agent_skills.gsd-planner',
'review.models.claude',
'features.some_feature',
'claude_md_assembly.blocks.intro',
'model_profile_overrides.codex.opus',
'model_profile_overrides.codex.sonnet',
'model_profile_overrides.my-runtime.haiku',
];
for (const key of samples) {
expect(isValidConfigKey(key).valid, `expected ${key} to be accepted`).toBe(true);
}
});
it('#2653 — accepts planning.sub_repos (CJS/docs key, previously rejected by SDK)', async () => {
const { isValidConfigKey } = await import('./config-mutation.js');
expect(isValidConfigKey('planning.sub_repos').valid).toBe(true);
});
});
// ─── parseConfigValue ──────────────────────────────────────────────────────