* fix(#2997): include phase_id_convention in resolved config _baseConfig in config-loader.cts is an explicit allowlist of keys copied from parsed into the resolved config. phase_id_convention was in VALID_CONFIG_KEYS (manifest line 83) and survived the unknown-key filter, but was never copied into _baseConfig — silently dropped on a clean read. The milestone-prefix validation check could only be activated via the ROADMAP frontmatter fallback, not the documented project-config surface. Added phase_id_convention: get('phase_id_convention') ?? null to _baseConfig. 3 tests: survives resolution, null round-trips, absent resolves to null. * chore(#2997): backfill changeset PR number 3098 --------- Co-authored-by: sim <sim@local>
This commit is contained in:
5
.changeset/wise-jays-romp.md
Normal file
5
.changeset/wise-jays-romp.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
type: Fixed
|
||||||
|
pr: 3098
|
||||||
|
---
|
||||||
|
**`phase_id_convention` set in `.planning/config.json` is no longer silently dropped** — the config loader's resolved-config constructor omitted the key despite it being in the valid-keys manifest, so the milestone-prefix validation check could only be activated via the ROADMAP frontmatter fallback. The key now survives resolution. (#2997)
|
||||||
@@ -812,6 +812,7 @@ function loadConfigResolved(cwd: string, options: Record<string, unknown> = {}):
|
|||||||
response_language: get('response_language') || null,
|
response_language: get('response_language') || null,
|
||||||
claude_md_path: get('claude_md_path') || null,
|
claude_md_path: get('claude_md_path') || null,
|
||||||
claude_md_assembly: (parsed['claude_md_assembly']) || null,
|
claude_md_assembly: (parsed['claude_md_assembly']) || null,
|
||||||
|
phase_id_convention: get('phase_id_convention') ?? null,
|
||||||
};
|
};
|
||||||
|
|
||||||
// ADR-857 phase 3b: federated config overlay
|
// ADR-857 phase 3b: federated config overlay
|
||||||
|
|||||||
@@ -1255,3 +1255,44 @@ describe("loadConfigResolved — corrupt config is distinguishable from absent",
|
|||||||
assert.equal(res.degraded, false, "an empty file is not corruption");
|
assert.equal(res.degraded, false, "an empty file is not corruption");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ─── #2997: phase_id_convention survives config resolution ─────────────────
|
||||||
|
|
||||||
|
describe('#2997: phase_id_convention is not silently dropped on a clean read', () => {
|
||||||
|
const { createTempDir } = require('./helpers.cjs');
|
||||||
|
const cfgPath = (dir) => path.join(dir, '.planning', 'config.json');
|
||||||
|
|
||||||
|
test('setting phase_id_convention in config.json survives into the resolved config', () => {
|
||||||
|
const tmpDir = createTempDir('gsd-2997-');
|
||||||
|
try {
|
||||||
|
fs.mkdirSync(path.join(tmpDir, '.planning'), { recursive: true });
|
||||||
|
fs.writeFileSync(cfgPath(tmpDir), JSON.stringify({ phase_id_convention: 'milestone-prefixed' }), 'utf-8');
|
||||||
|
const res = loadConfigResolved(tmpDir);
|
||||||
|
assert.equal(res.degraded, false, 'read must report as non-degraded');
|
||||||
|
assert.equal(res.config.phase_id_convention, 'milestone-prefixed',
|
||||||
|
`phase_id_convention must survive resolution; got: ${JSON.stringify(res.config.phase_id_convention)}`);
|
||||||
|
} finally { cleanup(tmpDir); }
|
||||||
|
});
|
||||||
|
|
||||||
|
test('phase_id_convention set to null round-trips correctly', () => {
|
||||||
|
const tmpDir = createTempDir('gsd-2997-null-');
|
||||||
|
try {
|
||||||
|
fs.mkdirSync(path.join(tmpDir, '.planning'), { recursive: true });
|
||||||
|
fs.writeFileSync(cfgPath(tmpDir), JSON.stringify({ phase_id_convention: null }), 'utf-8');
|
||||||
|
const res = loadConfigResolved(tmpDir);
|
||||||
|
assert.equal(res.config.phase_id_convention, null,
|
||||||
|
'null phase_id_convention must round-trip as null');
|
||||||
|
} finally { cleanup(tmpDir); }
|
||||||
|
});
|
||||||
|
|
||||||
|
test('phase_id_convention absent → null in resolved config (no false default)', () => {
|
||||||
|
const tmpDir = createTempDir('gsd-2997-absent-');
|
||||||
|
try {
|
||||||
|
fs.mkdirSync(path.join(tmpDir, '.planning'), { recursive: true });
|
||||||
|
fs.writeFileSync(cfgPath(tmpDir), JSON.stringify({ commit_docs: true }), 'utf-8');
|
||||||
|
const res = loadConfigResolved(tmpDir);
|
||||||
|
assert.equal(res.config.phase_id_convention, null,
|
||||||
|
'absent phase_id_convention must resolve to null, not undefined');
|
||||||
|
} finally { cleanup(tmpDir); }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user