Files
msd-core/tests/context-predicates-query.test.cjs
Jakub Zych a9a7a328e6 refactor: hard-fork GSD -> MSD (Make Software Done)
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.
2026-10-06 01:47:40 +02:00

279 lines
14 KiB
JavaScript

'use strict';
/**
* Integration tests for `msd_run query context-predicates` — the selector
* surface for the CONTEXT.md predicate fact-store (ADR-1671 S9, #2928 Phase
* 1, rows G1-G17).
*
* NET-NEW COMMAND, EXPECTED RED: `context-predicates` is not yet registered
* in msd-core/bin/msd-tools.cjs's HOST_COMMAND_ROUTERS / TOP_LEVEL_USAGE /
* SKIP_ROOT_RESOLUTION (S9's three hand-maintained sites). Every test in this
* file targets the REQUIRED behavior from 40-design.md rows 38-43 and
* currently fails because the command does not exist — that is expected and
* correct for this commit (a failing-first regression matrix), not a
* defect in the test.
*
* Invocation shape: `msd_run query <command> [args]` maps to
* `node msd-tools.cjs query <command> [args]` — `query` is a meta-prefix the
* dispatcher strips (msd-core/bin/msd-tools.cjs, "Accept `query` as a
* meta-prefix"). This is the same shape every existing query consumer in
* msd-core/workflows/*.md uses (e.g. `msd_run query stats.json`,
* `msd_run query prompt-budget ...`) — mirrored here since no real caller of
* `context-predicates` is wired yet (net-new capability).
*
* Structured assertions: success/failure is asserted via exit code, and via
* `--json-errors` (a real, already-shipped global msd-tools flag) parsed as
* `{ok, reason, message}` — `reason` is compared against the frozen
* ERROR_REASON enum (msd-core/bin/lib/io.cjs), never a substring match on
* `message` prose. On success, output is asserted via JSON.parse of stdout
* (msd-tools' shared `output()` helper always serializes JSON to stdout
* unless --raw is passed) — never a stdout regex.
*/
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { runMsdTools, createTempDir, cleanup } = require('./helpers.cjs');
const { ERROR_REASON } = require('../msd-core/bin/lib/io.cjs');
const { parsePredicates, selectPredicates } = require('../msd-core/bin/lib/context-predicates.cjs');
const ROOT = path.resolve(__dirname, '..');
const STACK_FRAME_RE = /\n\s+at\s+\S+\s+\(.*:\d+:\d+\)/;
function queryContextPredicates(args, cwd = ROOT, env = {}) {
return runMsdTools(['query', 'context-predicates', ...args], cwd, env);
}
function queryContextPredicatesJsonErrors(args, cwd = ROOT) {
const r = queryContextPredicates([...args, '--json-errors'], cwd);
let parsedError = null;
try {
parsedError = JSON.parse(r.error);
} catch {
// r.error was not JSON (e.g. a resource-starvation message) — leave null.
}
return { ...r, parsedError };
}
// Independently computed expectation set, from the real CONTEXT.md, via the
// exported pure parser/selector — NOT by parsing the CLI's own rendered text.
const REAL_PREDICATES = parsePredicates(fs.readFileSync(path.join(ROOT, 'CONTEXT.md'), 'utf8')).predicates;
describe('msd_run query context-predicates (G)', () => {
test('queryReturnsPredicatesForClass', () => {
const expected = selectPredicates(REAL_PREDICATES, { klass: 'META' });
assert.ok(expected.length > 0, 'fixture sanity: META class must exist in the real CONTEXT.md');
const r = queryContextPredicates(['--class', 'META']);
assert.equal(r.success, true, 'query --class META must succeed once the command is wired');
const parsed = JSON.parse(r.output);
const ids = (Array.isArray(parsed) ? parsed : parsed.predicates).map((p) => p.id).sort();
assert.deepEqual(ids, expected.map((p) => p.id).sort());
});
test('queryReturnsPredicatesForDottedPrefix', () => {
const expected = selectPredicates(REAL_PREDICATES, { prefix: 'RULESET.TESTS' });
assert.ok(expected.length > 0, 'fixture sanity: RULESET.TESTS.* must exist in the real CONTEXT.md');
const r = queryContextPredicates(['--prefix', 'RULESET.TESTS']);
assert.equal(r.success, true);
const parsed = JSON.parse(r.output);
const ids = (Array.isArray(parsed) ? parsed : parsed.predicates).map((p) => p.id).sort();
assert.deepEqual(ids, expected.map((p) => p.id).sort());
});
test('queryReturnsPredicatesForFreeTextContains', () => {
const expected = selectPredicates(REAL_PREDICATES, { contains: 'changeset' });
assert.ok(expected.length > 0, 'fixture sanity: "changeset" must appear in some real predicate id/value');
const r = queryContextPredicates(['--contains', 'changeset']);
assert.equal(r.success, true);
const parsed = JSON.parse(r.output);
const ids = (Array.isArray(parsed) ? parsed : parsed.predicates).map((p) => p.id).sort();
assert.deepEqual(ids, expected.map((p) => p.id).sort());
});
test('queryReportsZeroMatchesStructurally', () => {
const r = queryContextPredicates(['--class', 'ZZZ-NO-SUCH-CLASS-EXISTS']);
assert.equal(r.success, true, 'a no-match query is not itself a failure');
const parsed = JSON.parse(r.output);
assert.equal(parsed.matched, 0, 'a no-match result must be structurally distinguishable ({matched: 0}), not silent success or failure');
});
test('queryWithoutSelectorExitsWithUsage', () => {
const r = queryContextPredicatesJsonErrors([]);
assert.equal(r.success, false);
assert.notEqual(r.exitCode, 0);
assert.ok(r.parsedError, 'failure must be structured JSON under --json-errors');
assert.equal(r.parsedError.reason, ERROR_REASON.USAGE, 'missing selector must be a usage error, not an internal/unknown-command error');
});
test('queryWithEmptyClassExitsNonZero', () => {
const r = queryContextPredicatesJsonErrors(['--class', '']);
assert.equal(r.success, false);
assert.equal(r.parsedError && r.parsedError.reason, ERROR_REASON.USAGE);
});
test('queryWithWhitespaceOnlyClassExitsNonZero', () => {
const r = queryContextPredicatesJsonErrors(['--class', ' ']);
assert.equal(r.success, false);
assert.equal(r.parsedError && r.parsedError.reason, ERROR_REASON.USAGE);
});
test('queryWithDuplicateClassFlagsResolvesDeterministically', () => {
const first = queryContextPredicates(['--class', 'META', '--class', 'RULESET']);
const second = queryContextPredicates(['--class', 'META', '--class', 'RULESET']);
assert.equal(first.exitCode, second.exitCode, 'duplicate flags must resolve the same way on every invocation');
assert.equal(first.output, second.output, 'duplicate-flag resolution must be deterministic, not order-of-parse-dependent');
});
test('queryWithConflictingSelectorsAppliesDocumentedPrecedence', () => {
const r = queryContextPredicates(['--class', 'META', '--prefix', 'RULESET.TESTS']);
assert.doesNotMatch(r.error || '', STACK_FRAME_RE, 'conflicting selectors must resolve via documented precedence, never crash');
const repeat = queryContextPredicates(['--class', 'META', '--prefix', 'RULESET.TESTS']);
assert.equal(r.exitCode, repeat.exitCode, 'precedence resolution must be deterministic');
});
test('queryWithMalformedAssignmentExitsNonZero', () => {
const eq = queryContextPredicatesJsonErrors(['--class=']);
assert.equal(eq.success, false);
assert.equal(eq.parsedError && eq.parsedError.reason, ERROR_REASON.USAGE);
const doubleEq = queryContextPredicatesJsonErrors(['--class==A']);
assert.equal(doubleEq.success, false);
assert.equal(doubleEq.parsedError && doubleEq.parsedError.reason, ERROR_REASON.USAGE);
});
test('queryTreatsFlagLikeValueAsMissingValue', () => {
const r = queryContextPredicatesJsonErrors(['--class', '--weird']);
assert.equal(r.success, false, '--weird must not be silently consumed as the --class value');
assert.equal(r.parsedError && r.parsedError.reason, ERROR_REASON.USAGE);
});
// #2928 review finding C: a flag-shaped selector value (e.g. searching CONTEXT.md
// for the literal text "--since") was previously unmatchable — the space-separated
// form always reads a following `--...` token as a missing value, with no escape hatch.
describe('inline-assignment escape hatch for flag-shaped values (#2928 finding C)', () => {
test('contains=value form matches a flag-shaped selector value', () => {
const expected = selectPredicates(REAL_PREDICATES, { contains: '--since' });
assert.ok(expected.length > 0, 'fixture sanity: "--since" must appear in a real CONTEXT.md predicate value');
const r = queryContextPredicates(['--contains=--since']);
assert.equal(r.success, true, '--contains=--since must be accepted, not read as an unknown flag');
const parsed = JSON.parse(r.output);
const ids = parsed.predicates.map((p) => p.id).sort();
assert.deepEqual(ids, expected.map((p) => p.id).sort());
});
test('space-separated form still treats a flag-shaped value as missing (no escape without =)', () => {
const r = queryContextPredicatesJsonErrors(['--contains', '--since']);
assert.equal(r.success, false, '--contains --since (space-separated) must remain a usage error');
assert.equal(r.parsedError && r.parsedError.reason, ERROR_REASON.USAGE);
});
test('contains= with an empty value is still rejected', () => {
const r = queryContextPredicatesJsonErrors(['--contains=']);
assert.equal(r.success, false);
assert.equal(r.parsedError && r.parsedError.reason, ERROR_REASON.USAGE);
});
test('contains== (double-equals) is still rejected, not accepted as literal "=x"', () => {
const r = queryContextPredicatesJsonErrors(['--contains==x']);
assert.equal(r.success, false);
assert.equal(r.parsedError && r.parsedError.reason, ERROR_REASON.USAGE);
});
test('class=/prefix= inline-assignment form also works (not contains-only)', () => {
const expected = selectPredicates(REAL_PREDICATES, { klass: 'META' });
const r = queryContextPredicates(['--class=META']);
assert.equal(r.success, true);
const parsed = JSON.parse(r.output);
assert.deepEqual(
parsed.predicates.map((p) => p.id).sort(),
expected.map((p) => p.id).sort(),
);
});
});
test('queryRejectsPrototypePollutingSelectorKeys', () => {
for (const hostile of ['__proto__', 'constructor', 'prototype']) {
const r = queryContextPredicates(['--class', hostile]);
assert.equal(r.success, true, `${hostile} must be treated as an ordinary (non-matching) class name, not crash`);
const parsed = JSON.parse(r.output);
assert.equal(parsed.matched, 0);
assert.equal(Object.prototype.toString.call({}), '[object Object]', 'sanity: global Object.prototype must be untouched');
}
});
test('queryDoesNotInterpolateShellMetacharacters', () => {
const markerPath = path.join(ROOT, '.msd-test-shell-injection-marker-2928');
assert.equal(fs.existsSync(markerPath), false, 'fixture sanity: marker must not pre-exist');
const hostileValue = `; touch ${markerPath} #`;
const r = queryContextPredicates(['--contains', hostileValue]);
assert.equal(fs.existsSync(markerPath), false, 'a shell metacharacter payload must never be interpolated into a shell');
assert.doesNotMatch(r.error || '', STACK_FRAME_RE);
});
test('queryHandlesVeryLongSelectorValue', () => {
const longValue = 'x'.repeat(32 * 1024);
const r = queryContextPredicates(['--contains', longValue]);
assert.equal(typeof r.exitCode, 'number', 'a 32K selector value must not hang or crash the process');
assert.doesNotMatch(r.error || '', STACK_FRAME_RE);
});
test('queryHandlesUnicodeSelectorValue', () => {
const unicodeValue = String.fromCodePoint(0x9884, 0x6d4b, 0x5909, 0x6570, 0x1f525);
const r = queryContextPredicates(['--contains', unicodeValue]);
assert.equal(typeof r.exitCode, 'number', 'a Unicode selector value must not crash the process');
assert.doesNotMatch(r.error || '', STACK_FRAME_RE);
});
test('queryWorksFromSubdirectoryWithoutPlanningDir', (t) => {
const tmp = createTempDir('gci-query-subdir-');
t.after(() => cleanup(tmp));
const r = queryContextPredicates(['--class', 'META'], tmp);
assert.equal(r.success, true, 'the read-only selector must not require a .planning/ directory');
});
test('queryFailureOutputContainsNoStackTrace', () => {
const r = queryContextPredicates([]);
assert.equal(r.success, false);
assert.doesNotMatch(r.error || '', STACK_FRAME_RE, 'no bare stack trace in non-debug failure output');
});
});
// allow-test-rule: source-text-is-the-product #2928
// Wiring check for #2928's acceptance criterion — "an agent/orchestrator can invoke the
// selector through a wired `msd_run query` surface to assemble a brief". Before this, nothing
// in the repo called `context-predicates`; it was a stranded CLI. docs/contributor-standards.md
// §"Pre-work requirements" is the real, tested brief-assembly site governing how an AI-agent
// prompt must cite CONTEXT.md's META.RULE predicates — this asserts it routes through the
// selector instead of a grep-by-eye read, and that the selector actually returns the predicate
// set that site names.
describe('context-predicates wired into a real brief-assembly site (#2928)', () => {
const STANDARDS_DOC = path.join(ROOT, 'docs', 'contributor-standards.md');
const standardsText = fs.readFileSync(STANDARDS_DOC, 'utf8');
test('contributorStandardsRoutesPredicateCitationThroughTheQuerySelector', () => {
assert.ok(
standardsText.includes('node msd-tools.cjs query context-predicates --class'),
'docs/contributor-standards.md must invoke the context-predicates selector rather than instructing a grep-by-eye read'
);
assert.ok(
standardsText.includes('META.RULE.brief-must-cite-doc') && standardsText.includes('META.RULE.brief-no-paraphrase'),
'the wiring site must name the predicates it expects the selector to surface'
);
});
test('theSelectorActuallyReturnsThePredicatesTheWiringSiteNames', () => {
const r = queryContextPredicates(['--class', 'META']);
assert.equal(r.success, true);
const parsed = JSON.parse(r.output);
const ids = parsed.predicates.map((p) => p.id);
assert.ok(ids.includes('META.RULE.brief-must-cite-doc'));
assert.ok(ids.includes('META.RULE.brief-no-paraphrase'));
});
});