* test(3739): add RED tests for padded-prefix CONTEXT.md gap-checker miss Covers bare and padded (01-CONTEXT.md, 02.1-CONTEXT.md) forms, an uncovered-decision counter-test, and unit tests for the upcoming findContextMdIn() helper. All 6 new tests fail before the fix. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(3739): extract findContextMdIn() helper; fix gap-checker bare lookup gap-checker.cjs:136 used a bare path.join(absPhaseDir, 'CONTEXT.md') that silently returned '' for any phase using the padded-prefix convention (01-CONTEXT.md, 02.1-CONTEXT.md, etc.). Extract findContextMdIn(absDir) to planning-workspace.cjs — the module already imported by gap-checker, init, roadmap, and core — and wire gap-checker.cjs to call it instead of the bare lookup. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(3739): replace inline dual-form predicate with findContextMdIn() at all 4 remaining sites The dual-form predicate `f.endsWith('-CONTEXT.md') || f === 'CONTEXT.md'` existed verbatim at 5 sites across init.cjs (×3), roadmap.cjs, and core.cjs — Rule of Three mandates extraction at ≥3 sites. All 4 remaining call sites now delegate to findContextMdIn() from planning-workspace.cjs. No behaviour change; all existing tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(3739): update changeset to reference PR #3764 * fix(3739): findContextMdIn prefers bare CONTEXT.md over padded form (deterministic precedence) `.find()` with `f.endsWith('-CONTEXT.md') || f === 'CONTEXT.md'` returned the first match in `readdirSync` order — undefined on most filesystems. When both `CONTEXT.md` and `01-CONTEXT.md` exist the winner was arbitrary. The old gap-checker.cjs:136 code always used bare `CONTEXT.md` first (existsSync on the bare path). Restore that invariant: check `files.includes('CONTEXT.md')` before falling through to the padded scan. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(3739): dual-file precedence test — bare CONTEXT.md wins over padded form Adds two test cases for the scenario where both CONTEXT.md and 01-CONTEXT.md exist in the same phase directory: 1. Helper level: findContextMdIn() must return 'CONTEXT.md' (not the padded filename) when both files are present on disk. 2. Integration level: gap-analysis must resolve decisions from the bare form only; D-PADDED (from 01-CONTEXT.md) must not appear when the bare form shadows it. Without these tests a future change to findContextMdIn could silently regress the precedence guarantee. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(3739): bug-2798 tests skip cleanly when sdk/dist is absent (was hard-fail) The 3 tests in bug-2798-context-window-config-key.test.cjs invoke the built SDK CLI (sdk/dist/cli.js) and require sdk/dist/query/config-schema.js. When dist is absent, they threw hard errors rather than observable skips. Apply the same `if (!existsSync(...)) { t.skip(...); return; }` guard used in bug-2767-gsd-sdk-commit-files-flag.test.cjs (c2812313). Tests 1 & 2 guard on sdk/dist/cli.js; test 3 guards on sdk/dist/query/config-schema.js. When dist is present all 3 run; when absent all 3 emit actionable skip lines. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(3739): eliminate double readdirSync in findContextMdIn callers findContextMdIn now accepts either a directory path or an already-read files array, allowing callers that already hold a directory listing (core.cjs:getPhaseFileStats, roadmap.cjs:countPhasePlansAndSummaries, gap-checker.cjs:runGapAnalysis) to skip redundant readdirSync calls. Test coverage added for the array-argument overload. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: redesign flaky concurrent add-blocker test with deterministic barrier Previous design relied on OS scheduler to interleave two subprocess writes, producing a flake under CI load. Redesigned using a file-barrier (Option A) that forces both subprocesses to reach a ready-gate before either proceeds, guaranteeing true concurrent lock contention and eliminating timing dependency. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
102 lines
3.5 KiB
JavaScript
102 lines
3.5 KiB
JavaScript
/**
|
|
* Regression test for bug #2798
|
|
*
|
|
* `gsd-sdk query config-set context_window <n>` was rejected with
|
|
* "Unknown config key: context_window" because context_window was missing
|
|
* from VALID_CONFIG_KEYS in sdk/src/query/config-schema.ts.
|
|
*
|
|
* The fix added 'context_window' to the allowlist.
|
|
* This test prevents future drift where the key gets accidentally removed.
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const { describe, test, beforeEach, afterEach } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const { execFileSync } = require('node:child_process');
|
|
const { createTempProject, cleanup } = require('./helpers.cjs');
|
|
|
|
const REPO_ROOT = path.join(__dirname, '..');
|
|
const SDK_CLI = path.join(REPO_ROOT, 'sdk', 'dist', 'cli.js');
|
|
|
|
function runConfigSet(key, value, projectDir) {
|
|
const argv = ['query', 'config-set', key, String(value), '--project-dir', projectDir];
|
|
let stdout = '';
|
|
let exitCode = 0;
|
|
try {
|
|
stdout = execFileSync(process.execPath, [SDK_CLI, ...argv], {
|
|
encoding: 'utf-8',
|
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
env: { ...process.env, GSD_SESSION_KEY: '' },
|
|
});
|
|
} catch (err) {
|
|
exitCode = err.status ?? 1;
|
|
stdout = err.stdout?.toString() ?? '';
|
|
}
|
|
let json = null;
|
|
try { json = JSON.parse(stdout.trim()); } catch { /* ok */ }
|
|
return { exitCode, json };
|
|
}
|
|
|
|
describe('bug-2798: context_window is a valid config key', () => {
|
|
let tmpDir;
|
|
|
|
beforeEach(() => {
|
|
tmpDir = createTempProject('gsd-test-2798-');
|
|
fs.writeFileSync(
|
|
path.join(tmpDir, '.planning', 'config.json'),
|
|
JSON.stringify({ mode: 'balanced' })
|
|
);
|
|
});
|
|
|
|
afterEach(() => {
|
|
cleanup(tmpDir);
|
|
});
|
|
|
|
test('config-set context_window succeeds (not rejected as unknown key)', (t) => {
|
|
if (!fs.existsSync(SDK_CLI)) {
|
|
t.skip('sdk/dist/cli.js not built — run `cd sdk && npm run build` to enable this integration test');
|
|
return;
|
|
}
|
|
const result = runConfigSet('context_window', 1000000, tmpDir);
|
|
|
|
assert.strictEqual(result.exitCode, 0, 'should exit 0 (key is valid)');
|
|
assert.ok(result.json !== null, 'should emit JSON');
|
|
assert.strictEqual(result.json?.updated, true, 'updated should be true');
|
|
assert.strictEqual(result.json?.key, 'context_window');
|
|
});
|
|
|
|
test('context_window value is written to config.json', (t) => {
|
|
if (!fs.existsSync(SDK_CLI)) {
|
|
t.skip('sdk/dist/cli.js not built — run `cd sdk && npm run build` to enable this integration test');
|
|
return;
|
|
}
|
|
runConfigSet('context_window', 500000, tmpDir);
|
|
|
|
const config = JSON.parse(
|
|
fs.readFileSync(path.join(tmpDir, '.planning', 'config.json'), 'utf-8')
|
|
);
|
|
assert.strictEqual(config.context_window, 500000, 'context_window should be persisted');
|
|
});
|
|
|
|
test('config-schema CJS and SDK allowlists both include context_window', (t) => {
|
|
if (!fs.existsSync(path.join(REPO_ROOT, 'sdk', 'dist', 'query', 'config-schema.js'))) {
|
|
t.skip('sdk/dist/query/config-schema.js not built — run `cd sdk && npm run build` to enable this integration test');
|
|
return;
|
|
}
|
|
const cjsSchema = require(path.join(REPO_ROOT, 'get-shit-done', 'bin', 'lib', 'config-schema.cjs'));
|
|
const sdkSchema = require(path.join(REPO_ROOT, 'sdk', 'dist', 'query', 'config-schema.js'));
|
|
|
|
assert.ok(
|
|
cjsSchema.VALID_CONFIG_KEYS.has('context_window'),
|
|
'CJS VALID_CONFIG_KEYS must include context_window'
|
|
);
|
|
assert.ok(
|
|
sdkSchema.VALID_CONFIG_KEYS.has('context_window'),
|
|
'SDK VALID_CONFIG_KEYS must include context_window'
|
|
);
|
|
});
|
|
});
|