--- issue: 14 issue_title: "Set up prek for pre-commit linting (Biome + markdownlint)" --- # Set up prek for pre-commit linting ## Problem Statement During the #13 retro, a Biome version-skew issue surfaced: running `npm run lint:fix` locally with a different Biome version than the one pinned in `package-lock.json` produced formatting changes that CI rejected. A pre-commit hook running the project-local linters would catch these mismatches before they reach CI, saving round-trip time on failed builds. ## Goals - Install and configure [prek](https://prek.j178.dev/) as the git hook manager. - Run `npx biome check` on staged files in the pre-commit hook so the project-pinned Biome version is always used. - Run `npx markdownlint-cli2` on staged Markdown files in the pre-commit hook. - Document the setup so contributors know how to install hooks and what they enforce. ## Non-Goals - Adding new lint rules or changing existing Biome/markdownlint configuration — out of scope. - Running the full test suite or type-check in the pre-commit hook — too slow for a pre-commit gate; CI handles these. - Migrating CI to use prek — CI continues to run `npm run lint` and `npm run lint:md` directly. ## Background - **Origin:** #13 retro (`docs/retro/0013-consolidate-session-start-handlers.md`) — a `style:` commit with wrong Biome formatting caused a CI failure and 2 extra fixup commits. - **Existing lint scripts:** `npm run lint` (`biome check .`), `npm run lint:md` (`markdownlint-cli2 '*.md' 'docs/**/*.md' '.pi/prompts/**/*.md'`). - **CI:** `.github/workflows/ci.yml` runs `npm run lint` and `npm run lint:md` as separate steps. - **Permission surface:** none — this is a developer-tooling concern with no policy semantics. - **On-disk identity:** no impact — prek config lives in `.prek.yaml` at the repo root, not in the Pi config directory. ## Design Overview [prek](https://prek.j178.dev/) is a Rust-based git hook manager (drop-in replacement for `pre-commit`). It reads `.prek.yaml` at the repo root and installs git hooks that run configured checks on staged files. ### Hook configuration The `.prek.yaml` file defines two hooks under `pre-commit`: 1. **Biome** — runs `npx biome check` on staged JS/TS/JSON files. Using `npx biome` ensures the project-local version from `node_modules` is used, preventing version skew. 2. **markdownlint** — runs `npx markdownlint-cli2` on staged `.md` files. Both hooks should operate only on staged files (prek handles this via `types`/`files` filters and passes matching filenames to the command). ### Developer setup After cloning, a developer runs `prek install` once to set up the git hooks. This is a one-time step documented in `README.md` and optionally wired as a `prepare` npm script so `npm install` auto-installs hooks. ## Module-Level Changes ### Added - **`.prek.yaml`** — prek hook configuration with Biome and markdownlint hooks scoped to relevant file types. ### Changed - **`package.json`** — add a `prepare` script (`prek install`) so hooks are installed automatically after `npm install`. This is the standard convention for git hook managers. - **`README.md`** — document the pre-commit setup: what it runs, how to install hooks manually if needed, how to skip hooks (`git commit --no-verify`) in emergencies. - **`.gitignore`** — no changes needed; prek does not generate files that need ignoring. ### Not changed - **`biome.json`** — no changes; the hook invokes the existing config. - **`.markdownlint-cli2.yaml`** — no changes; the hook invokes the existing config. - **`.github/workflows/ci.yml`** — no changes; CI continues to run the full lint suite independently. ## TDD Order This issue is primarily a configuration/tooling change with no application logic, so the "test" cycles are validation-oriented rather than unit-test-oriented. 1. **Verify prek is installable and `.prek.yaml` is valid.** Create `.prek.yaml` with both hooks. Run `prek install` and confirm hooks are registered (`.git/hooks/pre-commit` exists and delegates to prek). Commit: `feat: add prek pre-commit config for Biome and markdownlint (#14)` 2. **Verify Biome hook catches lint violations on staged files.** Stage a file with a deliberate Biome violation, run `git commit` (or `prek run pre-commit`), confirm it fails. Stage a clean file, confirm it passes. Commit: `test: verify Biome pre-commit hook catches violations (#14)` (manual verification, no committed test file) 3. **Verify markdownlint hook catches Markdown violations on staged files.** Stage a `.md` file with a deliberate violation (e.g., trailing spaces), run the hook, confirm failure. Stage a clean `.md` file, confirm it passes. Commit: `test: verify markdownlint pre-commit hook catches violations (#14)` (manual verification) 4. **Add `prepare` script and update README.** Wire `prek install` into `package.json`'s `prepare` script. Add a "Pre-commit hooks" section to `README.md`. Commit: `docs: document prek pre-commit setup (#14)` ## Risks and Mitigations | Risk | Mitigation | | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | prek not installed on contributor's machine → `prepare` script fails | Document prek installation in README. The `prepare` script can use `prek install` with a guard (`command -v prek` or similar) so it warns but does not block `npm install`. | | Hook runs full-repo lint instead of staged-only → slow commits | Configure prek to pass only staged filenames to each command. Verify with a manual test. | | Could this silently weaken a permission? | No — this change is purely developer tooling. It adds a pre-commit gate; it does not touch any permission surface, policy file, or runtime code. | | markdownlint glob mismatch between hook and `npm run lint:md` | Use the same glob patterns in `.prek.yaml` as in `package.json`'s `lint:md` script. | | `npx biome` falls back to a global install if local is missing | `npx` resolves from `node_modules/.bin` first when run inside a project with a lockfile. Document that `npm install` must be run before committing. | ## Open Questions 1. **Should `prepare` silently skip if prek is not installed?** A guard like `command -v prek >/dev/null 2>&1 && prek install || true` avoids blocking `npm install` for contributors who haven't installed prek yet, but it also means hooks silently won't exist. Decide during implementation. 2. **Should we pin a prek version?** prek is installed globally (via `cargo install` or a binary download), not as an npm dependency. Pinning is not straightforward — defer unless version-specific breakage appears.