Files
msd-core/tests/cursor-reviewer.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

277 lines
10 KiB
JavaScript

// allow-test-rule: source-text-is-the-product
// Workflow .md / agent .md / command .md / reference .md files — their text
// IS what the runtime loads. Testing text content tests the deployed contract.
// Per CONTRIBUTING.md exception matrix.
/**
* Cursor CLI Reviewer Tests (#1960)
*
* Verifies that /msd-review includes Cursor CLI as a peer reviewer:
* - review.md workflow contains cursor detection, flag parsing, self-detection, invocation
* - commands/msd/review.md command file mentions --cursor flag
* - help.md lists --cursor in the /msd-review signature
* - docs/COMMANDS.md has --cursor flag row
* - docs/FEATURES.md has Cursor in the review section
* - i18n docs mirror the same content
* - REVIEWS.md template includes Cursor Review section
*/
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const ROOT = path.join(__dirname, '..');
describe('Cursor CLI reviewer in /msd-review (#1960)', () => {
// --- review.md workflow ---
describe('review.md workflow', () => {
const reviewPath = path.join(ROOT, 'msd-core', 'workflows', 'review.md');
let _content;
test('review.md exists', () => {
assert.ok(fs.existsSync(reviewPath), 'review.md should exist');
_content = fs.readFileSync(reviewPath, 'utf-8');
});
test('contains cursor CLI detection via command -v cursor-agent', () => {
const c = fs.readFileSync(reviewPath, 'utf-8');
assert.ok(
c.includes('command -v cursor-agent'),
'review.md should detect cursor CLI via "command -v cursor-agent" (not the cursor IDE launcher)'
);
});
test('contains --cursor flag parsing', () => {
const c = fs.readFileSync(reviewPath, 'utf-8');
assert.ok(
c.includes('--cursor'),
'review.md should parse --cursor flag'
);
});
test('contains CURSOR_SESSION_ID self-detection', () => {
const c = fs.readFileSync(reviewPath, 'utf-8');
assert.ok(
c.includes('CURSOR_SESSION_ID'),
'review.md should detect self-CLI via CURSOR_SESSION_ID env var'
);
});
// Phase 5b (#2799) moved the invocation out of review.md's bash and into the declared lane.
// These four assertions follow it: the descriptor and the resolved plan ARE the deployed
// contract now, and asserting on them is strictly stronger than matching fence text.
test('invocation uses the cursor-agent single binary, not two-token "cursor agent"', () => {
const { REVIEWER_LANES } = require('../msd-core/bin/lib/review-lane-descriptor.cjs');
const lane = REVIEWER_LANES.find((l) => l.slug === 'cursor');
assert.equal(lane.invoke.binary, 'cursor-agent');
assert.ok(lane.invoke.args.includes('-p'));
});
test('invocation includes --output-format text', () => {
const { REVIEWER_LANES } = require('../msd-core/bin/lib/review-lane-descriptor.cjs');
const lane = REVIEWER_LANES.find((l) => l.slug === 'cursor');
const i = lane.invoke.args.indexOf('--output-format');
assert.ok(i !== -1);
assert.equal(lane.invoke.args[i + 1], 'text');
});
test('the prompt is a file-path ARGUMENT, never piped on stdin', () => {
// Print mode takes the prompt as an argument, and a full plan set inline would approach the
// 32,767-char Windows execFileSync ceiling — hence the file reference.
const { REVIEWER_LANES } = require('../msd-core/bin/lib/review-lane-descriptor.cjs');
const { resolveLanePlan } = require('../msd-core/bin/lib/review-lane-invocation.cjs');
const lane = REVIEWER_LANES.find((l) => l.slug === 'cursor');
assert.equal(lane.invoke.promptChannel, 'argv-file-ref');
const r = resolveLanePlan({ lane, configGet: () => undefined, runDir: '/rd', repoRoot: '/repo' });
assert.equal(r.ok, true);
assert.equal(r.plan.stdin, null, 'nothing may be fed on stdin');
assert.ok(r.plan.argv[r.plan.argv.length - 1].includes('/rd/msd-review-prompt.md'));
});
test('does NOT use broken two-token "cursor agent " form', () => {
const c = fs.readFileSync(reviewPath, 'utf-8');
// Must not match "cursor agent " (cursor + space + agent + space)
// The hyphenated "cursor-agent" must NOT trip this check — the regex uses a space, not a hyphen.
assert.ok(
!/cursor agent /.test(c),
'review.md must NOT contain the broken two-token form "cursor agent " (use "cursor-agent" instead)'
);
});
test('does NOT pipe the prompt into a cursor command via stdin', () => {
const c = fs.readFileSync(reviewPath, 'utf-8');
// Must not match a pipe feeding into a cursor command (e.g. "| cursor" or "|cursor")
// "cursor-agent" (hyphenated) must NOT trip this — the regex anchors on "cursor" not followed by "-agent"
assert.ok(
!/\| *cursor(?!-agent)/.test(c),
'review.md must NOT pipe the prompt to a cursor command via stdin'
);
});
test('the lane declares its REVIEWS.md section', () => {
// The heading used to be a literal in review.md's write_reviews template. Phase 5b renders
// sections from each lane's declared `reviewsSection`, so THAT is the contract now — and
// uniqueness across lanes is enforced by the parity gate (ADR-2782 D8), which a hardcoded
// list never was.
const { REVIEWER_LANES } = require('../msd-core/bin/lib/review-lane-descriptor.cjs');
const lane = REVIEWER_LANES.find((l) => l.slug === 'cursor');
assert.equal(lane.reviewsSection, 'Cursor');
const sections = REVIEWER_LANES.map((l) => l.reviewsSection);
assert.equal(
sections.filter((x) => x === 'Cursor').length, 1,
'two lanes sharing a heading would silently merge their output in REVIEWS.md',
);
});
test('lists cursor in the reviewers frontmatter array', () => {
const c = fs.readFileSync(reviewPath, 'utf-8');
assert.ok(
/reviewers:.*cursor/.test(c),
'review.md should list cursor in the reviewers array'
);
});
});
// --- commands/msd/review.md ---
describe('commands/msd/review.md', () => {
const cmdPath = path.join(ROOT, 'commands', 'msd', 'review.md');
test('mentions --cursor flag', () => {
const c = fs.readFileSync(cmdPath, 'utf-8');
assert.ok(
c.includes('--cursor'),
'commands/msd/review.md should mention --cursor flag'
);
});
test('mentions Cursor in objective or context', () => {
const c = fs.readFileSync(cmdPath, 'utf-8');
assert.ok(
c.includes('Cursor'),
'commands/msd/review.md should mention Cursor'
);
});
});
// --- help.md ---
describe('help.md', () => {
// After #3039, help content moved into help/modes/full.md.
const helpPath = path.join(ROOT, 'msd-core', 'workflows', 'help', 'modes', 'full.md');
test('lists --cursor in /msd-review signature', () => {
const c = fs.readFileSync(helpPath, 'utf-8');
assert.ok(
c.includes('--cursor'),
'help.md should list --cursor in the /msd-review command signature'
);
});
});
// --- docs/COMMANDS.md ---
describe('docs/COMMANDS.md', () => {
const docsPath = path.join(ROOT, 'docs', 'COMMANDS.md');
test('has --cursor flag row', () => {
const c = fs.readFileSync(docsPath, 'utf-8');
assert.ok(
c.includes('--cursor'),
'docs/COMMANDS.md should have a --cursor flag row'
);
});
});
// --- docs/FEATURES.md ---
describe('docs/FEATURES.md', () => {
const featPath = path.join(ROOT, 'docs', 'FEATURES.md');
test('has --cursor in review command signature', () => {
const c = fs.readFileSync(featPath, 'utf-8');
assert.ok(
c.includes('--cursor'),
'docs/FEATURES.md should include --cursor in the review command signature'
);
});
test('mentions Cursor in the review purpose', () => {
const c = fs.readFileSync(featPath, 'utf-8');
assert.ok(
c.includes('Cursor'),
'docs/FEATURES.md should mention Cursor in the review section'
);
});
});
// --- i18n: ja-JP ---
describe('docs/ja-JP/COMMANDS.md', () => {
const jaPath = path.join(ROOT, 'docs', 'ja-JP', 'COMMANDS.md');
test('has --cursor flag row', () => {
const c = fs.readFileSync(jaPath, 'utf-8');
assert.ok(
c.includes('--cursor'),
'docs/ja-JP/COMMANDS.md should have a --cursor flag row'
);
});
});
describe('docs/ja-JP/FEATURES.md', () => {
const jaPath = path.join(ROOT, 'docs', 'ja-JP', 'FEATURES.md');
test('has --cursor in review command signature', () => {
const c = fs.readFileSync(jaPath, 'utf-8');
assert.ok(
c.includes('--cursor'),
'docs/ja-JP/FEATURES.md should include --cursor in the review command signature'
);
});
test('mentions Cursor in the review section', () => {
assert.ok(
/Cursor/i.test(fs.readFileSync(jaPath, 'utf-8')),
'docs/ja-JP/FEATURES.md should mention Cursor in the review section'
);
});
});
// --- i18n: ko-KR ---
describe('docs/ko-KR/COMMANDS.md', () => {
const koPath = path.join(ROOT, 'docs', 'ko-KR', 'COMMANDS.md');
test('has --cursor flag row', () => {
const c = fs.readFileSync(koPath, 'utf-8');
assert.ok(
c.includes('--cursor'),
'docs/ko-KR/COMMANDS.md should have a --cursor flag row'
);
});
});
describe('docs/ko-KR/FEATURES.md', () => {
const koPath = path.join(ROOT, 'docs', 'ko-KR', 'FEATURES.md');
test('has --cursor in review command signature', () => {
const c = fs.readFileSync(koPath, 'utf-8');
assert.ok(
c.includes('--cursor'),
'docs/ko-KR/FEATURES.md should include --cursor in the review command signature'
);
});
test('mentions Cursor in the review section', () => {
assert.ok(
/Cursor/i.test(fs.readFileSync(koPath, 'utf-8')),
'docs/ko-KR/FEATURES.md should mention Cursor in the review section'
);
});
});
});