Files
msd-core/tests/explore-command.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

536 lines
28 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.
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
describe('explore command', () => {
test('command file exists', () => {
const p = path.join(__dirname, '..', 'commands', 'msd', 'explore.md');
assert.ok(fs.existsSync(p), 'commands/msd/explore.md should exist');
});
test('command file has required frontmatter', () => {
const p = path.join(__dirname, '..', 'commands', 'msd', 'explore.md');
const content = fs.readFileSync(p, 'utf-8');
assert.ok(content.includes('name: msd:explore'), 'Command must have name frontmatter');
assert.ok(content.includes('description:'), 'Command must have description frontmatter');
assert.ok(content.includes('allowed-tools:'), 'Command must have allowed-tools frontmatter');
});
test('workflow file exists', () => {
const p = path.join(__dirname, '..', 'msd-core', 'workflows', 'explore.md');
assert.ok(fs.existsSync(p), 'workflows/explore.md should exist');
});
test('workflow references questioning.md and domain-probes.md', () => {
const p = path.join(__dirname, '..', 'msd-core', 'workflows', 'explore.md');
const content = fs.readFileSync(p, 'utf-8');
assert.ok(content.includes('questioning.md'), 'Workflow must reference questioning.md');
assert.ok(content.includes('domain-probes.md'), 'Workflow must reference domain-probes.md');
});
test('workflow documents all 6 output types', () => {
const p = path.join(__dirname, '..', 'msd-core', 'workflows', 'explore.md');
const content = fs.readFileSync(p, 'utf-8');
assert.ok(content.includes('Note'), 'Workflow must document Note output type');
assert.ok(content.includes('Todo'), 'Workflow must document Todo output type');
assert.ok(content.includes('Seed'), 'Workflow must document Seed output type');
assert.ok(content.includes('Research question'), 'Workflow must document Research question output type');
assert.ok(content.includes('Requirement'), 'Workflow must document Requirement output type');
assert.ok(content.includes('New phase') || content.includes('phase'), 'Workflow must document New phase output type');
});
test('workflow enforces one question at a time principle', () => {
const p = path.join(__dirname, '..', 'msd-core', 'workflows', 'explore.md');
const content = fs.readFileSync(p, 'utf-8');
assert.ok(content.includes('one question at a time'), 'Workflow must mention "one question at a time" principle');
});
test('workflow requires user confirmation before writing artifacts', () => {
const p = path.join(__dirname, '..', 'msd-core', 'workflows', 'explore.md');
const content = fs.readFileSync(p, 'utf-8');
assert.ok(
content.includes('explicit user selection') || content.includes('Never write artifacts without'),
'Workflow must require user confirmation before writing artifacts'
);
});
test('workflow respects commit_docs config', () => {
const p = path.join(__dirname, '..', 'msd-core', 'workflows', 'explore.md');
const content = fs.readFileSync(p, 'utf-8');
assert.ok(content.includes('commit_docs'), 'Workflow must respect commit_docs configuration');
});
test('command references the workflow via execution_context', () => {
const p = path.join(__dirname, '..', 'commands', 'msd', 'explore.md');
const content = fs.readFileSync(p, 'utf-8');
assert.ok(
content.includes('workflows/explore.md'),
'Command must reference workflows/explore.md in execution_context'
);
});
});
// Enhancement #2229 — three-way claim disposition (admit / refute / abstain) in the
// /msd-explore Step 3 research pass. The research pass is pure prompt orchestration (it
// spawns msd-phase-researcher and folds prose back), so the disposition contract lives in
// the workflow text itself — asserting the text asserts the deployed contract (the
// source-text-is-the-product exemption at the top of this file). This mirrors the #1154
// honest-verifier abstention PATTERN (never a silent pass; abstain-and-flag), not the
// verify-time probe-core code path (which sits on the verifier↔predicate rail, ADR-857).
describe('explore research-pass claim disposition (#2229)', () => {
const workflowPath = path.join(__dirname, '..', 'msd-core', 'workflows', 'explore.md');
const readWorkflow = () => fs.readFileSync(workflowPath, 'utf-8');
test('#2543 B1: ledger discipline is stated WITHIN the Step-3 disposition region, not merely somewhere in the file', () => {
// Replaces the deleted vacuous "abstained claims route to an unresolved ledger"
// test, which grepped the whole file for /unresolved/ and /ledger/ independently —
// it could not fail even if the sentence tying them together were deleted from
// Step 3 and moved somewhere unrelated. This is region-scoped AND falsifiable:
// the self-check below proves the anchor actually depends on the sentence it names.
const explore = readWorkflow();
const step3 = explore.indexOf('## Step 3');
const step4 = explore.indexOf('## Step 4');
assert.ok(step3 !== -1 && step4 !== -1 && step3 < step4, 'Steps 3 and 4 must exist in order');
const disposition = explore.slice(step3, step4);
const LEDGER_ANCHOR = 'put it in the **Unresolved ledger**, **never smoothed into the narrative**';
assert.ok(
disposition.includes(LEDGER_ANCHOR),
'the Step-3 region must state that an abstained claim is routed to the Unresolved ledger and never smoothed into prose'
);
// Falsifiability self-check: prove the anchor is unique to the ledger-routing
// sentence by stripping it and confirming the assertion above would then fail.
const stripped = disposition.split(LEDGER_ANCHOR).join('');
assert.ok(
!stripped.includes(LEDGER_ANCHOR),
'sanity check: removing the ledger-routing sentence must make the anchor disappear'
);
});
test('#2543 M1: the researcher agent is taught the disposition enum, so admit is reachable', () => {
// The admit arm only fires if the spawned researcher actually emits
// [admit/refute/abstain] tags. explore.md's spawn prompt asks for them, but
// msd-phase-researcher is a SHARED agent carrying its own [VERIFIED]/[CITED]/
// [ASSUMED] contract — if it never learned the disposition enum it follows its
// own template, every finding returns untagged, and the three-way disposition
// degenerates to all-abstain. Assert BOTH ends of the contract (the spawn
// prompt asks for the tags AND the agent defines them), not a bare
// word-presence grep — the two vacuous checks that did that were deleted (#2543 B1).
const explore = readWorkflow();
const spawn = explore.slice(
explore.indexOf('Agent('),
explore.indexOf('subagent_type="msd-phase-researcher"'),
);
for (const tag of ['[admit:', '[refute:', '[abstain:']) {
assert.ok(spawn.includes(tag), `the spawn prompt must ask the researcher for ${tag} …] tags`);
}
const agent = fs.readFileSync(
path.join(__dirname, '..', 'agents', 'msd-phase-researcher.md'),
'utf-8',
);
assert.match(
agent,
/claim-disposition mode/i,
'msd-phase-researcher.md must document the claim-disposition mode; without it the shared agent ' +
'follows its own [VERIFIED]/[CITED]/[ASSUMED] template and the admit arm never fires (#2543 M1)',
);
for (const tag of ['[admit:', '[refute:', '[abstain:']) {
assert.ok(agent.includes(tag), `the researcher agent must define the ${tag} …] disposition tag`);
}
});
test('#2543 M4: the ledger-reason enum is identical across every copy in explore.md, CONTEXT.md, and agents/msd-phase-researcher.md', () => {
// The prior extractor used text.match() with no /g — it only ever inspected the
// FIRST copy, so a second explore.md copy (or the CONTEXT.md copy) could drift
// unnoticed. Use matchAll so every occurrence is checked, not just one.
//
// A fourth copy lives in agents/msd-phase-researcher.md (~line 190) and was NOT
// scanned here, even though this exact class of defect — an enum duplicated
// across surfaces with no coupling between them — is why this test exists in the
// first place: a third copy (in CONTEXT.md) silently drifted earlier in this
// PR's history precisely because nothing compared it. A parity test that only
// looks at some of the copies gives false confidence; it must cover every known
// copy or it isn't proving what its name claims. The agent-file copy is a bare
// backtick-delimited list with no enclosing `{}`/`()`, unlike the other three, so
// the extractor below matches on the 5-value chain itself rather than requiring
// a bracket pair around it.
const ENUM_RE =
/`?unverifiable`?(?: \| `?[^`|{}()]+`?){3} \| `?untagged — disposition not reported`?/g;
const extractAll = (text, label) => {
const normalized = text.replace(/\s+/g, ' ');
const matches = [...normalized.matchAll(ENUM_RE)].map((m) =>
m[0].replace(/`/g, '').split('|').map((s) => s.trim()),
);
assert.ok(matches.length > 0, `${label} must carry at least one copy of the 5-value ledger-reason enum`);
return matches;
};
const exploreCopies = extractAll(readWorkflow(), 'explore.md');
// Measured: explore.md currently carries 2 copies (the Step-3 share template and
// the Step-4 carry-forward instruction), CONTEXT.md carries a 3rd, and
// agents/msd-phase-researcher.md carries a 4th. Assert >=2 in explore.md so a
// future removal of either copy is caught, without hardcoding a count this test
// can't independently justify.
assert.ok(
exploreCopies.length >= 2,
`explore.md must carry at least 2 copies of the 5-value ledger-reason enum; found ${exploreCopies.length}`,
);
for (const copy of exploreCopies) {
assert.deepStrictEqual(
copy,
exploreCopies[0],
'every ledger-enum copy within explore.md must be byte-identical to every other',
);
}
const contextCopies = extractAll(
fs.readFileSync(path.join(__dirname, '..', 'CONTEXT.md'), 'utf-8'),
'CONTEXT.md',
);
assert.deepStrictEqual(
contextCopies[0],
exploreCopies[0],
'the Unresolved-Ledger abstention reasons in explore.md and CONTEXT.md have drifted; keep them identical (#2543 M4)',
);
const agentCopies = extractAll(
fs.readFileSync(path.join(__dirname, '..', 'agents', 'msd-phase-researcher.md'), 'utf-8'),
'agents/msd-phase-researcher.md',
);
assert.deepStrictEqual(
agentCopies[0],
exploreCopies[0],
'the ledger-reason enum in agents/msd-phase-researcher.md has drifted from explore.md/CONTEXT.md; keep all copies identical (#2543 M4)',
);
// Total-copy floor across all three files so deleting a copy anywhere is also
// caught, not just a value-level drift within a surviving copy.
const totalCopies = exploreCopies.length + contextCopies.length + agentCopies.length;
assert.ok(
totalCopies >= 4,
`expected at least 4 total ledger-reason enum copies across explore.md, CONTEXT.md, and agents/msd-phase-researcher.md; found ${totalCopies}`,
);
});
test('conflict-abstention guard: a source-vs-prior conflict routes to the ledger', () => {
const content = readWorkflow().toLowerCase();
// Require the disposition-specific phrasing, not an incidental "conflicting edits" mention
// elsewhere in the workflow — this must be load-bearing for the abstain arm.
assert.ok(
content.includes('source-vs-prior') || content.includes('conflict-abstention') ||
/conflict[^.]*\bledger\b|\bledger\b[^.]*conflict/.test(content),
'the abstain arm must cover a source-vs-prior conflict (conflict-abstention), routing to the ledger — not a silent pick-a-side'
);
});
// The tier floor is only real if the orchestrator can OBSERVE its tier. Asserting
// that the guard's prose exists is the vacuous version of this test — it passed
// while the Agent() spawn bound no model and no profile, so nothing could ever
// evaluate "am I on the lowest tier?". These assert the mechanism instead.
test('tier-floor guard: the workflow RESOLVES the researcher tier, not just describes a floor', () => {
const content = readWorkflow();
assert.match(
content,
/resolve-model\s+msd-phase-researcher\s+--pick\s+tier/,
'the tier floor needs the resolved tier as its trigger; without a ' +
'`resolve-model … --pick tier` binding the guard has no operative input'
);
assert.match(
content,
/resolve-model\s+msd-phase-researcher\s+--pick\s+model/,
'the spawn must bind the researcher model per model-profile-resolution.md'
);
});
test('tier-floor guard: the Agent() spawn passes the bound model', () => {
const content = readWorkflow();
const spawn = content.slice(content.indexOf('Agent('), content.indexOf('subagent_type="msd-phase-researcher"'));
assert.match(
spawn + content.slice(content.indexOf('subagent_type="msd-phase-researcher"'), content.indexOf('subagent_type="msd-phase-researcher"') + 200),
/model="\{RESEARCHER_MODEL\}"/,
'omitting model= makes the agent inherit the orchestrator model rather than ' +
'the catalog-resolved tier, which is what left the floor unenforceable'
);
assert.match(
content,
/omit `model=` entirely when `RESEARCHER_MODEL` is `inherit` or empty/i,
'#2517 requires the omit-on-inherit/empty rule to ride with any model= binding'
);
});
// Keying the floor on the RESOLVED TIER (not the model id or a profile config key)
// is the design this PR moved to. Slice the Tier-floor bullet itself rather than
// searching the whole document: a whole-file proximity match is satisfied by ANY
// sentence that merely mentions RESEARCHER_TIER near "haiku", including a purely
// descriptive one sitting beside a floor that keys on something else. Asserting on
// the guard's own text is what makes this a barrier instead of a word-search.
//
// Hardened per #2543 3.1: anchor on the literal bullet marker (not a position),
// and self-verify the slice is non-empty and actually names the floor, so a decoy
// bullet inserted ABOVE the real one cannot silently change what gets sliced.
const tierFloorClause = (content) => {
const marker = '- **Tier floor**';
const start = content.indexOf(marker);
assert.notStrictEqual(start, -1, 'the Tier floor bullet must exist');
const rest = content.slice(start + 1);
const end = rest.indexOf('\n- **');
const clause = (end === -1 ? rest : rest.slice(0, end)).replace(/\s+/g, ' ');
assert.ok(clause.length > 0, 'the sliced Tier-floor clause must be non-empty');
assert.ok(clause.includes('Tier floor'), 'the sliced clause must actually contain the floor marker');
return clause;
};
test('tier-floor guard: the clause slicer is anchored, not positional', () => {
const original = readWorkflow();
const insertionPoint = original.indexOf('- **Tier floor**');
assert.notStrictEqual(insertionPoint, -1, 'the Tier floor bullet must exist in the real file');
const decoy = '- **Something else** — an unrelated guard bullet that must not leak into the slice.\n';
// Prepend a decoy bullet directly above the real one, on an in-memory COPY only.
const withDecoy = original.slice(0, insertionPoint) + decoy + original.slice(insertionPoint);
const clause = tierFloorClause(withDecoy);
assert.ok(clause.includes('Tier floor'), 'the slicer must still find the real Tier-floor clause with a decoy bullet prepended above it');
assert.ok(!clause.includes('Something else'), 'the slicer must not swallow the decoy bullet into the Tier-floor clause');
});
test('tier-floor guard: the floor keys on RESEARCHER_TIER and names haiku as the budget tier', () => {
const clause = tierFloorClause(readWorkflow());
assert.match(clause, /\bRESEARCHER_TIER\b/,
'the floor must reference RESEARCHER_TIER — the single signal now, replacing RESEARCHER_PROFILE');
assert.match(clause, /RESEARCHER_TIER[^.]*\bhaiku\b/,
'the floor must name `haiku` as the budget tier for msd-phase-researcher');
});
test('tier-floor guard: an unreadable tier (unknown/inherit/empty) is also floored', () => {
const clause = tierFloorClause(readWorkflow());
assert.match(clause, /\bunknown\b/i, 'the floor must cover an `unknown` tier');
assert.match(clause, /\binherit\b/i, 'the floor must cover an `inherit` tier');
assert.match(clause, /\bempty\b/i, 'the floor must cover an empty (unreadable) tier');
});
test('tier-floor guard: the floor SUPPRESSES an admit — polarity, not just vocabulary', () => {
const clause = tierFloorClause(readWorkflow());
// Without this, a clause saying "present every would-be admit as an admit, unchanged;
// do NOT suppress merely because RESEARCHER_TIER names a haiku-tier model" passes
// every keyword check above. Verified: that exact inversion passed the prior test 20/20.
assert.match(clause, /would-be \*\*admit\*\* as an \*\*abstain\*\*/,
'the floor must state that a would-be admit is presented as an abstain; a clause ' +
'that merely NAMES admit and abstain does not establish which way it converts');
assert.doesNotMatch(clause, /\bdo not suppress\b|as an \*\*admit\*\*, unchanged/i,
'an inverted floor must fail this test');
});
// The disclosure is the only thing standing between a user and an unfloored cheap
// model in the model_profile_overrides escape (a tier's model repointed at another
// tier's model, e.g. codex's `opus` tier repointed at codex's own `haiku`-tier model
// id — measured: reports tier "opus" while the model that actually answers is
// "gpt-5.6-luna"). It is load-bearing text, not commentary — assert BOTH residual
// cases are named, and that the count reads "two cases" so shrinking the disclosure
// without shrinking the residual fails this test.
test('tier-floor guard: the disclosed residual names BOTH escape cases, not just the model_overrides one', () => {
const clause = tierFloorClause(readWorkflow());
assert.match(clause, /\bmodel_overrides\b/,
'the disclosed residual must still name the model_overrides pin-to-raw-id case');
assert.match(clause, /\bmodel_profile_overrides\b/,
'the disclosed residual must also name the model_profile_overrides tier-repointing case');
assert.match(clause, /two cases/i,
'the residual must be counted as "two cases", not "one case" — shrinking the count ' +
'without shrinking the residual must fail this test');
assert.doesNotMatch(clause, /one case remains/i,
'the stale "one case remains" heading must not survive alongside the second disclosed case');
});
test('tier-floor guard: the floor suppresses only unearned confidence, sparing refute/abstain', () => {
const content = readWorkflow();
assert.match(
content,
/`refute` and `abstain` are unaffected/,
'the floor suppresses unearned confidence only — it must not also suppress corrections'
);
});
test('refute and abstain are distinguishable by a stated decision procedure', () => {
// Whitespace-collapsed: these are multi-word prose claims and markdown wraps
// lines, so a literal-space regex would break the moment a paragraph re-flows.
const content = readWorkflow().toLowerCase().replace(/\s+/g, ' ');
assert.ok(
content.includes('authoritative'),
'refute vs abstain needs a discriminator; source authority for the claim is it'
);
assert.ok(
/strong prior[^.]*never authoritative|never authoritative[^.]*prior/.test(content),
'a "strong prior" must be stated as never authoritative alone, or it can be ' +
'read as grounds for a refute'
);
});
test('spawn prompt: the refute arm requires a source AUTHORITATIVE for the claim, not merely any contradiction', () => {
// This is the assertion that would have caught the blocker: a spawn prompt whose
// refute arm reads "a source contradicts it" (no authority qualifier) lets any
// disagreeing source — wrong version, adjacent subject, secondary/derivative —
// trigger a refute instead of an abstain.
const explore = readWorkflow();
const spawn = explore.slice(explore.indexOf('Agent('), explore.indexOf('subagent_type="msd-phase-researcher"'));
assert.match(
spawn,
/\[refute:[^\]]*\]\s*\([^)]*authoritative[^)]*\)/i,
'the spawn prompt\'s refute arm must require the contradicting source be AUTHORITATIVE for the claim'
);
});
test('#2543 B2: the research pass reaches a msd_run bootstrapped ahead of Step 3', () => {
// The tier floor abstains EVERY claim when both resolve-model probes come back
// empty because msd_run is undefined. The launcher preamble now lives once,
// unconditionally, at the end of Step 1 (not re-inlined per bash block) — so this
// only needs to confirm the bootstrap precedes the probe, not that it is duplicated
// into the Step-3 fence.
const explore = readWorkflow();
const preamble = explore.indexOf('_MSD_SHIM_NAME="msd-tools.cjs"');
const probe = explore.indexOf('resolve-model msd-phase-researcher --pick tier');
assert.notStrictEqual(preamble, -1, 'the msd_run bootstrap preamble must exist in the file');
assert.notStrictEqual(probe, -1, 'the research pass must call resolve-model to arm the tier floor');
assert.ok(
preamble < probe,
'msd_run must be bootstrapped before the tier-floor probe runs, otherwise both probes ' +
'return empty and the tier floor abstains every claim (#2543 B2)'
);
});
test('#2543 B3: the crystallize step carries the disposition into durable artifacts', () => {
// An abstained claim must not be laundered into a flat Note/Requirement/phase.
// Assert Steps 4-5 (the durable-write surface) forbid crystallizing an
// unresolved-ledger claim as a flat assertion and mandate carrying the
// disposition — keyed on that region, not a generic earlier mention.
const explore = readWorkflow();
const step4 = explore.indexOf('## Step 4');
const step6 = explore.indexOf('## Step 6');
assert.ok(step4 !== -1 && step6 !== -1 && step4 < step6, 'Steps 4 and 6 must exist in order');
const crystallize = explore.slice(step4, step6).toLowerCase();
assert.ok(
/unresolved[^.]{0,60}never[^.]{0,60}crystalliz/.test(crystallize),
'Steps 4-5 must forbid crystallizing an unresolved-ledger claim as a flat assertion (#2543 B3)',
);
assert.ok(
/carry[^.]{0,60}disposition/.test(crystallize),
'Steps 4-5 must carry the research disposition into the durable artifact (#2543 B3)',
);
});
test('Step 4 sink fences untrusted research text before writing it into durable artifacts', () => {
const explore = readWorkflow();
const step4 = explore.indexOf('## Step 4');
const step6 = explore.indexOf('## Step 6');
assert.ok(step4 !== -1 && step6 !== -1 && step4 < step6, 'Steps 4 and 6 must exist in order');
const region = explore.slice(step4, step6);
assert.match(
region,
/untrusted-input-boundary/,
'Step 4 must reference @msd-core/references/untrusted-input-boundary.md when quoting research text into a durable file'
);
assert.match(
region,
/fresh random|DATA_/i,
'Step 4 must require a fresh/random per-wrap delimiter (not a fixed marker) when fencing quoted research text'
);
});
test('exactly two guards are documented; "Three guards" no longer appears', () => {
const content = readWorkflow();
assert.match(content, /Two guards ride with it:/, 'the guard-list intro must read "Two guards ride with it:"');
assert.doesNotMatch(
content,
/Three guards/,
'"Three guards" must not appear — the untagged-findings rule moved out of the guard list into its own paragraph'
);
const start = content.indexOf('Two guards ride with it:');
const end = content.indexOf('**Untagged findings.**', start);
assert.ok(start !== -1 && end !== -1 && start < end, 'the guard list and the untagged-findings paragraph must both exist, in order');
const guardRegion = content.slice(start, end);
// Only TOP-LEVEL bullets (no leading indent) count as guards; the Tier-floor
// bullet's own nested ` - ` sub-bullets must not be double-counted.
const topLevelBullets = guardRegion.match(/^- \*\*/gm) || [];
assert.strictEqual(topLevelBullets.length, 2, `expected exactly 2 top-level guard bullets, found ${topLevelBullets.length}`);
});
test('the launcher preamble is bootstrapped once, unconditionally, ahead of Step 3', () => {
const explore = readWorkflow();
const marker = '_MSD_SHIM_NAME="msd-tools.cjs"';
const firstMarker = explore.indexOf(marker);
const lastMarker = explore.lastIndexOf(marker);
assert.notStrictEqual(firstMarker, -1, 'the canonical preamble marker must exist');
assert.strictEqual(firstMarker, lastMarker, 'the preamble must be inlined exactly once in the file');
const firstMsdRun = explore.indexOf('msd_run');
assert.notStrictEqual(firstMsdRun, -1, 'msd_run must be used somewhere in the file');
assert.ok(
firstMarker < firstMsdRun,
'the preamble that DEFINES msd_run must appear before the first USE of msd_run anywhere in the file'
);
const step3 = explore.indexOf('## Step 3');
assert.notStrictEqual(step3, -1, 'Step 3 heading must exist');
// Must sit in Step 1, not inside Step 3's optional research-offer branch:
// declining the research offer must not leave Step 5's commit call unbootstrapped,
// since msd_run is only ever defined at this one call site.
assert.ok(
firstMarker < step3,
'the launcher preamble must be bootstrapped before Step 3, not gated behind the optional research offer'
);
});
});
describe('explore seeds: the plant-seed contract (#4648)', () => {
const workflowPath = path.join(__dirname, '..', 'msd-core', 'workflows', 'explore.md');
const workflow = () => fs.readFileSync(workflowPath, 'utf-8');
test('step 5 delegates seeds to the plant-seed workflow instead of hand-writing a file', () => {
// allow-test-rule: source-text-is-the-product (#4648) — explore.md text is the deployed contract
const content = workflow();
assert.ok(
content.includes('/msd:capture --seed'),
'explore step 5 must delegate seeds via /msd:capture --seed — the only command surface routing to the plant-seed workflow (#4648)',
);
assert.ok(
!content.includes('trigger_condition'),
'the divergent trigger_condition field name must be gone — readers read trigger_when (#4648)',
);
assert.ok(
!content.includes('planted_date'),
'the divergent planted_date field name must be gone — plant-seed writes planted/planted_during',
);
assert.ok(
!content.includes('Create `.planning/seeds/{slug}.md`'),
'the hand-written seeds/{slug}.md shape must be gone — no reader selects files without the SEED- prefix',
);
});
test('the delegation names why: visibility to every seed reader (#4648)', () => {
// allow-test-rule: source-text-is-the-product (#4648) — explore.md text is the deployed contract
const content = workflow();
for (const reader of ['list-seeds', 'audit-open', 'new-milestone']) {
assert.ok(
content.includes(reader),
`the seed instruction must name ${reader} — the readers the divergent shape was invisible to`,
);
}
});
test('the plant-seed workflow still defines the canonical contract the delegation relies on', () => {
const plantSeed = fs.readFileSync(
path.join(__dirname, '..', 'msd-core', 'workflows', 'plant-seed.md'),
'utf-8',
);
assert.ok(plantSeed.includes('SEED-'), 'plant-seed mints SEED- ids');
assert.match(plantSeed, /status: dormant/, 'plant-seed writes status: dormant');
assert.ok(plantSeed.includes('trigger_when'), 'plant-seed writes trigger_when');
});
});