refactor(#3309): extend planning-snapshot.cts with 8 more parsed fields
Phase 11 of epic #3180 (ADR-3180 §8.1 rule 2). PlanningSnapshot grows from 7 fields to 15: projectSections, statePhaseTokens, stateStatus, roadmapDeclaredPhases, roadmapPhaseCheckboxes, researchValidationStatus, milestoneArchiveStatus, planningRootFiles. Every field is a reused owner (buildRoadmapPhaseVariants/ buildNotStartedPhaseVariants from src/validate.cts, stateFieldValue) or a small relocation of already-working verify.cts logic (PHASE_NUMBER_TOKEN_SOURCE scanning, the checkMilestonePrefixMismatches sectionRx walk, W009/W018's file-existence checks) — never a new algorithm, and never raw document text: §8.1 rule 2 forbids exposing raw text, not exposing a parsed list or boolean derived from it once by the snapshot builder. roadmapPhaseCheckboxes deliberately reads the same ROADMAP checkbox isPhaseComplete (§7.4, disk-strict) refuses to consult — that owner decides completion and must not read it; this field only exposes what the checkbox says, for a diagnostic (W011) whose whole purpose is flagging disagreement. Not a re-derivation of §7.4, recorded explicitly to prevent that reading. Adds PROJECT_UNREADABLE to UNUSABLE_REASON (ninth #1879 site), closing a gap the implementing agent correctly flagged rather than silently leaving absent-vs-corrupt collapsed for PROJECT.md, matching the STATE_UNREADABLE/ CONFIG_UNREADABLE precedent from this same effort's prior commits. currentPhaseLabel/statePhaseTokens/stateStatus share one STATE.md read (buildStateFields) rather than three independent reads. Additive only — all prior fields and worstScope/buildPhaseSnapshot unchanged.
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(),
|
||||
['CONFIG_UNREADABLE', 'FRONTMATTER_UNTERMINATED', 'LAST_ACTIVITY_UNPARSEABLE', 'ROADMAP_UNREADABLE', 'STATE_UNREADABLE'],
|
||||
['CONFIG_UNREADABLE', 'FRONTMATTER_UNTERMINATED', 'LAST_ACTIVITY_UNPARSEABLE', 'PROJECT_UNREADABLE', 'ROADMAP_UNREADABLE', 'STATE_UNREADABLE'],
|
||||
);
|
||||
assert.strictEqual(UNUSABLE_REASON.FRONTMATTER_UNTERMINATED, 'frontmatter_unterminated');
|
||||
});
|
||||
@@ -181,6 +181,53 @@ describe('CONFIG_UNREADABLE', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ─── PROJECT_UNREADABLE: a PROJECT.md that exists but could not be read ──────
|
||||
|
||||
describe('PROJECT_UNREADABLE', () => {
|
||||
test('a genuinely unreadable PROJECT.md produces exactly one diagnostic', () => {
|
||||
_resetUnusableInputWarningsForTests();
|
||||
const emitted = emissionsDuring(() => {
|
||||
const wrote = warnUnusableInput({
|
||||
reason: UNUSABLE_REASON.PROJECT_UNREADABLE,
|
||||
source: '/u/project-unreadable.md',
|
||||
});
|
||||
assert.strictEqual(wrote, true);
|
||||
});
|
||||
assert.strictEqual(emitted, 1);
|
||||
});
|
||||
|
||||
test('the same PROJECT.md path reported twice yields one diagnostic', () => {
|
||||
_resetUnusableInputWarningsForTests();
|
||||
const source = '/u/project-unreadable-dedup/PROJECT.md';
|
||||
const emitted = emissionsDuring(() => {
|
||||
const first = warnUnusableInput({ reason: UNUSABLE_REASON.PROJECT_UNREADABLE, source });
|
||||
const repeat = warnUnusableInput({ reason: UNUSABLE_REASON.PROJECT_UNREADABLE, source });
|
||||
assert.strictEqual(first, true);
|
||||
assert.strictEqual(repeat, false, 'same (path, cause) must dedup');
|
||||
});
|
||||
assert.strictEqual(emitted, 1);
|
||||
});
|
||||
|
||||
test('two different PROJECT.md paths are never suppressed as one', () => {
|
||||
_resetUnusableInputWarningsForTests();
|
||||
const emitted = emissionsDuring(() => {
|
||||
warnUnusableInput({
|
||||
reason: UNUSABLE_REASON.PROJECT_UNREADABLE,
|
||||
source: '/u/project-unreadable-a/PROJECT.md',
|
||||
});
|
||||
warnUnusableInput({
|
||||
reason: UNUSABLE_REASON.PROJECT_UNREADABLE,
|
||||
source: '/u/project-unreadable-b/PROJECT.md',
|
||||
});
|
||||
});
|
||||
assert.strictEqual(emitted, 2, 'keying too coarsely would hide a real second fault');
|
||||
});
|
||||
|
||||
test('the reason value is the frozen string "project_unreadable"', () => {
|
||||
assert.strictEqual(UNUSABLE_REASON.PROJECT_UNREADABLE, 'project_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