const assert = require("assert"); const fs = require("fs"); const path = require("path"); const repoRoot = path.resolve(__dirname, "..", "..", ".."); function readText(relativePath) { return fs.readFileSync(path.join(repoRoot, relativePath), "utf8").replace(/\r\n/g, "\n"); } const packageJson = JSON.parse(readText("package.json")); const generatedFiles = JSON.parse(readText("tools/config/generated-files.json")); const ciWorkflow = readText(".github/workflows/ci.yml"); const offlineCatalogBuilder = readText("tools/scripts/build-aas-v1-offline-catalog.js"); const canonicalMergeScript = readText("tools/scripts/merge_canonical_sync_pr.cjs"); const publishWorkflow = readText(".github/workflows/publish-npm.yml"); const releaseWorkflowScript = readText("tools/scripts/release_workflow.js"); const hygieneWorkflowPath = path.join(repoRoot, ".github", "workflows", "repo-hygiene.yml"); const prepareReleaseBlock = releaseWorkflowScript.slice( releaseWorkflowScript.indexOf("function prepareRelease"), releaseWorkflowScript.indexOf("function publishRelease"), ); assert.ok( packageJson.scripts["sync:release-state"], "package.json should expose a deterministic release-state sync command", ); assert.ok( packageJson.scripts["check:warning-budget"], "package.json should expose a warning-budget guardrail command", ); assert.ok( packageJson.scripts["check:readme-credits"], "package.json should expose a README credit validation command", ); assert.ok( packageJson.scripts["merge:batch"], "package.json should expose a maintainer merge-batch command", ); assert.ok( packageJson.scripts["audit:maintainer"], "package.json should expose a maintainer audit command", ); assert.ok( packageJson.scripts["sync:web-assets"], "package.json should expose a web-asset sync command for tracked web artifacts", ); assert.match( packageJson.scripts["sync:release-state"], /sync:web-assets/, "sync:release-state should refresh tracked web assets before auditing release drift", ); assert.match( packageJson.scripts["sync:release-state"], /check:warning-budget/, "sync:release-state should enforce the frozen validation warning budget", ); assert.match( packageJson.scripts["sync:release-state"], /chain/, "sync:release-state should rebuild canonical release and plugin state", ); assert.match(packageJson.scripts.chain, /plugin-compat:sync/); assert.match(packageJson.scripts.chain, /bundles:sync/); const releaseSuiteBlock = releaseWorkflowScript.slice( releaseWorkflowScript.indexOf("function runReleaseSuite"), releaseWorkflowScript.indexOf("function runReleasePreflight"), ); assert.match( releaseSuiteBlock, /sync:release-state[\s\S]*plugin-compat:check[\s\S]*bundles:check/, "every release suite should explicitly prove plugin compatibility and bundle alignment after regeneration", ); assert.match( packageJson.scripts["sync:repo-state"], /sync:web-assets/, "sync:repo-state should refresh tracked web assets before maintainer audits", ); assert.match( packageJson.scripts["sync:repo-state"], /check:warning-budget/, "sync:repo-state should enforce the frozen validation warning budget", ); assert.match( packageJson.scripts.chain, /build:aas-v1-catalog/, "chain should refresh the offline AAS v1 catalog after skill index generation", ); assert.strictEqual( packageJson.scripts["app:install"], "cd apps/web-app && npm ci", "app:install should use npm ci for deterministic web-app installs", ); for (const filePath of [ "apps/web-app/public/sitemap.xml", "apps/web-app/public/skills.json.backup", "data/plugin-compatibility.json", "data/aas-v1/", ".agents/plugins/", ".claude-plugin/plugin.json", ".claude-plugin/marketplace.json", "plugins/", ]) { assert.ok( generatedFiles.derivedFiles.includes(filePath), `generated-files derivedFiles should include ${filePath}`, ); } for (const retiredCoreAsset of [ "tools/lib/aas-v1/metadata-overrides.v1.json", "tools/lib/aas-v1/metadata-reviews.v1.json", "tools/lib/aas-v1/review-queue.v1.json", ]) { assert.ok( !generatedFiles.derivedFiles.includes(retiredCoreAsset), `generated-files should not retain retired Core policy asset ${retiredCoreAsset}`, ); } const webAppGitignore = readText("apps/web-app/.gitignore"); assert.match( webAppGitignore, /^coverage$/m, "web-app coverage output should be ignored so maintainer sync jobs stay clean", ); for (const filePath of [ "README.md", "package.json", "apps/web-app/index.html", "apps/web-app/public/llms.txt", "docs/users/getting-started.md", "docs/users/bundles.md", "docs/users/claude-code-skills.md", "docs/users/gemini-cli-skills.md", "docs/users/usage.md", "docs/users/visual-guide.md", "docs/users/kiro-integration.md", "docs/maintainers/repo-growth-seo.md", "docs/maintainers/skills-update-guide.md", "docs/integrations/jetski-cortex.md", "docs/integrations/jetski-gemini-loader/README.md", ]) { assert.ok( generatedFiles.mixedFiles.includes(filePath), `generated-files mixedFiles should include ${filePath}`, ); } assert.match( ciWorkflow, /- name: Run repo-state sync[\s\S]*?run: npm run sync:repo-state/, "main CI should use the unified repo-state sync command", ); assert.ok( ciWorkflow.indexOf("- name: Install PR policy dependencies") < ciWorkflow.indexOf("- name: Intake PR change"), "PR policy dependencies must be installed before preflight executes", ); assert.match( ciWorkflow, /GH_TOKEN: \$\{\{ github\.token \}\}/, "main CI should provide GH_TOKEN for contributor synchronization", ); assert.match( ciWorkflow, /main-validation-and-sync:[\s\S]*?concurrency:[\s\S]*?group: canonical-main-sync[\s\S]*?cancel-in-progress: false/, "main validation should serialize canonical sync writers", ); assert.match( ciWorkflow, /pip install -r tools\/requirements\.txt/g, "CI workflows should install Python dependencies from tools/requirements.txt", ); assert.match( ciWorkflow, /- name: Audit npm dependencies[\s\S]*?run: npm audit --audit-level=high/, "CI should run npm audit at high severity", ); assert.match( ciWorkflow, /source-validation:[\s\S]*?- uses: actions\/checkout@[a-f0-9]{40}[\s\S]*?with:[\s\S]*?fetch-depth: 0/, "source-validation should use an unshallowed checkout so base-branch diffs have a merge base", ); const pagesWorkflow = readText(".github/workflows/pages.yml"); assert.match( pagesWorkflow, /- name: Checkout[\s\S]*?uses: actions\/checkout@[a-f0-9]{40}[\s\S]*?with:[\s\S]*?fetch-depth: 0[\s\S]*?persist-credentials: false/, "Pages should use an unshallowed, credential-free checkout because canonical provenance validation reads git history", ); assert.match( ciWorkflow, /artifact-preview:[\s\S]*?actions\/checkout@[a-f0-9]{40}[\s\S]*?fetch-depth: 0[\s\S]*?persist-credentials: false/, "artifact-preview should retain history because canonical provenance generation reads git history", ); assert.doesNotMatch( offlineCatalogBuilder, /buildMetadataOverrides|metadata-overrides|review-queue/, "offline catalog builds should not bind retired Core policy or review assets", ); assert.match( offlineCatalogBuilder, /catalogSchemaVersion:\s*versions\.catalogSchemaVersion/, "offline catalog manifests should bind the explicit catalog schema version", ); assert.match( ciWorkflow, /source-validation:[\s\S]*?- name: Fetch base branch[\s\S]*?run: git fetch origin "\$\{\{ github\.base_ref \|\| 'main' \}\}"/, "source-validation should fetch the PR base branch before changed-skill README credit checks", ); assert.match( ciWorkflow, /- name: Verify README source credits for changed skills[\s\S]*?run: npm run check:readme-credits -- --base "origin\/\$\{\{ github\.base_ref \}\}" --head HEAD/, "PR CI should verify README source credits for changed skills", ); assert.match( ciWorkflow, /source-validation:[\s\S]*?- name: Fetch base branch[\s\S]*?- name: Install npm dependencies[\s\S]*?- name: Verify README source credits for changed skills/, "source-validation should fetch the base branch before running the changed-skill README credit check", ); assert.match( ciWorkflow, /main-validation-and-sync:[\s\S]*?- name: Audit npm dependencies[\s\S]*?run: npm audit --audit-level=high/, "main validation should enforce npm audit before syncing canonical state", ); assert.doesNotMatch( ciWorkflow, /main-validation-and-sync:[\s\S]*?continue-on-error: true/, "main validation should not treat high-severity npm audit findings as non-blocking", ); assert.doesNotMatch( ciWorkflow, /^ - name: Generate index$/m, "main CI should not keep the old standalone Generate index step", ); assert.doesNotMatch( ciWorkflow, /^ - name: Update README$/m, "main CI should not keep the old standalone Update README step", ); assert.doesNotMatch( ciWorkflow, /^ - name: Build catalog$/m, "main CI should not keep the old standalone Build catalog step", ); assert.match( ciWorkflow, /uses: peter-evans\/create-pull-request@[a-f0-9]{40}/, "main CI should publish canonical drift through a pinned pull-request action", ); assert.match( ciWorkflow, /branch: automation\/canonical-repo-state/, "main CI should maintain one fixed canonical-sync branch", ); assert.doesNotMatch( ciWorkflow, /gh workflow run ci\.yml --ref "\$PR_BRANCH" -f canonical_sync_pr=true/, "canonical checks must remain associated with the pull request", ); assert.match( canonicalMergeScript, /actions\/runs\/\$\{run\.id\}\/rerun/, "main CI should restart the PR-associated workflow suppressed for a GITHUB_TOKEN-created PR", ); assert.match( ciWorkflow, /- name: Reproduce canonical-sync PR from main[\s\S]*?GH_TOKEN: \$\{\{ github\.token \}\}[\s\S]*?run: npm run sync:repo-state/, "canonical reproducibility should scope a read token to contributor synchronization", ); assert.doesNotMatch( ciWorkflow, /git push origin (?:HEAD|main)/, "main CI must not push directly to protected main", ); assert.match( ciWorkflow, /git ls-files --others --exclude-standard/, "main CI should fail if canonical sync leaves unmanaged untracked drift", ); assert.match( ciWorkflow, /git diff --name-only/, "main CI should fail if canonical sync leaves unmanaged tracked drift", ); assert.ok(fs.existsSync(hygieneWorkflowPath), "repo hygiene workflow should exist"); const hygieneWorkflow = readText(".github/workflows/repo-hygiene.yml"); assert.match(hygieneWorkflow, /^on:\n workflow_dispatch:\n schedule:/m, "repo hygiene workflow should support schedule and manual runs"); assert.match( hygieneWorkflow, /concurrency:\n\s+group: canonical-main-sync\n\s+cancel-in-progress: false/, "repo hygiene workflow should serialize canonical sync writers with main CI", ); assert.match( hygieneWorkflow, /GH_TOKEN: \$\{\{ github\.token \}\}/, "repo hygiene workflow should provide GH_TOKEN for gh-based contributor sync", ); assert.match( hygieneWorkflow, /pip install -r tools\/requirements\.txt/, "repo hygiene workflow should install Python dependencies from tools/requirements.txt", ); assert.match( hygieneWorkflow, /run: npm audit --audit-level=high/, "repo hygiene workflow should block on high-severity npm audit findings before syncing", ); assert.match( hygieneWorkflow, /run: npm run sync:repo-state/, "repo hygiene workflow should run the unified repo-state sync command", ); assert.match( hygieneWorkflow, /generated_files\.js --include-mixed/, "repo hygiene workflow should resolve and stage the mixed generated files contract", ); assert.match( hygieneWorkflow, /uses: peter-evans\/create-pull-request@[a-f0-9]{40}/, "repo hygiene should publish canonical drift through a pinned pull-request action", ); assert.doesNotMatch( hygieneWorkflow, /gh workflow run ci\.yml --ref "\$PR_BRANCH" -f canonical_sync_pr=true/, "repo hygiene should use the exact PR-associated workflow instead of a redundant dispatch", ); assert.doesNotMatch( hygieneWorkflow, /git push origin (?:HEAD|main)/, "repo hygiene must not push directly to protected main", ); assert.match( hygieneWorkflow, /git ls-files --others --exclude-standard/, "repo hygiene workflow should fail if canonical sync leaves unmanaged untracked drift", ); assert.match( hygieneWorkflow, /git diff --name-only/, "repo hygiene workflow should fail if canonical sync leaves unmanaged tracked drift", ); assert.match(publishWorkflow, /run: npm ci/, "npm publish workflow should install dependencies"); assert.match( publishWorkflow, /node-version: "22\.23\.1"/, "npm publish workflow should use the supported Node 22 runtime", ); assert.match( publishWorkflow, /npm publish --tag next/, "npm prereleases should publish to the next dist-tag", ); assert.match( publishWorkflow, /else[\s\S]*npm publish --tag latest/, "stable npm releases should publish explicitly to latest", ); assert.doesNotMatch( publishWorkflow, /^\s*npm publish\s*$/mu, "npm releases should never publish without an explicit dist-tag", ); assert.match( publishWorkflow, /semver\.test/, "npm releases should fail closed on an invalid package version", ); assert.match( publishWorkflow, /pip install -r tools\/requirements\.txt/, "npm publish workflow should install Python dependencies from tools/requirements.txt", ); assert.match( publishWorkflow, /run: npm audit --audit-level=high/, "npm publish workflow should block on high-severity npm audit findings", ); assert.match( publishWorkflow, /run: npm run app:install/, "npm publish workflow should install web-app dependencies before building", ); assert.match( publishWorkflow, /run: npm run sync:release-state/, "npm publish workflow should verify canonical release artifacts", ); assert.match( publishWorkflow, /run: git diff --exit-code/, "npm publish workflow should fail if canonical sync would leave release drift", ); assert.match(publishWorkflow, /run: npm run test/, "npm publish workflow should run tests before publish"); assert.match(publishWorkflow, /run: npm run app:build/, "npm publish workflow should build the app before publish"); assert.match( releaseWorkflowScript, /runCommand\("npm", \["run", "app:install"\], projectRoot\);[\s\S]*runCommand\("npm", \["run", "app:build"\], projectRoot\);/, "release workflow should install web-app dependencies before building the app", ); assert.ok( prepareReleaseBlock.indexOf('["run", "sync:metadata", "--", "--refresh-volatile"]') < prepareReleaseBlock.indexOf("runReleaseSuite(projectRoot)"), "release preparation should refresh volatile metadata before generating canonical release artifacts", ); assert.match( releaseWorkflowScript, /const releaseBranch = `release\/v\$\{version\}`/, "release preparation should use a protected release branch", ); assert.doesNotMatch( releaseWorkflowScript, /\["push", "origin", "main"\]/, "release tooling must not push directly to protected main", ); assert.match( publishWorkflow, /npm pack --dry-run --json/, "npm publish workflow should dry-run package creation before publishing", );