Skip to content

Commit f2c2812

Browse files
committed
build(hooks): replace hardcoded pre-commit hook with lefthook
Replace the 90-line bash script in .githooks/pre-commit and its setup-git-hooks.ts installer with a lefthook.yml configuration. Key change: the entire pre-commit hook is skipped when CI=true, which means the pi-coding-agent-action's create_pull_request tool commits cleanly without triggering typecheck + tests. Previously, a hook failure caused the tool to retry from scratch each time, generating a new branch on every attempt. The hook logic is otherwise identical to what it replaced: - Block stale bun.lockb (Vitest transition guard) - Block manually committed migration files (ALLOW_MIGRATIONS=1 to bypass) - Validate changed GitHub Actions workflow YAML - Biome format staged files in-place and re-stage - Biome lint - TypeScript typecheck - Frontend build (when frontend files changed) - Backend tests (when backend src files changed) Local dev: hooks skip in CI, run normally everywhere else. Hook bypass: LEFTHOOK=0 git commit (replaces --no-verify) prepare script updated to: bunx lefthook install --reset-hooks-path (clears any global core.hooksPath before installing)
1 parent a089f2c commit f2c2812

7 files changed

Lines changed: 123 additions & 162 deletions

File tree

.githooks/pre-commit

Lines changed: 0 additions & 91 deletions
This file was deleted.

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
## Critical Requirements
1010

