Development Guidelines¶
These are the conventions PAI enforces in review. They are opinionated on purpose: a build system that produces bit-identical artifacts has no room for stylistic drift.
1. Repo layout¶
pai/
├── build.sh # AMD64 top-level build orchestrator
├── arm64/build.sh # ARM64 counterpart (mirror structure)
├── Dockerfile.build # reproducible builder image
├── scripts/
│ ├── chroot/ # runs *inside* the target rootfs, numbered
│ ├── host/ # runs on the builder, never in chroot
│ └── lib/ # shared bash helpers, sourced only
├── prompts/ # LLM prompts used to regenerate docs/agents
├── agents/ # agent role definitions (see AGENTS.md)
├── skills/ # reusable Claude Code skills
├── tests/ # bats tests + fixtures
├── docs/ # end-user and contributor docs
├── website/ # Astro site, deployed separately
└── out/ # build artifacts (gitignored)
Keep this shape. New top-level directories need a maintainer sign-off; see CONTRIBUTING.md.
2. Bash conventions¶
Every script starts with:
Rules the reviewer will check:
- Quote everything.
"$var","$@","${arr[@]}". Unquoted expansions are rejected unless the unquoting is the point (rare). printf, notecho -e.echo -eis non-portable across/bin/shimplementations and has burned us before.- ShellCheck clean. Zero warnings on
shellcheck -x script.sh. Disable with# shellcheck disable=SCxxxxonly with a one-line reason. - Functions are
snake_case, locals are declaredlocal, globals areSCREAMING_SNAKEand defined at the top of the file. - Argument parsing uses a
while+caseloop with long options (--arch=arm64). No positional flag hacks.--helpis mandatory. - No
cdwithoutpushd/popdor a subshell(cd … && …). The build spans dozens of scripts and silent CWD drift is a nightmare. - Traps. Any script that creates a temp dir or mount registers a
trap cleanup EXIT INT TERM.
3. Python conventions¶
Where PAI uses Python (build helpers, agent glue):
- Version pin: Python 3.11, declared in
pyproject.toml. - Formatter + linter:
ruff formatandruff check. No black, no flake8, no isort — ruff owns all three. - Type hints everywhere. Public functions are
mypy --strictclean. - Entry points live in
pai.cli:mainand are exposed via[project.scripts]inpyproject.toml, not ad-hoc shebang scripts. - No
requirements.txt; dependencies live inpyproject.tomlwith upper bounds on major versions.
4. Astro / TS for website/¶
- Linter:
eslintwitheslint-plugin-astroand@typescript-eslint.npm run lintbefore pushing. - Accessibility:
astro check+axe-corein Playwright smoke tests. Any new interactive component needs a keyboard path. - Component location: shared UI in
website/src/components/, page-specific UI stays co-located with the page. - Content collections: docs and changelog are Astro content
collections — see
website/src/content.config.ts. Don't hand-roll markdown loaders.
5. Commit style¶
We use Conventional Commits:
feat(arm64): add Pi 5 device tree overlay
The Pi 5 ships with a newer BCM2712 that the default dtb doesn't
match. Ship a vendor dtb and wire it into the EFI stub. Fixes a boot
hang on power-on.
Refs: #482
Co-authored-by: Jane Doe <jane@example.com>
- Subject ≤ 72 chars, imperative mood, no trailing period.
- Body wraps at 72 columns. Explain why, not what.
- Breaking changes:
feat!:or aBREAKING CHANGE:footer. - Co-author trailer exactly as above — CI validates the format.
6. PR size¶
Small, please. A good PR is ≤ 400 lines diff and does one thing.
If the change must be big (new architecture support, top-level refactor):
- Open a tracking issue with the design sketch.
- Land preparatory refactors as independent PRs.
- Use a stacked series — each PR depends on the previous, each is
reviewable on its own. Tools:
git town,graphite, or plain branch-per-PR.
Reviewers will push back on mega-PRs even if the code is good.
7. Adding a new build step¶
Every new step is four artifacts:
- Prompt file in
prompts/<area>/<step>.md— the LLM-generated origin of the step, checked in for reproducibility. - Chroot or host script in
scripts/chroot/NN-<name>.sh(numbered to fix ordering) orscripts/host/<name>.sh. - Bats test in
tests/<name>.bats— covers happy path and the most likely failure mode. - Docs update — either a new page under
docs/or a section in an existing guide. A step that isn't documented doesn't exist.
8. Dependency hygiene¶
Reproducibility is load-bearing. Therefore:
- Pin versions in anything checksummed into the image:
debootstrap --include=foo=1.2.3-1, apt preferences files, exact git SHAs forgo install/cargo install. - Prefer Debian packages over
curl … | sh. If a vendor only ships curl-bash, wrap it in a script that verifies a checksum and pins the version. - Checksum everything downloaded — SHA-256 minimum, GPG signature
where available.
wget --no-check-certificateis never allowed. - No silent upgrades.
apt-get installin chroot scripts uses--no-install-recommendsand lists every package explicitly.
9. Security review triggers¶
Tag a maintainer with the security role (@maintainers-security — see
MAINTAINERS.md) on any PR that:
- Touches cryptographic code, keyring imports, or signature verification.
- Adds a network-reachable service to the base image.
- Adds or modifies sudoers, PAM, or polkit policy.
- Changes the boot path (initramfs, GRUB/systemd-boot, kernel cmdline).
- Introduces a new dependency that isn't in the Debian main archive.
- Modifies the build provenance chain or SBOM generation.
See SECURITY.md for the disclosure process and AGENTS.md for what the security role actually reviews.