@@ -376,6 +376,130 @@ jobs:
376376 --cov-config=.coveragerc.no-embeddings $TEST_PATHS
377377 fi
378378
379+ - name : Mark version as passed
380+ if : success()
381+ run : |
382+ mkdir -p /tmp/matrix-result
383+ echo "passed" > /tmp/matrix-result/py${{ matrix.python-version }}
384+
385+ - name : Upload pass marker
386+ if : success()
387+ uses : actions/upload-artifact@v3
388+ with :
389+ name : post-publish-pass-py${{ matrix.python-version }}
390+ path : /tmp/matrix-result/py${{ matrix.python-version }}
391+ retention-days : 1
392+
393+ # ─────────────────────────────────────────────────────────────
394+ # Post-publish test matrix retry (handles transient infra failures)
395+ # Only retries Python versions where the primary job failed.
396+ # ─────────────────────────────────────────────────────────────
397+ post-publish-matrix-retry :
398+ needs : [check-nightly, build-and-publish, post-publish-matrix]
399+ if : >-
400+ always() &&
401+ needs.build-and-publish.result == 'success' &&
402+ needs.check-nightly.outputs.matrix_covered != 'true' &&
403+ (needs.post-publish-matrix.result == 'failure' || needs.post-publish-matrix.result == 'cancelled')
404+ strategy :
405+ matrix :
406+ python-version : ['3.10', '3.11', '3.12', '3.13']
407+ fail-fast : false
408+
409+ runs-on : self-hosted
410+ container : python:${{ matrix.python-version }}-bookworm
411+ timeout-minutes : 15
412+ env :
413+ SEGMENT_DOWNLOAD_TIMEOUT_MINS : " 1"
414+
415+ steps :
416+ - name : Download pass marker
417+ id : check-primary
418+ uses : actions/download-artifact@v3
419+ with :
420+ name : post-publish-pass-py${{ matrix.python-version }}
421+ path : /tmp/matrix-result
422+ continue-on-error : true
423+
424+ - name : Skip if primary passed
425+ id : skip-check
426+ run : |
427+ if [ -f /tmp/matrix-result/py${{ matrix.python-version }} ]; then
428+ echo "Primary job passed for Python ${{ matrix.python-version }} — skipping retry"
429+ echo "should_skip=true" >> "$GITHUB_OUTPUT"
430+ else
431+ echo "Primary job failed for Python ${{ matrix.python-version }} — retrying"
432+ echo "should_skip=false" >> "$GITHUB_OUTPUT"
433+ fi
434+
435+ - name : Install Node.js for JS-based actions
436+ if : steps.skip-check.outputs.should_skip != 'true'
437+ run : apt-get update && apt-get install -y nodejs
438+
439+ - uses : actions/checkout@v4
440+ if : steps.skip-check.outputs.should_skip != 'true'
441+
442+ - name : Install dependencies
443+ if : steps.skip-check.outputs.should_skip != 'true'
444+ run : |
445+ pip install --upgrade pip
446+ if [ -d "packages/hypergumbo-core" ]; then
447+ pip install pytest pytest-cov pytest-xdist jsonschema
448+ pip install -e packages/hypergumbo-core \
449+ -e packages/hypergumbo-lang-mainstream \
450+ -e packages/hypergumbo-lang-common \
451+ -e packages/hypergumbo-lang-extended1 \
452+ -e "packages/hypergumbo-tracker[dev,tui]" \
453+ -e packages/hypergumbo
454+ else
455+ pip install -e .[dev]
456+ fi
457+
458+ - name : Build source-only grammars
459+ if : steps.skip-check.outputs.should_skip != 'true'
460+ run : ./scripts/build-source-grammars
461+
462+ - name : Check for sentence-transformers
463+ if : steps.skip-check.outputs.should_skip != 'true'
464+ run : |
465+ if python -c "import sentence_transformers" 2>/dev/null; then
466+ echo "sentence-transformers available"
467+ echo "EMBEDDINGS_AVAILABLE=true" >> "$GITHUB_ENV"
468+ else
469+ echo "sentence-transformers not installed, embedding tests will be skipped"
470+ echo "EMBEDDINGS_AVAILABLE=false" >> "$GITHUB_ENV"
471+ fi
472+
473+ - name : Run tests with coverage
474+ if : steps.skip-check.outputs.should_skip != 'true'
475+ run : |
476+ if [ -d "packages/hypergumbo-core" ]; then
477+ 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 --cov=packages/hypergumbo-tracker/src"
478+ TEST_PATHS="packages/*/tests/"
479+ else
480+ COV_PATHS="--cov=src"
481+ TEST_PATHS=""
482+ fi
483+
484+ # Detect available CPUs from cgroup limits
485+ if [ -f /sys/fs/cgroup/cpu.max ]; then
486+ read quota period < /sys/fs/cgroup/cpu.max
487+ if [ "$quota" != "max" ]; then
488+ WORKERS=$((quota / period))
489+ else
490+ WORKERS=$(nproc)
491+ fi
492+ else
493+ WORKERS=$(nproc)
494+ fi
495+
496+ if [ "$EMBEDDINGS_AVAILABLE" = "true" ]; then
497+ pytest -n "$WORKERS" $COV_PATHS --cov-report=term-missing --cov-fail-under=100 $TEST_PATHS
498+ else
499+ pytest -n "$WORKERS" $COV_PATHS --cov-report=term-missing --cov-fail-under=100 \
500+ --cov-config=.coveragerc.no-embeddings $TEST_PATHS
501+ fi
502+
379503 # ─────────────────────────────────────────────────────────────
380504 # Post-publish integration tests (runs only if nightly didn't cover it)
381505 # ─────────────────────────────────────────────────────────────
0 commit comments