Files
msd-core/tests/inventory-manifest-sync.test.cjs
sim 42729b21fa fix: scan bin/lib subdirectories in the inventory-manifest generator
gen-inventory-manifest.cjs's cli_modules family did a flat readdirSync
of gsd-core/bin/lib/, invisible to anything shipped in a subdirectory.
Found while registering this phase's 8 health-diagnostic-rules/*.cjs
files in docs/INVENTORY.md (Standards-axis review) — the automated
manifest cross-check couldn't see them even though the manual
INVENTORY.md rows were correct.

Adds collectOneLevelSubdirs (mirrors the existing collectNested's
defensive statOrNull style) and merges flat + one-level-subdirectory
results into cli_modules's single sorted array, using the same
<subdir>/<file>.cjs key format INVENTORY.md's rows already use.

Regenerating the manifest surfaced that three OTHER existing
subdirectories (installer-migrations/, host-integration-adapters/,
observability/ — pre-existing, unrelated to this phase) were equally
invisible and had zero docs/INVENTORY.md rows at all. Added all 15
missing rows rather than leave a gap the fix itself just exposed.

Also fixes 3 pre-existing lint-legacy-dir-name violations in the
installer-migrations rows (legitimate references to the historical
get-shit-done -> gsd-core rename these migrations clean up — marked
with the guard's own gsd-allow-legacy-name exemption) and a stale
health-diagnostic.cjs row that still said "RULES ships empty."
2026-08-13 02:56:12 -04:00

67 lines
2.9 KiB
JavaScript

'use strict';
/**
* Asserts docs/INVENTORY-MANIFEST.json is in sync with the filesystem.
* A stale manifest means a surface shipped without updating INVENTORY.md.
* Fix by running: node scripts/gen-inventory-manifest.cjs --write
* then adding the corresponding row(s) in docs/INVENTORY.md.
*/
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 MANIFEST_PATH = path.join(ROOT, 'docs', 'INVENTORY-MANIFEST.json');
// #2996: FAMILIES and NESTED_FAMILIES are IMPORTED, never redeclared. This file used to
// carry its own copy of the family table — the `DEFECT.GENERATIVE-FIX` divergence class:
// a family added to the generator but not here left this test silently verifying a
// subset while still reporting green. Importing makes divergence impossible rather than
// merely detectable.
const { FAMILIES, NESTED_FAMILIES, collectNested, collectOneLevelSubdirs } = require('../scripts/gen-inventory-manifest.cjs');
test('docs/INVENTORY-MANIFEST.json matches the filesystem', () => {
const committed = JSON.parse(fs.readFileSync(MANIFEST_PATH, 'utf8'));
const additions = [];
const removals = [];
for (const { name, dir, filter, toName } of FAMILIES) {
const flat = fs.readdirSync(dir)
.filter((f) => fs.statSync(path.join(dir, f)).isFile() && filter(f))
.map(toName);
// `cli_modules` also ships one level of subdirectory modules (#3309); mirror
// buildManifest's special-case merge exactly, or this test would report every
// subdirectory file as a phantom removal.
const nested = name === 'cli_modules' ? collectOneLevelSubdirs({ dir, filter }) : [];
const live = new Set([...flat, ...nested]);
const recorded = new Set((committed.families || {})[name] || []);
for (const entry of live) {
if (!recorded.has(entry)) additions.push(name + '/' + entry);
}
for (const entry of recorded) {
if (!live.has(entry)) removals.push(name + '/' + entry);
}
}
for (const family of NESTED_FAMILIES) {
const live = new Set(collectNested(family));
const recorded = new Set((committed.families || {})[family.name] || []);
for (const entry of live) {
if (!recorded.has(entry)) additions.push(family.name + '/' + entry);
}
for (const entry of recorded) {
if (!live.has(entry)) removals.push(family.name + '/' + entry);
}
}
const msg = [
additions.length ? 'New surfaces not in manifest (run node scripts/gen-inventory-manifest.cjs --write):\n' + additions.map((e) => ' + ' + e).join('\n') : '',
removals.length ? 'Manifest entries with no matching file:\n' + removals.map((e) => ' - ' + e).join('\n') : '',
].filter(Boolean).join('\n');
assert.ok(additions.length === 0 && removals.length === 0, msg);
});