diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..50ff3e96 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,76 @@ + + +## What this changes + + + +## How you verified it + + + +- [ ] `cargo fmt --check` +- [ ] clippy with `-D warnings` on the crates I changed +- [ ] tests pass on the crates I changed +- [ ] `cargo xtask layers` and `cargo xtask assets` (if you touched crate boundaries or any asset) + +**Tests.** New behaviour needs a test at the right scale — a property test, a unit test, a +crate integration test or an end-to-end one. For a bug fix, **please check that the test +fails before your fix** by reverting the fix and watching it go red. A test nobody has seen +fail is not evidence that it pins anything. + +## Never-crash + +Every PDF, script, MCP call, settings file and keystroke is untrusted input (`AGENTS.md` §4). + +- [ ] No new `unwrap`/`expect`/`panic!`/`unreachable!`/`todo!` outside tests, unless it is + provably infallible with a comment saying why +- [ ] No indexing or slicing with input-derived positions (`get`, and strings only at char + boundaries); checked or saturating arithmetic; allocations sized by input are capped; + recursion is bounded +- [ ] No `unsafe` +- [ ] Any crash this fixes has a small synthetic regression test + +## Provenance + +This is the part that cannot be undone after a merge, and the part a new contributor is +least likely to know about. Please read `AGENTS.md` §1 before adding any asset. + +- [ ] No asset from an Adobe product, and no Adobe icon recreated, traced or imitated +- [ ] No font files committed here (they live in + [storytold/craft-fonts](https://github.com/storytold/craft-fonts)) +- [ ] No GPL/AGPL code copied in — MuPDF, Ghostscript, Poppler, veraPDF and DSS may only be + run as external oracle processes +- [ ] Behaviour comes from the specification or black-box observation, **not** from reading + another implementation's source +- [ ] Every new asset has an `ATTRIBUTION.toml` entry and `cargo xtask assets` passes + +## Security + +- [ ] `--root` confinement still holds on every path, including ones reached by nested + tool dispatch +- [ ] MCP and the UI control channel stay opt-in, loopback-only and token-authenticated +- [ ] No new outbound network access (that decision is the owner's) + +## Parity and reach + +- [ ] `parity/acrobat-features.toml` updated in this PR, with `notes` saying what is *still* + missing — `shipped` means "exists and tested", not "as good as Acrobat" +- [ ] Reachable headlessly through an `automation` tool where that applies (`AGENTS.md` §3), + so agents can drive it and not just the UI +- [ ] Docs updated if this changes a documented behaviour (`docs/`, crate README) + +## Anything a reviewer should look at first + + diff --git a/CLAUDE.md b/CLAUDE.md index 77d6445b..12779b1e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -31,8 +31,15 @@ PdfCraft is a clean-room, open-source, Rust-native PDF application targeting Ado - No `unsafe` (`unsafe_code = "forbid"`). - Every crash fix gets a synthetic regression test. See `AGENTS.md` §4 (and, for team members, the internal `craftrules/standards/never-crash.md`). - **Rust only** in the product and build (`xtask`). No handwritten JS/TS. -- **Quality gates** before every commit: `cargo fmt --check`, `cargo clippy --workspace -- -D warnings`, `cargo test --workspace`, and the wasm check once `xtask ci` exists (M0). -- **Commits:** one task id per commit (e.g. `M1.4: xref stream reader`). Commit only green states. End messages with the attribution line required by the environment. +- **Review every update as you make it, at four zooms.** Not an end-of-task pass — a per-update habit, so a defect never gets built on. **Clause:** checked arithmetic, `get()` not `[i]`, char-boundary slicing, the right error through `?`, `<` against `<=`, the 1-based/0-based seam, capped allocations. **Function:** is it total — name an input that panics; walk empty, zero, max, malformed, cyclic; is the return type honest. **File/module:** two sources of truth, dead or duplicated code, is the public surface still smallest and the doc comment still true. **Project:** layering, wasm, undo/redo, save fidelity, encryption and permissions, a matching `automation` tool or registry entry, and whether a `parity/` status or note just became untrue. Scale the zoom to the change, and fix what a level turns up before widening. + - Tests mirror the zooms: property → unit → crate integration → end-to-end. + - **Prove a test fails before the fix by actually reverting the fix.** A test nobody has seen fail is not evidence. This has caught two worthless tests in this repo: one confinement test passed with path resolution switched off entirely, because the tools failed on a nonexistent document id before reaching the path. + - **Rebasing is a review step, not a mechanical one.** Read every hunk you resolve, and the auto-merged hunks in the files you touched too — `main` moves fast enough that a file can change by hundreds of lines under a branch. +- **External PRs are held to safety, security and completeness before style.** Provenance first (no Adobe-derived asset, no committed font, no copied GPL/AGPL code, behaviour from the spec and not from reading another implementation), then no new panic path or `unsafe`, then `--root` confinement on every path including ones reached by nested dispatch, opt-in-only MCP and control channel, no new outbound network, then a headless `automation` tool, tests at the right zoom and an honest `parity/` entry. Provenance is the part a contributor cannot be expected to know, and the only part that cannot be undone after merge. +- **Quality gates** before every commit: `cargo fmt --check`, clippy with `-D warnings`, the tests, `cargo xtask layers`, `cargo xtask assets`, and the wasm check once `xtask ci` exists (M0). + - **Scope builds and tests to the crates you touched** — `cargo clippy -p -j 2 --all-targets` and `cargo test -p -j 2`, under a `timeout`. A full `--workspace` build has locked up a development machine's CPU; it is CI's job and CI runs it on every PR. Run the workspace sweep locally only when you have a reason to and the machine is free. +- **Commits and PRs:** `main` is the only integration branch and is **never pushed to directly**. Branch from an up-to-date `main` as `/` (`feat/`, `fix/`, `docs/`, `chore/`, `refactor/`), one task id per commit (e.g. `M1.4: xref stream reader`), commit only green states, and open a PR into `main` using `.github/pull_request_template.md`. Merge only when CI is green. + - **`main` moves fast enough that PRs rot.** It took 396 commits in the three days to 2026-10-12, and most open PRs are conflicting at any given time. Rebase your own branch the same day `main` moves under it, and ask the author to rebase theirs — never rewrite a contributor's branch. Conflict risk tracks how *contested* the files are, not how large the change is: tooling, tests and docs are nearly free, while `crates/fonts`, `ui-egui` and the i18n catalogues are where several people collide at once. ## Running and looking at the app - `cargo run -p pdfcraft -- ` opens the desktop app.