1111
- **NEVER** commit or push without explicit request, unless running in CI (`CI=true`). In local/interactive sessions, every individual commit and push requires explicit user permission — even if permission was granted earlier in the same session. Do not assume continued consent. In GitHub Actions (CI), commits and pushes are expected as part of the workflow and do not require per-instance approval.
12-
- **NEVER** use --no-verify without user permission.
12+
- **NEVER** use --no-verify or LEFTHOOK=0 without user permission.
1313
- **AVOID** search library type definitions for documentation. Use search and context skills where available first.
1414
- **NEVER** produce implementation or summary documents unless specifically requested.
1515
- **NEVER** edit existing migration files or manually create SQL migrations. See [Migrations](#migrations) below.

CONTRIBUTING.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ cd plexus
88
bun install
99
```
1010

11-
Running `bun install` automatically configures git hooks (via the `prepare` script). No extra setup is needed.
11+
Running `bun install` automatically installs git hooks via [Lefthook](https://github.com/evilmartians/lefthook) (the `prepare` script runs `lefthook install`). No extra setup is needed.
1212

1313
## Database Schema Changes
1414

@@ -40,6 +40,14 @@ git reset HEAD -- packages/backend/drizzle/migrations/ packages/backend/drizzle/
4040

4141
Then commit again with only your schema `.ts` files.
4242

43+
### Skipping hooks
44+
45+
The hooks are skipped automatically when `CI=true` (GitHub Actions). To skip locally in exceptional circumstances:
46+
47+
```bash
48+
LEFTHOOK=0 git commit -m "..."
49+
```
50+
4351
## Drizzle Config Files
4452

4553
The project uses separate Drizzle ORM config files for each database dialect:

bun.lock

Lines changed: 23 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lefthook.yml

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
pre-commit:
2+
# Skip the entire hook in CI — GitHub Actions runs each check independently
3+
# in dedicated workflow steps, making the hook redundant and harmful
4+
# (failed hook = create_pull_request retries with a new branch each time).
5+
skip:
6+
- CI: "true"
7+
8+
commands:
9+
# --- Block stale bun.lockb (Vitest transition cutoff 2025-04-18) ----------
10+
lockfile-check:
11+
skip:
12+
- CI: "true"
13+
run: |
14+
CUTOFF="2025-04-18"
15+
MTIME=$(stat -f "%Sm" -t "%Y-%m-%d" bun.lockb 2>/dev/null \
16+
|| stat -c "%y" bun.lockb 2>/dev/null | cut -d' ' -f1 \
17+
|| echo "1970-01-01")
18+
if [ "$MTIME" \< "$CUTOFF" ]; then
19+
echo ""
20+
echo "[pre-commit] ERROR: bun.lockb is from $MTIME, before $CUTOFF."
21+
echo "The project switched to Vitest. Run: bun install"
22+
echo ""
23+
exit 1
24+
fi
25+
26+
# --- Block manually committed migration files ----------------------------
27+
no-migrations:
28+
skip:
29+
- CI: "true"
30+
run: |
31+
if [ "${ALLOW_MIGRATIONS:-}" = "1" ]; then exit 0; fi
32+
migration_files=$(git diff --cached --name-only -- \
33+
'packages/backend/drizzle/migrations/' \
34+
'packages/backend/drizzle/migrations_pg/' || true)
35+
if [ -n "$migration_files" ]; then
36+
echo ""
37+
echo "[pre-commit] ERROR: Migration files should not be committed manually."
38+
echo "Migrations are auto-generated on main by CI after schema changes merge."
39+
echo ""
40+
echo "Blocked files:"
41+
echo "$migration_files" | sed 's/^/ /'
42+
echo ""
43+
echo "To remove them: git reset HEAD -- packages/backend/drizzle/migrations/ packages/backend/drizzle/migrations_pg/"
44+
echo "To bypass (maintainers only): ALLOW_MIGRATIONS=1 git commit ..."
45+
echo ""
46+
exit 1
47+
fi
48+
49+
# --- Validate changed GitHub Actions workflow YAML -----------------------
50+
workflow-yaml:
51+
glob: ".github/workflows/*.{yml,yaml}"
52+
run: |
53+
for f in {staged_files}; do
54+
bun -e "
55+
const fs = require('fs');
56+
const yaml = require('./packages/backend/node_modules/yaml');
57+
try {
58+
yaml.parse(fs.readFileSync('$f', 'utf8'));
59+
console.log(' ✓ $f');
60+
} catch (e) {
61+
console.error(' ✗ $f:', e.message);
62+
process.exit(1);
63+
}
64+
" || exit 1
65+
done
66+
67+
# --- Biome: format staged source files in-place and re-stage -------------
68+
biome-format:
69+
glob: "*.{ts,tsx,js,jsx,json}"
70+
run: bunx biome format --write {staged_files} && git add {staged_files}
71+
72+
# --- Biome: lint all sources ---------------------------------------------
73+
biome-lint:
74+
run: bunx biome lint .
75+
76+
# --- TypeScript type-check across all packages ---------------------------
77+
typecheck:
78+
run: bun run typecheck
79+
80+
# --- Frontend build (catches broken imports/exports) ---------------------
81+
frontend-build:
82+
glob: "packages/frontend/src/**"
83+
run: bun run build:frontend
84+
85+
# --- Backend tests (only when backend source changes) --------------------
86+
backend-tests:
87+
glob: "packages/backend/src/**"
88+
run: cd packages/backend && bun run test

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"@redocly/cli": "^2.30.1",
77
"agent-browser": "^0.26.0",
88
"bun-types": "latest",
9+
"lefthook": "^2.1.6",
910
"octokit": "^5.0.5",
1011
"pi-agent-browser-native": "latest",
1112
"vitest": "^4.1.5"
@@ -27,8 +28,7 @@
2728
"release": "bun run scripts/release.ts",
2829
"populate-dev": "bun run scripts/populate-dev.ts",
2930
"clear-dev": "bun run scripts/clear-dev.ts",
30-
"prepare": "bun run scripts/setup-git-hooks.ts --no-fail --quiet",
31-
"setup:hooks": "bun run scripts/setup-git-hooks.ts",
31+
"prepare": "bunx lefthook install --reset-hooks-path",
3232
"format": "bunx biome format --write .",
3333
"format:check": "bunx biome format .",
3434
"lint": "bunx biome lint --write .",

scripts/setup-git-hooks.ts

Lines changed: 0 additions & 67 deletions
This file was deleted.

0 commit comments

Comments
 (0)