Files
ai-for-dummies/scripts/verify.mjs
T

398 lines
14 KiB
JavaScript

import { readFileSync } from 'node:fs';
const read = (path) => readFileSync(new URL(`../${path}`, import.meta.url), 'utf8');
const landingHtml = read('index.html');
const html = read('full-guide/index.html');
const js = read('app.js');
const refs = read('docs/references/README.md');
const additional = read('docs/references/additional-reading.md');
const starterHtml = read('hands-on/starter/index.html');
const starterJs = read('hands-on/starter/app.js');
const skillSources = read('docs/references/skill-sources.md');
const modelRouting = read('docs/references/model-routing.md');
const rulesHtml = read('rules/index.html');
const rulesJs = read('rules/app.js');
const rulesCss = read('rules/styles.css');
const reviewHtml = read('skills-review/index.html');
const reviewJs = read('skills-review/app.js');
const reviewFiles = read('skills-review/files.js');
const reviewLensCss = read('skills-review/change-lens.css');
const chaptersCss = read('chapters.css');
const summaryHtml = read('summary/index.html');
const modelsHtml = read('models/index.html');
const agentsHtml = read('agents/index.html');
const skillsHtml = read('skills/index.html');
const reviewCatalog = read('skills-review/catalog.js');
const reviewVoteJs = read('skills-review/vote.js');
const voteService = read('vote-service/main.go');
for (const url of [
'https://code.claude.com/docs/en/sub-agents',
'https://code.claude.com/docs/en/skills',
'https://code.claude.com/docs/en/worktrees',
'https://git-scm.com/docs/git-worktree.html',
'https://developers.openai.com/codex/skills',
])
if (!refs.includes(url)) throw new Error(`missing reference ${url}`);
console.log('content verification passed');
for (const token of [
'data-phase="plan"',
'data-phase="build"',
'data-phase="review"',
'data-tree="main"',
'data-tree="ui"',
'data-worker="ui"',
'data-route="plan"',
'data-model-provider="openai"',
'data-model-provider="claude"',
'data-model-provider="gemini"',
'data-effort="low"',
'data-effort="medium"',
'data-effort="high"',
'data-skill-file="skill"',
'data-skill-step="observe"',
'data-skill-step="validate"',
'data-common-skill="ponytail"',
'data-common-skill="caveman"',
'data-common-skill="unlazy"',
'id="hands-on"',
'data-copy-target="prompt-install-skills"',
'data-copy-target="prompt-basic"',
'data-copy-target="prompt-skills"',
'hands-on/starter/',
'additional-reading.md',
'role="tablist"',
'<table',
])
if (!html.includes(token)) throw new Error(`missing content ${token}`);
for (const token of [
'const phases',
'const handsOnPrompts',
'const modelGuide',
'const skillSources',
'const skillInstallPrompts',
'addEventListener',
"render('plan')",
'renderTree',
'renderWorker',
'renderRoute',
'renderModelProvider',
'renderEffort',
'renderSkillFile',
'renderSkillWorkflow',
'renderCommonSkill',
'renderHandsOn',
'copyPrompt',
])
if (!js.includes(token)) throw new Error(`missing interaction ${token}`);
for (const token of ['id="task-list"', 'id="task-count"'])
if (!starterHtml.includes(token)) throw new Error(`missing starter content ${token}`);
for (const token of ['const tasks', 'renderTasks()'])
if (!starterJs.includes(token)) throw new Error(`missing starter behavior ${token}`);
for (const token of [
'medium.com',
'anthropic.com/engineering',
'openai.com/business',
'git-scm.com/docs/git-worktree',
])
if (!additional.includes(token)) throw new Error(`missing additional source ${token}`);
if ((additional.match(/^### \d+\./gm) || []).length < 5)
throw new Error('fewer than five additional readings');
for (const token of [
'e7b42dc2d384a702240dea4d52a7bf5530b821b6',
'6654f6b60cd9d5be8b54c6fafe44346dabeb3b76',
'53048666b05b4799081517d00e09e0a2dd688678',
])
if (!skillSources.includes(token) || !js.includes(token))
throw new Error(`missing pinned skill source ${token}`);
for (const token of [
'developers.openai.com/api/docs/guides/latest-model',
'docs.anthropic.com/en/docs/claude-code/model-config',
'ai.google.dev/gemini-api/docs/thinking',
])
if (!modelRouting.includes(token) || !js.includes(token))
throw new Error(`missing model source ${token}`);
console.log('interaction verification passed');
if (html.match(/<(script|link)[^>]+(src|href)="https?:[^"]+"/i))
throw new Error('external runtime dependency found');
console.log('standalone verification passed');
for (const token of [
'id="pipeline"',
'id="skills"',
'id="examples"',
'data-stage="context"',
'data-stage="cli"',
'data-stage="commit"',
'data-stage="review"',
'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',
])
if (!rulesHtml.includes(token)) throw new Error(`missing rules content ${token}`);
console.log('rules content verification passed');
for (const token of [
'const languageCopy',
'const stages',
'const skills',
'const prompts',
'renderStage',
'renderSkill',
'renderLanguage',
'copyPrompt',
'addEventListener',
])
if (!rulesJs.includes(token)) throw new Error(`missing rules interaction ${token}`);
for (const token of [
'data-lang="en"',
'data-lang="pt"',
'data-copy-prompt',
'aria-live="polite"',
'role="tablist"',
])
if (!rulesHtml.includes(token)) throw new Error(`missing rules control ${token}`);
console.log('rules interaction verification passed');
if (!html.includes('href="../rules/"'))
throw new Error('main presentation does not link to rules page');
if (!html.includes('href="../skills-review/"'))
throw new Error('main presentation does not link to skills review page');
for (const token of ['../summary/', '../models/', '../agents/', '../skills/', 'chapter-route'])
if (!html.includes(token)) throw new Error(`main presentation missing chapter route ${token}`);
if (rulesHtml.includes('script src="http') || rulesHtml.includes('rel="stylesheet" href="http'))
throw new Error('rules page has an external runtime dependency');
for (const token of [
'@media(min-width:2200px)',
'@media(max-width:900px)',
'@media(max-width:600px)',
'prefers-reduced-motion',
])
if (!rulesCss.includes(token)) throw new Error(`missing rules responsive contract ${token}`);
console.log('rules standalone verification passed');
for (const token of [
'id="catalog"',
'id="skill-filter"',
'id="skill-list"',
'id="detail"',
'Preview Markdown',
'styles.css?v=20260904-vote-widget',
'change-lens.css?v=20260904-vote-widget',
'app.js?v=20260904-vote-widget',
'?author=Name&amp;skill=skill-id&amp;view=improved',
'SKILLS_REVIEW_VOTE_API',
])
if (!reviewHtml.includes(token)) throw new Error(`missing review page content ${token}`);
for (const token of [
"from './catalog.js'",
"from './files.js'",
'function renderList',
'function renderDetail',
'selectSkill',
'packageSummary',
'markdownHeadings',
'markdownToc',
"document.addEventListener('keydown'",
'loadSelectedFile',
'schedulePackageSearch',
'fetchSource',
'packageSearchText',
'diffMarkup',
'diffRows',
'data-diff',
"searchParams.set('compare'",
'markdownMarkup',
'data-render',
'preview-markdown',
'Preview Markdown',
'View source',
'FILE PREVIEW',
"searchParams.set('render'",
'AUTHOR ·',
'SKILL ·',
'function selectFromUrl',
'function syncUrl',
'URLSearchParams',
'navigator.clipboard',
'document.execCommand',
'download',
'data-file',
"searchParams.set('file'",
])
if (!reviewJs.includes(token)) throw new Error(`missing review interaction ${token}`);
for (const token of [
'ndo-repro',
'gfiber-logging',
'confluence-page',
'diagram-plantuml',
'page-reviewer',
'unslop',
'spanish-naturalizer',
'draft-mr',
'semantic-diff-review',
'reference.md',
'files =',
])
if (!`${reviewFiles}\n${read('skills-review/submitted-files.js')}`.includes(token))
throw new Error(`missing review file manifest ${token}`);
if (
(reviewCatalog.match(/id:'/g) || []).length +
(read('skills-review/submitted-catalog.js').match(/id:'/g) || []).length !==
24
)
throw new Error('review catalog does not cover all submissions');
if (
!reviewCatalog.includes('hardcoded password') ||
!reviewCatalog.includes('safety-redacted') ||
!reviewJs.includes('[REDACTED]') ||
!reviewJs.includes('[REDACTED LOCAL USER]') ||
!reviewJs.includes('[REDACTED USER]')
)
throw new Error('review catalog does not record secret safety handling');
console.log('skills review verification passed');
for (const page of [summaryHtml, modelsHtml, agentsHtml, skillsHtml])
if (!page.includes('../chapters.css') || !page.includes('ROUTE MAP'))
throw new Error('chapter page missing shared navigation');
for (const token of ['--ink', '@media(max-width:800px)', '@media(max-width:520px)'])
if (!chaptersCss.includes(token)) throw new Error(`missing chapter responsive contract ${token}`);
console.log('chapter route verification passed');
for (const token of [
'The short route',
'full-guide/',
'models/',
'agents/',
'skills/',
'rules/',
'hands-on/starter/',
'skills-review/',
])
if (!landingHtml.includes(token)) throw new Error(`landing page missing ${token}`);
if (landingHtml.includes('app.js'))
throw new Error('landing page should remain a fast, static route map');
console.log('landing route verification passed');
for (const token of [
'data-package-file="skill"',
'data-package-file="references"',
'data-package-file="scripts"',
'data-package-file="assets"',
'id="package-preview"',
'script src="app.js"',
])
if (!skillsHtml.includes(token)) throw new Error(`skills anatomy missing ${token}`);
const skillsApp = read('skills/app.js');
const skillsCss = read('skills/styles.css');
for (const token of [
'const packageFiles',
'function renderPackage',
'addEventListener',
"renderPackage('skill')",
])
if (!skillsApp.includes(token)) throw new Error(`skills anatomy interaction missing ${token}`);
for (const token of [
'grid-template-columns:minmax(190px',
'overflow-wrap:anywhere',
'@media(max-width:800px)',
'prefers-reduced-motion',
])
if (!skillsCss.includes(token))
throw new Error(`skills anatomy responsive contract missing ${token}`);
console.log('skills anatomy verification passed');
console.log('presentation verification passed');
const legacyName = String.fromCharCode(80, 101, 100, 114, 111, 32, 65, 114, 97, 110, 104, 97);
const legacyHandle = legacyName.toLowerCase().replace(' ', '.');
for (const path of [
'submitted-skills/Anonymous Operational Submission/skills/ndo-repro/SKILL.md',
'submitted-skills/Anonymous Operational Submission/skills/ndo-repro/envs.tsv',
'submitted-skills/Anonymous Operational Submission/skills/ndo-repro/lib/env.sh',
'submitted-skills/Anonymous Operational Submission/skills/ndo-repro/ndo-api.sh',
'submitted-skills/Anonymous Operational Submission/skills/ndo-repro/ndo-ship.sh',
'submitted-skills/Anonymous Operational Submission/skills/ndo-repro/reference/bom-Dockerfile_local.example',
'submitted-skills/Anonymous Operational Submission/skills/ndo-repro/reference/dockerfile-local.md',
'skill-reviews/improved/ndo-repro/SKILL.md',
]) {
const source = read(path);
if (
new RegExp(
`${legacyName}|${legacyHandle}|https?:\\/\\/|git\\.netcracker\\.com|artifactorycn|managed\\.netcracker\\.cloud`,
'i',
).test(source)
)
throw new Error(`operational submission privacy leak in ${path}`);
}
for (const source of [reviewCatalog, reviewFiles, reviewJs])
if (source.includes(legacyName) || source.includes(legacyName.replace(' ', '%20')))
throw new Error('operational submission identity remains in review desk source');
if (
!reviewFiles.includes('Anonymous%20Operational%20Submission') ||
!reviewJs.includes("entry.id === 'ndo-repro'")
)
throw new Error('operational submission redaction contract missing');
console.log('review privacy verification passed');
for (const token of [
'currentContent',
'unchangedDraft',
'state.preview = button.dataset.preview',
'state.file = available.find',
'loadSelectedFile',
])
if (!reviewJs.includes(token)) throw new Error(`review file-mode contract missing ${token}`);
if (reviewJs.includes("state.preview = 'original'; syncUrl(); renderDetail(); loadSelectedFile();"))
throw new Error('file selection still resets improved mode');
console.log('review file-mode verification passed');
for (const token of [
'const changeRows',
'function lensMarkup',
'data-lens',
'CHANGE LENS',
'What changed — and why.',
])
if (!reviewJs.includes(token)) throw new Error(`review change-lens contract missing ${token}`);
for (const token of [
'.change-lens',
'.change-rows',
'.skill-diff',
'.diff-lines',
'@media(max-width:620px)',
'prefers-reduced-motion',
])
if (!reviewLensCss.includes(token)) throw new Error(`review change-lens CSS missing ${token}`);
console.log('review change-lens verification passed');
for (const token of [
'.markdown-preview',
'max-height:540px',
'.markdown-table-wrap',
'.markdown-frontmatter',
'.markdown-toc',
'.preview-title',
'.preview-markdown',
'grid-template-columns:minmax(0,1fr)',
'height:120px',
'-webkit-line-clamp:2',
])
if (!read('skills-review/styles.css').includes(token))
throw new Error(`review markdown preview contract missing ${token}`);
console.log('review markdown preview verification passed');
for (const token of ["from './vote.js'", "renderVoteWidget($('#vote-widget'"])
if (!reviewJs.includes(token)) throw new Error(`review vote widget wiring missing ${token}`);
for (const token of [
'id="vote-widget"',
'function renderVoteWidget',
'X-Voter-Id',
'/api/votes',
'Voting is offline',
])
if (!reviewVoteJs.includes(token) && !reviewJs.includes(token))
throw new Error(`review vote widget contract missing ${token}`);
for (const token of ['.vote-widget', '.vote-buttons', '[aria-pressed="true"]'])
if (!read('skills-review/styles.css').includes(token))
throw new Error(`review vote widget CSS missing ${token}`);
if (
!voteService.includes('X-Forwarded-For') ||
(!voteService.includes('one active vote per skill') &&
!voteService.includes('at most one active vote'))
)
throw new Error('vote-service missing IP-based one-vote-per-source contract');
console.log('review vote widget verification passed');