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.
200 lines
6.4 KiB
JavaScript
200 lines
6.4 KiB
JavaScript
const { test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const os = require('node:os');
|
|
const fc = require('fast-check');
|
|
const { evaluatePredicate } = require('../msd-core/bin/lib/gate-predicate-evaluator.cjs');
|
|
const { buildPredicateDeps } = require('../msd-core/bin/lib/check-command-router.cjs');
|
|
|
|
const dummyCtx = { cwd: '/fake/project' };
|
|
const basePredicate = {
|
|
kind: 'artifact-frontmatter-equals',
|
|
artifact: 'WINDOWS.md',
|
|
field: 'open_count',
|
|
equals: 0,
|
|
};
|
|
|
|
function dummyDeps(overrides = {}) {
|
|
return {
|
|
runBoundedShell: () => ({ exitCode: 0, stdout: '', stderr: '', signal: null, timedOut: false }),
|
|
readFrontmatter: () => ({ open_count: '0' }),
|
|
findPhaseArtifact: (dir, suffix) => path.join(dir, suffix),
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
test('evaluatePredicate handles artifact-frontmatter-equals kind with numeric string coercion', () => {
|
|
const deps = dummyDeps({
|
|
readFrontmatter: (filePath) => {
|
|
const base = path.basename(filePath);
|
|
if (base === 'clean-WINDOWS.md') {
|
|
return { open_count: '0' };
|
|
}
|
|
if (base === 'unclean-WINDOWS.md') {
|
|
return { open_count: '2' };
|
|
}
|
|
return {};
|
|
},
|
|
});
|
|
|
|
const cleanPred = {
|
|
kind: 'artifact-frontmatter-equals',
|
|
artifact: 'clean-WINDOWS.md',
|
|
field: 'open_count',
|
|
equals: 0,
|
|
};
|
|
|
|
const passRes = evaluatePredicate(cleanPred, dummyCtx, deps);
|
|
assert.strictEqual(passRes.block, false, 'pass on matching numeric frontmatter field even if read as string');
|
|
|
|
const uncleanPred = {
|
|
kind: 'artifact-frontmatter-equals',
|
|
artifact: 'unclean-WINDOWS.md',
|
|
field: 'open_count',
|
|
equals: 0,
|
|
};
|
|
|
|
const blockRes = evaluatePredicate(uncleanPred, dummyCtx, deps);
|
|
assert.strictEqual(blockRes.block, true, 'block on non-matching frontmatter field');
|
|
});
|
|
|
|
test('artifact-frontmatter-equals rejects malformed declarations', () => {
|
|
for (const artifact of [undefined, '']) {
|
|
assert.throws(
|
|
() => evaluatePredicate({ ...basePredicate, artifact }, dummyCtx, dummyDeps()),
|
|
/non-empty string "artifact"/,
|
|
);
|
|
}
|
|
for (const field of [undefined, '']) {
|
|
assert.throws(
|
|
() => evaluatePredicate({ ...basePredicate, field }, dummyCtx, dummyDeps()),
|
|
/non-empty string "field"/,
|
|
);
|
|
}
|
|
const { equals: _equals, ...withoutEquals } = basePredicate;
|
|
assert.throws(
|
|
() => evaluatePredicate(withoutEquals, dummyCtx, dummyDeps()),
|
|
/requires an "equals" key/,
|
|
);
|
|
});
|
|
|
|
test('artifact-frontmatter-equals fails closed for missing artifacts and fields', () => {
|
|
const missingArtifact = evaluatePredicate(
|
|
basePredicate,
|
|
dummyCtx,
|
|
dummyDeps({ findPhaseArtifact: () => null }),
|
|
);
|
|
assert.equal(missingArtifact.block, true);
|
|
assert.equal(missingArtifact.details.artifactNotFound, true);
|
|
|
|
const missingField = evaluatePredicate(
|
|
basePredicate,
|
|
dummyCtx,
|
|
dummyDeps({ readFrontmatter: () => ({}) }),
|
|
);
|
|
assert.equal(missingField.block, true);
|
|
assert.equal(missingField.details.match, false);
|
|
assert.equal(missingField.details.actual, undefined);
|
|
});
|
|
|
|
test('artifact-frontmatter-equals blocks non-numeric values and surfaces parse failures', () => {
|
|
const nonNumeric = evaluatePredicate(
|
|
basePredicate,
|
|
dummyCtx,
|
|
dummyDeps({ readFrontmatter: () => ({ open_count: 'clean' }) }),
|
|
);
|
|
assert.equal(nonNumeric.block, true);
|
|
assert.equal(nonNumeric.details.actual, 'clean');
|
|
|
|
assert.throws(
|
|
() => evaluatePredicate(
|
|
basePredicate,
|
|
dummyCtx,
|
|
dummyDeps({ readFrontmatter: () => { throw new Error('invalid frontmatter'); } }),
|
|
),
|
|
/invalid frontmatter/,
|
|
);
|
|
});
|
|
|
|
test('artifact-frontmatter-equals validates its kind-specific dependencies', () => {
|
|
const deps = dummyDeps();
|
|
delete deps.findPhaseArtifact;
|
|
assert.throws(
|
|
() => evaluatePredicate(basePredicate, dummyCtx, deps),
|
|
/predicate deps require a "findPhaseArtifact" function/,
|
|
);
|
|
|
|
const depsWithoutReader = dummyDeps();
|
|
delete depsWithoutReader.readFrontmatter;
|
|
assert.throws(
|
|
() => evaluatePredicate(basePredicate, dummyCtx, depsWithoutReader),
|
|
/predicate deps require a "readFrontmatter" function/,
|
|
);
|
|
});
|
|
|
|
test('property: stringified scalar frontmatter equals its JSON scalar declaration', () => {
|
|
const scalar = fc.oneof(
|
|
fc.integer(),
|
|
fc.boolean(),
|
|
fc.string({ maxLength: 40 }),
|
|
fc.constant(null),
|
|
);
|
|
fc.assert(
|
|
fc.property(scalar, (value) => {
|
|
const result = evaluatePredicate(
|
|
{ ...basePredicate, equals: value },
|
|
dummyCtx,
|
|
dummyDeps({ readFrontmatter: () => ({ open_count: String(value) }) }),
|
|
);
|
|
assert.equal(result.block, false);
|
|
}),
|
|
);
|
|
});
|
|
|
|
test('evaluatePredicate with real buildPredicateDeps resolves project-level .planning/WINDOWS.md', () => {
|
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-test-pred-'));
|
|
try {
|
|
const planningDir = path.join(tmpDir, '.planning');
|
|
fs.mkdirSync(planningDir, { recursive: true });
|
|
const windowsPath = path.join(planningDir, 'WINDOWS.md');
|
|
fs.writeFileSync(windowsPath, '---\nopen_count: 0\n---\n# Windows\n', 'utf8');
|
|
|
|
const pred = {
|
|
kind: 'artifact-frontmatter-equals',
|
|
artifact: 'WINDOWS.md',
|
|
field: 'open_count',
|
|
equals: 0,
|
|
};
|
|
|
|
const ctx = { cwd: tmpDir };
|
|
const deps = buildPredicateDeps();
|
|
|
|
const res = evaluatePredicate(pred, ctx, deps);
|
|
assert.strictEqual(res.block, false, 'real buildPredicateDeps must find .planning/WINDOWS.md and pass on open_count: 0');
|
|
|
|
const parentDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-test-pred-parent-'));
|
|
try {
|
|
const projectDir = path.join(parentDir, 'project');
|
|
fs.mkdirSync(projectDir);
|
|
fs.writeFileSync(path.join(parentDir, 'secret.md'), '---\nopen_count: 0\n---\n', 'utf8');
|
|
assert.equal(deps.findPhaseArtifact(projectDir, '../secret.md'), null);
|
|
assert.equal(deps.findPhaseArtifact(projectDir, '..\\secret.md'), null);
|
|
} finally {
|
|
const { cleanup } = require('./helpers.cjs');
|
|
cleanup(parentDir);
|
|
}
|
|
} finally {
|
|
const { cleanup } = require('./helpers.cjs');
|
|
cleanup(tmpDir);
|
|
}
|
|
});
|
|
|
|
test('buildPredicateDeps reports an artifact removed before its frontmatter read', () => {
|
|
const missingPath = path.join(os.tmpdir(), 'msd-test-predicate-artifact-missing.md');
|
|
assert.throws(
|
|
() => buildPredicateDeps().readFrontmatter(missingPath),
|
|
/predicate artifact disappeared before it could be read/,
|
|
);
|
|
});
|