import { describe, test, expect } from 'bun:test'; import * as fs from 'fs'; import * as path from 'path'; import { generateReviewDashboard } from '../scripts/resolvers/review-dashboard'; import { HOST_PATHS } from '../scripts/resolvers/types'; import { ALL_HOST_CONFIGS } from '../hosts'; import { expectMentions } from './helpers/prompt-structure'; /** * Template-drift tripwire for the content-binding wave. The bins are * code-enforced; the GRADING rules live as prose in rendered templates that * agents follow. This test pins the load-bearing rule text in the GENERATED * files so a template refactor can't silently drop a rule while the bins keep * working. (Prompt-followed prose is honest tier-2 enforcement — this tripwire * is what keeps it from being tier-3 vibes.) */ const ROOT = path.resolve(import.meta.dir, '..'); function rendered(rel: string): string { return fs.readFileSync(path.join(ROOT, rel), 'utf-8'); } describe('content-binding template drift', () => { test('design-lite records outside coverage after the outside step in ship', () => { const text = rendered('ship/sections/review-army.md'); const outside = text.indexOf('design voice**'); expect(outside).toBeGreaterThan(-1); expect(text.indexOf('--finish DESIGN_START')).toBeGreaterThan(outside); expect(text).toContain('Use the original DESIGN_START token'); }); test('ship eval selection scopes the Rails example below the project-native path', () => { const text = rendered('ship/sections/tests.md'); const native = text.indexOf('**Project-native path:**'); const rails = text.indexOf('**Rails example only'); expect(native).toBeGreaterThan(-1); expect(rails).toBeGreaterThan(native); expect(text).not.toContain('**If no matches:**'); expect(text).toContain('If any eval fails'); }); test('ship historical readiness does not replace the current pre-landing gate', () => { const text = rendered('ship/SKILL.md'); expectMentions(text, [['never', 'convergence', 'approval']], 'text'); }); test('ship Step 16 carries the evidence check (mechanized IRON LAW)', () => { const ship = rendered('ship/SKILL.md'); expect(ship).toMatch(/gstack-evidence check --label tests --expect-cmd '[^']+' --label vitest --expect-cmd '[^']+' --max-age 24 --allow-paths CHANGELOG\.md,VERSION,package\.json/); expect(ship.replace(/\s+/g, ' ')).toContain("| STALE/MISSING: changed content, command or age, or no proven run | Run `~/.claude/skills/gstack/bin/gstack-evidence run --label -- ''`, read the result and recheck once"); expect(ship.replace(/\s+/g, ' ')).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(ship).toContain('return to Step 16 stage 1'); }); test('ship Step 5 lanes run wrapped with per-lane labels', () => { const tests = rendered('ship/sections/tests.md'); expect(tests).toContain('gstack-ship-preflight --tests-only --base --lane