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.
This commit is contained in:
154
tests/msd-workflow.structure.test.cjs
Normal file
154
tests/msd-workflow.structure.test.cjs
Normal file
@@ -0,0 +1,154 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Structure / contract tests for the /msd smart-entry command + workflow.
|
||||
*
|
||||
* Spec: docs/superpowers/specs/2026-06-27-msd-smart-entry-design.md
|
||||
*
|
||||
* These assert the CONTRACT the markdown layer exposes — not implementation
|
||||
* logic — over the shipped artifacts: frontmatter completeness, size caps, the
|
||||
* TEXT_MODE fallback clause, the /msd:progress fallback, and that every command
|
||||
* the classifier can emit resolves to a real existing slash command file.
|
||||
*/
|
||||
|
||||
const { describe, test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const ROOT = path.join(__dirname, '..');
|
||||
const CMD = path.join(ROOT, 'commands', 'msd', 'next.md');
|
||||
const WF = path.join(ROOT, 'msd-core', 'workflows', 'smart-entry.md');
|
||||
const CMDS_DIR = path.join(ROOT, 'commands', 'msd');
|
||||
|
||||
// NEW_FILE_CAP from tests/workflow-size-budget.test.cjs (#1074).
|
||||
const NEW_FILE_CAP = 32768;
|
||||
|
||||
function read(p) {
|
||||
return fs.readFileSync(p, 'utf-8');
|
||||
}
|
||||
|
||||
/** Parse YAML-ish frontmatter (key: value lines between --- fences). */
|
||||
function parseFrontmatter(content) {
|
||||
const m = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
||||
if (!m) return {};
|
||||
const fm = {};
|
||||
for (const line of m[1].split(/\r?\n/)) {
|
||||
const mm = line.match(/^([a-z_-]+):\s*(.*)$/);
|
||||
if (mm) fm[mm[1]] = mm[2];
|
||||
}
|
||||
return fm;
|
||||
}
|
||||
|
||||
describe('/msd:next command file (commands/msd/next.md)', () => {
|
||||
const content = read(CMD);
|
||||
|
||||
test('exists', () => {
|
||||
assert.ok(fs.existsSync(CMD));
|
||||
});
|
||||
|
||||
test('frontmatter has name: msd:next (surfaces as /msd:next)', () => {
|
||||
const fm = parseFrontmatter(content);
|
||||
assert.equal(fm.name, 'msd:next');
|
||||
});
|
||||
|
||||
test('has NO requires field (must work pre-project)', () => {
|
||||
const fm = parseFrontmatter(content);
|
||||
assert.equal(fm.requires, undefined);
|
||||
});
|
||||
|
||||
test('allowed-tools includes AskUserQuestion, Bash, SlashCommand', () => {
|
||||
const fmBlock = content.match(/^---\r?\n[\s\S]*?\r?\n---/)[0];
|
||||
assert.match(fmBlock, /AskUserQuestion/);
|
||||
assert.match(fmBlock, /Bash/);
|
||||
assert.match(fmBlock, /SlashCommand/);
|
||||
});
|
||||
|
||||
test('references the smart-entry workflow', () => {
|
||||
assert.match(content, /workflows\/smart-entry\.md/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('/msd:next workflow (msd-core/workflows/smart-entry.md)', () => {
|
||||
const content = read(WF);
|
||||
|
||||
test('exists and is under the NEW_FILE_CAP (32 KiB)', () => {
|
||||
assert.ok(fs.existsSync(WF));
|
||||
const bytes = fs.statSync(WF).size;
|
||||
assert.ok(bytes < NEW_FILE_CAP, `workflow is ${bytes} bytes, must be < ${NEW_FILE_CAP}`);
|
||||
});
|
||||
|
||||
test('contains the TEXT_MODE fallback clause', () => {
|
||||
assert.match(content, /TEXT_MODE/);
|
||||
assert.match(content, /numbered list/i);
|
||||
});
|
||||
|
||||
test('contains the msd_run shim resolver block', () => {
|
||||
assert.match(content, /_MSD_SHIM_NAME/);
|
||||
assert.match(content, /msd_run\(\)/);
|
||||
});
|
||||
|
||||
test('runs smart-entry --json to detect', () => {
|
||||
assert.match(content, /smart-entry --json/);
|
||||
});
|
||||
|
||||
test('falls back to /msd:progress on detection failure (never strands)', () => {
|
||||
assert.match(content, /\/msd:progress/);
|
||||
assert.match(content, /unavailable/i);
|
||||
});
|
||||
|
||||
test('dispatches exactly one command then stops', () => {
|
||||
assert.match(content, /dispatch/i);
|
||||
// "stop" / "do not chain" guard language present
|
||||
assert.match(content, /(stop|do not chain|do not re-enter)/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe('smart-entry: every emitted command resolves to a real slash command', () => {
|
||||
// Drive the classifier across all situations and collect every command string
|
||||
// it can emit, then assert each resolves to commands/msd/<name>.md.
|
||||
const smartEntry = require('../msd-core/bin/lib/smart-entry.cjs');
|
||||
const { SITUATIONS, actionsFor } = smartEntry;
|
||||
|
||||
// A minimal signal shape sufficient for actionsFor (which only reads
|
||||
// current_phase for label text). actionsFor ignores other fields.
|
||||
const sampleSignals = {
|
||||
current_phase: 2,
|
||||
total_phases: 5,
|
||||
status: 'executing',
|
||||
progress: 60,
|
||||
has_planning: true,
|
||||
has_roadmap: true,
|
||||
git_dirty: false,
|
||||
git_unpushed: false,
|
||||
paused: false,
|
||||
blockers: [],
|
||||
has_git: true,
|
||||
verify_failed: false,
|
||||
stale_activity: false,
|
||||
};
|
||||
|
||||
const allCommands = new Set();
|
||||
for (const situation of SITUATIONS) {
|
||||
for (const a of actionsFor(situation, sampleSignals)) {
|
||||
allCommands.add(a.command);
|
||||
}
|
||||
}
|
||||
|
||||
test(`collected commands from all ${SITUATIONS.length} situations`, () => {
|
||||
assert.ok(allCommands.size >= 8, `expected a broad command set, got ${allCommands.size}`);
|
||||
});
|
||||
|
||||
for (const cmd of allCommands) {
|
||||
test(`command "${cmd}" resolves to a real commands/msd/*.md file`, () => {
|
||||
// Strip leading "/msd:" and any trailing flags, map to a file stem.
|
||||
const stem = cmd.replace(/^\/msd:/, '').split(/\s+/)[0];
|
||||
// Special-case: "progress --next" / "progress --do" still → progress.md.
|
||||
const file = path.join(CMDS_DIR, `${stem}.md`);
|
||||
assert.ok(
|
||||
fs.existsSync(file),
|
||||
`command "${cmd}" → expected file commands/msd/${stem}.md (not found)`,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user