Skip to content

Commit 099d8ac

Browse files
author
jgstern
committed
Merge pull request 'Release v2.0.2' (#770) from jgstern-agent/release-2.0.2 into main
Reviewed-on: https://codeberg.org/iterabloom/hypergumbo/pulls/770
2 parents 30622d6 + 5026198 commit 099d8ac

71 files changed

Lines changed: 2015 additions & 790 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.githooks/pre-commit

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,20 @@ else
102102
exit 1
103103
fi
104104

105+
# Check README sync (meta-package must match root)
106+
echo -n " README sync... "
107+
if [ -f "packages/hypergumbo/README.md" ] && [ -f "README.md" ]; then
108+
if diff -q README.md packages/hypergumbo/README.md >/dev/null 2>&1; then
109+
echo -e "${GREEN}${NC}"
110+
else
111+
echo -e "${RED}${NC}"
112+
echo ""
113+
echo -e "${RED}Meta-package README differs from root README. Run:${NC}"
114+
echo " cp README.md packages/hypergumbo/README.md"
115+
exit 1
116+
fi
117+
else
118+
echo -e "${YELLOW}skipped (files not found)${NC}"
119+
fi
120+
105121
echo -e "${GREEN}✅ Pre-commit checks passed${NC}"

.github/workflows/ci.yml

Lines changed: 150 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,9 @@ jobs:
162162
pip install --upgrade pip
163163
# Install from packages if available, otherwise legacy
164164
if [ -d "packages/hypergumbo-core" ]; then
165-
pip install -e packages/hypergumbo-core -e packages/hypergumbo-lang-mainstream -e packages/hypergumbo-lang-common -e packages/hypergumbo-lang-extended1 -e "packages/hypergumbo[dev]"
165+
# Install test deps directly - NEVER use meta-package (it pulls from PyPI)
166+
pip install pytest pytest-cov pytest-xdist ruff bandit pip-audit jsonschema yamllint
167+
pip install -e packages/hypergumbo-core -e packages/hypergumbo-lang-mainstream -e packages/hypergumbo-lang-common -e packages/hypergumbo-lang-extended1 -e packages/hypergumbo
166168
else
167169
pip install -e .[dev]
168170
fi
@@ -178,6 +180,8 @@ jobs:
178180
timeout-minutes: 30
179181
steps:
180182
- uses: actions/checkout@v4
183+
with:
184+
fetch-depth: 0
181185
- name: Set up Python
182186
uses: actions/setup-python@v5
183187
with:
@@ -188,7 +192,9 @@ jobs:
188192
pip install --upgrade pip
189193
# Install from packages if available, otherwise legacy
190194
if [ -d "packages/hypergumbo-core" ]; then
191-
pip install -e packages/hypergumbo-core -e packages/hypergumbo-lang-mainstream -e packages/hypergumbo-lang-common -e packages/hypergumbo-lang-extended1 -e "packages/hypergumbo[dev]"
195+
# Install test deps directly - NEVER use meta-package (it pulls from PyPI)
196+
pip install pytest pytest-cov pytest-xdist ruff bandit pip-audit jsonschema yamllint
197+
pip install -e packages/hypergumbo-core -e packages/hypergumbo-lang-mainstream -e packages/hypergumbo-lang-common -e packages/hypergumbo-lang-extended1 -e packages/hypergumbo
192198
else
193199
pip install -e .[dev]
194200
fi
@@ -205,94 +211,172 @@ jobs:
205211
echo "sentence-transformers not installed, embedding tests will be skipped"
206212
echo "EMBEDDINGS_AVAILABLE=false" >> "$GITHUB_ENV"
207213
fi
208-
- name: Validate manifest (if present)
214+
- name: Validate test manifest (smart test selection)
209215
id: manifest
210216
run: |
217+
# Smart test selection: contributor commits .ci/affected-tests.txt
218+
# CI validates it with cheap sanity checks (no hypergumbo installation)
219+
# See ADR-0010 for design rationale
211220
MANIFEST=".ci/affected-tests.txt"
212-
if [ -f "$MANIFEST" ]; then
213-
echo "Manifest found: $MANIFEST"
214221
215-
# Count tests per module for sanity check
216-
CORE_TESTS=$(grep -c "hypergumbo-core/tests" "$MANIFEST" 2>/dev/null || echo 0)
217-
MAINSTREAM_TESTS=$(grep -c "hypergumbo-lang-mainstream/tests" "$MANIFEST" 2>/dev/null || echo 0)
218-
COMMON_TESTS=$(grep -c "hypergumbo-lang-common/tests" "$MANIFEST" 2>/dev/null || echo 0)
219-
EXTENDED_TESTS=$(grep -c "hypergumbo-lang-extended1/tests" "$MANIFEST" 2>/dev/null || echo 0)
220-
TOTAL_TESTS=$((CORE_TESTS + MAINSTREAM_TESTS + COMMON_TESTS + EXTENDED_TESTS))
222+
# For PRs, validate the committed manifest
223+
if [ "${{ github.event_name }}" = "pull_request" ]; then
224+
BASE="${{ github.event.pull_request.base.sha }}"
225+
HEAD="${{ github.event.pull_request.head.sha }}"
226+
227+
echo "=== Changed files (BASE=$BASE HEAD=$HEAD) ==="
228+
git diff --name-only "$BASE" "$HEAD" | head -20
229+
230+
# Check if core infrastructure changed (requires full suite regardless)
231+
CORE_CHANGED=false
232+
for core_file in sketch.py slice.py cli.py ir.py all_analyzers.py base.py registry.py; do
233+
if git diff --name-only "$BASE" "$HEAD" | grep -qE "$core_file$"; then
234+
CORE_CHANGED=true
235+
echo "✓ Core file changed: $core_file - will run full suite"
236+
break
237+
fi
238+
done
239+
240+
if [ "$CORE_CHANGED" = "true" ]; then
241+
echo "❌ Core infrastructure changed - manifest must include comprehensive tests"
242+
echo ""
243+
echo "When changing core files (sketch.py, slice.py, etc.), smart-test"
244+
echo "should generate a full-suite manifest. Run locally:"
245+
echo " ./scripts/smart-test --manifest"
246+
echo ""
247+
echo "If smart-test falls back to full suite, that's expected for core changes."
248+
# For bootstrap: allow if manifest exists and has full suite marker
249+
if [ -f ".ci/affected-tests.txt" ] && grep -q "^# Full test suite required" ".ci/affected-tests.txt"; then
250+
echo "✓ Found full-suite manifest (bootstrap mode)"
251+
echo "skip_tests=true" >> "$GITHUB_OUTPUT"
252+
echo "bootstrap_mode=true" >> "$GITHUB_OUTPUT"
253+
exit 0
254+
fi
255+
echo ""
256+
echo "During bootstrap (before stable hypergumbo has --files):"
257+
echo "PRs touching core files require manual merge."
258+
exit 1
259+
fi
260+
261+
# Check if manifest exists (contributor ran scripts/smart-test --manifest locally)
262+
if [ ! -f "$MANIFEST" ]; then
263+
echo "❌ No manifest committed"
264+
echo ""
265+
echo "Smart test selection requires a manifest. Run locally:"
266+
echo " ./scripts/smart-test --manifest"
267+
echo " git add .ci/affected-tests.txt"
268+
echo " git commit --amend"
269+
echo ""
270+
echo "See ADR-0010 for details."
271+
exit 1
272+
fi
273+
274+
echo "=== Validating committed manifest ==="
275+
head -10 "$MANIFEST"
221276
222-
echo "Manifest tests: core=$CORE_TESTS mainstream=$MAINSTREAM_TESTS common=$COMMON_TESTS extended=$EXTENDED_TESTS total=$TOTAL_TESTS"
277+
# Check if manifest indicates full suite is required (bootstrap case)
278+
# This happens when smart-test couldn't run proper slice (no stable hypergumbo with --files)
279+
if grep -q "^# Full test suite required" "$MANIFEST"; then
280+
echo "⚠️ Bootstrap mode: stable hypergumbo lacks --files flag"
281+
echo ""
282+
echo "Smart-test fell back to full suite because:"
283+
echo " - Stable hypergumbo on PyPI doesn't have 'slice --files' yet"
284+
echo " - This is expected during the bootstrap period"
285+
echo ""
286+
echo "Options:"
287+
echo " 1. Manually merge this PR (tests will run in full-suite.yml after merge)"
288+
echo " 2. Wait for hypergumbo with --files to be published to PyPI"
289+
echo ""
290+
echo "Skipping CI tests - full-suite.yml will validate after merge."
291+
echo "skip_tests=true" >> "$GITHUB_OUTPUT"
292+
echo "bootstrap_mode=true" >> "$GITHUB_OUTPUT"
293+
exit 0
294+
fi
223295
224-
# Sanity check: if total is suspiciously low, fall back to full suite
225-
if [ "$TOTAL_TESTS" -lt 10 ]; then
226-
echo "⚠️ Manifest has only $TOTAL_TESTS tests - falling back to full suite"
296+
# Cheap sanity check: verify manifest has reasonable coverage for changed modules
297+
# This prevents gaming (e.g., committing empty manifest to skip tests)
298+
CHANGED_MODULES=$(git diff --name-only "$BASE" "$HEAD" | grep "^packages/" | cut -d/ -f2 | sort -u || true)
299+
300+
SANITY_OK=true
301+
for mod in $CHANGED_MODULES; do
302+
case $mod in
303+
hypergumbo-core)
304+
MIN_TESTS=20 ;;
305+
hypergumbo-lang-*)
306+
MIN_TESTS=10 ;;
307+
hypergumbo)
308+
# Meta-package has no tests of its own
309+
MIN_TESTS=0 ;;
310+
*)
311+
MIN_TESTS=5 ;;
312+
esac
313+
314+
# Count tests that mention this module (very cheap heuristic)
315+
# Matches paths like packages/hypergumbo-core/tests/test_*.py
316+
ACTUAL=$(grep -c "packages/$mod/tests/" "$MANIFEST" 2>/dev/null || echo 0)
317+
318+
if [ "$ACTUAL" -lt "$MIN_TESTS" ]; then
319+
echo "⚠️ Manifest has $ACTUAL tests for $mod, expected ≥$MIN_TESTS"
320+
SANITY_OK=false
321+
else
322+
echo "✓ $mod: $ACTUAL tests (≥$MIN_TESTS required)"
323+
fi
324+
done
325+
326+
if [ "$SANITY_OK" = "false" ]; then
327+
echo "⚠️ Manifest sanity check failed - falling back to full suite"
227328
echo "use_manifest=false" >> "$GITHUB_OUTPUT"
228329
else
229-
echo "✅ Manifest validated ($TOTAL_TESTS tests)"
330+
TOTAL_TESTS=$(grep -c "^packages/" "$MANIFEST" 2>/dev/null || echo 0)
331+
echo "✅ Manifest validated ($TOTAL_TESTS test files)"
230332
echo "use_manifest=true" >> "$GITHUB_OUTPUT"
231333
fi
232334
else
233-
echo "No manifest found, running full suite"
234-
echo "use_manifest=false" >> "$GITHUB_OUTPUT"
335+
# Push events skip pytest in ci.yml - full-suite.yml handles that
336+
echo "Push event - skipping pytest (full-suite.yml handles comprehensive validation)"
337+
echo "skip_tests=true" >> "$GITHUB_OUTPUT"
235338
fi
236339
- name: Run tests with coverage
340+
if: steps.manifest.outputs.skip_tests != 'true'
237341
run: |
238342
COV_PATHS="--cov=packages/hypergumbo-core/src --cov=packages/hypergumbo-lang-mainstream/src --cov=packages/hypergumbo-lang-common/src --cov=packages/hypergumbo-lang-extended1/src"
239343
240344
echo "=== Python version ==="
241345
python --version
242346
243347
echo "=== Installed packages ==="
244-
pip list | grep -E "hypergumbo|pytest|tree-sitter"
245-
246-
echo "=== Test directory structure ==="
247-
# Use || true to avoid SIGPIPE exit code when head closes early
248-
ls -la packages/*/tests/ | head -20 || true
249-
250-
echo "=== Running full test suite ==="
251-
# Run without explicit test path - pytest will auto-discover from packages/*/tests/
252-
if [ -d "packages/hypergumbo-core" ]; then
253-
pytest -n 2 --tb=short $COV_PATHS --cov-report=term 2>&1 | tee coverage-output.txt
254-
else
255-
# Legacy structure
256-
pytest -n 2 --tb=short --cov=src --cov-report=term 2>&1 | tee coverage-output.txt
257-
fi
258-
- name: Update coverage badge
259-
if: github.event_name == 'push' && (github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main')
260-
run: |
261-
# Extract coverage percentage from pytest output
262-
COV=$(grep "^TOTAL" coverage-output.txt | awk '{print $NF}' | tr -d '%')
263-
if [ -z "$COV" ]; then
264-
echo "Could not extract coverage percentage"
265-
exit 0 # Don't fail the build
266-
fi
348+
pip list | grep -E "hypergumbo|pytest|tree-sitter" | head -20 || true
267349
268-
# Determine badge color based on coverage
269-
if [ "$COV" -ge 90 ]; then
270-
COLOR="brightgreen"
271-
elif [ "$COV" -ge 70 ]; then
272-
COLOR="yellow"
350+
MANIFEST=".ci/affected-tests.txt"
351+
if [ -f "$MANIFEST" ] && [ "${{ steps.manifest.outputs.use_manifest }}" = "true" ]; then
352+
echo "=== Running affected tests only (smart test selection) ==="
353+
# Filter out comment lines (starting with #) from manifest
354+
AFFECTED_TESTS=$(grep -v '^#' "$MANIFEST" | grep -v '^$' | tr '\n' ' ')
355+
echo "Running: pytest $AFFECTED_TESTS"
356+
pytest -n 2 --tb=short $COV_PATHS --cov-report=term $AFFECTED_TESTS 2>&1 | tee coverage-output.txt
273357
else
274-
COLOR="red"
358+
echo "❌ No valid manifest - cannot run tests"
359+
echo ""
360+
echo "Fast-CI requires a manifest with targeted tests."
361+
echo "Full suite runs in full-suite.yml after merge, not here."
362+
exit 1
275363
fi
276364
277-
# Create shields.io endpoint JSON in temp location
278-
echo "{\"schemaVersion\":1,\"label\":\"coverage\",\"message\":\"${COV}%\",\"color\":\"${COLOR}\"}" > /tmp/coverage.json
279-
280-
# Configure git
281-
git config user.name "CI Bot"
282-
git config user.email "ci@iterabloom.com"
283-
284-
# Fetch badges branch or create orphan
285-
git fetch origin badges:badges 2>/dev/null || true
286-
git checkout badges 2>/dev/null || git checkout --orphan badges
287-
288-
# Clean branch and add only coverage.json
289-
git rm -rf . 2>/dev/null || true
290-
cp /tmp/coverage.json coverage.json
291-
292-
# Commit and push
293-
git add coverage.json
294-
git commit -m "Update coverage badge: ${COV}%" || echo "No changes to commit"
295-
git push origin badges --force || echo "Push failed - may need write permissions"
365+
- name: Bootstrap mode notice
366+
if: steps.manifest.outputs.bootstrap_mode == 'true'
367+
run: |
368+
echo "╔════════════════════════════════════════════════════════════╗"
369+
echo "║ BOOTSTRAP MODE - Tests skipped in fast-CI ║"
370+
echo "╚════════════════════════════════════════════════════════════╝"
371+
echo ""
372+
echo "This PR can be manually merged. After merge, full-suite.yml"
373+
echo "will run comprehensive validation."
374+
echo ""
375+
echo "To enable smart test selection, publish hypergumbo with"
376+
echo "'slice --files' to PyPI, then contributors can generate"
377+
echo "targeted manifests."
378+
# Coverage badge is updated by full-suite.yml, not here
379+
# (ci.yml skips pytest on push events)
296380

297381
# Final gate job - ensures workflow completes even when jobs are skipped
298382
# This fixes Forgejo Actions bug where skipped jobs leave workflow in "running" state

0 commit comments

Comments
 (0)