fix(chart): premise-first cascades and unambiguous initial-map aliase… #1143
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: flow-next | |
| on: | |
| push: | |
| branches: [main] | |
| paths: | |
| - "plugins/flow-next/**" | |
| - ".github/workflows/test-flow-next.yml" | |
| # Dogfood copies guarded against template drift (test_dogfood_template_parity). | |
| - ".flow/usage.md" | |
| - ".flow/templates/spec.md" | |
| # Cursor + Codex local-installers live at repo-root scripts/ — outside | |
| # plugins/flow-next/**, so trigger explicitly to run the install smokes. | |
| - "scripts/install-cursor.sh" | |
| - "scripts/install-cursor.ps1" | |
| - "scripts/install-codex.sh" | |
| - "scripts/ci/verify_cursor_install.py" | |
| # Root Cursor team-marketplace manifest (fn-123) — outside plugins/** | |
| - ".cursor-plugin/**" | |
| # fn-119 parallel unit-suite entrypoint | |
| - "scripts/run_tests_parallel.py" | |
| # Ruff gate: config, plus every path `ruff check .` actually lints. | |
| # These must stay in step - a filter narrower than the lint scope | |
| # lets a violation land without the gate ever running (#244 review). | |
| - "ruff.toml" | |
| - "scripts/**.py" | |
| - "agent_docs/**.py" | |
| pull_request: | |
| branches: [main] | |
| paths: | |
| - "plugins/flow-next/**" | |
| - ".github/workflows/test-flow-next.yml" | |
| - ".flow/usage.md" | |
| - ".flow/templates/spec.md" | |
| - "scripts/install-cursor.sh" | |
| - "scripts/install-cursor.ps1" | |
| - "scripts/install-codex.sh" | |
| - "scripts/ci/verify_cursor_install.py" | |
| - ".cursor-plugin/**" | |
| - "scripts/run_tests_parallel.py" | |
| # Ruff gate: config, plus every path `ruff check .` actually lints. | |
| # These must stay in step - a filter narrower than the lint scope | |
| # lets a violation land without the gate ever running (#244 review). | |
| - "ruff.toml" | |
| - "scripts/**.py" | |
| - "agent_docs/**.py" | |
| workflow_dispatch: | |
| inputs: | |
| shuffle: | |
| description: "Ordering canary - run unit suite with --shuffle (manual/weekly; file-level only)" | |
| required: false | |
| type: boolean | |
| default: false | |
| legacy_baseline: | |
| description: "Measurement lever (fn-155) - run the unit suite at the pre-CI-detection job count, computed per runner" | |
| required: false | |
| type: boolean | |
| default: false | |
| jobs: | |
| test: | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| python-version: "3.11" | |
| - os: macos-latest | |
| python-version: "3.11" | |
| - os: windows-latest | |
| python-version: "3.11" | |
| # Latest stable full gate. `3.x` advances with setup-python while the | |
| # explicit 3.11 rows preserve the minimum-runtime contract. | |
| - os: ubuntu-latest | |
| python-version: "3.x" | |
| runs-on: ${{ matrix.os }} | |
| defaults: | |
| run: | |
| shell: bash # Git Bash on Windows; bash on Linux/macOS — unifies the matrix. | |
| steps: | |
| - name: Disable git autocrlf so checkout preserves LF on Windows | |
| run: git config --global core.autocrlf false | |
| # Default Windows-runner config converts LF→CRLF on checkout, which | |
| # changes heredoc bytes that smokes compare byte-identically against | |
| # flowctl output. Force LF on all OSes for consistent fixtures. | |
| # Reached-path B0/B1 validation reads immutable prompt sources from their | |
| # recorded commits. A shallow checkout makes valid frozen evidence look | |
| # corrupt because those commits are absent. | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Configure git identity (smoke tests create commits) | |
| run: | | |
| git config --global user.email "ci@flow-next.test" | |
| git config --global user.name "flow-next CI" | |
| git config --global init.defaultBranch main | |
| # ──────────── Static checks (cheap, fail fast) ──────────── | |
| - name: Syntax check — flowctl.py | |
| run: python -m py_compile plugins/flow-next/scripts/flowctl.py | |
| # Correctness-only ruleset; see ruff.toml for what is deliberately out. | |
| # PIN THE VERSION: ruff 0.16 moved its default set from 59 rules to 413, | |
| # which turns an unpinned `ruff` into an unannounced CI break. | |
| # One row only - lint results do not vary by OS or Python version. | |
| - name: Lint — ruff | |
| if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11' | |
| run: | | |
| python -m pip install --disable-pip-version-check ruff==0.16.0 | |
| ruff check . | |
| - name: Syntax check — ralph.sh | |
| run: bash -n plugins/flow-next/skills/flow-next-ralph-init/templates/ralph.sh | |
| # ──────────── Python unit tests (file-level parallel, fn-119) ──────────── | |
| # Canonical full-suite entrypoint: scripts/run_tests_parallel.py | |
| # Shards by FILE only (never within a file). Default jobs = the runner's | |
| # full core count on CI and cores-2 locally (fn-155; the job-count policy | |
| # lives in the runner, not here). --serial is the equivalence fallback. | |
| # workflow_dispatch.inputs.shuffle is the ordering canary (manual / | |
| # weekly; do not schedule a new workflow). | |
| # workflow_dispatch.inputs.legacy_baseline is the fn-155 measurement | |
| # lever: it reproduces the pre-change job count from this same commit, | |
| # computed per runner because nothing establishes that all three OS legs | |
| # report the same core count. Inert on push and pull_request. | |
| - name: Python unit tests - parallel suite (fn-119) | |
| env: | |
| LEGACY_BASELINE: ${{ github.event.inputs.legacy_baseline }} | |
| run: | | |
| JOBS_ARGS=() | |
| if [ "$LEGACY_BASELINE" = "true" ]; then | |
| JOBS_ARGS=(--jobs "$(python -c 'import os; print(max(1, (os.cpu_count() or 2) - 2))')") | |
| echo "Legacy baseline (fn-155): ${JOBS_ARGS[*]}" | |
| fi | |
| SHUFFLE_ARGS=() | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ] \ | |
| && [ "${{ github.event.inputs.shuffle }}" = "true" ]; then | |
| SHUFFLE_ARGS=(--shuffle --seed "$GITHUB_RUN_ID") | |
| echo "Ordering canary: --shuffle --seed $GITHUB_RUN_ID" | |
| fi | |
| EXCLUDES=() | |
| if [ "$RUNNER_OS" = "Windows" ]; then | |
| # Windows-only exclusions (fn-119): latent incompatibilities in files | |
| # NEVER exercised on windows CI before the parallel entrypoint | |
| # (pre-fn-119 windows unit coverage was 29/87 files; these keep it | |
| # at 81/87 - strictly wider than before). Causes on the fn-119 PR: | |
| # backend_spec hangs; flow_gitignore cp1252 read-back; gate_receipt | |
| # POSIX sh shim + literal-backslash filename; normalize_section / | |
| # reveval / task_create_files CLI e2e failures on 8.3 short paths. | |
| # Windows-compat sweep tracked as a follow-up spec - remove entries | |
| # there, never silently here. | |
| EXCLUDES+=(--exclude test_backend_spec.py) | |
| EXCLUDES+=(--exclude test_flow_gitignore.py) | |
| EXCLUDES+=(--exclude test_gate_receipt.py) | |
| EXCLUDES+=(--exclude test_normalize_section_content.py) | |
| EXCLUDES+=(--exclude test_reveval_parse_guard.py) | |
| EXCLUDES+=(--exclude test_task_create_files.py) | |
| fi | |
| python scripts/run_tests_parallel.py "${JOBS_ARGS[@]}" "${SHUFFLE_ARGS[@]}" "${EXCLUDES[@]}" | |
| # ──────────── Cursor local-install (1.9.0) ──────────── | |
| # The bash + PowerShell Cursor installers (scripts/install-cursor.{sh,ps1}) | |
| # must not drift, and must actually produce a complete + clean install on | |
| # each OS. Installer-parity unit tests ride the parallel suite above; the | |
| # real-install smoke runs the platform's OWN installer (rsync on Unix, | |
| # robocopy on Windows) into ~/.cursor/plugins/local and verifies against | |
| # the source tree (component trees match 1:1; codex/ tests/ *.pyc excluded). | |
| - name: install-cursor.sh real-install smoke (Unix) | |
| if: always() && runner.os != 'Windows' | |
| run: | | |
| bash "$GITHUB_WORKSPACE/scripts/install-cursor.sh" | |
| python "$GITHUB_WORKSPACE/scripts/ci/verify_cursor_install.py" | |
| - name: install-cursor.ps1 real-install smoke (Windows) | |
| if: always() && runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force | |
| ./scripts/install-cursor.ps1 | |
| python ./scripts/ci/verify_cursor_install.py | |
| # ──────────── Functional smokes (ordered cheap-first) ──────────── | |
| # All smokes refuse to run from the main plugin repo, so cd into the | |
| # runner's temp dir before invoking. $RUNNER_TEMP is set on every | |
| # GitHub-hosted runner (ubuntu/macos/windows). | |
| - name: pick_python_test.sh (Windows 9009-stub probe regression, POSIX/Git-Bash, fn-77.5) | |
| if: always() && runner.os != 'Windows' | |
| run: | | |
| cd "$RUNNER_TEMP" | |
| bash "$GITHUB_WORKSPACE/plugins/flow-next/scripts/pick_python_test.sh" | |
| # POSIX/mac-linux unit layer for the interpreter probe: a fake 9009 | |
| # `python3` stub first on PATH must be rejected by the shared resolver + | |
| # the bash launcher, PYTHON_BIN override is itself probed, py -3 preferred, | |
| # python3 still first on mac/linux (R8). The real windows-latest end-to-end | |
| # of flowctl.cmd + the bash launcher lives in the windows-python3-stub job. | |
| - name: ci_test.sh (flowctl integration, 58 cases) | |
| if: always() | |
| run: | | |
| cd "$RUNNER_TEMP" | |
| bash "$GITHUB_WORKSPACE/plugins/flow-next/scripts/ci_test.sh" | |
| - name: resolve-pr_smoke_test.sh (58 cases, ~1s) | |
| if: always() | |
| run: | | |
| cd "$RUNNER_TEMP" | |
| bash "$GITHUB_WORKSPACE/plugins/flow-next/scripts/resolve-pr_smoke_test.sh" | |
| - name: strategy_smoke_test.sh (62 cases, ~5s) | |
| if: always() | |
| run: | | |
| cd "$RUNNER_TEMP" | |
| bash "$GITHUB_WORKSPACE/plugins/flow-next/scripts/strategy_smoke_test.sh" | |
| - name: audit_smoke_test.sh (41 cases, ~6s) | |
| if: always() | |
| run: | | |
| cd "$RUNNER_TEMP" | |
| bash "$GITHUB_WORKSPACE/plugins/flow-next/scripts/audit_smoke_test.sh" | |
| - name: glossary_smoke_test.sh (80 cases, ~7s) | |
| if: always() | |
| run: | | |
| cd "$RUNNER_TEMP" | |
| bash "$GITHUB_WORKSPACE/plugins/flow-next/scripts/glossary_smoke_test.sh" | |
| - name: prospect_smoke_test.sh (94 cases, ~60s) | |
| if: always() | |
| run: | | |
| cd "$RUNNER_TEMP" | |
| bash "$GITHUB_WORKSPACE/plugins/flow-next/scripts/prospect_smoke_test.sh" | |
| - name: make-pr_smoke_test.sh (67 cases, ~7s) | |
| if: always() | |
| run: | | |
| cd "$RUNNER_TEMP" | |
| bash "$GITHUB_WORKSPACE/plugins/flow-next/scripts/make-pr_smoke_test.sh" | |
| - name: map_smoke_test.sh (75 cases, fn-50.1 + fn-50.6 directory-level ignore + config-state --json guards) | |
| if: always() | |
| run: | | |
| cd "$RUNNER_TEMP" | |
| bash "$GITHUB_WORKSPACE/plugins/flow-next/scripts/map_smoke_test.sh" | |
| - name: impl-review_smoke_test.sh (74 cases, ~70s) | |
| if: always() | |
| run: | | |
| cd "$RUNNER_TEMP" | |
| bash "$GITHUB_WORKSPACE/plugins/flow-next/scripts/impl-review_smoke_test.sh" | |
| - name: smoke_test.sh (130 cases, ~100s) | |
| if: always() | |
| run: | | |
| cd "$RUNNER_TEMP" | |
| bash "$GITHUB_WORKSPACE/plugins/flow-next/scripts/smoke_test.sh" | |
| # Smokes intentionally skipped (require external CLIs not on GitHub runners): | |
| # - ralph_smoke_test.sh / ralph_smoke_rp.sh — need claude / codex / rp-cli | |
| # - plan_review_prompt_smoke.sh — needs rp-cli | |
| python-intermediate-smoke: | |
| name: Python ${{ matrix.python-version }} compatibility smoke | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| python-version: ["3.12", "3.13"] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Compile flowctl source and bootstrap | |
| run: | | |
| python -m py_compile \ | |
| plugins/flow-next/scripts/flowctl.py \ | |
| plugins/flow-next/scripts/flowctl_bootstrap.py | |
| - name: Runtime, launcher, usage, and setup-mode contracts | |
| run: | | |
| cd plugins/flow-next/tests | |
| python -m unittest \ | |
| test_startup_bootstrap \ | |
| test_init_stamp_launchers \ | |
| test_bin_launcher_parity \ | |
| test_cmd_usage \ | |
| test_setup_mode_stamp \ | |
| test_precheck_mode_contract \ | |
| -q | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # fn-77.5 (R10): real windows-latest end-to-end. A DETERMINISTIC fake `python3` | |
| # stub (exit 9009 — the Microsoft Store App Execution Alias failure mode) is | |
| # forced FIRST on PATH for BOTH a Git Bash step AND a PowerShell/cmd step (NOT | |
| # reliant on the runner's own Store alias, which may be absent). A real | |
| # python.org Python (+ the py launcher, already on the hosted image) is present | |
| # via setup-python; then the bash `flowctl` launcher and the `flowctl.cmd` batch | |
| # shim must each probe past the stub, resolve a working interpreter, and run | |
| # `flowctl init` — proving the fix works in the exact shells where Claude | |
| # Desktop / native Codex / Cursor invoke flowctl on Windows. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| windows-python3-stub: | |
| runs-on: windows-latest | |
| steps: | |
| - name: Disable git autocrlf so the LF-pinned bash launcher stays LF | |
| shell: bash | |
| run: git config --global core.autocrlf false | |
| # The bash `flowctl` launcher is pinned eol=lf (.gitattributes); CRLF would | |
| # break `bash` parsing. Default Windows-runner autocrlf would rewrite it. | |
| - uses: actions/checkout@v4 | |
| - name: Setup Python (real python.org interpreter + py launcher) | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| - name: Configure git identity (flowctl init touches git) | |
| shell: bash | |
| run: | | |
| git config --global user.email "ci@flow-next.test" | |
| git config --global user.name "flow-next CI" | |
| git config --global init.defaultBranch main | |
| - name: Build a deterministic 9009 python3 stub for both shells | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| STUB="$RUNNER_TEMP/pystub" | |
| mkdir -p "$STUB" | |
| # Extensionless stub for Git Bash: MSYS resolves bare `python3` to this | |
| # shebang script (it does not append .cmd), so `python3` runs the stub. | |
| { | |
| printf '#!/bin/bash\n' | |
| printf 'echo "Python was not found; run without arguments to install from the Microsoft Store, or disable this shortcut from Settings > Apps > Advanced app settings > App execution aliases." >&2\n' | |
| printf 'exit 9009\n' | |
| } > "$STUB/python3" | |
| chmod +x "$STUB/python3" | |
| # .cmd stub for cmd.exe / PowerShell: PATHEXT resolves `python3` here. | |
| { | |
| printf '@ECHO OFF\n' | |
| printf 'ECHO Python was not found; disable the Microsoft Store App execution alias. 1>&2\n' | |
| printf 'EXIT /b 9009\n' | |
| } > "$STUB/python3.cmd" | |
| # Prove the stub really is non-functional before wiring it onto PATH. | |
| if "$STUB/python3" -c "import sys" >/dev/null 2>&1; then | |
| echo "FAIL: stub python3 should exit non-zero" >&2; exit 1 | |
| fi | |
| printf 'PYSTUB_DIR=%s\n' "$STUB" >> "$GITHUB_ENV" | |
| - name: Build deterministic working-but-too-old probes for both shells | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| OLD="$RUNNER_TEMP/pyold" | |
| mkdir -p "$OLD" | |
| for name in py python3 python; do | |
| { | |
| printf '#!/bin/bash\n' | |
| printf 'exit 3\n' | |
| } > "$OLD/$name" | |
| chmod +x "$OLD/$name" | |
| { | |
| printf '@ECHO OFF\r\n' | |
| printf 'EXIT /b 3\r\n' | |
| } > "$OLD/$name.cmd" | |
| done | |
| printf 'PYOLD_DIR=%s\n' "$OLD" >> "$GITHUB_ENV" | |
| - name: Git Bash — bash `flowctl` bypasses the 9009 stub | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| export PATH="$PYSTUB_DIR:$PATH" | |
| echo "python3 resolves to: $(command -v python3)" | |
| # The stub is genuinely first on PATH and non-functional. | |
| if python3 -c "import sys" >/dev/null 2>&1; then | |
| echo "FAIL: python3 stub should exit non-zero under Git Bash" >&2; exit 1 | |
| fi | |
| FLOWCTL="$GITHUB_WORKSPACE/plugins/flow-next/scripts/flowctl" | |
| REPO="$RUNNER_TEMP/bash-stub-repo"; mkdir -p "$REPO"; cd "$REPO"; git init -q | |
| OUT="$("$FLOWCTL" init --json 2>&1)" || { echo "FAIL: launcher exited non-zero"; echo "$OUT"; exit 1; } | |
| echo "$OUT" | |
| echo "$OUT" | grep -q '"success": true' || { echo "FAIL: flowctl init did not succeed" >&2; exit 1; } | |
| if echo "$OUT" | grep -q "Python was not found"; then | |
| echo "FAIL: stub message leaked — launcher did not bypass the stub" >&2; exit 1 | |
| fi | |
| echo "PASS: bash flowctl bypassed the 9009 python3 stub" | |
| - name: PowerShell — `flowctl.cmd` bypasses the 9009 stub | |
| shell: pwsh | |
| run: | | |
| # GitHub pwsh sets $ErrorActionPreference='Stop'; with the 7.3+ default | |
| # $PSNativeCommandUseErrorActionPreference=$true a non-zero native exit | |
| # THROWS. We invoke a stub that is MEANT to fail, so drive control flow | |
| # off $LASTEXITCODE explicitly instead. | |
| $PSNativeCommandUseErrorActionPreference = $false | |
| $env:PATH = "$env:PYSTUB_DIR;$env:PATH" | |
| Write-Host "python3 resolves to: $((Get-Command python3 -ErrorAction SilentlyContinue).Source)" | |
| # The stub is genuinely first on PATH and non-functional (native 9009). | |
| & python3 -c "import sys" 2>$null | |
| if ($LASTEXITCODE -eq 0) { Write-Error "python3 stub should exit non-zero in pwsh"; exit 1 } | |
| $flowctl = Join-Path $env:GITHUB_WORKSPACE "plugins/flow-next/scripts/flowctl.cmd" | |
| $repo = Join-Path $env:RUNNER_TEMP "cmd-stub-repo" | |
| New-Item -ItemType Directory -Force -Path $repo | Out-Null | |
| Set-Location $repo | |
| git init -q | |
| $outputLines = & $flowctl init --json 2>&1 | |
| $exitCode = $LASTEXITCODE | |
| $out = $outputLines | Out-String | |
| Write-Host $out | |
| if ($exitCode -ne 0) { Write-Error "flowctl.cmd exited $exitCode"; exit 1 } | |
| if ($out -notmatch '"success": true') { Write-Error "flowctl.cmd init did not succeed"; exit 1 } | |
| if ($out -match "Python was not found") { Write-Error "stub message leaked — flowctl.cmd did not bypass the stub"; exit 1 } | |
| Write-Host "PASS: flowctl.cmd bypassed the 9009 python3 stub" | |
| - name: Git Bash — launcher rejects Python below 3.11 before source load | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| export PATH="$PYOLD_DIR:/usr/bin:/bin" | |
| FLOWCTL="$GITHUB_WORKSPACE/plugins/flow-next/scripts/flowctl" | |
| set +e | |
| OUT="$("$FLOWCTL" --help 2>&1)"; RC=$? | |
| set -e | |
| [ "$RC" -ne 0 ] | |
| echo "$OUT" | grep -q "Python 3.11 or newer is required" | |
| ! echo "$OUT" | grep -q '^usage:' | |
| - name: PowerShell — batch launcher rejects Python below 3.11 before source load | |
| shell: pwsh | |
| run: | | |
| $PSNativeCommandUseErrorActionPreference = $false | |
| $env:PATH = $env:PYOLD_DIR | |
| $flowctl = Join-Path $env:GITHUB_WORKSPACE "plugins/flow-next/scripts/flowctl.cmd" | |
| # PowerShell's direct .cmd invocation does not reliably preserve the | |
| # batch exit code on every runner. Assert it at the native cmd.exe | |
| # boundary; the preceding step already proves direct pwsh invocation. | |
| $command = "call `"$flowctl`" --help" | |
| $outputLines = & $env:ComSpec /d /c $command 2>&1 | |
| $exitCode = $LASTEXITCODE | |
| $out = $outputLines | Out-String | |
| Write-Host $out | |
| if ($exitCode -eq 0) { Write-Error "old Python probe unexpectedly succeeded"; exit 1 } | |
| if ($out -notmatch "Python 3.11 or newer is required") { Write-Error "missing minimum-version error: $out"; exit 1 } | |
| if ($out -match '^usage:') { Write-Error "flowctl.py loaded before minimum-version rejection"; exit 1 } | |
| # The tested native failure is expected; do not let the Actions pwsh | |
| # wrapper propagate its retained LASTEXITCODE after our assertions. | |
| exit 0 |