refactor(#3309): extend planning-snapshot.cts with config/agentInstall/worktreeHealth
Phase 11 of epic #3180 (ADR-3180 §8.2/§8.3/§8.5) foundation. Extends the already-merged Phase-10 PlanningSnapshot additively with three fields the upcoming health-diagnostic rule table needs and Phase 10 never required: - config: {value, scope, exists} — parsed .planning/config.json. `exists` distinguishes absent (no diagnostic, non-answer) from present-but-invalid (CONFIG_UNREADABLE diagnostic, corruption) — both collapse to scope UNREADABLE, so a rule needs the extra bit to tell "not configured yet" apart from "config.json is broken." - agentInstall / worktreeHealth — not .planning/-sourced, wrap the existing checkAgentsInstalled/inspectWorktreeHealth owners with the same arguments cmdValidateHealth already passes them, so a later migration step reads these fields instead of calling the owners itself. Adds CONFIG_UNREADABLE to src/unusable-input.cts's UNUSABLE_REASON (eighth #1879 site), mirroring STATE_UNREADABLE's exact shape from Phase 10. Additive only — the four Phase-10 fields and worstScope/buildPhaseSnapshot are unchanged; existing tests for them are untouched.
This commit is contained in:
@@ -72,7 +72,7 @@ describe('UNUSABLE_REASON', () => {
|
||||
// (enum + call site + this assertion) instead of a silent widening.
|
||||
assert.deepStrictEqual(
|
||||
Object.keys(UNUSABLE_REASON).sort(),
|
||||
['FRONTMATTER_UNTERMINATED', 'LAST_ACTIVITY_UNPARSEABLE', 'ROADMAP_UNREADABLE', 'STATE_UNREADABLE'],
|
||||
['CONFIG_UNREADABLE', 'FRONTMATTER_UNTERMINATED', 'LAST_ACTIVITY_UNPARSEABLE', 'ROADMAP_UNREADABLE', 'STATE_UNREADABLE'],
|
||||
);
|
||||
assert.strictEqual(UNUSABLE_REASON.FRONTMATTER_UNTERMINATED, 'frontmatter_unterminated');
|
||||
});
|
||||
@@ -134,6 +134,53 @@ describe('STATE_UNREADABLE', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ─── CONFIG_UNREADABLE: a config.json that exists but could not be read/parsed ─
|
||||
|
||||
describe('CONFIG_UNREADABLE', () => {
|
||||
test('a genuinely unreadable config.json produces exactly one diagnostic', () => {
|
||||
_resetUnusableInputWarningsForTests();
|
||||
const emitted = emissionsDuring(() => {
|
||||
const wrote = warnUnusableInput({
|
||||
reason: UNUSABLE_REASON.CONFIG_UNREADABLE,
|
||||
source: '/u/config-unreadable.json',
|
||||
});
|
||||
assert.strictEqual(wrote, true);
|
||||
});
|
||||
assert.strictEqual(emitted, 1);
|
||||
});
|
||||
|
||||
test('the same config.json path reported twice yields one diagnostic', () => {
|
||||
_resetUnusableInputWarningsForTests();
|
||||
const source = '/u/config-unreadable-dedup/config.json';
|
||||
const emitted = emissionsDuring(() => {
|
||||
const first = warnUnusableInput({ reason: UNUSABLE_REASON.CONFIG_UNREADABLE, source });
|
||||
const repeat = warnUnusableInput({ reason: UNUSABLE_REASON.CONFIG_UNREADABLE, source });
|
||||
assert.strictEqual(first, true);
|
||||
assert.strictEqual(repeat, false, 'same (path, cause) must dedup');
|
||||
});
|
||||
assert.strictEqual(emitted, 1);
|
||||
});
|
||||
|
||||
test('two different config.json paths are never suppressed as one', () => {
|
||||
_resetUnusableInputWarningsForTests();
|
||||
const emitted = emissionsDuring(() => {
|
||||
warnUnusableInput({
|
||||
reason: UNUSABLE_REASON.CONFIG_UNREADABLE,
|
||||
source: '/u/config-unreadable-a/config.json',
|
||||
});
|
||||
warnUnusableInput({
|
||||
reason: UNUSABLE_REASON.CONFIG_UNREADABLE,
|
||||
source: '/u/config-unreadable-b/config.json',
|
||||
});
|
||||
});
|
||||
assert.strictEqual(emitted, 2, 'keying too coarsely would hide a real second fault');
|
||||
});
|
||||
|
||||
test('the reason value is the frozen string "config_unreadable"', () => {
|
||||
assert.strictEqual(UNUSABLE_REASON.CONFIG_UNREADABLE, 'config_unreadable');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── The discriminator: truncated vs. everything that merely looks like it ───
|
||||
|
||||
describe('extractFrontmatter — flags a genuinely truncated frontmatter', () => {
|
||||
|
||||
Reference in New Issue
Block a user