d15c301fdb
Verify rendered routes, translations, CSS dependencies, variables, and built CSS values. Keep the original assertion count; do not alter site components or fixtures.
264 lines
9.6 KiB
JavaScript
264 lines
9.6 KiB
JavaScript
import { readFileSync } from 'node:fs';
|
|
|
|
const read = (path) => readFileSync(new URL(`../${path}`, import.meta.url), 'utf8');
|
|
const built = (path) => read(`dist/${path}`);
|
|
const rendered = (value) =>
|
|
value
|
|
.replace(/<script[\s\S]*?<\/script>/gi, '')
|
|
.replace(/<style[\s\S]*?<\/style>/gi, '')
|
|
.replace(/<!--[\s\S]*?-->/g, '')
|
|
.replace(/<[^>]+>/g, ' ')
|
|
.replace(/&/g, '&')
|
|
.replace(
|
|
/&(lt|gt|quot|nbsp);/g,
|
|
(_, entity) => ({ lt: '<', gt: '>', quot: '"', nbsp: ' ' })[entity],
|
|
)
|
|
.replace(/�?39;/g, "'")
|
|
.replace(/\s+/g, ' ')
|
|
.trim();
|
|
const normalize = (value) => value.replace(/\s+/g, ' ').trim();
|
|
|
|
const html = {
|
|
landing: built('index.html'),
|
|
guide: built('full-guide/index.html'),
|
|
summary: built('summary/index.html'),
|
|
models: built('models/index.html'),
|
|
agents: built('agents/index.html'),
|
|
skills: built('skills/index.html'),
|
|
rules: built('rules/index.html'),
|
|
review: built('skills-review/index.html'),
|
|
starter: built('hands-on/starter/index.html'),
|
|
labRules: built('hands-on/rules/index.html'),
|
|
};
|
|
const snapshot = (path) => normalize(read(`.agents/snapshots/${path}`));
|
|
const reviewAssetSrc = html.review.match(/src="([^"]+\.[a-zA-Z0-9_-]{8,}\.js)"/)?.[1];
|
|
const reviewAsset = reviewAssetSrc ? read(`dist/_astro/${reviewAssetSrc.split('/').at(-1)}`) : '';
|
|
|
|
// Rendered prose catches a deleted paragraph that string-token checks miss.
|
|
if (rendered(html.landing) !== snapshot('index.txt'))
|
|
throw new Error('landing rendered-text snapshot changed');
|
|
if (rendered(html.guide) !== snapshot('full-guide.txt'))
|
|
throw new Error('full-guide rendered-text snapshot changed');
|
|
if (rendered(html.summary) !== snapshot('summary.txt'))
|
|
throw new Error('summary rendered-text snapshot changed');
|
|
if (rendered(html.models) !== snapshot('models.txt'))
|
|
throw new Error('models rendered-text snapshot changed');
|
|
if (rendered(html.agents) !== snapshot('agents.txt'))
|
|
throw new Error('agents rendered-text snapshot changed');
|
|
if (rendered(html.skills) !== snapshot('skills.txt'))
|
|
throw new Error('skills rendered-text snapshot changed');
|
|
if (rendered(html.rules) !== snapshot('rules.txt'))
|
|
throw new Error('rules rendered-text snapshot changed');
|
|
if (rendered(html.review) !== snapshot('skills-review.txt'))
|
|
throw new Error('skills-review rendered-text snapshot changed');
|
|
if (rendered(html.starter) !== snapshot('hands-on/starter.txt'))
|
|
throw new Error('starter rendered-text snapshot changed');
|
|
if (rendered(html.labRules) !== snapshot('hands-on/rules.txt'))
|
|
throw new Error('rules lab rendered-text snapshot changed');
|
|
|
|
const braceMatch = (source, open) => {
|
|
let depth = 0;
|
|
let quote = '';
|
|
let escaped = false;
|
|
for (let index = open; index < source.length; index += 1) {
|
|
const character = source[index];
|
|
if (quote) {
|
|
if (escaped) escaped = false;
|
|
else if (character === '\\') escaped = true;
|
|
else if (character === quote) quote = '';
|
|
continue;
|
|
}
|
|
if (character === "'" || character === '"' || character === '`') quote = character;
|
|
else if (character === '{') depth += 1;
|
|
else if (character === '}' && --depth === 0) return index;
|
|
}
|
|
throw new Error('could not brace-match legacy Portuguese translations');
|
|
};
|
|
const legacyGuide = read('app.js');
|
|
const translationsStart = legacyGuide.indexOf('const translations =');
|
|
const objectStart = legacyGuide.indexOf('{', translationsStart);
|
|
const objectEnd = braceMatch(legacyGuide, objectStart);
|
|
const translations = Function(`return (${legacyGuide.slice(objectStart, objectEnd + 1)})`)();
|
|
const portuguese = Object.values(translations.pt).map(rendered);
|
|
if (portuguese.length !== 102)
|
|
throw new Error('full-guide Portuguese source no longer has 102 translated strings');
|
|
if (!portuguese.every((value) => rendered(html.guide).includes(value)))
|
|
throw new Error('built full-guide lost a Portuguese translation');
|
|
|
|
const has = (page, tokens) => tokens.every((token) => page.includes(token));
|
|
if (!has(html.guide, ['data-phase="plan"', 'data-phase="build"', 'data-phase="review"']))
|
|
throw new Error('built full-guide lost its phase selector');
|
|
if (!has(html.guide, ['data-tree="main"', 'data-tree="ui"', 'data-worker="ui"']))
|
|
throw new Error('built full-guide lost its worker or worktree selector');
|
|
if (
|
|
!has(html.guide, [
|
|
'data-route="plan"',
|
|
'data-model-provider="openai"',
|
|
'data-model-provider="claude"',
|
|
'data-model-provider="gemini"',
|
|
])
|
|
)
|
|
throw new Error('built full-guide lost its model-routing controls');
|
|
if (!has(html.guide, ['data-effort="low"', 'data-effort="medium"', 'data-effort="high"']))
|
|
throw new Error('built full-guide lost its reasoning-effort controls');
|
|
if (
|
|
!has(html.guide, [
|
|
'data-skill-file="skill"',
|
|
'data-skill-step="observe"',
|
|
'data-skill-step="validate"',
|
|
])
|
|
)
|
|
throw new Error('built full-guide lost its skill anatomy controls');
|
|
if (
|
|
!has(html.guide, [
|
|
'data-common-skill="ponytail"',
|
|
'data-common-skill="caveman"',
|
|
'data-common-skill="unlazy"',
|
|
])
|
|
)
|
|
throw new Error('built full-guide lost its common-skill controls');
|
|
if (
|
|
!has(html.guide, [
|
|
'id="hands-on"',
|
|
'data-copy-target="prompt-install-skills"',
|
|
'data-copy-target="prompt-basic"',
|
|
'data-copy-target="prompt-skills"',
|
|
])
|
|
)
|
|
throw new Error('built full-guide lost its hands-on prompts');
|
|
if (!has(html.guide, ['hands-on/starter/', 'additional-reading.md', 'role="tablist"', '<table']))
|
|
throw new Error('built full-guide lost its guide navigation or semantics');
|
|
if (!html.guide.includes('<script type="module">'))
|
|
throw new Error('built full-guide lost its interaction module');
|
|
|
|
if (
|
|
!has(html.rules, [
|
|
'id="pipeline"',
|
|
'id="skills"',
|
|
'id="examples"',
|
|
'data-stage="context"',
|
|
'data-stage="cli"',
|
|
'data-stage="commit"',
|
|
'data-stage="review"',
|
|
])
|
|
)
|
|
throw new Error('built rules page lost its enforcement pipeline');
|
|
if (
|
|
!has(html.rules, [
|
|
'gate-discipline',
|
|
'parallel-agents',
|
|
'repo-db',
|
|
'tech-debt',
|
|
'skill-writer',
|
|
'scripts/check-ui-contract.mjs',
|
|
'.husky/pre-commit',
|
|
'.pr-review.json',
|
|
'.agents/skills',
|
|
'netcracker/interview',
|
|
])
|
|
)
|
|
throw new Error('built rules page lost its case-study content');
|
|
if (
|
|
!has(html.rules, [
|
|
'data-lang="en"',
|
|
'data-lang="pt"',
|
|
'data-copy-prompt',
|
|
'aria-live="polite"',
|
|
'role="tablist"',
|
|
])
|
|
)
|
|
throw new Error('built rules page lost its accessible controls');
|
|
if (!html.rules.includes('data-rules-copy') || !html.rules.includes('"pt"'))
|
|
throw new Error('built rules page lost its Portuguese interaction data');
|
|
|
|
if (
|
|
!has(html.review, [
|
|
'id="catalog"',
|
|
'id="skill-filter"',
|
|
'id="skill-list"',
|
|
'id="detail"',
|
|
'Preview Markdown',
|
|
'?author=Name&skill=skill-id&view=improved',
|
|
'SKILLS_REVIEW_VOTE_API',
|
|
])
|
|
)
|
|
throw new Error('built review desk lost its catalog or deep-link contract');
|
|
if (
|
|
!has(html.review, [
|
|
'ndo-repro',
|
|
'gfiber-logging',
|
|
'confluence-page',
|
|
'diagram-plantuml',
|
|
'page-reviewer',
|
|
'unslop',
|
|
'spanish-naturalizer',
|
|
'draft-mr',
|
|
'semantic-diff-review',
|
|
])
|
|
)
|
|
throw new Error('built review desk lost submitted-skill coverage');
|
|
if (
|
|
!html.review.includes('Anonymous%20Operational%20Submission') ||
|
|
!has(reviewAsset, ['[REDACTED]', '[REDACTED LOCAL USER]', '[REDACTED USER]'])
|
|
)
|
|
throw new Error('built review desk lost operational-submission privacy redactions');
|
|
if (!reviewAssetSrc) throw new Error('built review desk does not load its hashed island asset');
|
|
|
|
if (
|
|
![html.summary, html.models, html.agents, html.skills].every((page) => page.includes('ROUTE MAP'))
|
|
)
|
|
throw new Error('built chapter pages lost shared navigation');
|
|
if (
|
|
!has(html.landing, [
|
|
'The short route',
|
|
'full-guide/',
|
|
'models/',
|
|
'agents/',
|
|
'skills/',
|
|
'rules/',
|
|
'hands-on/starter/',
|
|
'skills-review/',
|
|
])
|
|
)
|
|
throw new Error('built landing page lost a route-map destination');
|
|
if (/<script\b/i.test(html.landing))
|
|
throw new Error('built landing page is no longer a static route map');
|
|
if (
|
|
!has(html.skills, [
|
|
'data-skill-file="skill"',
|
|
'data-skill-file="references"',
|
|
'data-skill-file="scripts"',
|
|
'data-skill-file="assets"',
|
|
'id="package-preview"',
|
|
])
|
|
)
|
|
throw new Error('built skills page lost its package explorer');
|
|
if (!has(html.starter, ['id="task-list"', 'id="task-count"']))
|
|
throw new Error('starter lab lost its task-list contract');
|
|
|
|
const allPages = Object.entries(html);
|
|
if (!allPages.every(([, page]) => page.includes('name="viewport"')))
|
|
throw new Error('a built route lacks a viewport declaration');
|
|
if (allPages.some(([, page]) => /<(script|link)[^>]+(src|href)="https?:[^\"]+"/i.test(page)))
|
|
throw new Error('a built route has an external runtime dependency');
|
|
if (
|
|
!read('package.json').includes(
|
|
'"verify": "node scripts/verify.mjs && node scripts/audit-ui.mjs && node .agents/scripts/check-tokens.mjs"',
|
|
)
|
|
)
|
|
throw new Error('pnpm verify no longer runs audit-ui and check-tokens');
|
|
if (!read('.agents/scripts/gate.sh').includes('pnpm run verify'))
|
|
throw new Error('the gate no longer runs the output contract');
|
|
if (!read('docs/references/README.md').includes('https://developers.openai.com/codex/skills'))
|
|
throw new Error('primary reference list lost Codex skills');
|
|
if ((read('docs/references/additional-reading.md').match(/^### \d+\./gm) || []).length < 5)
|
|
throw new Error('additional reading list has fewer than five entries');
|
|
if (
|
|
/Pedro\s+Aranha|pedro\.aranha|git\.netcracker\.com|artifactorycn|managed\.netcracker\.cloud/i.test(
|
|
read('src/content/reviews/ndo-repro.md'),
|
|
)
|
|
)
|
|
throw new Error('operational review content has a privacy leak');
|
|
|
|
console.log('built output verification passed');
|