Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD across contents and paths, upstream package/repo coordinates -> @golem15/msd-core and golem15com/msd-core. Deep links into upstream history, sibling upstream packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is. Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line, package/plugin identity, regenerated lockfile, install-tree fixtures, derived registries and benchmark baseline; migration checksum baseline re-locked (MSD keeps its own install state, so no install had applied the old sums); sort-order and regex-escaped expectations in tests adjusted.
124 lines
5.8 KiB
JavaScript
124 lines
5.8 KiB
JavaScript
'use strict';
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// #3804 — audit-uat must see all three phase-archive layouts.
|
|
//
|
|
// listArchiveVersionDirs matched exactly one layout
|
|
// (`milestones/vX.Y-phases/<dir>/`). Two workstream layouts missed:
|
|
// archived workstream milestones (`milestones/ws-<slug>-<date>/phases/<dir>/`)
|
|
// and active workstream milestones
|
|
// (`workstreams/<ws>/milestones/vX.Y-phases/<dir>/`) — so a project using
|
|
// workstreams got an "All Clear" audit while items were open (the reporter's
|
|
// repo: 20 hidden phase artifacts), and `--ws` emptied the report entirely.
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
const { test, describe } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
const { createTempProject, cleanup, runMsdTools } = require('./helpers.cjs');
|
|
|
|
function seedPhaseArtifact(dir, kind) {
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
if (kind === 'deferred') {
|
|
fs.writeFileSync(path.join(dir, 'deferred-items.md'), [
|
|
'## Deferred Items',
|
|
'',
|
|
'- open workstream item needing a human',
|
|
'',
|
|
].join('\n'));
|
|
} else {
|
|
// The canonical UAT.md shape the parser reads: `### N. <name>` test
|
|
// blocks with expected/result field lines (see tests/uat.test.cjs's
|
|
// fixtures) — the numbered-list form parses to zero items.
|
|
fs.writeFileSync(path.join(dir, `${path.basename(dir)}-UAT.md`), [
|
|
'---',
|
|
'status: testing',
|
|
'---',
|
|
'',
|
|
'## Tests',
|
|
'',
|
|
'### 1. Boot Flow',
|
|
'expected: Workstream phase boots',
|
|
'result: pending',
|
|
'',
|
|
].join('\n'));
|
|
}
|
|
}
|
|
|
|
function runAudit(cwd, extraArgs = []) {
|
|
const r = runMsdTools(['query', 'audit-uat', ...extraArgs], cwd);
|
|
assert.ok(r.success, r.error);
|
|
return JSON.parse(r.output);
|
|
}
|
|
|
|
describe('#3804: audit-uat sees all three phase layouts', () => {
|
|
test('#3804: archived workstream milestones surface in audit-uat', (t) => {
|
|
const tmpDir = createTempProject('msd-3804-wsarch-');
|
|
t.after(() => cleanup(tmpDir));
|
|
const wsArchive = path.join(tmpDir, '.planning', 'milestones',
|
|
'ws-v7-3-catalog-models-2026-08-23', 'phases', '185-catalog-admin');
|
|
seedPhaseArtifact(wsArchive, 'deferred');
|
|
|
|
const out = runAudit(tmpDir);
|
|
const phase185 = out.results.filter((r) => String(r.phase).startsWith('185'));
|
|
assert.ok(
|
|
phase185.length >= 1 && phase185.some((r) => (r.items || []).length > 0),
|
|
`#3804: the archived workstream phase must surface with its open item; got ${JSON.stringify(out.results.map((r) => ({ phase: r.phase, items: (r.items || []).length })))}`,
|
|
);
|
|
});
|
|
|
|
test('#3804: active workstream milestones surface in audit-uat (and --ws does not empty the report)', (t) => {
|
|
const tmpDir = createTempProject('msd-3804-wsact-');
|
|
t.after(() => cleanup(tmpDir));
|
|
const wsMilestone = path.join(tmpDir, '.planning', 'workstreams', 'v7-2-boot-obs',
|
|
'milestones', 'v1.0-phases', '176-boot-flow');
|
|
seedPhaseArtifact(wsMilestone, 'uat');
|
|
fs.mkdirSync(path.join(tmpDir, '.planning', 'workstreams', 'v7-2-boot-obs'), { recursive: true });
|
|
fs.writeFileSync(path.join(tmpDir, '.planning', 'workstreams', 'v7-2-boot-obs', 'config.json'), '{}');
|
|
|
|
const bare = runAudit(tmpDir);
|
|
assert.ok(
|
|
bare.results.some((r) => String(r.phase).startsWith('176') && (r.items || []).length > 0),
|
|
`#3804: a flat (no --ws) audit must see the active workstream milestone's open item; got ${JSON.stringify(bare.summary)}`,
|
|
);
|
|
|
|
const scoped = runAudit(tmpDir, ['--ws', 'v7-2-boot-obs']);
|
|
assert.ok(
|
|
scoped.summary.total_items > 0,
|
|
`#3804: --ws must not empty the report; got ${JSON.stringify(scoped.summary)}`,
|
|
);
|
|
});
|
|
|
|
test('#3804 control: the flat archive layout still surfaces', (t) => {
|
|
const tmpDir = createTempProject('msd-3804-flat-');
|
|
t.after(() => cleanup(tmpDir));
|
|
seedPhaseArtifact(path.join(tmpDir, '.planning', 'milestones', 'v1.0-phases', '09-old'), 'deferred');
|
|
|
|
const out = runAudit(tmpDir);
|
|
assert.ok(
|
|
out.results.some((r) => String(r.phase).startsWith('09') && (r.items || []).length > 0),
|
|
'the pre-existing flat archive layout must keep surfacing',
|
|
);
|
|
});
|
|
|
|
test('#3804 review: root and workstream v1.0 archives carry DISTINCT milestone labels', (t) => {
|
|
// Two v1.0-phases trees (root + workstream) must not both label 'v1.0' —
|
|
// audit acknowledge resolves --archived-milestone by strict label
|
|
// equality, first-wins, so an ambiguous label can write the marker into
|
|
// the wrong root's phase dir (#2237 class).
|
|
const tmpDir = createTempProject('msd-3804-labels-');
|
|
t.after(() => cleanup(tmpDir));
|
|
seedPhaseArtifact(path.join(tmpDir, '.planning', 'milestones', 'v1.0-phases', '42-root'), 'deferred');
|
|
fs.mkdirSync(path.join(tmpDir, '.planning', 'workstreams', 'alpha'), { recursive: true });
|
|
seedPhaseArtifact(path.join(tmpDir, '.planning', 'workstreams', 'alpha', 'milestones', 'v1.0-phases', '42-alpha'), 'deferred');
|
|
|
|
const out = runAudit(tmpDir);
|
|
const labels = out.results.map((r) => r.archived_milestone).filter(Boolean);
|
|
assert.ok(labels.includes('v1.0'), `the root label stays the bare version; got ${JSON.stringify(labels)}`);
|
|
assert.ok(labels.includes('alpha/v1.0'), `the workstream label is prefixed; got ${JSON.stringify(labels)}`);
|
|
assert.equal(new Set(labels).size, labels.length, 'labels must be unique across roots');
|
|
});
|
|
});
|