-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlefthook.yml
More file actions
186 lines (177 loc) · 7.78 KB
/
Copy pathlefthook.yml
File metadata and controls
186 lines (177 loc) · 7.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# Canvas Learning System - Lefthook Configuration
# Replaces hand-written .git/hooks/{pre-commit,post-commit,pre-push}
#
# Install: npx lefthook install
# Test: npx lefthook run pre-commit
#
# Architecture:
# pre-commit → parallel checks (spec-sync, python-lint, python-typecheck, ghost-files)
# commit-msg → commitlint + spec-reference
# post-commit → backup-push
# pre-push → frontend-test + backend-smoke
# ============================================================
# Pre-commit: Parallel checks on staged files
# ============================================================
pre-commit:
parallel: true
commands:
# --- Spec Sync: Export OpenAPI when backend API files change ---
spec-sync:
glob: "backend/app/{api,models,schemas}/**/*.py"
run: |
echo "[Spec Sync] API changes detected, exporting OpenAPI..."
cd backend && python ../scripts/spec-tools/export-openapi.py 2>/dev/null && cd ..
if [ -f openapi.json ] && ! git diff --quiet openapi.json 2>/dev/null; then
git add openapi.json
echo "[Spec Sync] + openapi.json added to commit"
fi
if [ -d specs/data/generated ]; then
for schema in specs/data/generated/*.schema.json; do
if [ -f "$schema" ] && ! git diff --quiet "$schema" 2>/dev/null; then
git add "$schema"
echo "[Spec Sync] + $(basename $schema) added to commit"
fi
done
fi
echo "[Spec Sync] Done."
# --- Ghost Files: Detect untracked documentation ---
ghost-files:
run: |
GHOST_STORIES=$(git ls-files --others --exclude-standard -- "docs/stories/" 2>/dev/null || true)
GHOST_EPICS=$(git ls-files --others --exclude-standard -- "docs/epics/" 2>/dev/null || true)
GHOSTS=""
if [ -n "$GHOST_STORIES" ]; then GHOSTS="$GHOST_STORIES"; fi
if [ -n "$GHOST_EPICS" ]; then
if [ -n "$GHOSTS" ]; then GHOSTS="$GHOSTS
$GHOST_EPICS"; else GHOSTS="$GHOST_EPICS"; fi
fi
if [ -n "$GHOSTS" ]; then
COUNT=$(echo "$GHOSTS" | wc -l | tr -d ' ')
echo "[Ghost Files] WARNING: $COUNT untracked doc file(s):"
echo "$GHOSTS"
echo "[Ghost Files] To track: git add <file>"
else
echo "[Ghost Files] No untracked docs found."
fi
# --- Python lint: ruff lint + format check on staged files ---
python-lint:
glob: "{backend,src}/**/*.py"
run: |
# Activate backend venv for ruff
if [ -f "backend/.venv/bin/activate" ]; then source backend/.venv/bin/activate; fi
echo "[Python] Running ruff lint..."
ruff check {staged_files} 2>&1 || exit 1
echo "[Python] Lint OK."
echo "[Python] Checking format..."
ruff format --check {staged_files} 2>&1 || {
echo "[Python] Format check FAILED! Fix: ruff format {staged_files}"
exit 1
}
echo "[Python] Format OK."
# --- Python typecheck: pyright on staged files ---
python-typecheck:
glob: "{backend,src}/**/*.py"
run: |
# Activate backend venv for pyright
if [ -f "backend/.venv/bin/activate" ]; then source backend/.venv/bin/activate; fi
echo "[Python] Running pyright type check..."
pyright {staged_files} 2>&1
PYRIGHT_EXIT=$?
if [ $PYRIGHT_EXIT -ne 0 ]; then
echo "[Python] Type errors found! Review and fix before committing."
echo "[Python] Hint: Some errors may be pre-existing. Focus on YOUR changes."
fi
echo "[Python] Typecheck done (exit: $PYRIGHT_EXIT)."
# ============================================================
# Commit-msg: Conventional commits with commitlint
# ============================================================
commit-msg:
commands:
commitlint:
run: npx commitlint --edit {1}
# --- Spec reference check: code commits must reference FR/spec ---
spec-reference:
run: |
MSG=$(cat "$1" 2>/dev/null || cat .git/COMMIT_EDITMSG 2>/dev/null || echo "")
# Check if commit touches code files
CODE_CHANGED=$(git diff --cached --name-only | grep -E "^(backend/app/|frontend/src/)" || true)
if [ -n "$CODE_CHANGED" ]; then
# Require @spec:, FR-, or PLAN- reference in commit message
if ! echo "$MSG" | grep -qE "(@spec:|FR-|PLAN-|Co-Authored-By)"; then
echo ""
echo " COMMIT BLOCKED: Code change requires spec/plan reference."
echo " Add @spec: domain-nnn, FR-XXX-NN, or PLAN-NNN to your commit message."
echo " Example: fix(FR-EXAM-01): implement exam scoring PLAN-003 @spec: algo-scoring-001"
echo ""
exit 1
fi
# Require Story: trailer for backend/frontend code changes
if ! echo "$MSG" | grep -qE "^Story:\s*[0-9]+\.[0-9]+" && ! echo "$MSG" | grep -q "Co-Authored-By"; then
echo ""
echo " WARNING: backend/frontend change missing 'Story: x.y' trailer."
echo " Add 'Story: 30.23' as a git trailer for traceability."
echo ""
fi
fi
echo "[Spec Ref] OK."
# ============================================================
# Post-commit: Dual-remote push
# ─ backup → archive semantics, --no-verify (never lose work)
# ─ origin → canonical semantics, triggers pre-push tests
# (ChatGPT Deep Research reads from origin)
# ============================================================
post-commit:
commands:
backup-push:
run: git push backup HEAD --quiet --no-verify 2>&1; echo '[Backup] push done'
origin-push:
run: git push origin HEAD --quiet 2>&1; echo '[Origin] push done (pre-push tests ran)'
# ============================================================
# Pre-push: Blocking checks before push
# ============================================================
pre-push:
parallel: true
commands:
# --- Frontend test: Run vitest before push ---
frontend-test:
glob: "frontend/src/**/*.{ts,tsx}"
run: |
if [ -d "frontend/node_modules" ]; then
echo "[Pre-push] Running frontend tests..."
cd frontend && npx vitest run --run 2>&1 | tail -20
echo "[Pre-push] Frontend tests done."
fi
# --- Backend smoke test: A11 regression suite (narrow gate) ---
# WHY NARROW: 2026-04-07 audit discovered 136 failures + 38 collection
# errors in the full backend/tests/unit/ suite (pre-existing test debt,
# unrelated to this gate). The gate was previously silent-passing for
# weeks because it used system `python` which lacks pytest.
#
# WHY A11 SUITE: test_kg_relevance_weighted + test_a11_kg_relevance_e2e
# together form the FR-KG-04 schema drift regression guard (30 tests,
# ~1s, known-green as of commit b50a089). Changes to CanvasNode schema,
# Cypher queries in question_generator, or kg_relevance weighting will
# be caught by this gate.
#
# WHAT'S NOT CAUGHT: changes outside the FR-KG-04 code path. A broader
# gate requires first paying down the 136-failure pre-existing debt.
#
# Exit code is preserved via explicit $? capture (previous `| tail -5`
# pipe silently swallowed pytest exit code).
backend-smoke:
run: |
if [ -f "backend/.venv/bin/python" ]; then
echo "[Pre-push] Running A11 regression suite..."
cd backend
.venv/bin/python -m pytest \
tests/unit/test_kg_relevance_weighted.py \
tests/e2e/test_a11_kg_relevance_e2e.py \
-q --tb=line --no-header \
-p no:cacheprovider \
--override-ini="addopts="
TEST_EXIT=$?
echo "[Pre-push] A11 regression done (exit: $TEST_EXIT)."
exit $TEST_EXIT
else
echo "[Pre-push] backend/.venv not found, skipping."
fi