diff --git a/.envrc b/.envrc index 8392d159f..03113d3db 100644 --- a/.envrc +++ b/.envrc @@ -1 +1,5 @@ -use flake \ No newline at end of file +use flake + +# Repo `gh` policy shim: publishing runs the ship gate first (see AGENTS.md). +# Installed by scripts/install-git-hooks.mjs; run `direnv allow` once after cloning. +PATH_add .tools/bin diff --git a/.githooks/pre-push b/.githooks/pre-push new file mode 100755 index 000000000..8aa31a67d --- /dev/null +++ b/.githooks/pre-push @@ -0,0 +1,16 @@ +# Agent-only ship gate (humans: no-op). See scripts/agent-pre-push.mjs. +# Draft / no PR: free push. Ready PR: check + lint + test. +# Publish: pnpm pr:ready, or raw `gh pr ready` — the shim runs the same gate first. +# +# Humans: SKIP_AGENT_PREPUSH=1 git push +# Agents: never SKIP_AGENT_PREPUSH / never --no-verify +# Nested Git commands must discover their own repositories, not inherit this +# hook's GIT_DIR/GIT_WORK_TREE from the outer push. +for name in $(git rev-parse --local-env-vars); do + unset "$name" +done +# Plain `node`, not `pnpm exec node`: pnpm runs a dependency-status check first +# and will try to purge node_modules when the lockfile moved (a rebase does it), +# which aborts without a TTY — the hook then fails with a pnpm error instead of +# anything about the gate. The gate puts node_modules/.bin on PATH itself. +node scripts/agent-pre-push.mjs diff --git a/.gitignore b/.gitignore index 99d20f97a..e98229014 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,7 @@ run.sh # Generated publish staging directories packages/*/.publish/ + +# agent gh shim (generated) + local ship-gate state +.tools/ +.run/ diff --git a/AGENTS.md b/AGENTS.md index a152daf98..61bcf99c5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,11 +11,40 @@ This is the Effect App library repository, focusing on functional programming pa Always open a PR for agent work that changes the repo — do not leave finished work only on a local branch. -- **Draft early**: open a draft PR as soon as there is a meaningful commit (or when starting multi-step work that will land), so review/CI can track progress while local validation is still in flight. +- **Draft early**: open a draft PR as soon as there is a meaningful commit (or when starting multi-step work that will land), so review/CI can track progress while local validation is still in flight. Pushes to a draft are free — the gate does not run on them. - **Keep the PR current**: push commits as you go; update the PR body if scope shifts. -- **Ready when done**: after mandatory validation (`pnpm lint-fix`, `pnpm check`, and relevant tests) passes, mark the PR ready for review (undraft / publish). Do not leave a finished, validated change as draft. +- **Ready when done**: publish with `pnpm pr:ready`, or with plain `gh pr ready` — both run the ship gate first and undraft only if it passes. Do not hand-run the checks beforehand and do not leave a finished, validated change as draft. - **Base**: target `main` unless the work is explicitly stacked on another branch. +### The gate runs the checks — you do not + +`.githooks/pre-push` is the agent ship gate. It is a **no-op for humans** and +fires only for coding agents (`GROK_AGENT` / `T3_AGENT` / `AI_AGENT` / Claude / +Cursor / Codex env markers). + +| Branch PR state | Agent pre-push | +| ----------------------- | --------------------------------- | +| No open PR | **skip** — free push | +| **Draft** | **skip** — free push, share early | +| **Ready** for review | full ship gate | +| `gh` / PR lookup failed | full ship gate (**fail closed**) | + +The gate is `pnpm check` → `pnpm lint` → `pnpm test`, which is what CI runs and +nothing more. This is a library monorepo — no application to stand up, no browser +suite — so the unit run *is* the gate. It caches the validated HEAD SHA in +`.run/agent-ship-gate.json`, so **one commit is validated once** however many +times you push or publish it. Force a re-run with `AGENT_SHIP_GATE_FORCE=1`. + +**Do not hand-run `pnpm check` / `lint` / `test` as routine verification.** +Validating the same commit repeatedly costs the same each time and proves nothing +the first run did not. While iterating, narrow proof is the right tool — the one +test you are fixing, or a typecheck of the package you touched. Whole-gate runs +belong to the push. + +`.envrc` puts the `gh` shim on `PATH`; run **`direnv allow`** once after cloning +(`command -v gh` should print `.tools/bin/gh`). Never `--no-verify`, and never set +`SKIP_AGENT_PREPUSH` — that is the human escape hatch. + ### Core Principles - **Zero Tolerance for Errors**: All automated checks must pass @@ -48,13 +77,18 @@ Anti-patterns that mean you skipped the checklist: - Adding a sleep / retry to "give it time to work" instead of finding the missing wake signal. - Disabling a hook (`--no-verify`) or a check to make the diff land. -### Mandatory Validation Steps +### Validation + +The ship gate owns validation — see *The gate runs the checks — you do not*. It +runs `pnpm check` → `pnpm lint` → `pnpm test` on publish and on pushes to a ready +PR, once per commit. -After **all** changes are made, run these from the **repo root**: +`pnpm lint-fix` is the one thing worth running by hand, because it *writes*: it +formats and auto-fixes across packages, and the gate only reports what it would +have fixed. Run it when you are done editing, stage what it changes, then push. -1. `pnpm lint-fix` — auto-formats and fixes lint issues across all packages; apply all resulting changes -2. `pnpm check` — type-checks all packages (dependency changes in one package can break others); fix all reported errors - - If type checking continues to fail, run `pnpm clean` to clear caches, then re-run `pnpm check` +If type checking fails in a way that makes no sense against the diff, `pnpm clean` +clears the caches — stale incremental state produces phantom errors.