#!/usr/bin/env node const assert = require("assert"); const fs = require("fs"); const path = require("path"); const repo = path.resolve(__dirname, ".."); if ( fs.existsSync(path.join(repo, "DISASMER_PUBLIC_TREE.json")) && !fs.existsSync(path.join(repo, "acceptance_criteria.md")) ) { console.log( "Acceptance doc contract smoke skipped: root acceptance markdown is filtered from this public tree" ); process.exit(0); } function read(relativePath) { return fs.readFileSync(path.join(repo, relativePath), "utf8"); } function criterionLines(source) { return source .split(/\r?\n/) .filter((line) => /^- \[[ x]\] \*\*/.test(line)); } function assertEveryCriterionHasStatus(source, name) { const lines = criterionLines(source); assert(lines.length > 0, `${name} must contain acceptance criteria`); for (const line of lines) { assert.match( line, /^- \[[ x]\] \*\*(Passed|Partial|Open|Postponed)(?: \([^)]+\))?:\*\*/, `${name} criterion lacks an explicit status prefix: ${line}` ); } } function assertNoOpenCriteria(source, name) { const open = criterionLines(source).filter((line) => /\*\*Open(?::| \()/.test(line)); assert.deepStrictEqual(open, [], `${name} still has Open criteria`); } const phase2 = read("acceptance_criteria_phase2.md"); const base = read("acceptance_criteria.md"); const cliFirst = read("cli_acceptance_criteria.md"); const website = read("website_mvp_inventory.md"); const docsSmoke = read("scripts/docs-smoke.js"); const releaseBlockerSmoke = read("scripts/release-blocker-smoke.js"); const publicAcceptance = read("scripts/acceptance-public.sh"); const privateAcceptance = read("scripts/acceptance-private.sh"); const cliFirstAcceptance = read("scripts/acceptance-cli-first.sh"); const publicSplit = read("scripts/verify-public-split.sh"); assert.match( phase2, /phase 2 superset of `acceptance_criteria\.md`/, "phase 2 criteria must declare that they are a superset of the base criteria" ); assert.match( phase2, /Existing `acceptance_criteria\.md` remains required unless it conflicts with this stricter release document; this document wins in conflicts/, "phase 2 criteria must keep base acceptance criteria required unless stricter phase 2 criteria conflict" ); assert.match( phase2, /- \[x\] \*\*Passed:\*\* Existing `acceptance_criteria\.md` remains required unless it conflicts with this stricter release document; this document wins in conflicts\./, "phase 2 cross-document requirement must be marked passed only when this guard is wired" ); for (const [source, name] of [ [base, "acceptance_criteria.md"], [phase2, "acceptance_criteria_phase2.md"], [cliFirst, "cli_acceptance_criteria.md"], ]) { assertEveryCriterionHasStatus(source, name); assertNoOpenCriteria(source, name); assert.match( source, /Design-document references to billing, paid plans, or plan flags are future metadata placeholders; they do not require MVP/, `${name} must keep billing/paid-plan placeholders outside MVP implementation scope` ); } assertEveryCriterionHasStatus(website, "website_mvp_inventory.md"); assert.match( website, /private hosted website addendum to `acceptance_criteria\.md`, `acceptance_criteria_phase2\.md`, and `cli_acceptance_criteria\.md`/, "website acceptance criteria must declare itself as the private hosted website addendum" ); assert.match( website, /barebones functional HTML with no CSS/, "website acceptance criteria must keep the MVP website barebones with no CSS" ); assert.match( website, /Billing is not part of the MVP[\s\S]*Design-document references to billing, paid plans, or plan flags are future metadata placeholders; they do not require MVP website routes/, "website acceptance criteria must keep billing and paid-plan placeholders outside MVP website scope" ); assert.match( website, /- \[ \] \*\*Open:\*\* The private hosted website acceptance gate exists and exercises the barebones website through `disasmer\.michelpaulissen\.com`/, "website acceptance criteria must leave the private website deployment gate explicit and open" ); for (const file of [ "MVP.md", "acceptance_criteria.md", "acceptance_criteria_phase2.md", "cli_acceptance_criteria.md", "website_mvp_inventory.md", ]) { assert( docsSmoke.includes(`"${file}"`), `docs smoke must include ${file} as user-facing acceptance context` ); } assert( releaseBlockerSmoke.includes('const phase2 = read("acceptance_criteria_phase2.md")'), "release-blocker smoke must read phase 2 acceptance criteria" ); assert( releaseBlockerSmoke.includes('const base = read("acceptance_criteria.md")'), "release-blocker smoke must read base acceptance criteria" ); for (const [source, name] of [ [base, "acceptance_criteria.md"], [phase2, "acceptance_criteria_phase2.md"], ]) { for (const [label, pattern] of [ ["MVP selected locals", /selected (?:top-level )?locals|selected real source locals/], ["MVP task args", /task arguments|task args/], ["MVP handle inspection", /Artifact.*SourceSnapshot.*Blob|Disasmer handles/], ["MVP stdout stderr", /stdout\/stderr/], ["MVP unavailable locals", /cannot be inspected|unavailable-local/], ["MVP required DAP surface", /initialize[\s\S]*launch.*attach[\s\S]*setBreakpoints[\s\S]*configurationDone[\s\S]*threads[\s\S]*stackTrace[\s\S]*scopes[\s\S]*variables[\s\S]*continue[\s\S]*pause/], ]) { assert.match(source, pattern, `${name} must include MVP debugging criterion: ${label}`); } } for (const [scriptName, script] of [ ["public acceptance", publicAcceptance], ["private acceptance", privateAcceptance], ["CLI-first acceptance", cliFirstAcceptance], ["public split", publicSplit], ]) { assert( script.includes("node scripts/acceptance-doc-contract-smoke.js"), `${scriptName} must run acceptance-doc-contract-smoke.js` ); } console.log("Acceptance doc contract smoke passed");