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:
@@ -18,6 +18,8 @@ const PLAN_PHASE = path.join(__dirname, '..', 'get-shit-done', 'workflows', 'pla
|
||||
const VERIFY_PHASE = path.join(__dirname, '..', 'get-shit-done', 'workflows', 'verify-phase.md');
|
||||
const CONFIG_TS = path.join(__dirname, '..', 'sdk', 'src', 'config.ts');
|
||||
const CONFIG_MUTATION_TS = path.join(__dirname, '..', 'sdk', 'src', 'query', 'config-mutation.ts');
|
||||
// #2653 — allowlist moved to shared schema module.
|
||||
const CONFIG_SCHEMA_TS = path.join(__dirname, '..', 'sdk', 'src', 'query', 'config-schema.ts');
|
||||
const CONFIG_GATES_TS = path.join(__dirname, '..', 'sdk', 'src', 'query', 'config-gates.ts');
|
||||
const QUERY_INDEX_TS = path.join(__dirname, '..', 'sdk', 'src', 'query', 'index.ts');
|
||||
|
||||
@@ -144,8 +146,9 @@ describe('SDK wiring for #2492 gates', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('config-mutation.ts VALID_CONFIG_KEYS allows workflow.context_coverage_gate', () => {
|
||||
const c = fs.readFileSync(CONFIG_MUTATION_TS, 'utf-8');
|
||||
test('config-schema.ts VALID_CONFIG_KEYS allows workflow.context_coverage_gate', () => {
|
||||
// #2653 — allowlist moved out of config-mutation.ts into shared config-schema.ts.
|
||||
const c = fs.readFileSync(CONFIG_SCHEMA_TS, 'utf-8');
|
||||
assert.ok(
|
||||
c.includes("'workflow.context_coverage_gate'"),
|
||||
'workflow.context_coverage_gate must be in VALID_CONFIG_KEYS',
|
||||
|
||||
96
tests/config-schema-sdk-parity.test.cjs
Normal file
96
tests/config-schema-sdk-parity.test.cjs
Normal file
@@ -0,0 +1,96 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* CJS↔SDK config-schema parity (#2653).
|
||||
*
|
||||
* The SDK has its own config-set handler at sdk/src/query/config-mutation.ts,
|
||||
* which validates keys against sdk/src/query/config-schema.ts. That allowlist
|
||||
* MUST match the CJS allowlist at get-shit-done/bin/lib/config-schema.cjs or
|
||||
* SDK users are told "Unknown config key" for documented keys (regression
|
||||
* that #2653 fixes).
|
||||
*
|
||||
* This test parses the TS file as text (to avoid requiring a TS toolchain
|
||||
* in the node:test runner) and asserts:
|
||||
* 1. Every key in CJS VALID_CONFIG_KEYS appears in the SDK literal set.
|
||||
* 2. Every dynamic pattern source in CJS has an identical counterpart
|
||||
* in the SDK file.
|
||||
* 3. The reverse direction — SDK has no keys/patterns the CJS side lacks.
|
||||
*/
|
||||
|
||||
const { test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '..');
|
||||
const { VALID_CONFIG_KEYS: CJS_KEYS, DYNAMIC_KEY_PATTERNS: CJS_PATTERNS } =
|
||||
require('../get-shit-done/bin/lib/config-schema.cjs');
|
||||
|
||||
const SDK_SCHEMA_PATH = path.join(ROOT, 'sdk', 'src', 'query', 'config-schema.ts');
|
||||
const SDK_SRC = fs.readFileSync(SDK_SCHEMA_PATH, 'utf8');
|
||||
|
||||
function extractSdkKeys(src) {
|
||||
const start = src.indexOf('VALID_CONFIG_KEYS');
|
||||
assert.ok(start > -1, 'SDK config-schema.ts must export VALID_CONFIG_KEYS');
|
||||
const setOpen = src.indexOf('new Set([', start);
|
||||
const setClose = src.indexOf('])', setOpen);
|
||||
assert.ok(setOpen > -1 && setClose > -1, 'VALID_CONFIG_KEYS must be a new Set([...]) literal');
|
||||
const body = src.slice(setOpen + 'new Set(['.length, setClose);
|
||||
const keys = new Set();
|
||||
for (const match of body.matchAll(/'([^']+)'/g)) keys.add(match[1]);
|
||||
return keys;
|
||||
}
|
||||
|
||||
function extractSdkPatternSources(src) {
|
||||
const sources = [];
|
||||
for (const match of src.matchAll(/source:\s*'([^']+)'/g)) {
|
||||
// TS source file stores escape sequences; convert \\ -> \ so the
|
||||
// extracted value matches RegExp.source from the CJS side.
|
||||
sources.push(match[1].replace(/\\\\/g, '\\'));
|
||||
}
|
||||
return sources;
|
||||
}
|
||||
|
||||
test('#2653 — SDK VALID_CONFIG_KEYS matches CJS VALID_CONFIG_KEYS', () => {
|
||||
const sdkKeys = extractSdkKeys(SDK_SRC);
|
||||
const missingInSdk = [...CJS_KEYS].filter((k) => !sdkKeys.has(k));
|
||||
const extraInSdk = [...sdkKeys].filter((k) => !CJS_KEYS.has(k));
|
||||
assert.deepStrictEqual(
|
||||
missingInSdk,
|
||||
[],
|
||||
'CJS keys missing from sdk/src/query/config-schema.ts:\n' +
|
||||
missingInSdk.map((k) => ' ' + k).join('\n'),
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
extraInSdk,
|
||||
[],
|
||||
'SDK keys missing from get-shit-done/bin/lib/config-schema.cjs:\n' +
|
||||
extraInSdk.map((k) => ' ' + k).join('\n'),
|
||||
);
|
||||
});
|
||||
|
||||
test('#2653 — SDK DYNAMIC_KEY_PATTERNS sources match CJS regex .source', () => {
|
||||
const sdkSources = new Set(extractSdkPatternSources(SDK_SRC));
|
||||
const cjsSources = CJS_PATTERNS.map((p) => {
|
||||
// Reconstruct each CJS pattern's .source by probing with a known string
|
||||
// that identifies the regex. CJS stores a `test` arrow only, so derive
|
||||
// `.source` by running against sentinel inputs — instead, inspect function
|
||||
// text as a fallback cross-check.
|
||||
const fnSrc = p.test.toString();
|
||||
const regexMatch = fnSrc.match(/\/(\^[^/]+\$)\//);
|
||||
assert.ok(regexMatch, 'CJS dynamic pattern test function must embed a literal regex: ' + fnSrc);
|
||||
return regexMatch[1];
|
||||
});
|
||||
for (const src of cjsSources) {
|
||||
assert.ok(
|
||||
sdkSources.has(src),
|
||||
`CJS dynamic pattern ${src} not mirrored in SDK config-schema.ts (sources: ${[...sdkSources].join(', ')})`,
|
||||
);
|
||||
}
|
||||
for (const src of sdkSources) {
|
||||
assert.ok(
|
||||
cjsSources.includes(src),
|
||||
`SDK dynamic pattern ${src} not mirrored in CJS config-schema.cjs`,
|
||||
);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user