From 7496ab76900f6f1910f7e4f02746895f1410fed8 Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Wed, 4 Feb 2026 16:17:55 -0500 Subject: [PATCH 01/33] Add CI lint gate and local precheck command - Add lint-gate job to test.yml that runs fast checks (formatting, spelling, linting) before expensive test matrix and HPC jobs start - Add concurrency groups to test.yml, coverage.yml, cleanliness.yml, and bench.yml to cancel superseded runs on new pushes - Add ./mfc.sh precheck command for local CI validation before pushing Co-Authored-By: Claude Opus 4.5 --- .github/workflows/bench.yml | 4 + .github/workflows/cleanliness.yml | 4 + .github/workflows/coverage.yml | 4 + .github/workflows/test.yml | 48 +++++++++++- mfc.sh | 4 + toolchain/bootstrap/precheck.sh | 118 ++++++++++++++++++++++++++++++ 6 files changed, 179 insertions(+), 3 deletions(-) create mode 100755 toolchain/bootstrap/precheck.sh diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 1427f9d693..0b6f069f0a 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -6,6 +6,10 @@ on: types: [submitted] workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: file-changes: name: Detect File Changes diff --git a/.github/workflows/cleanliness.yml b/.github/workflows/cleanliness.yml index b02df12898..bcbe35caaa 100644 --- a/.github/workflows/cleanliness.yml +++ b/.github/workflows/cleanliness.yml @@ -2,6 +2,10 @@ name: Cleanliness on: [push, pull_request, workflow_dispatch] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: file-changes: name: Detect File Changes diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index ad0ea7a220..469048f8a6 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -2,6 +2,10 @@ name: Coverage Check on: [push, pull_request, workflow_dispatch] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: file-changes: name: Detect File Changes diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f0dc72783d..fb16384045 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,8 +1,50 @@ name: 'Test Suite' on: [push, pull_request, workflow_dispatch] - + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: + lint-gate: + name: Lint Gate + runs-on: ubuntu-latest + steps: + - name: Clone + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Initialize MFC + run: ./mfc.sh init + + - name: Check Formatting + run: | + ./mfc.sh format -j $(nproc) + git diff --exit-code || (echo "::error::Code is not formatted. Run './mfc.sh format' locally." && exit 1) + + - name: Spell Check + run: ./mfc.sh spelling + + - name: Lint Toolchain + run: ./mfc.sh lint + + - name: Lint Source - No Raw Directives + run: | + ! grep -iR '!\$acc\|!\$omp' --exclude="parallel_macros.fpp" --exclude="acc_macros.fpp" --exclude="omp_macros.fpp" --exclude="shared_parallel_macros.fpp" --exclude="syscheck.fpp" ./src/* + + - name: Lint Source - No Double Precision Intrinsics + run: | + ! grep -iR 'double_precision\|dsqrt\|dexp\|dlog\|dble\|dabs\|double\ precision\|real(8)\|real(4)\|dprod\|dmin\|dmax\|dfloat\|dreal\|dcos\|dsin\|dtan\|dsign\|dtanh\|dsinh\|dcosh\|d0' --exclude-dir=syscheck --exclude="*nvtx*" --exclude="*precision_select*" ./src/* + + - name: Lint Source - No Junk Code + run: | + ! grep -iR -e '\.\.\.' -e '\-\-\-' -e '===' ./src/* + file-changes: name: Detect File Changes runs-on: 'ubuntu-latest' @@ -21,7 +63,7 @@ jobs: github: name: Github if: needs.file-changes.outputs.checkall == 'true' - needs: file-changes + needs: [lint-gate, file-changes] strategy: matrix: os: ['ubuntu', 'macos'] @@ -95,7 +137,7 @@ jobs: self: name: "${{ matrix.cluster_name }} (${{ matrix.device }}${{ matrix.interface != 'none' && format('-{0}', matrix.interface) || '' }})" if: github.repository == 'MFlowCode/MFC' && needs.file-changes.outputs.checkall == 'true' - needs: file-changes + needs: [lint-gate, file-changes] continue-on-error: false timeout-minutes: 480 strategy: diff --git a/mfc.sh b/mfc.sh index 9fb95ae61a..3eb015e1e9 100755 --- a/mfc.sh +++ b/mfc.sh @@ -40,6 +40,10 @@ elif [ "$1" '==' "spelling" ]; then . "$(pwd)/toolchain/bootstrap/python.sh" shift; . "$(pwd)/toolchain/bootstrap/spelling.sh" $@; exit 0 +elif [ "$1" '==' "precheck" ]; then + . "$(pwd)/toolchain/bootstrap/python.sh" + + shift; . "$(pwd)/toolchain/bootstrap/precheck.sh" $@; exit 0 fi mkdir -p "$(pwd)/build" diff --git a/toolchain/bootstrap/precheck.sh b/toolchain/bootstrap/precheck.sh new file mode 100755 index 0000000000..b5d98258c7 --- /dev/null +++ b/toolchain/bootstrap/precheck.sh @@ -0,0 +1,118 @@ +#!/bin/bash +set -e +set -o pipefail + +# Function to display help message +show_help() { + echo "Usage: ./mfc.sh precheck [OPTIONS]" + echo "Run the same fast checks that CI runs before expensive tests start." + echo "Use this locally before pushing to catch issues early." + echo "" + echo "Options:" + echo " -h, --help Display this help message and exit." + echo " -j, --jobs JOBS Runs JOBS number of parallel jobs for formatting." + echo "" + exit 0 +} + +JOBS=1 + +while [[ $# -gt 0 ]]; do + case "$1" in + -j|--jobs) + JOBS="$2" + shift + ;; + -h | --help) + show_help + ;; + *) + echo "Precheck: unknown argument: $1." + exit 1 + ;; + esac + + shift +done + +FAILED=0 + +log "Running$MAGENTA precheck$COLOR_RESET (same checks as CI lint-gate)..." +echo "" + +# 1. Check formatting +log "[$CYAN 1/4$COLOR_RESET] Checking$MAGENTA formatting$COLOR_RESET..." +# Capture state before formatting +BEFORE_HASH=$(git diff -- '*.f90' '*.fpp' '*.py' 2>/dev/null | md5sum | cut -d' ' -f1) +if ! ./mfc.sh format -j "$JOBS" > /dev/null 2>&1; then + error "Formatting check failed to run." + FAILED=1 +else + # Check if formatting changed any Fortran/Python files + AFTER_HASH=$(git diff -- '*.f90' '*.fpp' '*.py' 2>/dev/null | md5sum | cut -d' ' -f1) + if [ "$BEFORE_HASH" != "$AFTER_HASH" ]; then + error "Code is not formatted. Run$MAGENTA ./mfc.sh format$COLOR_RESET to fix." + echo "" + git diff --stat -- '*.f90' '*.fpp' '*.py' 2>/dev/null || true + echo "" + FAILED=1 + else + ok "Formatting check passed." + fi +fi + +# 2. Spell check +log "[$CYAN 2/4$COLOR_RESET] Running$MAGENTA spell check$COLOR_RESET..." +if ./mfc.sh spelling > /dev/null 2>&1; then + ok "Spell check passed." +else + error "Spell check failed." + FAILED=1 +fi + +# 3. Lint toolchain (Python) +log "[$CYAN 3/4$COLOR_RESET] Running$MAGENTA toolchain lint$COLOR_RESET..." +if ./mfc.sh lint > /dev/null 2>&1; then + ok "Toolchain lint passed." +else + error "Toolchain lint failed. Run$MAGENTA ./mfc.sh lint$COLOR_RESET for details." + FAILED=1 +fi + +# 4. Source code lint checks +log "[$CYAN 4/4$COLOR_RESET] Running$MAGENTA source lint$COLOR_RESET checks..." +SOURCE_FAILED=0 + +# Check for raw OpenACC/OpenMP directives +if grep -qiR '!\$acc\|!\$omp' --exclude="parallel_macros.fpp" --exclude="acc_macros.fpp" --exclude="omp_macros.fpp" --exclude="shared_parallel_macros.fpp" --exclude="syscheck.fpp" ./src/* 2>/dev/null; then + error "Found raw OpenACC/OpenMP directives. Use macros instead." + SOURCE_FAILED=1 +fi + +# Check for double precision intrinsics +if grep -qiR 'double_precision\|dsqrt\|dexp\|dlog\|dble\|dabs\|double\ precision\|real(8)\|real(4)\|dprod\|dmin\|dmax\|dfloat\|dreal\|dcos\|dsin\|dtan\|dsign\|dtanh\|dsinh\|dcosh\|d0' --exclude-dir=syscheck --exclude="*nvtx*" --exclude="*precision_select*" ./src/* 2>/dev/null; then + error "Found double precision intrinsics. Use generic intrinsics." + SOURCE_FAILED=1 +fi + +# Check for junk code patterns +if grep -qiR -e '\.\.\.' -e '\-\-\-' -e '===' ./src/* 2>/dev/null; then + error "Found junk code patterns (..., ---, ===) in source." + SOURCE_FAILED=1 +fi + +if [ $SOURCE_FAILED -eq 0 ]; then + ok "Source lint passed." +else + FAILED=1 +fi + +echo "" + +if [ $FAILED -eq 0 ]; then + ok "All precheck tests passed! Safe to push." + exit 0 +else + error "Some precheck tests failed. Fix issues before pushing." + exit 1 +fi From 26f7e17295c08d9a84cb412aa5df3101c3d9bf56 Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Wed, 4 Feb 2026 16:33:27 -0500 Subject: [PATCH 02/33] Fix precheck.sh portability and usability issues - Add cross-platform hash function (macOS uses md5, Linux uses md5sum) - Validate -j/--jobs argument (require value, must be numeric) - Improve error messages with actionable guidance - Clarify that formatting has been auto-applied when check fails Co-Authored-By: Claude Opus 4.5 --- toolchain/bootstrap/precheck.sh | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/toolchain/bootstrap/precheck.sh b/toolchain/bootstrap/precheck.sh index b5d98258c7..e215b5945e 100755 --- a/toolchain/bootstrap/precheck.sh +++ b/toolchain/bootstrap/precheck.sh @@ -15,11 +15,31 @@ show_help() { exit 0 } +# Cross-platform hash function (macOS uses md5, Linux uses md5sum) +compute_hash() { + if command -v md5sum > /dev/null 2>&1; then + md5sum | cut -d' ' -f1 + elif command -v md5 > /dev/null 2>&1; then + md5 -q + else + # Fallback: use cksum if neither available + cksum | cut -d' ' -f1 + fi +} + JOBS=1 while [[ $# -gt 0 ]]; do case "$1" in -j|--jobs) + if [[ -z "$2" || "$2" == -* ]]; then + echo "Precheck: -j/--jobs requires a value." + exit 1 + fi + if ! [[ "$2" =~ ^[0-9]+$ ]]; then + echo "Precheck: jobs value '$2' is not a valid number." + exit 1 + fi JOBS="$2" shift ;; @@ -43,15 +63,15 @@ echo "" # 1. Check formatting log "[$CYAN 1/4$COLOR_RESET] Checking$MAGENTA formatting$COLOR_RESET..." # Capture state before formatting -BEFORE_HASH=$(git diff -- '*.f90' '*.fpp' '*.py' 2>/dev/null | md5sum | cut -d' ' -f1) +BEFORE_HASH=$(git diff -- '*.f90' '*.fpp' '*.py' 2>/dev/null | compute_hash) if ! ./mfc.sh format -j "$JOBS" > /dev/null 2>&1; then error "Formatting check failed to run." FAILED=1 else # Check if formatting changed any Fortran/Python files - AFTER_HASH=$(git diff -- '*.f90' '*.fpp' '*.py' 2>/dev/null | md5sum | cut -d' ' -f1) + AFTER_HASH=$(git diff -- '*.f90' '*.fpp' '*.py' 2>/dev/null | compute_hash) if [ "$BEFORE_HASH" != "$AFTER_HASH" ]; then - error "Code is not formatted. Run$MAGENTA ./mfc.sh format$COLOR_RESET to fix." + error "Code was not formatted. Files have been auto-formatted; review and stage the changes." echo "" git diff --stat -- '*.f90' '*.fpp' '*.py' 2>/dev/null || true echo "" @@ -66,7 +86,7 @@ log "[$CYAN 2/4$COLOR_RESET] Running$MAGENTA spell check$COLOR_RESET..." if ./mfc.sh spelling > /dev/null 2>&1; then ok "Spell check passed." else - error "Spell check failed." + error "Spell check failed. Run$MAGENTA ./mfc.sh spelling$COLOR_RESET for details." FAILED=1 fi From 4b17fa245b34f36cda98423f17ef8bf67753a93f Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Wed, 4 Feb 2026 18:00:32 -0500 Subject: [PATCH 03/33] Gate benchmarks on Test Suite completion Add wait-for-tests job that polls GitHub API to ensure: - Lint Gate passes first (fast fail) - All Github test jobs complete successfully - Only then do benchmark jobs start This prevents wasting HPC resources on benchmarking code that fails tests, while preserving the existing maintainer approval gate. Co-Authored-By: Claude Opus 4.5 --- .github/workflows/bench.yml | 58 +++++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 0b6f069f0a..f75631b2dd 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -14,7 +14,7 @@ jobs: file-changes: name: Detect File Changes runs-on: 'ubuntu-latest' - outputs: + outputs: checkall: ${{ steps.changes.outputs.checkall }} steps: - name: Clone @@ -23,13 +23,65 @@ jobs: - name: Detect Changes uses: dorny/paths-filter@v3 id: changes - with: + with: filters: ".github/file-filter.yml" + wait-for-tests: + name: Wait for Test Suite + runs-on: ubuntu-latest + steps: + - name: Wait for Test Suite to Pass + env: + GH_TOKEN: ${{ github.token }} + run: | + echo "Waiting for Test Suite workflow to complete..." + SHA="${{ github.event.pull_request.head.sha || github.sha }}" + + # Poll every 60 seconds for up to 3 hours + for i in $(seq 1 180); do + # Get the Test Suite workflow runs for this commit + STATUS=$(gh api repos/${{ github.repository }}/commits/$SHA/check-runs \ + --jq '.check_runs[] | select(.name == "Lint Gate") | .conclusion' | head -1) + + if [ "$STATUS" = "success" ]; then + echo "Lint Gate passed. Checking test jobs..." + + # Check if any Github test jobs failed + FAILED=$(gh api repos/${{ github.repository }}/commits/$SHA/check-runs \ + --jq '[.check_runs[] | select(.name | startswith("Github")) | select(.conclusion == "failure")] | length') + + if [ "$FAILED" != "0" ]; then + echo "::error::Test Suite has failing jobs. Benchmarks will not run." + exit 1 + fi + + # Check if Github tests are still running + PENDING=$(gh api repos/${{ github.repository }}/commits/$SHA/check-runs \ + --jq '[.check_runs[] | select(.name | startswith("Github")) | select(.conclusion == null)] | length') + + if [ "$PENDING" = "0" ]; then + echo "All Test Suite jobs completed successfully!" + exit 0 + fi + + echo "Tests still running ($PENDING pending)..." + elif [ "$STATUS" = "failure" ]; then + echo "::error::Lint Gate failed. Benchmarks will not run." + exit 1 + else + echo "Lint Gate status: ${STATUS:-pending}..." + fi + + sleep 60 + done + + echo "::error::Timeout waiting for Test Suite to complete." + exit 1 + self: name: "${{ matrix.name }} (${{ matrix.device }}${{ matrix.interface != 'none' && format('-{0}', matrix.interface) || '' }})" if: ${{ github.repository=='MFlowCode/MFC' && needs.file-changes.outputs.checkall=='true' && ((github.event_name=='pull_request_review' && github.event.review.state=='approved') || (github.event_name=='pull_request' && (github.event.pull_request.user.login=='sbryngelson' || github.event.pull_request.user.login=='wilfonba'))) }} - needs: file-changes + needs: [file-changes, wait-for-tests] strategy: fail-fast: false matrix: From 1b211a53e29b8e7fdd0264ca668f742b4b9e0c08 Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Wed, 4 Feb 2026 18:15:18 -0500 Subject: [PATCH 04/33] Auto-install git pre-commit hook for precheck - Add .githooks/pre-commit that runs ./mfc.sh precheck before commits - Auto-install hook on first ./mfc.sh invocation (symlinks to .git/hooks/) - Hook only installs once; subsequent runs skip if already present - Developers can bypass with: git commit --no-verify Co-Authored-By: Claude Opus 4.5 --- .githooks/pre-commit | 27 +++++++++++++++++++++++++++ mfc.sh | 6 ++++++ 2 files changed, 33 insertions(+) create mode 100755 .githooks/pre-commit diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000000..2bae9c8653 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,27 @@ +#!/bin/bash + +# MFC pre-commit hook +# Runs ./mfc.sh precheck before allowing commits +# Bypass with: git commit --no-verify + +# Only run if we're in the MFC repo root (where mfc.sh exists) +if [ ! -f "$(git rev-parse --show-toplevel)/mfc.sh" ]; then + exit 0 +fi + +cd "$(git rev-parse --show-toplevel)" + +echo "" +echo "mfc: Running precheck before commit..." +echo "" + +# Run precheck with parallel jobs +if ./mfc.sh precheck -j 4; then + echo "" + exit 0 +else + echo "" + echo "mfc: Commit blocked. Fix issues above or use 'git commit --no-verify' to bypass." + echo "" + exit 1 +fi diff --git a/mfc.sh b/mfc.sh index 3eb015e1e9..5a1308ca98 100755 --- a/mfc.sh +++ b/mfc.sh @@ -10,6 +10,12 @@ fi # Load utility script . "$(pwd)/toolchain/util.sh" +# Auto-install git pre-commit hook (once, silently) +if [ -d "$(pwd)/.git" ] && [ ! -e "$(pwd)/.git/hooks/pre-commit" ] && [ -f "$(pwd)/.githooks/pre-commit" ]; then + ln -sf "$(pwd)/.githooks/pre-commit" "$(pwd)/.git/hooks/pre-commit" + log "Installed git pre-commit hook (runs$MAGENTA ./mfc.sh precheck$COLOR_RESET before commits)." +fi + # Handle upgrading from older MFC build systems if [ -d "$(pwd)/bootstrap" ] || [ -d "$(pwd)/dependencies" ] || [ -f "$(pwd)/build/mfc.lock.yaml" ]; then error "Please remove, if applicable, the following directories:" From 9304137c8c3af1c16a4a87e36da6ac9b036f904d Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Wed, 4 Feb 2026 18:41:50 -0500 Subject: [PATCH 05/33] Use dynamic CPU count in pre-commit hook Auto-detect available CPUs for parallel formatting: - Linux: nproc - macOS: sysctl -n hw.ncpu - Fallback: 4 Co-Authored-By: Claude Opus 4.5 --- .githooks/pre-commit | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 2bae9c8653..84d0110dab 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -15,8 +15,9 @@ echo "" echo "mfc: Running precheck before commit..." echo "" -# Run precheck with parallel jobs -if ./mfc.sh precheck -j 4; then +# Run precheck with parallel jobs (auto-detect CPU count) +JOBS=$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4) +if ./mfc.sh precheck -j "$JOBS"; then echo "" exit 0 else From 77ac8cc391b75854383ec7226340acd566ebcb2c Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Wed, 4 Feb 2026 18:45:38 -0500 Subject: [PATCH 06/33] Show CPU count in pre-commit hook output Co-Authored-By: Claude Opus 4.5 --- .githooks/pre-commit | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 84d0110dab..01e6f3c257 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -11,12 +11,13 @@ fi cd "$(git rev-parse --show-toplevel)" +# Auto-detect CPU count +JOBS=$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4) + echo "" -echo "mfc: Running precheck before commit..." +echo "mfc: Running precheck before commit (-j $JOBS)..." echo "" -# Run precheck with parallel jobs (auto-detect CPU count) -JOBS=$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4) if ./mfc.sh precheck -j "$JOBS"; then echo "" exit 0 From 4b55c30102f70a81a87b6d28b9c53ad05e4eaa4a Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Wed, 4 Feb 2026 20:27:52 -0500 Subject: [PATCH 07/33] Add precheck command to CLI and autocomplete Register precheck in commands.py so it appears in: - Shell tab completion - CLI documentation - ./mfc.sh precheck --help Co-Authored-By: Claude Opus 4.5 --- toolchain/mfc/cli/commands.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/toolchain/mfc/cli/commands.py b/toolchain/mfc/cli/commands.py index 0f827a4b74..9697078aa2 100644 --- a/toolchain/mfc/cli/commands.py +++ b/toolchain/mfc/cli/commands.py @@ -751,6 +751,27 @@ ], ) +PRECHECK_COMMAND = Command( + name="precheck", + help="Run CI lint checks locally before committing.", + description="Run the same fast checks that CI runs before expensive tests start. Use this locally before pushing to catch issues early.", + arguments=[ + Argument( + name="jobs", + short="j", + help="Number of parallel jobs for formatting.", + metavar="N", + ), + ], + examples=[ + Example("./mfc.sh precheck", "Run all lint checks"), + Example("./mfc.sh precheck -j 8", "Run with 8 parallel jobs"), + ], + key_options=[ + ("-j, --jobs N", "Number of parallel jobs for formatting"), + ], +) + INTERACTIVE_COMMAND = Command( name="interactive", help="Launch interactive menu-driven interface.", @@ -990,6 +1011,7 @@ LINT_COMMAND, FORMAT_COMMAND, SPELLING_COMMAND, + PRECHECK_COMMAND, INTERACTIVE_COMMAND, BENCH_COMMAND, BENCH_DIFF_COMMAND, From e017cf0993906f428342dfe74361ce3432347480 Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Wed, 4 Feb 2026 20:34:18 -0500 Subject: [PATCH 08/33] Auto-update installed shell completions on regeneration When completion scripts are auto-regenerated, also update the installed completions at ~/.local/share/mfc/completions/ if they exist. Co-Authored-By: Claude Opus 4.5 --- toolchain/main.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/toolchain/main.py b/toolchain/main.py index 10e6e44ab0..3c0d1c2d5d 100644 --- a/toolchain/main.py +++ b/toolchain/main.py @@ -12,6 +12,7 @@ def __do_regenerate(toolchain: str): """Perform the actual regeneration of completion scripts and schema.""" import json # pylint: disable=import-outside-toplevel + import shutil # pylint: disable=import-outside-toplevel from pathlib import Path # pylint: disable=import-outside-toplevel from mfc.cli.commands import MFC_CLI_SCHEMA # pylint: disable=import-outside-toplevel from mfc.cli.completion_gen import generate_bash_completion, generate_zsh_completion # pylint: disable=import-outside-toplevel @@ -23,14 +24,24 @@ def __do_regenerate(toolchain: str): completions_dir.mkdir(exist_ok=True) # Generate completion files - (completions_dir / "mfc.bash").write_text(generate_bash_completion(MFC_CLI_SCHEMA)) - (completions_dir / "_mfc").write_text(generate_zsh_completion(MFC_CLI_SCHEMA)) + bash_content = generate_bash_completion(MFC_CLI_SCHEMA) + zsh_content = generate_zsh_completion(MFC_CLI_SCHEMA) + (completions_dir / "mfc.bash").write_text(bash_content) + (completions_dir / "_mfc").write_text(zsh_content) # Generate JSON schema schema = generate_json_schema(include_descriptions=True) with open(Path(toolchain) / "mfc-case-schema.json", 'w', encoding='utf-8') as f: json.dump(schema, f, indent=2) + # Also update installed completions if they exist + install_dir = Path.home() / ".local" / "share" / "mfc" / "completions" + if install_dir.exists(): + if (install_dir / "mfc.bash").exists(): + shutil.copy2(completions_dir / "mfc.bash", install_dir / "mfc.bash") + if (install_dir / "_mfc").exists(): + shutil.copy2(completions_dir / "_mfc", install_dir / "_mfc") + def __ensure_generated_files(): """Auto-regenerate completion scripts and docs if source files have changed.""" From 780138ba03366bc7941640f8fd71e80a40de2754 Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Wed, 4 Feb 2026 20:39:53 -0500 Subject: [PATCH 09/33] Show source command when completions auto-update When installed shell completions are auto-updated, print a message with the appropriate source command for the user's detected shell. Co-Authored-By: Claude Opus 4.5 --- toolchain/main.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/toolchain/main.py b/toolchain/main.py index 3c0d1c2d5d..b2b43385a6 100644 --- a/toolchain/main.py +++ b/toolchain/main.py @@ -23,24 +23,29 @@ def __do_regenerate(toolchain: str): completions_dir = Path(toolchain) / "completions" completions_dir.mkdir(exist_ok=True) - # Generate completion files - bash_content = generate_bash_completion(MFC_CLI_SCHEMA) - zsh_content = generate_zsh_completion(MFC_CLI_SCHEMA) - (completions_dir / "mfc.bash").write_text(bash_content) - (completions_dir / "_mfc").write_text(zsh_content) + # Generate and write completion files directly + (completions_dir / "mfc.bash").write_text(generate_bash_completion(MFC_CLI_SCHEMA)) + (completions_dir / "_mfc").write_text(generate_zsh_completion(MFC_CLI_SCHEMA)) # Generate JSON schema - schema = generate_json_schema(include_descriptions=True) with open(Path(toolchain) / "mfc-case-schema.json", 'w', encoding='utf-8') as f: - json.dump(schema, f, indent=2) + json.dump(generate_json_schema(include_descriptions=True), f, indent=2) # Also update installed completions if they exist install_dir = Path.home() / ".local" / "share" / "mfc" / "completions" if install_dir.exists(): - if (install_dir / "mfc.bash").exists(): + bash_updated = (install_dir / "mfc.bash").exists() + zsh_updated = (install_dir / "_mfc").exists() + if bash_updated: shutil.copy2(completions_dir / "mfc.bash", install_dir / "mfc.bash") - if (install_dir / "_mfc").exists(): + if zsh_updated: shutil.copy2(completions_dir / "_mfc", install_dir / "_mfc") + if bash_updated or zsh_updated: + # Detect user's shell and show appropriate source command + if "zsh" in os.environ.get("SHELL", "") and zsh_updated: + cons.print(f"[dim]Tab completions updated. Run: source {install_dir}/_mfc[/dim]") + elif bash_updated: + cons.print(f"[dim]Tab completions updated. Run: source {install_dir}/mfc.bash[/dim]") def __ensure_generated_files(): From 06f042500c82adec8bb0b0806663a47f145478ed Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Wed, 4 Feb 2026 21:05:51 -0500 Subject: [PATCH 10/33] Always check installed completions on every run Previously, installed completions only updated when source files changed and regeneration occurred. Now we also check if the installed completions are older than the generated ones (e.g., after git pull brings new pre-generated completions). Co-Authored-By: Claude Opus 4.5 --- toolchain/main.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/toolchain/main.py b/toolchain/main.py index b2b43385a6..e025d3da8d 100644 --- a/toolchain/main.py +++ b/toolchain/main.py @@ -48,6 +48,31 @@ def __do_regenerate(toolchain: str): cons.print(f"[dim]Tab completions updated. Run: source {install_dir}/mfc.bash[/dim]") +def __update_installed_completions(toolchain: str): + """Update installed completions if they're older than the generated ones.""" + import shutil # pylint: disable=import-outside-toplevel + from pathlib import Path # pylint: disable=import-outside-toplevel + + completions_dir = Path(toolchain) / "completions" + install_dir = Path.home() / ".local" / "share" / "mfc" / "completions" + + if not install_dir.exists(): + return + + updated = [] + for src_name, dst_name in [("mfc.bash", "mfc.bash"), ("_mfc", "_mfc")]: + src, dst = completions_dir / src_name, install_dir / dst_name + if src.exists() and dst.exists() and os.path.getmtime(src) > os.path.getmtime(dst): + shutil.copy2(src, dst) + updated.append(("bash" if src_name == "mfc.bash" else "zsh", dst)) + + if updated: + if "zsh" in os.environ.get("SHELL", "") and any(s[0] == "zsh" for s in updated): + cons.print(f"[dim]Tab completions updated. Run: source {install_dir}/_mfc[/dim]") + elif any(s[0] == "bash" for s in updated): + cons.print(f"[dim]Tab completions updated. Run: source {install_dir}/mfc.bash[/dim]") + + def __ensure_generated_files(): """Auto-regenerate completion scripts and docs if source files have changed.""" toolchain = os.path.join(MFC_ROOT_DIR, "toolchain") @@ -79,6 +104,10 @@ def __ensure_generated_files(): if needs_regen: __do_regenerate(toolchain) + else: + # Even if we didn't regenerate, check if installed completions need updating + # (e.g., after git pull with new generated files) + __update_installed_completions(toolchain) def __print_greeting(): MFC_LOGO_LINES = MFC_LOGO.splitlines() From f65ed30e2adbc10d9537144a0cb6f7ad59f452d6 Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Wed, 4 Feb 2026 21:11:53 -0500 Subject: [PATCH 11/33] Prevent directory completion fallback in shell completions - Remove -o bashdefault from bash complete command to prevent falling back to directory completion when no matches found - Add explicit : (no-op) for zsh commands without arguments to prevent default file/directory completion Co-Authored-By: Claude Opus 4.5 --- toolchain/mfc/cli/completion_gen.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/toolchain/mfc/cli/completion_gen.py b/toolchain/mfc/cli/completion_gen.py index d34d7d9cdc..a12f55e686 100644 --- a/toolchain/mfc/cli/completion_gen.py +++ b/toolchain/mfc/cli/completion_gen.py @@ -245,9 +245,10 @@ def generate_bash_completion(schema: CLISchema) -> str: ' return 0', '}', '', - 'complete -o filenames -o bashdefault -F _mfc_completions ./mfc.sh', - 'complete -o filenames -o bashdefault -F _mfc_completions mfc.sh', - 'complete -o filenames -o bashdefault -F _mfc_completions mfc', + '# Note: -o nospace prevents trailing space; we only use -o filenames when actually completing files', + 'complete -F _mfc_completions ./mfc.sh', + 'complete -F _mfc_completions mfc.sh', + 'complete -F _mfc_completions mfc', ]) return '\n'.join(lines) @@ -401,6 +402,9 @@ def generate_zsh_completion(schema: CLISchema) -> str: if arg_lines: lines.append(' _arguments \\') lines.append(' ' + ' \\\n '.join(arg_lines)) + else: + # Explicitly disable default completion for commands with no args + lines.append(' :') lines.append(' ;;') lines.extend([ From d23addc7ea98fa1c3518ce88f57adaaa59251216 Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Wed, 4 Feb 2026 21:19:45 -0500 Subject: [PATCH 12/33] Auto-install completions and fix bash completion options - Auto-install completions on first mfc.sh run (via main.py) - Add -o filenames back to bash complete (needed for file completion) - Keep -o bashdefault removed to prevent directory fallback - Simplify code by having __update_installed_completions handle both install and update cases Co-Authored-By: Claude Opus 4.5 --- toolchain/main.py | 36 +++++++++++------------------ toolchain/mfc/cli/completion_gen.py | 9 ++++---- 2 files changed, 19 insertions(+), 26 deletions(-) diff --git a/toolchain/main.py b/toolchain/main.py index e025d3da8d..bc684024c6 100644 --- a/toolchain/main.py +++ b/toolchain/main.py @@ -12,7 +12,6 @@ def __do_regenerate(toolchain: str): """Perform the actual regeneration of completion scripts and schema.""" import json # pylint: disable=import-outside-toplevel - import shutil # pylint: disable=import-outside-toplevel from pathlib import Path # pylint: disable=import-outside-toplevel from mfc.cli.commands import MFC_CLI_SCHEMA # pylint: disable=import-outside-toplevel from mfc.cli.completion_gen import generate_bash_completion, generate_zsh_completion # pylint: disable=import-outside-toplevel @@ -31,34 +30,28 @@ def __do_regenerate(toolchain: str): with open(Path(toolchain) / "mfc-case-schema.json", 'w', encoding='utf-8') as f: json.dump(generate_json_schema(include_descriptions=True), f, indent=2) - # Also update installed completions if they exist - install_dir = Path.home() / ".local" / "share" / "mfc" / "completions" - if install_dir.exists(): - bash_updated = (install_dir / "mfc.bash").exists() - zsh_updated = (install_dir / "_mfc").exists() - if bash_updated: - shutil.copy2(completions_dir / "mfc.bash", install_dir / "mfc.bash") - if zsh_updated: - shutil.copy2(completions_dir / "_mfc", install_dir / "_mfc") - if bash_updated or zsh_updated: - # Detect user's shell and show appropriate source command - if "zsh" in os.environ.get("SHELL", "") and zsh_updated: - cons.print(f"[dim]Tab completions updated. Run: source {install_dir}/_mfc[/dim]") - elif bash_updated: - cons.print(f"[dim]Tab completions updated. Run: source {install_dir}/mfc.bash[/dim]") - def __update_installed_completions(toolchain: str): - """Update installed completions if they're older than the generated ones.""" + """Install or update shell completions automatically.""" import shutil # pylint: disable=import-outside-toplevel from pathlib import Path # pylint: disable=import-outside-toplevel completions_dir = Path(toolchain) / "completions" install_dir = Path.home() / ".local" / "share" / "mfc" / "completions" + # Auto-install if not installed yet if not install_dir.exists(): + install_dir.mkdir(parents=True, exist_ok=True) + shutil.copy2(completions_dir / "mfc.bash", install_dir / "mfc.bash") + shutil.copy2(completions_dir / "_mfc", install_dir / "_mfc") + user_shell = os.environ.get("SHELL", "") + if "zsh" in user_shell: + cons.print(f"[dim]Tab completions installed. Run: source {install_dir}/_mfc[/dim]") + else: + cons.print(f"[dim]Tab completions installed. Run: source {install_dir}/mfc.bash[/dim]") return + # Update if installed but older than generated updated = [] for src_name, dst_name in [("mfc.bash", "mfc.bash"), ("_mfc", "_mfc")]: src, dst = completions_dir / src_name, install_dir / dst_name @@ -104,10 +97,9 @@ def __ensure_generated_files(): if needs_regen: __do_regenerate(toolchain) - else: - # Even if we didn't regenerate, check if installed completions need updating - # (e.g., after git pull with new generated files) - __update_installed_completions(toolchain) + + # Always check if completions need to be installed or updated + __update_installed_completions(toolchain) def __print_greeting(): MFC_LOGO_LINES = MFC_LOGO.splitlines() diff --git a/toolchain/mfc/cli/completion_gen.py b/toolchain/mfc/cli/completion_gen.py index a12f55e686..45ab1e94a2 100644 --- a/toolchain/mfc/cli/completion_gen.py +++ b/toolchain/mfc/cli/completion_gen.py @@ -245,10 +245,11 @@ def generate_bash_completion(schema: CLISchema) -> str: ' return 0', '}', '', - '# Note: -o nospace prevents trailing space; we only use -o filenames when actually completing files', - 'complete -F _mfc_completions ./mfc.sh', - 'complete -F _mfc_completions mfc.sh', - 'complete -F _mfc_completions mfc', + '# -o filenames: handle escaping/slashes for file completions', + '# Removed -o bashdefault to prevent unwanted directory fallback', + 'complete -o filenames -F _mfc_completions ./mfc.sh', + 'complete -o filenames -F _mfc_completions mfc.sh', + 'complete -o filenames -F _mfc_completions mfc', ]) return '\n'.join(lines) From 33cbd0e182d62492e2006a63ee2731580bd419d7 Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Wed, 4 Feb 2026 21:27:49 -0500 Subject: [PATCH 13/33] Auto-install completions from mfc.sh with shell rc setup Move completion auto-install to mfc.sh so it runs for ALL commands including help, precheck, etc. This ensures completions are always set up on first run. - Install completion files to ~/.local/share/mfc/completions/ - Add source line to .bashrc or fpath to .zshrc - Tell user to restart shell or source the file - main.py now only handles updates when generated files change Co-Authored-By: Claude Opus 4.5 --- mfc.sh | 27 +++++++++++++++++++++++++++ toolchain/main.py | 39 +++++++++++++++------------------------ 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/mfc.sh b/mfc.sh index 5ad491d602..fa3d420df9 100755 --- a/mfc.sh +++ b/mfc.sh @@ -16,6 +16,33 @@ if [ -d "$(pwd)/.git" ] && [ ! -e "$(pwd)/.git/hooks/pre-commit" ] && [ -f "$(pw log "Installed git pre-commit hook (runs$MAGENTA ./mfc.sh precheck$COLOR_RESET before commits)." fi +# Auto-install shell completions (once) +COMPLETION_DIR="$HOME/.local/share/mfc/completions" +if [ ! -d "$COMPLETION_DIR" ]; then + mkdir -p "$COMPLETION_DIR" + cp "$(pwd)/toolchain/completions/mfc.bash" "$COMPLETION_DIR/" + cp "$(pwd)/toolchain/completions/_mfc" "$COMPLETION_DIR/" + + # Add to shell rc file based on current shell + if [[ "$SHELL" == *"zsh"* ]]; then + RC_FILE="$HOME/.zshrc" + RC_LINE="fpath=(\"$COMPLETION_DIR\" \$fpath)" + SOURCE_CMD="source $COMPLETION_DIR/_mfc" + else + RC_FILE="$HOME/.bashrc" + RC_LINE="[ -f \"$COMPLETION_DIR/mfc.bash\" ] && source \"$COMPLETION_DIR/mfc.bash\"" + SOURCE_CMD="source $COMPLETION_DIR/mfc.bash" + fi + + if [ -f "$RC_FILE" ] && ! grep -q "$COMPLETION_DIR" "$RC_FILE" 2>/dev/null; then + echo "" >> "$RC_FILE" + echo "# MFC shell completion" >> "$RC_FILE" + echo "$RC_LINE" >> "$RC_FILE" + fi + + log "Installed tab completions. Restart shell or run:$MAGENTA $SOURCE_CMD$COLOR_RESET" +fi + # Print startup message immediately for user feedback log "Starting..." diff --git a/toolchain/main.py b/toolchain/main.py index bc684024c6..568db1db67 100644 --- a/toolchain/main.py +++ b/toolchain/main.py @@ -32,38 +32,29 @@ def __do_regenerate(toolchain: str): def __update_installed_completions(toolchain: str): - """Install or update shell completions automatically.""" + """Update installed shell completions if they're older than generated ones.""" import shutil # pylint: disable=import-outside-toplevel from pathlib import Path # pylint: disable=import-outside-toplevel - completions_dir = Path(toolchain) / "completions" - install_dir = Path.home() / ".local" / "share" / "mfc" / "completions" - - # Auto-install if not installed yet - if not install_dir.exists(): - install_dir.mkdir(parents=True, exist_ok=True) - shutil.copy2(completions_dir / "mfc.bash", install_dir / "mfc.bash") - shutil.copy2(completions_dir / "_mfc", install_dir / "_mfc") - user_shell = os.environ.get("SHELL", "") - if "zsh" in user_shell: - cons.print(f"[dim]Tab completions installed. Run: source {install_dir}/_mfc[/dim]") - else: - cons.print(f"[dim]Tab completions installed. Run: source {install_dir}/mfc.bash[/dim]") + src_dir = Path(toolchain) / "completions" + dst_dir = Path.home() / ".local" / "share" / "mfc" / "completions" + + # Only update if already installed (mfc.sh handles initial install) + if not dst_dir.exists(): return # Update if installed but older than generated - updated = [] - for src_name, dst_name in [("mfc.bash", "mfc.bash"), ("_mfc", "_mfc")]: - src, dst = completions_dir / src_name, install_dir / dst_name - if src.exists() and dst.exists() and os.path.getmtime(src) > os.path.getmtime(dst): - shutil.copy2(src, dst) - updated.append(("bash" if src_name == "mfc.bash" else "zsh", dst)) + updated = False + for name in ["mfc.bash", "_mfc"]: + if (src_dir / name).exists() and (dst_dir / name).exists(): + if os.path.getmtime(src_dir / name) > os.path.getmtime(dst_dir / name): + shutil.copy2(src_dir / name, dst_dir / name) + updated = True if updated: - if "zsh" in os.environ.get("SHELL", "") and any(s[0] == "zsh" for s in updated): - cons.print(f"[dim]Tab completions updated. Run: source {install_dir}/_mfc[/dim]") - elif any(s[0] == "bash" for s in updated): - cons.print(f"[dim]Tab completions updated. Run: source {install_dir}/mfc.bash[/dim]") + is_zsh = "zsh" in os.environ.get("SHELL", "") + src_cmd = f"source {dst_dir}/_mfc" if is_zsh else f"source {dst_dir}/mfc.bash" + cons.print(f"[dim]Tab completions updated. Run: {src_cmd}[/dim]") def __ensure_generated_files(): From 1ba466614d8969b755c4f169ceef3992c6d0de0f Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Wed, 4 Feb 2026 22:43:34 -0500 Subject: [PATCH 14/33] Clarify verbose, debug, and debug-log flag documentation - -v/-vv/-vvv: output verbosity levels - --debug: build with debug compiler flags (for MFC Fortran code) - --debug-log/-d: Python toolchain debug logging (not MFC code) Co-Authored-By: Claude Opus 4.5 --- toolchain/mfc/cli/commands.py | 8 ++++---- toolchain/mfc/cli/completion_gen.py | 4 ++-- toolchain/mfc/cli/docs_gen.py | 13 ++++++++++--- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/toolchain/mfc/cli/commands.py b/toolchain/mfc/cli/commands.py index 9697078aa2..8ad8c4bd07 100644 --- a/toolchain/mfc/cli/commands.py +++ b/toolchain/mfc/cli/commands.py @@ -85,7 +85,7 @@ Argument( name="verbose", short="v", - help="Increase verbosity level. Use -v, -vv, or -vvv for more detail.", + help="Increase output verbosity (-v, -vv, -vvv for more detail).", action=ArgAction.COUNT, default=0, ), @@ -98,7 +98,7 @@ Argument( name="debug-log", short="d", - help="Enable debug logging for troubleshooting.", + help="Enable Python toolchain debug logging (not MFC code).", action=ArgAction.STORE_TRUE, dest="debug_log", ), @@ -519,10 +519,10 @@ ], examples=[ Example("./mfc.sh validate case.py", "Check syntax and constraints"), - Example("./mfc.sh validate case.py -d", "Validate with debug output"), + Example("./mfc.sh validate case.py -d", "Validate with toolchain debug output"), ], key_options=[ - ("-d, --debug-log", "Enable debug logging"), + ("-d, --debug-log", "Enable toolchain debug logging"), ], ) diff --git a/toolchain/mfc/cli/completion_gen.py b/toolchain/mfc/cli/completion_gen.py index 45ab1e94a2..7a286bc981 100644 --- a/toolchain/mfc/cli/completion_gen.py +++ b/toolchain/mfc/cli/completion_gen.py @@ -319,8 +319,8 @@ def _generate_zsh_command_args(cmd: Command, schema: CLISchema) -> List[str]: "'--no-mpi[Disable MPI]'", "'--gpu[Enable GPU]:mode:(acc mp)'", "'--no-gpu[Disable GPU]'", - "'--debug[Enable debug mode]'", - "'--no-debug[Disable debug mode]'", + "'--debug[Build with debug compiler flags (for MFC code)]'", + "'--no-debug[Build without debug flags]'", "'--gcov[Enable gcov coverage]'", "'--no-gcov[Disable gcov coverage]'", "'--unified[Enable unified memory]'", diff --git a/toolchain/mfc/cli/docs_gen.py b/toolchain/mfc/cli/docs_gen.py index 0c4b8b69a7..cf120fc07f 100644 --- a/toolchain/mfc/cli/docs_gen.py +++ b/toolchain/mfc/cli/docs_gen.py @@ -83,7 +83,7 @@ def _generate_options_table(cmd: Command, schema: CLISchema) -> List[str]: if "mfc_config" in cmd.include_common: lines.append("| `--mpi`, `--no-mpi` | Enable/disable MPI | `true` |") lines.append("| `--gpu [acc/mp]`, `--no-gpu` | Enable GPU (OpenACC/OpenMP) | `no` |") - lines.append("| `--debug`, `--no-debug` | Enable debug mode | `false` |") + lines.append("| `--debug`, `--no-debug` | Build with debug compiler flags | `false` |") lines.append("") @@ -270,16 +270,23 @@ def generate_cli_reference(schema: CLISchema) -> str: "|------|-------------|", "| `--mpi` / `--no-mpi` | Enable/disable MPI support |", "| `--gpu [acc/mp]` / `--no-gpu` | Enable GPU with OpenACC or OpenMP |", - "| `--debug` / `--no-debug` | Enable debug build |", + "| `--debug` / `--no-debug` | Build with debug compiler flags |", "| `--gcov` / `--no-gcov` | Enable code coverage |", "| `--single` / `--no-single` | Single precision |", "| `--mixed` / `--no-mixed` | Mixed precision |", "", "### Verbosity (`-v, --verbose`)", "", + "Controls output verbosity level:", + "", "- `-v` - Basic verbose output", "- `-vv` - Show build commands", - "- `-vvv` - Full debug output including CMake debug", + "- `-vvv` - Full verbose output including CMake details", + "", + "### Debug Logging (`-d, --debug-log`)", + "", + "Enables debug logging for the Python toolchain (mfc.sh internals).", + "This is for troubleshooting the build system, not the MFC simulation code.", "", ]) From adfcb7f961be00f05d57db1bbfe2888a1a78b5fc Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Thu, 5 Feb 2026 09:10:17 -0500 Subject: [PATCH 15/33] Cap pre-commit hook parallelism at 12 jobs Avoid hogging resources on machines with many cores. Co-Authored-By: Claude Opus 4.5 --- .githooks/pre-commit | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 01e6f3c257..b13ccf0cfb 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -11,8 +11,9 @@ fi cd "$(git rev-parse --show-toplevel)" -# Auto-detect CPU count +# Auto-detect CPU count (capped at 12 to avoid hogging resources) JOBS=$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4) +[ "$JOBS" -gt 12 ] && JOBS=12 echo "" echo "mfc: Running precheck before commit (-j $JOBS)..." From 02ef3de45816ac72741759623c74f275281ca1f5 Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Thu, 5 Feb 2026 10:28:52 -0500 Subject: [PATCH 16/33] Fix completion auto-install to check for files, not just directory Previously checked if ~/.local/share/mfc/completions/ existed. Now checks if the actual completion file exists for the user's shell. This handles the edge case of an empty completions directory. Co-Authored-By: Claude Opus 4.5 --- mfc.sh | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/mfc.sh b/mfc.sh index fa3d420df9..e44fd863ff 100755 --- a/mfc.sh +++ b/mfc.sh @@ -18,22 +18,23 @@ fi # Auto-install shell completions (once) COMPLETION_DIR="$HOME/.local/share/mfc/completions" -if [ ! -d "$COMPLETION_DIR" ]; then +if [[ "$SHELL" == *"zsh"* ]]; then + COMPLETION_FILE="$COMPLETION_DIR/_mfc" + RC_FILE="$HOME/.zshrc" + RC_LINE="fpath=(\"$COMPLETION_DIR\" \$fpath)" + SOURCE_CMD="source $COMPLETION_DIR/_mfc" +else + COMPLETION_FILE="$COMPLETION_DIR/mfc.bash" + RC_FILE="$HOME/.bashrc" + RC_LINE="[ -f \"$COMPLETION_DIR/mfc.bash\" ] && source \"$COMPLETION_DIR/mfc.bash\"" + SOURCE_CMD="source $COMPLETION_DIR/mfc.bash" +fi + +if [ ! -f "$COMPLETION_FILE" ]; then mkdir -p "$COMPLETION_DIR" cp "$(pwd)/toolchain/completions/mfc.bash" "$COMPLETION_DIR/" cp "$(pwd)/toolchain/completions/_mfc" "$COMPLETION_DIR/" - # Add to shell rc file based on current shell - if [[ "$SHELL" == *"zsh"* ]]; then - RC_FILE="$HOME/.zshrc" - RC_LINE="fpath=(\"$COMPLETION_DIR\" \$fpath)" - SOURCE_CMD="source $COMPLETION_DIR/_mfc" - else - RC_FILE="$HOME/.bashrc" - RC_LINE="[ -f \"$COMPLETION_DIR/mfc.bash\" ] && source \"$COMPLETION_DIR/mfc.bash\"" - SOURCE_CMD="source $COMPLETION_DIR/mfc.bash" - fi - if [ -f "$RC_FILE" ] && ! grep -q "$COMPLETION_DIR" "$RC_FILE" 2>/dev/null; then echo "" >> "$RC_FILE" echo "# MFC shell completion" >> "$RC_FILE" From fc5e2a50ffb7e737780067cc79d0b4b1712cbd31 Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Thu, 5 Feb 2026 10:40:13 -0500 Subject: [PATCH 17/33] Auto-update outdated shell completions Check if installed completions are older than source files and update them automatically. Shows message with source command only on install or update, silent otherwise. Co-Authored-By: Claude Opus 4.5 --- mfc.sh | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/mfc.sh b/mfc.sh index e44fd863ff..583ab09321 100755 --- a/mfc.sh +++ b/mfc.sh @@ -16,24 +16,29 @@ if [ -d "$(pwd)/.git" ] && [ ! -e "$(pwd)/.git/hooks/pre-commit" ] && [ -f "$(pw log "Installed git pre-commit hook (runs$MAGENTA ./mfc.sh precheck$COLOR_RESET before commits)." fi -# Auto-install shell completions (once) +# Shell completions setup COMPLETION_DIR="$HOME/.local/share/mfc/completions" +COMPLETION_SRC="$(pwd)/toolchain/completions" if [[ "$SHELL" == *"zsh"* ]]; then COMPLETION_FILE="$COMPLETION_DIR/_mfc" + SOURCE_FILE="$COMPLETION_SRC/_mfc" RC_FILE="$HOME/.zshrc" RC_LINE="fpath=(\"$COMPLETION_DIR\" \$fpath)" SOURCE_CMD="source $COMPLETION_DIR/_mfc" else COMPLETION_FILE="$COMPLETION_DIR/mfc.bash" + SOURCE_FILE="$COMPLETION_SRC/mfc.bash" RC_FILE="$HOME/.bashrc" RC_LINE="[ -f \"$COMPLETION_DIR/mfc.bash\" ] && source \"$COMPLETION_DIR/mfc.bash\"" SOURCE_CMD="source $COMPLETION_DIR/mfc.bash" fi +# Install or update completions if [ ! -f "$COMPLETION_FILE" ]; then + # Fresh install mkdir -p "$COMPLETION_DIR" - cp "$(pwd)/toolchain/completions/mfc.bash" "$COMPLETION_DIR/" - cp "$(pwd)/toolchain/completions/_mfc" "$COMPLETION_DIR/" + cp "$COMPLETION_SRC/mfc.bash" "$COMPLETION_DIR/" + cp "$COMPLETION_SRC/_mfc" "$COMPLETION_DIR/" if [ -f "$RC_FILE" ] && ! grep -q "$COMPLETION_DIR" "$RC_FILE" 2>/dev/null; then echo "" >> "$RC_FILE" @@ -42,6 +47,11 @@ if [ ! -f "$COMPLETION_FILE" ]; then fi log "Installed tab completions. Restart shell or run:$MAGENTA $SOURCE_CMD$COLOR_RESET" +elif [ "$SOURCE_FILE" -nt "$COMPLETION_FILE" ]; then + # Update outdated completions + cp "$COMPLETION_SRC/mfc.bash" "$COMPLETION_DIR/" + cp "$COMPLETION_SRC/_mfc" "$COMPLETION_DIR/" + log "Updated tab completions. Run:$MAGENTA $SOURCE_CMD$COLOR_RESET" fi # Print startup message immediately for user feedback From 9c15c8a5b20013f87e62bab2d9c90fd13a4b4ea0 Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Thu, 5 Feb 2026 10:57:46 -0500 Subject: [PATCH 18/33] Auto-activate completions when mfc.sh is sourced When running 'source ./mfc.sh' instead of './mfc.sh', completions are activated immediately in the current shell. This is useful for users who want tab completion without restarting their shell. - './mfc.sh' - installs/updates, shows source command to run - 'source ./mfc.sh' - installs/updates AND activates immediately Co-Authored-By: Claude Opus 4.5 --- mfc.sh | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/mfc.sh b/mfc.sh index 583ab09321..2abf9c23d9 100755 --- a/mfc.sh +++ b/mfc.sh @@ -34,24 +34,35 @@ else fi # Install or update completions +COMPLETIONS_CHANGED=false if [ ! -f "$COMPLETION_FILE" ]; then # Fresh install mkdir -p "$COMPLETION_DIR" cp "$COMPLETION_SRC/mfc.bash" "$COMPLETION_DIR/" cp "$COMPLETION_SRC/_mfc" "$COMPLETION_DIR/" + COMPLETIONS_CHANGED=true if [ -f "$RC_FILE" ] && ! grep -q "$COMPLETION_DIR" "$RC_FILE" 2>/dev/null; then echo "" >> "$RC_FILE" echo "# MFC shell completion" >> "$RC_FILE" echo "$RC_LINE" >> "$RC_FILE" fi - - log "Installed tab completions. Restart shell or run:$MAGENTA $SOURCE_CMD$COLOR_RESET" elif [ "$SOURCE_FILE" -nt "$COMPLETION_FILE" ]; then # Update outdated completions cp "$COMPLETION_SRC/mfc.bash" "$COMPLETION_DIR/" cp "$COMPLETION_SRC/_mfc" "$COMPLETION_DIR/" - log "Updated tab completions. Run:$MAGENTA $SOURCE_CMD$COLOR_RESET" + COMPLETIONS_CHANGED=true +fi + +# If sourced (not executed), we can activate completions in the current shell +if [ "$COMPLETIONS_CHANGED" = true ]; then + if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then + # Script is being sourced - activate completions now + source "$COMPLETION_FILE" 2>/dev/null && log "Tab completions activated." + else + # Script is being executed - can't modify parent shell + log "Tab completions updated. Run:$MAGENTA $SOURCE_CMD$COLOR_RESET" + fi fi # Print startup message immediately for user feedback From 52d0ddd23dfd0f0aa98ed553100298bf1a62139b Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Thu, 5 Feb 2026 11:05:52 -0500 Subject: [PATCH 19/33] Use workflow_run for benchmarks and extract completion logic - Replace polling-based wait-for-tests job with workflow_run trigger that fires when Test Suite completes (more efficient, no wasted runner minutes) - Extract shell completion setup from mfc.sh to dedicated toolchain/bootstrap/completions.sh script for better maintainability Co-Authored-By: Claude Opus 4.5 --- .github/workflows/bench.yml | 98 ++++++++++++++---------------- mfc.sh | 50 +-------------- toolchain/bootstrap/completions.sh | 74 ++++++++++++++++++++++ 3 files changed, 123 insertions(+), 99 deletions(-) create mode 100644 toolchain/bootstrap/completions.sh diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index f75631b2dd..1b95456927 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -1,24 +1,32 @@ name: 'Benchmark' on: - pull_request: - pull_request_review: - types: [submitted] + # Trigger when Test Suite completes (no polling needed) + workflow_run: + workflows: ["Test Suite"] + types: [completed] workflow_dispatch: concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch || github.ref }} cancel-in-progress: true jobs: file-changes: name: Detect File Changes + # Only run if Test Suite passed (or manual dispatch) + if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' runs-on: 'ubuntu-latest' outputs: checkall: ${{ steps.changes.outputs.checkall }} + pr_number: ${{ steps.pr-info.outputs.pr_number }} + pr_approved: ${{ steps.pr-info.outputs.approved }} + pr_author: ${{ steps.pr-info.outputs.author }} steps: - name: Clone uses: actions/checkout@v4 + with: + ref: ${{ github.event.workflow_run.head_sha || github.sha }} - name: Detect Changes uses: dorny/paths-filter@v3 @@ -26,62 +34,49 @@ jobs: with: filters: ".github/file-filter.yml" - wait-for-tests: - name: Wait for Test Suite - runs-on: ubuntu-latest - steps: - - name: Wait for Test Suite to Pass + - name: Get PR Info + id: pr-info env: GH_TOKEN: ${{ github.token }} run: | - echo "Waiting for Test Suite workflow to complete..." - SHA="${{ github.event.pull_request.head.sha || github.sha }}" - - # Poll every 60 seconds for up to 3 hours - for i in $(seq 1 180); do - # Get the Test Suite workflow runs for this commit - STATUS=$(gh api repos/${{ github.repository }}/commits/$SHA/check-runs \ - --jq '.check_runs[] | select(.name == "Lint Gate") | .conclusion' | head -1) - - if [ "$STATUS" = "success" ]; then - echo "Lint Gate passed. Checking test jobs..." - - # Check if any Github test jobs failed - FAILED=$(gh api repos/${{ github.repository }}/commits/$SHA/check-runs \ - --jq '[.check_runs[] | select(.name | startswith("Github")) | select(.conclusion == "failure")] | length') - - if [ "$FAILED" != "0" ]; then - echo "::error::Test Suite has failing jobs. Benchmarks will not run." - exit 1 + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "pr_number=" >> $GITHUB_OUTPUT + echo "approved=true" >> $GITHUB_OUTPUT + echo "author=${{ github.actor }}" >> $GITHUB_OUTPUT + else + # Get PR number from workflow_run + PR_NUMBER="${{ github.event.workflow_run.pull_requests[0].number }}" + if [ -n "$PR_NUMBER" ]; then + echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT + echo "author=${{ github.event.workflow_run.actor.login }}" >> $GITHUB_OUTPUT + + # Check if PR is approved + APPROVED=$(gh api repos/${{ github.repository }}/pulls/$PR_NUMBER/reviews \ + --jq '[.[] | select(.state == "APPROVED")] | length') + if [ "$APPROVED" -gt 0 ]; then + echo "approved=true" >> $GITHUB_OUTPUT + else + echo "approved=false" >> $GITHUB_OUTPUT fi - - # Check if Github tests are still running - PENDING=$(gh api repos/${{ github.repository }}/commits/$SHA/check-runs \ - --jq '[.check_runs[] | select(.name | startswith("Github")) | select(.conclusion == null)] | length') - - if [ "$PENDING" = "0" ]; then - echo "All Test Suite jobs completed successfully!" - exit 0 - fi - - echo "Tests still running ($PENDING pending)..." - elif [ "$STATUS" = "failure" ]; then - echo "::error::Lint Gate failed. Benchmarks will not run." - exit 1 else - echo "Lint Gate status: ${STATUS:-pending}..." + echo "pr_number=" >> $GITHUB_OUTPUT + echo "approved=false" >> $GITHUB_OUTPUT + echo "author=" >> $GITHUB_OUTPUT fi - - sleep 60 - done - - echo "::error::Timeout waiting for Test Suite to complete." - exit 1 + fi self: name: "${{ matrix.name }} (${{ matrix.device }}${{ matrix.interface != 'none' && format('-{0}', matrix.interface) || '' }})" - if: ${{ github.repository=='MFlowCode/MFC' && needs.file-changes.outputs.checkall=='true' && ((github.event_name=='pull_request_review' && github.event.review.state=='approved') || (github.event_name=='pull_request' && (github.event.pull_request.user.login=='sbryngelson' || github.event.pull_request.user.login=='wilfonba'))) }} - needs: [file-changes, wait-for-tests] + if: > + github.repository == 'MFlowCode/MFC' && + needs.file-changes.outputs.checkall == 'true' && + ( + github.event_name == 'workflow_dispatch' || + needs.file-changes.outputs.pr_approved == 'true' || + needs.file-changes.outputs.pr_author == 'sbryngelson' || + needs.file-changes.outputs.pr_author == 'wilfonba' + ) + needs: [file-changes] strategy: fail-fast: false matrix: @@ -145,6 +140,7 @@ jobs: - name: Clone - PR uses: actions/checkout@v4 with: + ref: ${{ github.event.workflow_run.head_sha || github.sha }} path: pr - name: Clone - Master diff --git a/mfc.sh b/mfc.sh index 2abf9c23d9..e8496f8ba5 100755 --- a/mfc.sh +++ b/mfc.sh @@ -16,54 +16,8 @@ if [ -d "$(pwd)/.git" ] && [ ! -e "$(pwd)/.git/hooks/pre-commit" ] && [ -f "$(pw log "Installed git pre-commit hook (runs$MAGENTA ./mfc.sh precheck$COLOR_RESET before commits)." fi -# Shell completions setup -COMPLETION_DIR="$HOME/.local/share/mfc/completions" -COMPLETION_SRC="$(pwd)/toolchain/completions" -if [[ "$SHELL" == *"zsh"* ]]; then - COMPLETION_FILE="$COMPLETION_DIR/_mfc" - SOURCE_FILE="$COMPLETION_SRC/_mfc" - RC_FILE="$HOME/.zshrc" - RC_LINE="fpath=(\"$COMPLETION_DIR\" \$fpath)" - SOURCE_CMD="source $COMPLETION_DIR/_mfc" -else - COMPLETION_FILE="$COMPLETION_DIR/mfc.bash" - SOURCE_FILE="$COMPLETION_SRC/mfc.bash" - RC_FILE="$HOME/.bashrc" - RC_LINE="[ -f \"$COMPLETION_DIR/mfc.bash\" ] && source \"$COMPLETION_DIR/mfc.bash\"" - SOURCE_CMD="source $COMPLETION_DIR/mfc.bash" -fi - -# Install or update completions -COMPLETIONS_CHANGED=false -if [ ! -f "$COMPLETION_FILE" ]; then - # Fresh install - mkdir -p "$COMPLETION_DIR" - cp "$COMPLETION_SRC/mfc.bash" "$COMPLETION_DIR/" - cp "$COMPLETION_SRC/_mfc" "$COMPLETION_DIR/" - COMPLETIONS_CHANGED=true - - if [ -f "$RC_FILE" ] && ! grep -q "$COMPLETION_DIR" "$RC_FILE" 2>/dev/null; then - echo "" >> "$RC_FILE" - echo "# MFC shell completion" >> "$RC_FILE" - echo "$RC_LINE" >> "$RC_FILE" - fi -elif [ "$SOURCE_FILE" -nt "$COMPLETION_FILE" ]; then - # Update outdated completions - cp "$COMPLETION_SRC/mfc.bash" "$COMPLETION_DIR/" - cp "$COMPLETION_SRC/_mfc" "$COMPLETION_DIR/" - COMPLETIONS_CHANGED=true -fi - -# If sourced (not executed), we can activate completions in the current shell -if [ "$COMPLETIONS_CHANGED" = true ]; then - if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then - # Script is being sourced - activate completions now - source "$COMPLETION_FILE" 2>/dev/null && log "Tab completions activated." - else - # Script is being executed - can't modify parent shell - log "Tab completions updated. Run:$MAGENTA $SOURCE_CMD$COLOR_RESET" - fi -fi +# Shell completions auto-install/update +. "$(pwd)/toolchain/bootstrap/completions.sh" "$(pwd)" # Print startup message immediately for user feedback log "Starting..." diff --git a/toolchain/bootstrap/completions.sh b/toolchain/bootstrap/completions.sh new file mode 100644 index 0000000000..42436fe356 --- /dev/null +++ b/toolchain/bootstrap/completions.sh @@ -0,0 +1,74 @@ +#!/bin/bash +# MFC Shell Completion Auto-installer +# +# This script handles automatic installation and updating of shell completions. +# It is sourced by mfc.sh on startup. +# +# Features: +# - Auto-detects bash vs zsh +# - Installs completions to ~/.local/share/mfc/completions/ +# - Updates completions when source files change +# - Configures shell rc files on first install +# - Sources completions immediately when mfc.sh is sourced + +_mfc_setup_completions() { + local MFC_ROOT="$1" + local COMPLETION_DIR="$HOME/.local/share/mfc/completions" + local COMPLETION_SRC="$MFC_ROOT/toolchain/completions" + local COMPLETION_FILE SOURCE_FILE RC_FILE RC_LINE SOURCE_CMD + + # Detect shell and set appropriate paths + if [[ "$SHELL" == *"zsh"* ]]; then + COMPLETION_FILE="$COMPLETION_DIR/_mfc" + SOURCE_FILE="$COMPLETION_SRC/_mfc" + RC_FILE="$HOME/.zshrc" + RC_LINE="fpath=(\"$COMPLETION_DIR\" \$fpath)" + SOURCE_CMD="source $COMPLETION_DIR/_mfc" + else + COMPLETION_FILE="$COMPLETION_DIR/mfc.bash" + SOURCE_FILE="$COMPLETION_SRC/mfc.bash" + RC_FILE="$HOME/.bashrc" + RC_LINE="[ -f \"$COMPLETION_DIR/mfc.bash\" ] && source \"$COMPLETION_DIR/mfc.bash\"" + SOURCE_CMD="source $COMPLETION_DIR/mfc.bash" + fi + + # Check if we need to install or update + local COMPLETIONS_CHANGED=false + + if [ ! -f "$COMPLETION_FILE" ]; then + # Fresh install + mkdir -p "$COMPLETION_DIR" + cp "$COMPLETION_SRC/mfc.bash" "$COMPLETION_DIR/" + cp "$COMPLETION_SRC/_mfc" "$COMPLETION_DIR/" + COMPLETIONS_CHANGED=true + + # Add to shell rc file on first install + if [ -f "$RC_FILE" ] && ! grep -q "$COMPLETION_DIR" "$RC_FILE" 2>/dev/null; then + echo "" >> "$RC_FILE" + echo "# MFC shell completion" >> "$RC_FILE" + echo "$RC_LINE" >> "$RC_FILE" + fi + elif [ "$SOURCE_FILE" -nt "$COMPLETION_FILE" ]; then + # Update outdated completions + cp "$COMPLETION_SRC/mfc.bash" "$COMPLETION_DIR/" + cp "$COMPLETION_SRC/_mfc" "$COMPLETION_DIR/" + COMPLETIONS_CHANGED=true + fi + + # Notify user about changes + if [ "$COMPLETIONS_CHANGED" = true ]; then + if [[ "${BASH_SOURCE[1]}" != "${0}" ]] 2>/dev/null; then + # Script is being sourced - activate completions now + # shellcheck disable=SC1090 + source "$COMPLETION_FILE" 2>/dev/null && log "Tab completions activated." + else + # Script is being executed - can't modify parent shell + log "Tab completions updated. Run:$MAGENTA $SOURCE_CMD$COLOR_RESET" + fi + fi +} + +# Run setup if this script is sourced with MFC_ROOT_DIR set +if [ -n "$1" ]; then + _mfc_setup_completions "$1" +fi From 8e3f4053a8756afc32c6cecf09654c3c2b94ee6c Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Thu, 5 Feb 2026 11:17:25 -0500 Subject: [PATCH 20/33] Suppress verbose package list from uv install by default Filter out individual package lines (+ pkg==1.0) from uv output while keeping progress info (Resolved, Built, Installed). Use -v flag to see full package list. Co-Authored-By: Claude Opus 4.5 --- toolchain/bootstrap/python.sh | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/toolchain/bootstrap/python.sh b/toolchain/bootstrap/python.sh index d2c087a26c..e6af22faf8 100644 --- a/toolchain/bootstrap/python.sh +++ b/toolchain/bootstrap/python.sh @@ -138,6 +138,7 @@ if ! cmp "$(pwd)/toolchain/pyproject.toml" "$(pwd)/build/pyproject.toml" > /dev/ next_arg=0 nthreads=1 + verbose=0 for arg in "$@"; do if [ "$arg" == "-j" ] || [ "$arg" == "--jobs" ]; then next_arg=1 @@ -148,6 +149,10 @@ if ! cmp "$(pwd)/toolchain/pyproject.toml" "$(pwd)/build/pyproject.toml" > /dev/ nthreads=$arg continue fi + # Check for verbosity flags + if [ "$arg" == "-v" ] || [ "$arg" == "-vv" ] || [ "$arg" == "-vvv" ] || [ "$arg" == "--verbose" ]; then + verbose=1 + fi done # Run package installer and show progress @@ -173,12 +178,12 @@ if ! cmp "$(pwd)/toolchain/pyproject.toml" "$(pwd)/build/pyproject.toml" > /dev/ # Use uv if available, otherwise fall back to pip if [ "$USE_UV" = "1" ]; then - # uv is much faster and has its own progress display - show it # UV_LINK_MODE=copy avoids slow hardlink failures on cross-filesystem installs (common on HPC) export UV_LINK_MODE=copy log "(venv) Using$MAGENTA uv$COLOR_RESET for fast installation..." - if [ -t 1 ]; then - # Interactive terminal: show uv's native progress + + if [ "$verbose" = "1" ]; then + # Verbose mode: show full uv output if uv pip install "$(pwd)/toolchain"; then ok "(venv) Installation succeeded." cp "$(pwd)/toolchain/pyproject.toml" "$(pwd)/build/" @@ -189,13 +194,18 @@ if ! cmp "$(pwd)/toolchain/pyproject.toml" "$(pwd)/build/pyproject.toml" > /dev/ exit 1 fi else - # Non-interactive: capture output for logging - if uv pip install "$(pwd)/toolchain" > "$PIP_LOG" 2>&1; then + # Default: show progress but filter out individual package lines (+ pkg==ver) + uv pip install "$(pwd)/toolchain" > "$PIP_LOG" 2>&1 + UV_EXIT=$? + # Show filtered output (progress info without package list) + # Filter out lines like " + pkg==1.0", " - pkg==1.0", " ~ pkg==1.0" + grep -v '^ [+~-] ' "$PIP_LOG" || true + if [ $UV_EXIT -eq 0 ]; then rm -f "$PIP_LOG" ok "(venv) Installation succeeded." cp "$(pwd)/toolchain/pyproject.toml" "$(pwd)/build/" else - error "(venv) Installation failed. See output below:" + error "(venv) Installation failed. Full output:" echo "" cat "$PIP_LOG" echo "" From 989a79d010b8f2457fd8b18c44f3c8525a30e440 Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Thu, 5 Feb 2026 11:23:39 -0500 Subject: [PATCH 21/33] Compact splash screen from 45 to 20 lines Remove decorative boxes and condense layout while keeping all essential information: commands with aliases, descriptions, and quick start guide. Co-Authored-By: Claude Opus 4.5 --- toolchain/mfc/user_guide.py | 84 +++++++++++++++---------------------- 1 file changed, 33 insertions(+), 51 deletions(-) diff --git a/toolchain/mfc/user_guide.py b/toolchain/mfc/user_guide.py index d83597e98d..2f0e6c064d 100644 --- a/toolchain/mfc/user_guide.py +++ b/toolchain/mfc/user_guide.py @@ -380,66 +380,48 @@ def print_help_topics(): # ============================================================================= def print_help(): - """Print enhanced, colorized help overview.""" + """Print compact, colorized help overview.""" - # Header + # Header (no box) cons.print() - cons.raw.print(Panel( - "[bold cyan]MFC[/bold cyan] - [dim]Multi-component Flow Code[/dim]\n" - "[dim]Exascale CFD solver for compressible multi-phase flows[/dim]", - box=box.ROUNDED, - padding=(0, 2) - )) + cons.print("[bold cyan]MFC[/bold cyan] - Multi-component Flow Code") + cons.print("[dim]Exascale CFD solver for compressible multi-phase flows[/dim]") cons.print() - # Commands table - table = Table( - title="[bold]Commands[/bold]", - box=box.SIMPLE, - show_header=True, - header_style="bold cyan", - title_justify="left", - padding=(0, 2) - ) - table.add_column("Command", style="green", no_wrap=True) - table.add_column("Alias", style="dim", no_wrap=True) - table.add_column("Description", style="white") - - # Primary commands with aliases - for cmd in ["build", "run", "test", "validate", "new", "clean"]: - alias = COMMANDS[cmd].get("alias", "") - alias_str = alias if alias else "" - table.add_row(cmd, alias_str, COMMANDS[cmd]["description"]) - - table.add_row("", "", "") # Spacer - - # Secondary commands - for cmd in ["params", "count", "packer", "load"]: - table.add_row(f"[dim]{cmd}[/dim]", "", f"[dim]{COMMANDS[cmd]['description']}[/dim]") + # Commands section - compact format + cons.print("[bold]Commands:[/bold]") + + # Primary commands with inline aliases and short descriptions + commands_primary = [ + ("build", "b", "Build MFC targets with optional GPU support"), + ("run", "r", "Run simulation interactively or as batch job"), + ("test", "t", "Run test suite"), + ("validate", "v", "Check case file for errors"), + ("new", "", "Create new case from template"), + ("clean", "c", "Remove build artifacts"), + ] + + for cmd, alias, desc in commands_primary: + alias_str = f" ({alias})" if alias else " " + cons.print(f" [green]{cmd:9}[/green][dim]{alias_str:4}[/dim] {desc}") + + # Secondary commands (dimmed) + commands_secondary = [ + ("params", "Search ~3,300 case parameters"), + ("load", "Load environment modules (use with source)"), + ("help", "Topic help (gpu, clusters, batch, debugging)"), + ] + + for cmd, desc in commands_secondary: + cons.print(f" [dim]{cmd:13} {desc}[/dim]") - table.add_row("", "", "") # Spacer - table.add_row("[dim]help[/dim]", "", "[dim]Show help on a topic (gpu, clusters, batch, debugging)[/dim]") - - cons.raw.print(table) cons.print() - # Quick start - cons.raw.print(Panel( - "[bold]Quick Start[/bold]\n\n" - " [green]1.[/green] [cyan]./mfc.sh new my_case[/cyan] Create a new case\n" - " [green]2.[/green] [cyan]vim my_case/case.py[/cyan] Edit parameters\n" - " [green]3.[/green] [cyan]./mfc.sh validate my_case/case.py[/cyan] Check for errors\n" - " [green]4.[/green] [cyan]./mfc.sh build -j $(nproc)[/cyan] Build MFC\n" - " [green]5.[/green] [cyan]./mfc.sh run my_case/case.py[/cyan] Run simulation", - box=box.ROUNDED, - border_style="green", - padding=(1, 2) - )) - cons.print() + # Quick start - single line + cons.print("[bold]Quick start:[/bold] [cyan]./mfc.sh new my_case[/cyan] → edit case.py → [cyan]./mfc.sh build[/cyan] → [cyan]./mfc.sh run[/cyan]") # Footer - cons.print("[dim]Run [cyan]./mfc.sh --help[/cyan] for detailed options[/dim]") - cons.print("[dim]Run [cyan]./mfc.sh help [/cyan] for topic help (gpu, clusters, batch, debugging)[/dim]") + cons.print("[dim]Run ./mfc.sh --help for options[/dim]") cons.print() From e913f7c7918ff9226ec53d674c5ad94d02fe5c60 Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Thu, 5 Feb 2026 11:26:46 -0500 Subject: [PATCH 22/33] Pass arguments to python.sh for verbose flag support Now ./mfc.sh init -v and similar commands respect verbosity flags during venv/package installation. Co-Authored-By: Claude Opus 4.5 --- mfc.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mfc.sh b/mfc.sh index e8496f8ba5..3bbf3e0923 100755 --- a/mfc.sh +++ b/mfc.sh @@ -50,11 +50,11 @@ if [ "$1" '==' 'load' ] && [ "$2" != "--help" ] && [ "$2" != "-h" ]; then fi shift; . "$(pwd)/toolchain/bootstrap/modules.sh" $@; return elif [ "$1" '==' "lint" ] && [ "$2" != "--help" ] && [ "$2" != "-h" ]; then - . "$(pwd)/toolchain/bootstrap/python.sh" + . "$(pwd)/toolchain/bootstrap/python.sh" "$@" shift; . "$(pwd)/toolchain/bootstrap/lint.sh" $@; exit 0 elif [ "$1" '==' "format" ] && [ "$2" != "--help" ] && [ "$2" != "-h" ]; then - . "$(pwd)/toolchain/bootstrap/python.sh" + . "$(pwd)/toolchain/bootstrap/python.sh" "$@" shift; . "$(pwd)/toolchain/bootstrap/format.sh" $@; exit 0 elif [ "$1" '==' "venv" ]; then @@ -62,11 +62,11 @@ elif [ "$1" '==' "venv" ]; then elif [ "$1" '==' "clean" ] && [ "$2" != "--help" ] && [ "$2" != "-h" ]; then rm -rf "$(pwd)/build"; exit 0 elif [ "$1" '==' "spelling" ] && [ "$2" != "--help" ] && [ "$2" != "-h" ]; then - . "$(pwd)/toolchain/bootstrap/python.sh" + . "$(pwd)/toolchain/bootstrap/python.sh" "$@" shift; . "$(pwd)/toolchain/bootstrap/spelling.sh" $@; exit 0 elif [ "$1" '==' "precheck" ]; then - . "$(pwd)/toolchain/bootstrap/python.sh" + . "$(pwd)/toolchain/bootstrap/python.sh" "$@" shift; . "$(pwd)/toolchain/bootstrap/precheck.sh" $@; exit 0 fi @@ -74,7 +74,7 @@ fi mkdir -p "$(pwd)/build" . "$(pwd)/toolchain/bootstrap/cmake.sh" -. "$(pwd)/toolchain/bootstrap/python.sh" +. "$(pwd)/toolchain/bootstrap/python.sh" "$@" # init command: just bootstrap the environment and exit (no Python command) if [ "$1" '==' 'init' ]; then From 6972e6e8485b30bf237ab90a4736a62b2c25a350 Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Thu, 5 Feb 2026 11:43:37 -0500 Subject: [PATCH 23/33] Handle ./mfc.sh -v without command (show help, not error) When only flags are given without a command, show help screen instead of passing flags to main.py which would error. Co-Authored-By: Claude Opus 4.5 --- mfc.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mfc.sh b/mfc.sh index 3bbf3e0923..1db83f4649 100755 --- a/mfc.sh +++ b/mfc.sh @@ -84,7 +84,12 @@ fi echo # Run the main.py bootstrap script -python3 "$(pwd)/toolchain/main.py" "$@" +# If only flags given (no command), show help without passing flags +if [ -z "$1" ] || [[ "$1" == -* ]]; then + python3 "$(pwd)/toolchain/main.py" +else + python3 "$(pwd)/toolchain/main.py" "$@" +fi code=$? echo From cf49bccc6fb58d61321c7b13b0ac904ebcc5d49f Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Thu, 5 Feb 2026 11:45:52 -0500 Subject: [PATCH 24/33] Fix splash screen to use COMMANDS as single source of truth Use the CLI schema from commands.py instead of hardcoded descriptions for the compact splash screen. Co-Authored-By: Claude Opus 4.5 --- toolchain/mfc/user_guide.py | 40 +++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/toolchain/mfc/user_guide.py b/toolchain/mfc/user_guide.py index 2f0e6c064d..ba21f91dc7 100644 --- a/toolchain/mfc/user_guide.py +++ b/toolchain/mfc/user_guide.py @@ -379,6 +379,13 @@ def print_help_topics(): # ENHANCED HELP OUTPUT # ============================================================================= +def _truncate_desc(desc: str, max_len: int = 50) -> str: + """Truncate description to fit compact display.""" + if len(desc) <= max_len: + return desc + return desc[:max_len-3] + "..." + + def print_help(): """Print compact, colorized help overview.""" @@ -388,31 +395,26 @@ def print_help(): cons.print("[dim]Exascale CFD solver for compressible multi-phase flows[/dim]") cons.print() - # Commands section - compact format + # Commands section - compact format (using COMMANDS as source of truth) cons.print("[bold]Commands:[/bold]") - # Primary commands with inline aliases and short descriptions - commands_primary = [ - ("build", "b", "Build MFC targets with optional GPU support"), - ("run", "r", "Run simulation interactively or as batch job"), - ("test", "t", "Run test suite"), - ("validate", "v", "Check case file for errors"), - ("new", "", "Create new case from template"), - ("clean", "c", "Remove build artifacts"), - ] - - for cmd, alias, desc in commands_primary: + # Primary commands (shown prominently with aliases) + primary = ["build", "run", "test", "validate", "new", "clean"] + for cmd in primary: + if cmd not in COMMANDS: + continue + info = COMMANDS[cmd] + alias = info.get("alias") or "" alias_str = f" ({alias})" if alias else " " + desc = _truncate_desc(info["description"]) cons.print(f" [green]{cmd:9}[/green][dim]{alias_str:4}[/dim] {desc}") # Secondary commands (dimmed) - commands_secondary = [ - ("params", "Search ~3,300 case parameters"), - ("load", "Load environment modules (use with source)"), - ("help", "Topic help (gpu, clusters, batch, debugging)"), - ] - - for cmd, desc in commands_secondary: + secondary = ["params", "load", "help"] + for cmd in secondary: + if cmd not in COMMANDS: + continue + desc = _truncate_desc(COMMANDS[cmd]["description"]) cons.print(f" [dim]{cmd:13} {desc}[/dim]") cons.print() From 72d8a389c8bc510a7234e944aa32613bee994bd3 Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Thu, 5 Feb 2026 12:05:22 -0500 Subject: [PATCH 25/33] Address AI reviewer feedback on PR #1124 1. Fix workflow_run.actor.login returning re-runner instead of PR author by fetching actual PR author from GitHub API 2. Quote all $@ in mfc.sh to handle arguments with spaces correctly 3. Add existence check for completion source files before copying (prevents errors on fresh clones before generation) Co-Authored-By: Claude Opus 4.5 --- .github/workflows/bench.yml | 5 ++++- mfc.sh | 12 ++++++------ toolchain/bootstrap/completions.sh | 5 +++++ 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 1b95456927..6279f5f578 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -48,7 +48,10 @@ jobs: PR_NUMBER="${{ github.event.workflow_run.pull_requests[0].number }}" if [ -n "$PR_NUMBER" ]; then echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT - echo "author=${{ github.event.workflow_run.actor.login }}" >> $GITHUB_OUTPUT + + # Fetch actual PR author from API (workflow_run.actor is the re-runner, not PR author) + PR_AUTHOR=$(gh api repos/${{ github.repository }}/pulls/$PR_NUMBER --jq '.user.login') + echo "author=$PR_AUTHOR" >> $GITHUB_OUTPUT # Check if PR is approved APPROVED=$(gh api repos/${{ github.repository }}/pulls/$PR_NUMBER/reviews \ diff --git a/mfc.sh b/mfc.sh index 1db83f4649..da784c5510 100755 --- a/mfc.sh +++ b/mfc.sh @@ -48,27 +48,27 @@ if [ "$1" '==' 'load' ] && [ "$2" != "--help" ] && [ "$2" != "-h" ]; then echo "" exit 1 fi - shift; . "$(pwd)/toolchain/bootstrap/modules.sh" $@; return + shift; . "$(pwd)/toolchain/bootstrap/modules.sh" "$@"; return elif [ "$1" '==' "lint" ] && [ "$2" != "--help" ] && [ "$2" != "-h" ]; then . "$(pwd)/toolchain/bootstrap/python.sh" "$@" - shift; . "$(pwd)/toolchain/bootstrap/lint.sh" $@; exit 0 + shift; . "$(pwd)/toolchain/bootstrap/lint.sh" "$@"; exit 0 elif [ "$1" '==' "format" ] && [ "$2" != "--help" ] && [ "$2" != "-h" ]; then . "$(pwd)/toolchain/bootstrap/python.sh" "$@" - shift; . "$(pwd)/toolchain/bootstrap/format.sh" $@; exit 0 + shift; . "$(pwd)/toolchain/bootstrap/format.sh" "$@"; exit 0 elif [ "$1" '==' "venv" ]; then - shift; . "$(pwd)/toolchain/bootstrap/python.sh" $@; return + shift; . "$(pwd)/toolchain/bootstrap/python.sh" "$@"; return elif [ "$1" '==' "clean" ] && [ "$2" != "--help" ] && [ "$2" != "-h" ]; then rm -rf "$(pwd)/build"; exit 0 elif [ "$1" '==' "spelling" ] && [ "$2" != "--help" ] && [ "$2" != "-h" ]; then . "$(pwd)/toolchain/bootstrap/python.sh" "$@" - shift; . "$(pwd)/toolchain/bootstrap/spelling.sh" $@; exit 0 + shift; . "$(pwd)/toolchain/bootstrap/spelling.sh" "$@"; exit 0 elif [ "$1" '==' "precheck" ]; then . "$(pwd)/toolchain/bootstrap/python.sh" "$@" - shift; . "$(pwd)/toolchain/bootstrap/precheck.sh" $@; exit 0 + shift; . "$(pwd)/toolchain/bootstrap/precheck.sh" "$@"; exit 0 fi mkdir -p "$(pwd)/build" diff --git a/toolchain/bootstrap/completions.sh b/toolchain/bootstrap/completions.sh index 42436fe356..e4fca1c7b1 100644 --- a/toolchain/bootstrap/completions.sh +++ b/toolchain/bootstrap/completions.sh @@ -35,6 +35,11 @@ _mfc_setup_completions() { # Check if we need to install or update local COMPLETIONS_CHANGED=false + # Only proceed if source completion files exist (they're generated by ./mfc.sh generate) + if [ ! -f "$COMPLETION_SRC/mfc.bash" ] || [ ! -f "$COMPLETION_SRC/_mfc" ]; then + return + fi + if [ ! -f "$COMPLETION_FILE" ]; then # Fresh install mkdir -p "$COMPLETION_DIR" From 88d8736b3018f1594b671c9d3bafe1ae928dac46 Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Thu, 5 Feb 2026 15:39:02 -0500 Subject: [PATCH 26/33] Show full build error output instead of truncating to 40 lines Truncation hides important context when diagnosing build failures. Co-Authored-By: Claude Opus 4.5 --- toolchain/mfc/build.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/toolchain/mfc/build.py b/toolchain/mfc/build.py index 0ee07dc5fa..c0e6b7e478 100644 --- a/toolchain/mfc/build.py +++ b/toolchain/mfc/build.py @@ -266,21 +266,16 @@ def _show_build_error(result: subprocess.CompletedProcess, stage: str): # Show stdout if available (often contains the actual error for CMake) if result.stdout: stdout_text = result.stdout if isinstance(result.stdout, str) else result.stdout.decode('utf-8', errors='replace') - stdout_lines = stdout_text.strip().split('\n') - # Show last 40 lines to capture the relevant error - if len(stdout_lines) > 40: - stdout_lines = ['... (truncated) ...'] + stdout_lines[-40:] - if stdout_lines and stdout_lines != ['']: - cons.raw.print(Panel('\n'.join(stdout_lines), title="Output", border_style="yellow")) + stdout_text = stdout_text.strip() + if stdout_text: + cons.raw.print(Panel(stdout_text, title="Output", border_style="yellow")) # Show stderr if available if result.stderr: stderr_text = result.stderr if isinstance(result.stderr, str) else result.stderr.decode('utf-8', errors='replace') - stderr_lines = stderr_text.strip().split('\n') - if len(stderr_lines) > 40: - stderr_lines = ['... (truncated) ...'] + stderr_lines[-40:] - if stderr_lines and stderr_lines != ['']: - cons.raw.print(Panel('\n'.join(stderr_lines), title="Errors", border_style="red")) + stderr_text = stderr_text.strip() + if stderr_text: + cons.raw.print(Panel(stderr_text, title="Errors", border_style="red")) cons.print() From c96edf29a8b5143c702a5ec8846d778d632d4233 Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Thu, 5 Feb 2026 15:43:46 -0500 Subject: [PATCH 27/33] Fix troubleshooting tips to suggest --debug instead of --debug-log --debug-log only enables Python toolchain logging, while --debug enables debug compiler flags which is actually useful for diagnosing build and run failures. Co-Authored-By: Claude Opus 4.5 --- toolchain/mfc/user_guide.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/toolchain/mfc/user_guide.py b/toolchain/mfc/user_guide.py index ba21f91dc7..f30201b892 100644 --- a/toolchain/mfc/user_guide.py +++ b/toolchain/mfc/user_guide.py @@ -481,7 +481,7 @@ def after_build_failure(): cons.print() cons.raw.print(Panel( "[bold yellow]Troubleshooting Tips[/bold yellow]\n\n" - " [cyan]1.[/cyan] Run with [green]--debug-log[/green] to see detailed output\n" + " [cyan]1.[/cyan] Rebuild with [green]--debug[/green] for debug compiler flags and verbose output\n" " [cyan]2.[/cyan] Check [green]docs/documentation/troubleshooting.md[/green]\n" " [cyan]3.[/cyan] Ensure required modules are loaded: [green]source ./mfc.sh load -c -m [/green]\n" " [cyan]4.[/cyan] Try [green]./mfc.sh clean[/green] and rebuild", @@ -529,7 +529,7 @@ def after_run_failure(): "[bold yellow]Troubleshooting Tips[/bold yellow]\n\n" " [cyan]1.[/cyan] Validate your case: [green]./mfc.sh validate case.py[/green]\n" " [cyan]2.[/cyan] Check the output in [green]/[/green]\n" - " [cyan]3.[/cyan] Run with [green]--debug-log[/green] for more details\n" + " [cyan]3.[/cyan] Rebuild with [green]--debug[/green] for debug compiler flags\n" " [cyan]4.[/cyan] Check MFC documentation: [green]docs/[/green]", box=box.ROUNDED, border_style="yellow", From b44360f598894e3656267d8206240b95449d445f Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Thu, 5 Feb 2026 16:06:46 -0500 Subject: [PATCH 28/33] Fix flags-before-command detection and shell detection - Scan all args for a non-flag to detect if a command is present, so ./mfc.sh -v build works correctly instead of dropping args - Use ZSH_VERSION instead of $SHELL for shell detection (detects the running shell, not the login shell) Co-Authored-By: Claude Opus 4.5 --- mfc.sh | 10 +++++++--- toolchain/bootstrap/completions.sh | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/mfc.sh b/mfc.sh index da784c5510..bbdbb33adf 100755 --- a/mfc.sh +++ b/mfc.sh @@ -85,10 +85,14 @@ echo # Run the main.py bootstrap script # If only flags given (no command), show help without passing flags -if [ -z "$1" ] || [[ "$1" == -* ]]; then - python3 "$(pwd)/toolchain/main.py" -else +has_command=false +for arg in "$@"; do + case "$arg" in -*) ;; *) has_command=true; break ;; esac +done +if [ "$has_command" = true ]; then python3 "$(pwd)/toolchain/main.py" "$@" +else + python3 "$(pwd)/toolchain/main.py" fi code=$? diff --git a/toolchain/bootstrap/completions.sh b/toolchain/bootstrap/completions.sh index e4fca1c7b1..55779731d2 100644 --- a/toolchain/bootstrap/completions.sh +++ b/toolchain/bootstrap/completions.sh @@ -17,8 +17,8 @@ _mfc_setup_completions() { local COMPLETION_SRC="$MFC_ROOT/toolchain/completions" local COMPLETION_FILE SOURCE_FILE RC_FILE RC_LINE SOURCE_CMD - # Detect shell and set appropriate paths - if [[ "$SHELL" == *"zsh"* ]]; then + # Detect shell: use ZSH_VERSION (current shell) rather than $SHELL (login shell) + if [ -n "${ZSH_VERSION-}" ]; then COMPLETION_FILE="$COMPLETION_DIR/_mfc" SOURCE_FILE="$COMPLETION_SRC/_mfc" RC_FILE="$HOME/.zshrc" From f24c62b9495ad902f5ed2149dba31582acc69a1f Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Thu, 5 Feb 2026 16:50:10 -0500 Subject: [PATCH 29/33] Raise minimum Python version to 3.12 (pyrometheus requires it) The pyrometheus dependency requires Python >= 3.12. The previous minimum of 3.11 would allow bootstrapping but fail at package installation. Co-Authored-By: Claude Opus 4.5 --- toolchain/bootstrap/python.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toolchain/bootstrap/python.sh b/toolchain/bootstrap/python.sh index e6af22faf8..adf28090bb 100644 --- a/toolchain/bootstrap/python.sh +++ b/toolchain/bootstrap/python.sh @@ -1,7 +1,7 @@ #!/bin/bash MFC_PYTHON_MIN_MAJOR=3 -MFC_PYTHON_MIN_MINOR=11 +MFC_PYTHON_MIN_MINOR=12 MFC_PYTHON_MIN_STR="$MFC_PYTHON_MIN_MAJOR.$MFC_PYTHON_MIN_MINOR" is_python_compatible() { From e0bdc88e56513c12ad5fc948befc5684ecdd6851 Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Thu, 5 Feb 2026 17:41:46 -0500 Subject: [PATCH 30/33] Lower minimum Python to 3.10 by pinning pyrometheus to pre-3.12 commit Pin pyrometheus to commit 49833404f (before it added a Python >= 3.12 requirement) so MFC can support Python 3.10+. Verified that bootstrap, build, and chemistry test cases all pass with Python 3.10. Co-Authored-By: Claude Opus 4.6 --- toolchain/bootstrap/python.sh | 2 +- toolchain/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/toolchain/bootstrap/python.sh b/toolchain/bootstrap/python.sh index adf28090bb..59458edc6a 100644 --- a/toolchain/bootstrap/python.sh +++ b/toolchain/bootstrap/python.sh @@ -1,7 +1,7 @@ #!/bin/bash MFC_PYTHON_MIN_MAJOR=3 -MFC_PYTHON_MIN_MINOR=12 +MFC_PYTHON_MIN_MINOR=10 MFC_PYTHON_MIN_STR="$MFC_PYTHON_MIN_MAJOR.$MFC_PYTHON_MIN_MINOR" is_python_compatible() { diff --git a/toolchain/pyproject.toml b/toolchain/pyproject.toml index 53e2140290..a7d2d6c1b4 100644 --- a/toolchain/pyproject.toml +++ b/toolchain/pyproject.toml @@ -40,7 +40,7 @@ dependencies = [ # Chemistry "cantera>=3.1.0", #"pyrometheus == 1.0.5", - "pyrometheus @ git+https://github.com/pyrometheus/pyrometheus.git", + "pyrometheus @ git+https://github.com/pyrometheus/pyrometheus.git@49833404f91dd80e598902cdf68dbcc1f6e9bd16", # Frontier Profiling "astunparse==1.6.2", From f9fa9191d43c26d9c9e2a72930d842b1940360ad Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Thu, 5 Feb 2026 18:08:59 -0500 Subject: [PATCH 31/33] Add -v flag to all CI build/run/test/bench commands Enables verbose output in CI for easier debugging of failures. Co-Authored-By: Claude Opus 4.6 --- .github/Dockerfile | 8 ++++---- .github/workflows/frontier/bench.sh | 4 ++-- .github/workflows/frontier/build.sh | 4 ++-- .github/workflows/frontier/test.sh | 4 ++-- .github/workflows/frontier_amd/bench.sh | 4 ++-- .github/workflows/frontier_amd/build.sh | 4 ++-- .github/workflows/frontier_amd/test.sh | 4 ++-- .github/workflows/phoenix/bench.sh | 4 ++-- .github/workflows/phoenix/test.sh | 4 ++-- 9 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/Dockerfile b/.github/Dockerfile index 5b86e0f711..23c08d8154 100644 --- a/.github/Dockerfile +++ b/.github/Dockerfile @@ -50,16 +50,16 @@ RUN python3.12 -m venv /opt/MFC/build/venv && \ RUN echo "TARGET=$TARGET CC=$CC_COMPILER FC=$FC_COMPILER" && \ cd /opt/MFC && \ if [ "$TARGET" = "gpu" ]; then \ - ./mfc.sh build --gpu -j $(nproc); \ + ./mfc.sh build -v --gpu -j $(nproc); \ else \ - ./mfc.sh build -j $(nproc); \ + ./mfc.sh build -v -j $(nproc); \ fi RUN cd /opt/MFC && \ if [ "$TARGET" = "gpu" ]; then \ - ./mfc.sh test -a --dry-run --gpu -j $(nproc); \ + ./mfc.sh test -v -a --dry-run --gpu -j $(nproc); \ else \ - ./mfc.sh test -a --dry-run -j $(nproc); \ + ./mfc.sh test -v -a --dry-run -j $(nproc); \ fi WORKDIR /opt/MFC diff --git a/.github/workflows/frontier/bench.sh b/.github/workflows/frontier/bench.sh index 35b4c5950e..6498846b9b 100644 --- a/.github/workflows/frontier/bench.sh +++ b/.github/workflows/frontier/bench.sh @@ -16,7 +16,7 @@ if [ "$job_device" = "gpu" ]; then fi if [ "$job_device" = "gpu" ]; then - ./mfc.sh bench --mem 12 -j $n_ranks -o "$job_slug.yaml" -- -c frontier $device_opts -n $n_ranks + ./mfc.sh bench -v --mem 12 -j $n_ranks -o "$job_slug.yaml" -- -c frontier $device_opts -n $n_ranks else - ./mfc.sh bench --mem 1 -j $(nproc) -o "$job_slug.yaml" -- -c frontier $device_opts -n $n_ranks + ./mfc.sh bench -v --mem 1 -j $(nproc) -o "$job_slug.yaml" -- -c frontier $device_opts -n $n_ranks fi diff --git a/.github/workflows/frontier/build.sh b/.github/workflows/frontier/build.sh index 70c29204c1..ba128b6ca4 100644 --- a/.github/workflows/frontier/build.sh +++ b/.github/workflows/frontier/build.sh @@ -18,9 +18,9 @@ fi if [ "$run_bench" == "bench" ]; then for dir in benchmarks/*/; do dirname=$(basename "$dir") - ./mfc.sh run "$dir/case.py" --case-optimization -j 8 --dry-run $build_opts + ./mfc.sh run -v "$dir/case.py" --case-optimization -j 8 --dry-run $build_opts done else - ./mfc.sh test -a --dry-run --rdma-mpi -j 8 $build_opts + ./mfc.sh test -v -a --dry-run --rdma-mpi -j 8 $build_opts fi diff --git a/.github/workflows/frontier/test.sh b/.github/workflows/frontier/test.sh index dd678d53c9..17fbbaf8e5 100644 --- a/.github/workflows/frontier/test.sh +++ b/.github/workflows/frontier/test.sh @@ -14,7 +14,7 @@ if [ "$job_device" = "gpu" ]; then fi if [ "$job_device" = "gpu" ]; then - ./mfc.sh test -a --rdma-mpi --max-attempts 3 -j $ngpus $device_opts -- -c frontier + ./mfc.sh test -v -a --rdma-mpi --max-attempts 3 -j $ngpus $device_opts -- -c frontier else - ./mfc.sh test -a --max-attempts 3 -j 32 --no-gpu -- -c frontier + ./mfc.sh test -v -a --max-attempts 3 -j 32 --no-gpu -- -c frontier fi diff --git a/.github/workflows/frontier_amd/bench.sh b/.github/workflows/frontier_amd/bench.sh index 6e01687e79..48062f60c0 100644 --- a/.github/workflows/frontier_amd/bench.sh +++ b/.github/workflows/frontier_amd/bench.sh @@ -16,7 +16,7 @@ if [ "$job_device" = "gpu" ]; then fi if [ "$job_device" = "gpu" ]; then - ./mfc.sh bench --mem 12 -j $n_ranks -o "$job_slug.yaml" -- -c frontier_amd $device_opts -n $n_ranks + ./mfc.sh bench -v --mem 12 -j $n_ranks -o "$job_slug.yaml" -- -c frontier_amd $device_opts -n $n_ranks else - ./mfc.sh bench --mem 1 -j $(nproc) -o "$job_slug.yaml" -- -c frontier_amd $device_opts -n $n_ranks + ./mfc.sh bench -v --mem 1 -j $(nproc) -o "$job_slug.yaml" -- -c frontier_amd $device_opts -n $n_ranks fi diff --git a/.github/workflows/frontier_amd/build.sh b/.github/workflows/frontier_amd/build.sh index b8b1f7051c..8f83666546 100644 --- a/.github/workflows/frontier_amd/build.sh +++ b/.github/workflows/frontier_amd/build.sh @@ -18,9 +18,9 @@ fi if [ "$run_bench" == "bench" ]; then for dir in benchmarks/*/; do dirname=$(basename "$dir") - ./mfc.sh run "$dir/case.py" --case-optimization -j 8 --dry-run $build_opts + ./mfc.sh run -v "$dir/case.py" --case-optimization -j 8 --dry-run $build_opts done else - ./mfc.sh test -a --dry-run -j 8 $build_opts + ./mfc.sh test -v -a --dry-run -j 8 $build_opts fi diff --git a/.github/workflows/frontier_amd/test.sh b/.github/workflows/frontier_amd/test.sh index 10d63fb767..39e37835ac 100644 --- a/.github/workflows/frontier_amd/test.sh +++ b/.github/workflows/frontier_amd/test.sh @@ -14,7 +14,7 @@ if [ "$job_device" = "gpu" ]; then fi if [ "$job_device" = "gpu" ]; then - ./mfc.sh test -a --max-attempts 3 -j $ngpus $device_opts -- -c frontier_amd + ./mfc.sh test -v -a --max-attempts 3 -j $ngpus $device_opts -- -c frontier_amd else - ./mfc.sh test -a --max-attempts 3 -j 32 --no-gpu -- -c frontier_amd + ./mfc.sh test -v -a --max-attempts 3 -j 32 --no-gpu -- -c frontier_amd fi diff --git a/.github/workflows/phoenix/bench.sh b/.github/workflows/phoenix/bench.sh index 99d0a07562..2ec4a02710 100644 --- a/.github/workflows/phoenix/bench.sh +++ b/.github/workflows/phoenix/bench.sh @@ -24,9 +24,9 @@ mkdir -p $currentdir export TMPDIR=$currentdir if [ "$job_device" = "gpu" ]; then - ./mfc.sh bench --mem 12 -j $(nproc) -o "$job_slug.yaml" -- -c phoenix-bench $device_opts -n $n_ranks + ./mfc.sh bench -v --mem 12 -j $(nproc) -o "$job_slug.yaml" -- -c phoenix-bench $device_opts -n $n_ranks else - ./mfc.sh bench --mem 1 -j $(nproc) -o "$job_slug.yaml" -- -c phoenix-bench $device_opts -n $n_ranks + ./mfc.sh bench -v --mem 1 -j $(nproc) -o "$job_slug.yaml" -- -c phoenix-bench $device_opts -n $n_ranks fi sleep 10 diff --git a/.github/workflows/phoenix/test.sh b/.github/workflows/phoenix/test.sh index 47a25cf596..5cbb73f809 100644 --- a/.github/workflows/phoenix/test.sh +++ b/.github/workflows/phoenix/test.sh @@ -10,7 +10,7 @@ if [ "$job_device" = "gpu" ]; then fi fi -./mfc.sh test --dry-run -j 8 $build_opts +./mfc.sh test -v --dry-run -j 8 $build_opts n_test_threads=8 @@ -21,5 +21,5 @@ if [ "$job_device" = "gpu" ]; then n_test_threads=`expr $gpu_count \* 2` fi -./mfc.sh test --max-attempts 3 -a -j $n_test_threads $device_opts -- -c phoenix +./mfc.sh test -v --max-attempts 3 -a -j $n_test_threads $device_opts -- -c phoenix From 0ec406b361b8c476525250a7dee242f409af1608 Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Thu, 5 Feb 2026 18:11:42 -0500 Subject: [PATCH 32/33] Add -v flag to coverage and GitHub runner CI commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Missed these in the previous commit — adds verbose output to the codecov build/test and the GitHub-hosted runner build/test steps. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/coverage.yml | 4 ++-- .github/workflows/test.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 469048f8a6..a8c4acd1bf 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -39,10 +39,10 @@ jobs: libfftw3-dev libhdf5-dev libblas-dev liblapack-dev - name: Build - run: /bin/bash mfc.sh build -j $(nproc) --gcov + run: /bin/bash mfc.sh build -v -j $(nproc) --gcov - name: Test - run: /bin/bash mfc.sh test -a -j $(nproc) + run: /bin/bash mfc.sh test -v -a -j $(nproc) - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fb16384045..7e7d24f262 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -125,11 +125,11 @@ jobs: - name: Build run: | - /bin/bash mfc.sh test --dry-run -j $(nproc) --${{ matrix.debug }} --${{ matrix.mpi }} --${{ matrix.precision }} + /bin/bash mfc.sh test -v --dry-run -j $(nproc) --${{ matrix.debug }} --${{ matrix.mpi }} --${{ matrix.precision }} - name: Test run: | - /bin/bash mfc.sh test --max-attempts 3 -j $(nproc) $OPT1 $OPT2 + /bin/bash mfc.sh test -v --max-attempts 3 -j $(nproc) $OPT1 $OPT2 env: OPT1: ${{ matrix.mpi == 'mpi' && '--test-all' || '' }} OPT2: ${{ matrix.debug == 'debug' && '-% 20' || '' }} From 1e863aee2a5bb73206537322c34bcbfa389dc18a Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Thu, 5 Feb 2026 18:33:08 -0500 Subject: [PATCH 33/33] Revert pyrometheus pin to track git HEAD The Python 3.10 compatibility changes have been merged upstream, so we no longer need to pin to a specific commit. Co-Authored-By: Claude Opus 4.6 --- toolchain/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toolchain/pyproject.toml b/toolchain/pyproject.toml index a7d2d6c1b4..53e2140290 100644 --- a/toolchain/pyproject.toml +++ b/toolchain/pyproject.toml @@ -40,7 +40,7 @@ dependencies = [ # Chemistry "cantera>=3.1.0", #"pyrometheus == 1.0.5", - "pyrometheus @ git+https://github.com/pyrometheus/pyrometheus.git@49833404f91dd80e598902cdf68dbcc1f6e9bd16", + "pyrometheus @ git+https://github.com/pyrometheus/pyrometheus.git", # Frontier Profiling "astunparse==1.6.2",