Files
msd-core/tests/lint-docs-command-form.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

229 lines
8.7 KiB
JavaScript

'use strict';
// docs-guard-exempt: this file only WRITES synthetic 'docs/...' fixtures into
// a throwaway temp git repo (writeFile()) to exercise scripts/lint-docs-command-form.cjs's
// own behavior — it never reads real shipped docs/ content.
/**
* TDD tests for scripts/lint-docs-command-form.cjs (#2903).
*
* Uses spawnSync to invoke the guard script against a temporary git repo so
* we can inject fixtures without touching the real repo. Mirrors the fixture
* pattern in tests/lint-legacy-dir-name.test.cjs.
*/
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { runNode } = require('./helpers/process-seam.cjs');
const { gitOrThrow } = require('./helpers/git-fixture.cjs');
const { GENERATOR_SCRIPT_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
const GUARD_SCRIPT = path.resolve(__dirname, '..', 'scripts', 'lint-docs-command-form.cjs');
function createTempRepo() {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-lint-docs-command-form-test-'));
gitOrThrow(['init', '--initial-branch=main'], { cwd: dir });
gitOrThrow(['config', 'user.email', 'test@example.com'], { cwd: dir });
gitOrThrow(['config', 'user.name', 'Test'], { cwd: dir });
// Roster source: the guard reads commands/msd/*.md filenames as valid
// command names, regardless of tracked/staged status.
writeFile(dir, 'commands/msd/plan-phase.md', '# plan-phase\n');
return dir;
}
function writeFile(dir, relPath, content) {
const fullPath = path.join(dir, relPath);
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
fs.writeFileSync(fullPath, content, 'utf8');
}
function gitAdd(dir, relPath) {
gitOrThrow(['add', relPath], { cwd: dir });
}
function cleanup(dir) {
// eslint-disable-next-line local/no-raw-rmsync-in-tests -- local cleanup in lint test; no helpers import available
fs.rmSync(dir, { recursive: true, force: true });
}
/**
* Returns an object shaped like the raw spawnSync() result (status/stdout/
* stderr) because every call site in this file was written against that
* shape; the seam itself returns exitCode, not status, so it is mapped here.
* 30_000ms: matches the sibling-suite default for guard scripts that shell
* out to git (the guard runs `git ls-files` over the fixture repo), which
* is comparable workload to this script's own `git ls-files` invocation.
*/
function runGuard(cwd) {
const r = runNode([GUARD_SCRIPT], {
cwd,
env: { ...process.env, MSD_LINT_DOCS_COMMAND_FORM_REPO_ROOT: cwd },
timeoutMs: GENERATOR_SCRIPT_TIMEOUT_MS,
});
return { status: r.exitCode, stdout: r.stdout, stderr: r.stderr };
}
describe('lint-docs-command-form — colon slash form flagged', () => {
test('exits non-zero and names the file when docs contain /msd:<cmd>', () => {
const dir = createTempRepo();
try {
writeFile(dir, 'docs/how-to/example.md', 'Run `/msd:plan-phase` to start.\n');
gitAdd(dir, 'commands/msd/plan-phase.md');
gitAdd(dir, 'docs/how-to/example.md');
const result = runGuard(dir);
assert.notEqual(result.status, 0, `expected non-zero exit, got ${result.status}; stdout: ${result.stdout}`);
assert.ok(result.stderr.includes('docs/how-to/example.md'), `stderr should name the file: ${result.stderr}`);
} finally {
cleanup(dir);
}
});
});
describe('lint-docs-command-form — plugin namespace permitted', () => {
test('exits 0 when docs contain /msd-core:<cmd>', () => {
const dir = createTempRepo();
try {
writeFile(dir, 'docs/how-to/example.md', 'Run `/msd-core:plan-phase` to start.\n');
gitAdd(dir, 'commands/msd/plan-phase.md');
gitAdd(dir, 'docs/how-to/example.md');
const result = runGuard(dir);
assert.equal(result.status, 0, `expected exit 0 for /msd-core:, got ${result.status}; stderr: ${result.stderr}`);
} finally {
cleanup(dir);
}
});
});
describe('lint-docs-command-form — docs/adr exempt', () => {
test('exits 0 for a fixture under docs/adr/', () => {
const dir = createTempRepo();
try {
writeFile(dir, 'docs/adr/999-example.md', 'Historically we typed `/msd:plan-phase`.\n');
gitAdd(dir, 'commands/msd/plan-phase.md');
gitAdd(dir, 'docs/adr/999-example.md');
const result = runGuard(dir);
assert.equal(result.status, 0, `expected exit 0 under docs/adr/, got ${result.status}; stderr: ${result.stderr}`);
} finally {
cleanup(dir);
}
});
});
describe('lint-docs-command-form — source trees never checked', () => {
test('exits 0 for a fixture under msd-core/workflows/ (colon form is correct there)', () => {
const dir = createTempRepo();
try {
writeFile(dir, 'msd-core/workflows/example.md', 'Dispatch `/msd:plan-phase`.\n');
gitAdd(dir, 'commands/msd/plan-phase.md');
gitAdd(dir, 'msd-core/workflows/example.md');
const result = runGuard(dir);
assert.equal(result.status, 0, `expected exit 0 under msd-core/workflows/, got ${result.status}; stderr: ${result.stderr}`);
} finally {
cleanup(dir);
}
});
});
describe('lint-docs-command-form — bare colon form flagged', () => {
test('exits non-zero when docs contain bare msd:<cmd>', () => {
const dir = createTempRepo();
try {
writeFile(dir, 'docs/how-to/example.md', 'The command is msd:plan-phase.\n');
gitAdd(dir, 'commands/msd/plan-phase.md');
gitAdd(dir, 'docs/how-to/example.md');
const result = runGuard(dir);
assert.notEqual(result.status, 0, `expected non-zero exit, got ${result.status}; stdout: ${result.stdout}`);
assert.ok(result.stderr.includes('docs/how-to/example.md'), `stderr should name the file: ${result.stderr}`);
} finally {
cleanup(dir);
}
});
});
describe('lint-docs-command-form — name: frontmatter key citation exempt', () => {
test('exits 0 when docs quote `name: msd:next` as a source frontmatter citation', () => {
const dir = createTempRepo();
try {
writeFile(dir, 'commands/msd/next.md', '---\nname: msd:next\n---\n');
writeFile(
dir,
'docs/how-to/example.md',
'Frontmatter:\n- `name: msd:next` (surfaces as `/msd-next`)\n',
);
gitAdd(dir, 'commands/msd/next.md');
gitAdd(dir, 'docs/how-to/example.md');
const result = runGuard(dir);
assert.equal(
result.status,
0,
`expected exit 0 for a name: frontmatter citation, got ${result.status}; stderr: ${result.stderr}`,
);
assert.ok(result.stdout.includes('0 violations'), `stdout: ${result.stdout}`);
} finally {
cleanup(dir);
}
});
});
describe('lint-docs-command-form — name: exemption is narrow, not a blanket hole', () => {
test('exits non-zero when bare msd:next appears without a preceding name: key', () => {
const dir = createTempRepo();
try {
writeFile(dir, 'commands/msd/next.md', '---\nname: msd:next\n---\n');
writeFile(dir, 'docs/how-to/example.md', 'Just type msd:next to run it.\n');
gitAdd(dir, 'commands/msd/next.md');
gitAdd(dir, 'docs/how-to/example.md');
const result = runGuard(dir);
assert.notEqual(result.status, 0, `expected non-zero exit, got ${result.status}; stdout: ${result.stdout}`);
assert.ok(result.stderr.includes('docs/how-to/example.md'), `stderr should name the file: ${result.stderr}`);
} finally {
cleanup(dir);
}
});
});
describe('lint-docs-command-form — case-insensitive detection', () => {
test('exits non-zero when docs contain /MSD:next (mixed case)', () => {
const dir = createTempRepo();
try {
writeFile(dir, 'commands/msd/next.md', '---\nname: msd:next\n---\n');
writeFile(dir, 'docs/how-to/example.md', 'Run `/MSD:next` to start.\n');
gitAdd(dir, 'commands/msd/next.md');
gitAdd(dir, 'docs/how-to/example.md');
const result = runGuard(dir);
assert.notEqual(result.status, 0, `expected non-zero exit, got ${result.status}; stdout: ${result.stdout}`);
assert.ok(result.stderr.includes('docs/how-to/example.md'), `stderr should name the file: ${result.stderr}`);
} finally {
cleanup(dir);
}
});
});
describe('lint-docs-command-form — clean docs tree', () => {
test('exits 0 on a clean fixture repo with no colon-form commands', () => {
const dir = createTempRepo();
try {
writeFile(dir, 'docs/how-to/example.md', 'Run `/msd-plan-phase` to start.\n');
gitAdd(dir, 'commands/msd/plan-phase.md');
gitAdd(dir, 'docs/how-to/example.md');
const result = runGuard(dir);
assert.equal(result.status, 0, `expected exit 0, got ${result.status}; stderr: ${result.stderr}`);
assert.ok(result.stdout.includes('0 violations'), `stdout: ${result.stdout}`);
} finally {
cleanup(dir);
}
});
});