diff --git a/test/ship-control-flow.test.ts b/test/ship-control-flow.test.ts index 5c5350197..129a0ce63 100644 --- a/test/ship-control-flow.test.ts +++ b/test/ship-control-flow.test.ts @@ -22,49 +22,40 @@ const gate = compact(entry.slice(entry.indexOf('## Step 16:'), entry.indexOf('## describe('ship source controller', () => { test('documentation freshness covers selected code paths as well as release metadata', () => { const docs = compact(read('ship/sections/documentation.md.tmpl')); - expect(docs).toContain('hashes of the selected release paths, generated outputs and docs/templates'); + expect(docs).toContain('selected release paths, generated outputs and docs/templates'); const freshness = gate.slice(gate.indexOf('### 3.'), gate.indexOf('### 4.')); expect(freshness).toContain('selected release paths, generated outputs and docs/templates'); - expect(freshness).toContain("A prior invocation's audit or risk decision never qualifies"); - expect(freshness).toContain('the same approved scope and exact content'); + expect(freshness).toMatch(/a prior invocation's audit or risk decision never qualifies/i); }); test('changed documentation inputs permit only the remaining bounded re-audit', () => { const docs = compact(read('ship/sections/documentation.md.tmpl')); - const recovery = docs.slice(docs.indexOf('## Blocked recovery')); - expect(recovery).toContain('If an attempt remains and either the audited inputs changed'); - expect(recovery).toContain('a concrete launch/input/permission correction or reviewed patch repair is available'); - expect(docs).toContain('an initial audit plus ONE repair/re-audit'); - expect(docs).toContain('never a third attempt, even after Step 16 changes'); + expect(docs.indexOf('## Blocked recovery')).toBeGreaterThan(-1); + expect(docs).toMatch(/initial[- ]audit plus one repair\/re-audit/i); + expect(docs).toMatch(/never a third attempt/i); const freshness = gate.slice(gate.indexOf('### 3.'), gate.indexOf('### 4.')); expect(freshness).toContain('**An attempt remains, with changed inputs or an available repair:**'); - expect(freshness).toContain('**Otherwise:** STOP unless the user accepts'); + expect(freshness).toMatch(/\*\*Otherwise:\*\* stop unless the user accepts/i); expect(freshness).not.toContain('**No attempt remains or no repair is available:**'); }); test('repairs use one ordered work list without a return stack', () => { const text = compact(controller); - expect(compact(entry)).toContain('**Next steps:** one ordered work list, with the current step marked'); - expect(text).toContain('Expand a repair into individual steps and insert them before the still-pending work'); - expect(text).toContain('This replaces the current item, whose actual result stays in the record'); - expect(text).toContain('Add its destination only if not already the next pending step'); - expect(text).toContain('The saved list takes precedence over ordinary next-step sentences inside a repair'); + expect(compact(entry)).toContain('**Next steps:**'); expect(text).toContain('5 → 6 → 7 → 8 → 9 → 10 → 11 → 11.5'); expect(text).not.toContain('finish the inner repair, then resume the unfinished outer range'); - expect(text).toContain('Keep the same attempt counts throughout the invocation'); - expect(text).toContain('initial-plus-ONE limit never resets'); + expect(text).toMatch(/initial-plus-one limit never resets/i); }); test('the roadmap and recovery groups define ownership and zero-edit eligibility', () => { const text = compact(entry); expect(text).toContain('integrate (1–3) → test and review (4–11.5) → prepare the release (12–15) → verify frozen content (16) → push and publish (17–21)'); - expect(text).toContain('children return evidence, not permission to proceed'); - expect(review).toContain('**No edits in this pass:** Resolve the required-probe gate below'); - expect(review).toContain('Only after it clears may you continue to Step 10'); - expect(review).toContain('With completed checklist and dispatched reviewers, failed/unavailable required probes block continuation'); - expect(review).toContain('This cannot waive missing reviewer output, recurring fixes or independent test/security gates'); - expect(review).toContain('Undispatched gated/unsupported specialists do not block independently'); - expect(text).toContain('Reuse it only for that same scope; a repair never resets approvals or expands them'); + expect(text).toMatch(/children return evidence, not permission to proceed/i); + expect(review).toContain('**No edits in this pass:**'); + expect(review).toMatch(/only after it clears may you continue to Step 10/i); + expect(review).toMatch(/failed\/unavailable required probes block continuation/i); + expect(review).toMatch(/cannot waive missing reviewer output, recurring fixes or independent test\/security gates/i); + expect(text).toMatch(/a repair never resets approvals or expands them/i); expect(text).not.toContain('eligible zero-edit pass'); expect(text).not.toContain('the same waiver'); expect(text).not.toContain("the controller's detour"); @@ -72,13 +63,9 @@ describe('ship source controller', () => { test('distribution discovery is a shortlist, not a manifest-only artifact decision', () => { const distribution = compact(entry.slice(entry.indexOf('## Step 2:'), entry.indexOf('## Step 3:'))); - expect(distribution).toContain('List candidate distribution paths'); - expect(distribution).toContain("Also inspect matching untracked files from Step 1's status"); - expect(distribution).toContain('a new `package.json` or `Cargo.toml` alone does not establish a publishable artifact'); - expect(distribution).toContain('inspect existing manifests for newly declared binaries or package exports'); - expect(distribution).toContain('New artifact without a pipeline'); + expect(distribution).toContain('**New artifact without a pipeline'); expect(distribution).toContain('AskUserQuestion'); - expect(distribution).toContain('Do not publish a release during `/ship`'); + expect(distribution).toMatch(/do not publish a release during `\/ship`/i); }); test('queue qualification exhaustively distinguishes online, git fallback and unusable output', () => { @@ -89,13 +76,11 @@ describe('ship source controller', () => { expect(qualify).toBeGreaterThan(0); expect(usable).toBeGreaterThan(qualify); expect(missing).toBeGreaterThan(usable); - expect(version).toContain('require successful utility output and a nonempty valid version'); - expect(version).toContain('`offline:false` qualifies; `offline:true` qualifies only with `fallback:"git"`'); - expect(version).toContain('Offline output without that fallback, failure, malformed output or an empty version is unusable, even if it contains a version-looking string'); - expect(version).toContain('FRESH sets `NEW_VERSION=CANDIDATE_VERSION`'); - expect(version).toContain('Only approval changes the existing version'); - expect(version).toContain('a sibling holding `>= NEW_VERSION` requires a choice: advance past it, or stop this attempt and sync'); - expect(version.slice(missing)).toContain('FRESH uses local `BUMP_LEVEL` arithmetic; ALREADY_BUMPED keeps `currentVersion`'); + expect(version).toContain('`NEW_VERSION=CANDIDATE_VERSION`'); + expect(version).toMatch(/only approval changes the existing version/i); + expect(version).toContain('`>= NEW_VERSION`'); + expect(version.slice(missing)).toContain('`BUMP_LEVEL`'); + expect(version.slice(missing)).toContain('`currentVersion`'); }); test.each(['home', 'override', 'plugin'])('the actual nudge shell honors %s roots, repeat suppression and enabled tuning', mode => { @@ -142,11 +127,8 @@ describe('ship source controller', () => { test('nested repairs resume the unfinished outer range before its destination', () => { const text = compact(controller); - expect(text).toContain('Expand a repair into individual steps and insert them before the still-pending work'); - expect(text).toContain('For another repair, repeat rule 2 without discarding pending work'); - expect(text).toContain('Step 11 fixes insert `9 → 10 → 11` before 11.5'); - expect(text).toContain('A further Step 9 fix affecting 6–8 makes the list `5 → 6 → 7 → 8 → 9 → 10 → 11 → 11.5`'); - expect(text).toContain('The unchanged release steps follow'); + expect(text).toContain('`9 → 10 → 11` before 11.5'); + expect(text).toContain('`5 → 6 → 7 → 8 → 9 → 10 → 11 → 11.5`'); expect(text).not.toContain('Enter Step 9 before REVIEW_START capture and full review'); }); @@ -157,10 +139,8 @@ describe('ship source controller', () => { expect(shared).toBeLessThan(coverage.indexOf('Dispatch the audit through Agent')); const contract = compact(coverage.slice(shared, coverage.indexOf('Dispatch the audit through Agent'))); expect(contract).toContain('use the Agent tool with `run_in_background: false`'); - expect(contract).toContain('Omitting the flag runs the subagent in the background'); - expect(contract).toContain('keeping a fresh context'); - expect(contract).toContain('Do not invoke the target as a Skill or run it inline instead'); - expect(contract).toContain("only under that section's documented fallback, after a failed subagent has stopped"); + expect(contract).toMatch(/do not invoke the target as a Skill or run it inline instead/i); + expect(contract).toMatch(/after a failed subagent has stopped/i); for (const section of ['test-coverage', 'plan-completion', 'greptile']) { const text = read(`ship/sections/${section}.md.tmpl`); expect(text).toContain('shared foreground-dispatch rule'); @@ -178,8 +158,7 @@ describe('ship source controller', () => { const ship = generateReviewDashboard(ctx); expect(ship).toContain('REVIEW READINESS DASHBOARD'); expect(ship).toContain('| Review | Runs | Last run | Status | Required |'); - expect(ship).toContain('Use one row for each entry in step 1'); - expect(ship).toContain('Only Eng Review is marked required'); + expect(ship).toMatch(/only Eng Review is marked required/i); expect(ship).toContain('{actual status and reason}'); expect(ship).toContain('VERDICT: {CLEARED or NOT CLEARED} — {reason}'); expect(ship).not.toContain('+====================================================================+'); @@ -190,18 +169,17 @@ describe('ship source controller', () => { test('coverage fallback settles the child and still applies the coverage gate', () => { const text = compact(read('ship/sections/test-coverage.md.tmpl')); const fallback = text.slice(text.indexOf('**Audit failure:**'), text.indexOf('{{TEST_COVERAGE_GATE_SHIP}}')); - expect(fallback).toContain('confirm it stopped before running the same audit inline'); - expect(fallback).toContain('does not pass or bypass the coverage gate'); - expect(fallback).toContain('including its undetermined-percentage and test-only rules'); + expect(fallback).toMatch(/confirm it stopped before running the same audit inline/i); + expect(fallback).toMatch(/does not pass or bypass the coverage gate/i); expect(text).not.toContain('partial results are better than none'); }); test('no-plan gate skips only the audit and retains verification and the remaining section', () => { const text = generatePlanCompletionGateShip({ host: 'claude', skillName: 'ship', tmplPath: '', paths: HOST_PATHS.claude }); const noPlan = compact(text.slice(text.indexOf('**No plan file found:**'))); - expect(noPlan).toContain('Skip only the plan completion audit'); - expect(noPlan).toContain('Continue with Step 8.1, Scope Drift and Prior Learnings'); - expect(noPlan).toContain('Step 9 QA still runs'); + expect(noPlan).toMatch(/skip only the plan completion audit/i); + expect(noPlan).toContain('Step 8.1'); + expect(noPlan).toMatch(/Step 9 QA still runs/i); expect(noPlan).not.toContain('Skip entirely'); }); @@ -211,8 +189,8 @@ describe('ship source controller', () => { const positions = steps.map(step => text.indexOf(step)); expect(positions.every(position => position >= 0)).toBe(true); expect(positions).toEqual([...positions].sort((a,b) => a-b)); - expect(text).toContain('All three snapshots must match'); - expect(text).toContain('does not mean the failed or unrun probes passed'); + expect(text).toMatch(/all three snapshots must match/i); + expect(text).toMatch(/does not mean the failed or unrun probes passed/i); expect(text).not.toContain('equal snapshots do not pass waived probes'); }); @@ -221,9 +199,8 @@ describe('ship source controller', () => { expect(text).toContain('**Behavior, tests or build inputs changed:**'); expect(text).toContain('**Only authored docs or release metadata changed:**'); expect(text).toContain('**No changes, or the docs-only checks still support the plan:**'); - expect(text).toContain('accepted audit matches all inputs | Continue to stage 4'); - expect(text).toContain("Use Blocked recovery with the existing count"); - expect(text).toContain('Use this example only after confirming that every allowed edit is release metadata'); + expect(text).toContain('| Continue to stage 4'); + expect(text).toMatch(/Blocked recovery with the existing count/i); expect(text).not.toContain('Every listed change below is metadata:'); }); @@ -233,26 +210,24 @@ describe('ship source controller', () => { expect(end).toBeLessThan(entry.indexOf('{{BASE_BRANCH_DETECT}}')); expect(entry.match(/### Ship control flow/g)).toHaveLength(1); const text = compact(controller); - expect(text).toContain('You, the **parent** running /ship, own advancement'); - expect(text).toContain('Follow the saved work list'); - expect(text).toContain('STOP and AskUserQuestion gates still apply during repairs'); - expect(text).toContain('The saved list takes precedence over ordinary next-step sentences inside a repair. A range never adds unlisted steps'); - expect(text).toContain('Keep the same attempt counts throughout the invocation'); + expect(text).toMatch(/the \*\*parent\*\* running \/ship, own advancement/i); + expect(text).toMatch(/stop and AskUserQuestion gates still apply during repairs/i); + expect(text).toMatch(/a range never adds unlisted steps/i); + expect(text).toMatch(/keep the same attempt counts throughout the invocation/i); expect(text).toContain('A range ending at Step 14 does not enter Step 14.5'); - expect(text).toContain('its initial-plus-ONE limit never resets'); expect(text).not.toContain('| At step |'); expect(entry.match(/Ship control flow/g)).toHaveLength(1); - expect(review).toContain('Every repeat starts before the checklist read and captures a fresh REVIEW_START'); + expect(review).toMatch(/captures a fresh REVIEW_START/); }); test('distribution and bootstrap detours end at their exact forward resume', () => { const merge = compact(entry.slice(entry.indexOf('## Step 3:'), entry.indexOf('{{SECTION:tests}}'))); - expect(merge).toContain('repeat Step 2 on the merged content, including its decisions, then continue to Step 4'); - expect(merge).toContain('Otherwise continue to Step 4 directly'); + expect(merge).toMatch(/repeat Step 2 on the merged content[^.]*continue to Step 4/i); + expect(merge).toMatch(/otherwise continue to Step 4 directly/i); const tests = compact(read('ship/sections/tests.md.tmpl')); - expect(tests).toContain('A runs Step 4 with this new bootstrap choice, then returns here to run the tests'); + expect(tests).toMatch(/A runs Step 4 with this new bootstrap choice, then returns here/i); expect(tests).toContain('A) Add tests (recommended)'); - expect(tests).toContain('declining bootstrap alone is not that approval'); + expect(tests).toMatch(/declining bootstrap alone is not that approval/i); }); test('missing dispatched output outranks the cycle cap, fixes and zero-edit continuation', () => { @@ -262,49 +237,40 @@ describe('ship source controller', () => { expect(positions.every(position => position >= 0)).toBe(true); expect(positions).toEqual([...positions].sort((a, b) => a - b)); const missing = decisions.slice(positions[0], positions[1]); - expect(missing).toContain('STOP and name each failed specialist or Red Team'); - expect(missing).toContain('Retain queued fixes and restore coverage'); - expect(missing).toContain('If this pass made edits, resume at the next decision; otherwise run a fresh complete Step 9'); - expect(missing).toContain('A successful peer or a QA exception cannot replace missing dispatched coverage'); + expect(missing).toMatch(/^1\. \*\*Dispatched reviewer output missing:\*\* stop\b/i); + expect(missing).toMatch(/cannot replace missing dispatched coverage/i); const cap = decisions.slice(positions[1], positions[2]); - expect(cap).toContain('STOP'); - expect(cap).toContain('`converged:false`; do not run a fourth fixing cycle'); + expect(cap).toMatch(/\bstop\b/i); + expect(cap).toContain('`converged:false`'); + expect(cap).toMatch(/do not run a fourth fixing cycle/i); const fixes = decisions.slice(positions[2], positions[3]); - expect(fixes).toContain('Insert Step 5, affected Steps 6–8 and all of Step 9 before the pending Step 10 in the work list'); - expect(fixes).toContain('Tests must pass or retain approval for the same verified pre-existing failures and scope'); - expect(fixes).toContain('Keep CYCLES and scoped approvals across this repeat'); - expect(decisions.slice(positions[3])).toContain('Only after it clears may you continue to Step 10'); - expect(review).toContain('Undispatched host-unsupported/gated specialists do not block'); - expect(review).toContain('Failed, blocked, inconclusive or not-run required probes mean false, never clean'); - expect(review).toContain('This cannot waive missing reviewer output, recurring fixes or independent test/security gates'); + expect(fixes).toContain('Insert Step 5, affected Steps 6–8 and all of Step 9 before the pending Step 10'); + expect(fixes).toMatch(/retain approval for the same verified pre-existing failures and scope/i); + expect(decisions.slice(positions[3])).toMatch(/only after it clears may you continue to Step 10/i); + expect(review).toMatch(/failed, blocked, inconclusive or not-run required probes mean false, never clean/i); }); test('comment fixes resume triage and preserve settled approvals and replies', () => { const section = compact(read('ship/sections/greptile.md.tmpl')); - expect(section).toContain('If fixes were approved, save their approvals and comment references'); - expect(section).toContain("Run Step 9's full review/fix loop, then return here"); - expect(review).toContain('Finish the complete review and QA before applying any fix in Step 9.4'); - expect(section).toContain('Finish the saved replies without asking again about completed fixes, and classify new comments'); - expect(section).toContain('With no queued fixes, continue to Step 11'); - expect(section).toContain('This optional triage does not block ship'); - expect(section).toContain('unknown or missing status is unavailable'); + expect(section).toMatch(/save their approvals and comment references/i); + expect(section).toMatch(/Run Step 9's full review\/fix loop, then return here/i); + expect(review).toMatch(/finish the complete review and QA before applying any fix in Step 9\.4/i); + expect(section).toMatch(/with no queued fixes, continue to Step 11/i); + expect(section).toMatch(/this optional triage does not block ship/i); expect(section).not.toContain('return to Step 9'); }); test('native recovery has one corrected attempt and cannot borrow outside completion', () => { const finish = adversarial.slice(adversarial.indexOf('### Finish the adversarial phase')); const recovery = finish.slice(finish.indexOf('1. **Required native'), finish.indexOf('2. **Fixes queued')); - expect(recovery).toContain('STOP and confirm the native task stopped'); - expect(recovery).toContain('Outside-provider output cannot replace this pass'); - expect(recovery).toContain('One recovery retry is allowed only after a concrete prerequisite correction and restored access'); - expect(recovery).toContain('count it in the invocation record before launch'); - expect(recovery).toContain('Capture a fresh PASS_START and persist the new attempt separately'); - expect(recovery).toContain('Without that correction, or if the recovery fails, ask for repair and remain blocked'); + expect(recovery).toMatch(/stop and confirm the native task stopped/i); + expect(recovery).toMatch(/outside-provider output cannot replace this pass/i); + expect(recovery).toMatch(/one recovery retry is allowed only after a concrete prerequisite correction/i); + expect(recovery).toContain('PASS_START'); + expect(recovery).toMatch(/ask for repair and remain blocked/i); const queued = finish.slice(finish.indexOf('2. **Fixes queued'), finish.indexOf('3. **Native complete')); - expect(queued).toContain('Keep the findings and their approvals'); - expect(queued).toContain('Insert Steps 9, 10 and 11 before the pending Step 11.5 in the work list. Step 9 completes full review before fixes'); - expect(queued).toContain('any further repair inserts its checks ahead of the remaining items'); - expect(queued).toContain('not recovery retries'); + expect(queued).toContain('Insert Steps 9, 10 and 11 before the pending Step 11.5'); + expect(queued).toMatch(/not recovery retries/i); expect(finish.slice(finish.indexOf('3. **Native complete'))).toContain('then continue to Step 11.5'); }); @@ -312,19 +278,19 @@ describe('ship source controller', () => { const ctx = { host, skillName: 'ship', tmplPath: '', paths: HOST_PATHS[host] }; const ship = generateAdversarialStep(ctx); const finish = compact(ship.slice(ship.indexOf('### Finish the adversarial phase'))); - expect(finish).toContain('Apply these decisions in order before leaving Step 11'); - expect(finish).toContain('Required native review incomplete'); - expect(finish).toContain('Outside-provider output cannot replace this pass'); - expect(finish).toContain('Fixes queued after native completion'); - expect(finish).toContain('Native complete with no queued fixes'); + const branches = ['Required native review incomplete', 'Fixes queued after native completion', 'Native complete with no queued fixes'] + .map(label => finish.indexOf(label)); + expect(branches.every(index => index >= 0)).toBe(true); + expect(branches).toEqual([...branches].sort((a, b) => a - b)); + expect(finish).toMatch(/outside-provider output cannot replace this pass/i); expect(finish).toContain('Step 11.5'); expect(finish).not.toContain('proceed to Step 12'); expect(finish).not.toContain('return to Step 9'); const review = generateAdversarialStep({ ...ctx, skillName: 'review' }); expect(review).not.toContain('Ship control flow'); expect(review).not.toContain('Step 11.5'); - expect(review).toContain('Return all findings and structured-review decisions to Step 5'); - expect(review).toContain('do not start an inner repair loop'); + expect(review).toMatch(/return all findings and structured-review decisions to Step 5/i); + expect(review).toMatch(/do not start an inner repair loop/i); }); test('Step 11.5 verifies original record identity before any release write', () => { @@ -332,59 +298,56 @@ describe('ship source controller', () => { expect(bindingStart).toBeGreaterThan(entry.indexOf('{{SECTION:adversarial}}')); expect(bindingStart).toBeLessThan(entry.indexOf('## Step 12:')); const binding = compact(entry.slice(bindingStart, entry.indexOf('## Step 12:'))); - for (const field of ['saved handle, original token and source', 'skill:"review"', 'via:"ship"', 'skill:"adversarial-review"', + for (const field of ['skill:"review"', 'via:"ship"', 'skill:"adversarial-review"', 'review_binding.state', 'verified', 'review_binding.start_wtree', 'review_binding.end_wtree']) expect(binding).toContain(field); - expect(binding).toContain('Never attach new tokens to old work'); - expect(binding).toContain('Keep Step 9.4\'s incomplete flags'); - expect(binding).toContain('does not mean the failed or unrun probes passed'); - expect(binding).toContain('insert `9 → 10 → 11 → 11.5` before Step 12. Bind the new records at 11.5'); + expect(binding).toMatch(/never attach new tokens to old work/i); + expect(binding).toMatch(/does not mean the failed or unrun probes passed/i); + expect(binding).toContain('`9 → 10 → 11 → 11.5` before Step 12'); }); test('late build and behavior changes complete bounded ranges before docs', () => { const build = gate.slice(gate.indexOf('### 1.'), gate.indexOf('### 2.')); - expect(build).toContain('A missing prerequisite or failed build stops shipping'); - expect(build).toContain('Repair the prerequisite or build, then repeat stage 1'); - expect(build).toContain('After it passes, continue to stage 2; treat any content repair as a behavioral change there'); + expect(build).toMatch(/a missing prerequisite or failed build stops shipping/i); + expect(build).toMatch(/repeat stage 1/i); + expect(build).toMatch(/continue to stage 2/i); const behavior = gate.slice(gate.indexOf('1. **Behavior'), gate.indexOf('2. **Only authored')); expect(behavior).toContain('Insert `5–11.5 → 12–14 → 16` before the pending Step 17'); - expect(behavior).toContain('before the pending Step 17, then stop this step. This repair excludes Step 14.5'); - expect(behavior).toContain('rebuild and compare again before stage 3 decides documentation freshness'); + expect(behavior).toMatch(/excludes Step 14\.5/i); + expect(behavior).toMatch(/rebuild and compare again before stage 3/i); const plan = gate.slice(gate.indexOf('2. **Only authored'), gate.indexOf('3. **No changes')); - expect(plan).toContain("run Step 8's audit and decision gates only, then return to Step 16 stage 1"); - expect(plan).toContain("Never edit the child's counts yourself"); + expect(plan).toMatch(/run Step 8's audit and decision gates only, then return to Step 16 stage 1/i); + expect(plan).toMatch(/never edit the child's counts yourself/i); }); test('docs freshness has bounded re-audit and exact-content exception routes', () => { const docs = gate.slice(gate.indexOf('### 3.'), gate.indexOf('### 4.')); - expect(docs).toContain("Use Blocked recovery with the existing count"); + expect(docs).toMatch(/Blocked recovery with the existing count/i); expect(docs).toContain('**An attempt remains, with changed inputs or an available repair:** insert `14.5 → 15 → 16` before Step 17'); - expect(docs).toContain('restart Step 16 stage 1 to regenerate and compare again'); - expect(docs).toContain('STOP unless the user accepts the specific named documentation risk and all unwaivable gates clear'); - expect(docs).toContain('Never run a third audit'); - expect(docs).toContain('Unchanged approved content goes to stage 4; repaired content goes to stage 1'); - expect(docs).toContain('retain `Documentation: blocked`'); - expect(docs).toContain('Missing, stale or blocked | Use recovery below. Never silently refresh hashes'); + expect(docs).toMatch(/stop unless the user accepts the specific named documentation risk and all unwaivable gates clear/i); + expect(docs).toMatch(/never run a third audit/i); + expect(docs).toMatch(/unchanged approved content goes to stage 4; repaired content goes to stage 1/i); + expect(docs).toContain('`Documentation: blocked`'); + expect(docs).toMatch(/never silently refresh hashes/i); }); test('tests, absent test approval and new writes reopen final verification', () => { const tests = gate.slice(gate.indexOf('### 4.'), gate.indexOf('### 5.')); - expect(tests).toContain("**New, changed or unwaived test failure:** STOP publication. Run Steps 5–15, starting with Step 5's triage, then return to Step 16 stage 1"); - expect(tests).toContain('This recovery also applies if a failure appears while reporting in stage 5'); - expect(tests).toContain('run Steps 5–15, including the no-tests decision, then return to Step 16 stage 1'); - expect(tests).toContain('it does not authorize a third attempt'); - expect(gate).toContain('If content changes during or after verification, restart at stage 1 and complete all five stages before Step 17'); - expect(gate).toContain('Content-preserving commits keep valid evidence'); + expect(tests).toMatch(/\*\*New, changed or unwaived test failure:\*\* stop publication\. Run Steps 5–15/i); + expect(tests).toMatch(/run Steps 5–15, including the no-tests decision, then return to Step 16 stage 1/i); + expect(tests).toMatch(/does not authorize a third attempt/i); + expect(gate).toMatch(/restart at stage 1 and complete all five stages before Step 17/i); }); test('push failure kinds and publication races have distinct resumes', () => { const push = compact(entry.slice(entry.indexOf('## Step 17:'), entry.indexOf('## Step 18:'))); - expect(push).toContain('**If the push fails, STOP.** No Step 19 or publication claim'); - expect(push).toContain("fetch and inspect the remote, then merge under Step 3's conflict rules"); - expect(push).toContain('Run Steps 5–16 before returning to Step 17. Never rewrite history'); - expect(push).toContain('repeat Step 16 even if content is unchanged before returning to Step 17'); - expect(push).toContain('Never bypass failed guards'); + expect(push).toMatch(/push fails[^.]*\bstop\b[\s\S]{0,20}no Step 19 or publication claim/i); + expect(push).toMatch(/merge under Step 3's conflict rules/i); + expect(push).toContain('Run Steps 5–16 before returning to Step 17'); + expect(push).toMatch(/never rewrite history/i); + expect(push).toMatch(/repeat Step 16 even if content is unchanged/i); + expect(push).toMatch(/never bypass failed guards/i); const publication = compact(read('ship/sections/pr-body.md.tmpl')); - expect(publication).toContain("If the open PR/MR or title changed, repeat Step 18's identity/title preparation"); - expect(publication).toContain('then return here for a new lookup, fresh body and both redaction scans before publishing'); + expect(publication).toMatch(/repeat Step 18's identity\/title preparation/i); + expect(publication).toMatch(/both redaction scans before publishing/i); }); });