#!/usr/bin/env bash set -euo pipefail # shellcheck source=lib/portable.sh source "$(dirname "${BASH_SOURCE[0]}")/lib/portable.sh" REQUIRED_FILES=( "docs/PI-RESEARCH.md" "docs/PI.md" "docs/PI-SUPERPOWERS.md" "docs/PI-COMMON-REVIEWER.md" "skills/atlassian/pi/SKILL.md" "skills/create-plan/pi/SKILL.md" "skills/create-plan/pi/templates/continuation-runbook.md" "skills/create-plan/pi/templates/milestone-plan.md" "skills/create-plan/pi/templates/story-tracker.md" "skills/do-task/pi/SKILL.md" "skills/do-task/pi/templates/task-plan.md" "skills/implement-plan/pi/SKILL.md" "skills/web-automation/pi/SKILL.md" "skills/reviewer-runtime/pi/run-review.sh" "skills/reviewer-runtime/pi/notify-telegram.sh" "scripts/install-pi-package.sh" "scripts/sync-pi-package-skills.sh" "pi-package/skills/atlassian/SKILL.md" "pi-package/skills/create-plan/SKILL.md" "pi-package/skills/do-task/SKILL.md" "pi-package/skills/implement-plan/SKILL.md" "pi-package/skills/web-automation/SKILL.md" "package.json" ) # These required-file checks are intentionally hard failures: removing any # required artifact should make this script exit non-zero immediately. for file in "${REQUIRED_FILES[@]}"; do test -f "$file" done test -x skills/reviewer-runtime/pi/run-review.sh test -x skills/reviewer-runtime/pi/notify-telegram.sh test -x scripts/install-pi-package.sh test -x scripts/sync-pi-package-skills.sh find skills/web-automation/pi/scripts -type f -print -quit | grep -q . find skills/atlassian/pi/scripts -type f -print -quit | grep -q . for file in skills/create-plan/pi/SKILL.md skills/do-task/pi/SKILL.md skills/implement-plan/pi/SKILL.md; do grep -q 'docs/PI-SUPERPOWERS.md' "$file" grep -q 'docs/PI-COMMON-REVIEWER.md' "$file" grep -q 'Reviewer CLI: `codex`, `claude`, `cursor`, `opencode`, `pi`, or `skip`' "$file" grep -q 'pi --no-session --no-skills --no-prompt-templates --no-extensions --no-context-files' "$file" grep -q -- '--tools read,grep,find,ls -p' "$file" grep -q 'pi --list-models \[search\]' "$file" done grep -q 'reviewer model is configured independently' docs/PI-COMMON-REVIEWER.md grep -q 'provider-qualified model IDs' docs/PI-COMMON-REVIEWER.md grep -q 'MUST NOT include `write`, `edit`, or `bash`' docs/PI-COMMON-REVIEWER.md grep -q 'Reviewer CLI | codex \\| claude \\| cursor \\| opencode \\| pi' skills/do-task/pi/templates/task-plan.md grep -q 'pi-package/skills/atlassian/scripts' skills/atlassian/pi/SKILL.md grep -q 'pi-package/skills/web-automation/scripts' skills/web-automation/pi/SKILL.md grep -q 'local checkout package install keeps the runtime in `pi-package/skills//scripts`' docs/PI.md grep -q 'install-pi-package.sh --global' docs/PI.md grep -q 'install-pi-package.sh --local' docs/PI.md grep -q 'install-pi-package.sh --global' README.md grep -q 'install-pi-package.sh --local' README.md for family in atlassian create-plan do-task implement-plan web-automation; do source_dir="skills/${family}/pi" source_skill_md="${source_dir}/SKILL.md" skill_name=$(awk '/^name:/ { print $2; exit }' "$source_skill_md") mirror_dir="pi-package/skills/${skill_name}" mirror_skill_md="${mirror_dir}/SKILL.md" test -d "$mirror_dir" test -f "$mirror_skill_md" test "$skill_name" = "$(basename "$mirror_dir")" test "$skill_name" = "$(awk '/^name:/ { print $2; exit }' "$mirror_skill_md")" diff -qr \ --exclude '.DS_Store' \ --exclude 'node_modules' \ "$source_dir" "$mirror_dir" >/dev/null while IFS= read -r -d '' source_path; do rel_path=${source_path#"$source_dir"/} mirror_path="${mirror_dir}/${rel_path}" test -e "$mirror_path" test "$(portable_stat_perms "$source_path")" = "$(portable_stat_perms "$mirror_path")" done < <(find "$source_dir" \ \( -name '.DS_Store' -o -name 'node_modules' \) -prune -o \ -mindepth 1 -print0) done ! grep -nE 'update_plan|plan mode|sub-agent|subagents' \ skills/create-plan/pi/SKILL.md \ skills/do-task/pi/SKILL.md \ skills/implement-plan/pi/SKILL.md node <<'EOF' const fs = require("fs"); const pkg = JSON.parse(fs.readFileSync("package.json", "utf8")); if (!pkg.pi || !Array.isArray(pkg.pi.skills) || pkg.pi.skills.length !== 5) { console.error("package.json must define pi.skills with exactly 5 entries"); process.exit(1); } const expectedSkills = [ "./pi-package/skills/atlassian", "./pi-package/skills/create-plan", "./pi-package/skills/do-task", "./pi-package/skills/implement-plan", "./pi-package/skills/web-automation", ]; if (JSON.stringify(pkg.pi.skills) !== JSON.stringify(expectedSkills)) { console.error("package.json must point pi.skills at the pi-package mirror"); process.exit(1); } if (!Array.isArray(pkg.files) || pkg.files.length === 0) { console.error("package.json must define a non-empty files allowlist"); process.exit(1); } for (const requiredFile of [ "pi-package/skills", "docs/PI-COMMON-REVIEWER.md", "scripts/install-pi-package.sh", "scripts/sync-pi-package-skills.sh", ]) { if (!pkg.files.includes(requiredFile)) { console.error(`package.json files must include ${requiredFile}`); process.exit(1); } } for (const forbiddenFile of [ "skills/atlassian/pi", "skills/create-plan/pi", "skills/do-task/pi", "skills/implement-plan/pi", "skills/web-automation/pi", ]) { if (pkg.files.includes(forbiddenFile)) { console.error(`package.json files must not include ${forbiddenFile}`); process.exit(1); } } if (!Array.isArray(pkg.keywords) || !pkg.keywords.includes("pi-package")) { console.error("package.json must include the pi-package keyword"); process.exit(1); } console.log("package metadata ok"); EOF echo "pi resources verified"