From f335d230d532587343c49f44e313d398e96bbd12 Mon Sep 17 00:00:00 2001 From: Marlon Costa Date: Mon, 3 Aug 2026 15:19:16 -0300 Subject: [PATCH 1/3] chore(ci): promote draft-aware CI policy and fixtures to main Align blocking CI and ci-matrix with flext-infra SSOT on main: draft PRs skip, integration pushes run ci.yml only, main runs full matrix; Dockerfiles under tests/fixtures/ci/docker. Co-authored-by: Cursor --- .github/workflows/ci-matrix.yml | 116 +++++++++++++++++++++ .github/workflows/ci.yml | 63 ++++++++--- tests/fixtures/ci/docker/alpine.Dockerfile | 52 +++++++++ tests/fixtures/ci/docker/arch.Dockerfile | 54 ++++++++++ tests/fixtures/ci/docker/debian.Dockerfile | 55 ++++++++++ tests/fixtures/ci/docker/fedora.Dockerfile | 54 ++++++++++ tests/fixtures/ci/docker/ubuntu.Dockerfile | 55 ++++++++++ 7 files changed, 432 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/ci-matrix.yml create mode 100644 tests/fixtures/ci/docker/alpine.Dockerfile create mode 100644 tests/fixtures/ci/docker/arch.Dockerfile create mode 100644 tests/fixtures/ci/docker/debian.Dockerfile create mode 100644 tests/fixtures/ci/docker/fedora.Dockerfile create mode 100644 tests/fixtures/ci/docker/ubuntu.Dockerfile diff --git a/.github/workflows/ci-matrix.yml b/.github/workflows/ci-matrix.yml new file mode 100644 index 000000000..8e975ba13 --- /dev/null +++ b/.github/workflows/ci-matrix.yml @@ -0,0 +1,116 @@ +# Generated by `flext_infra codegen conform` for flext-cli. +# === SECTION: header (managed) === +# Source: template (base/.github/workflows/ci-matrix.yml.j2) +# Free: no +# End SECTION: header +# Multi-environment CI base: proves the project bootstrap and canonical Make +# verbs work identically across distros, macOS, and Windows. The CI invokes +# the project's own Make surface; it never reimplements bootstrap. +# Runs only on main (direct push or non-draft PR targeting main). +--- +name: ci-matrix + +# === SECTION: triggers (managed) === +# Source: main promotion only (integration branch uses blocking ci.yml alone) +"on": + push: + branches: [main] + pull_request: + branches: [main] + types: [opened, synchronize, reopened, ready_for_review] + workflow_dispatch: {} +# End SECTION: triggers + +# === SECTION: permissions (managed) === +# Source: template (minimal read-only permissions) +permissions: + contents: read +# End SECTION: permissions + +# === SECTION: concurrency (managed) === +# Source: template (one run per workflow+ref) +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +# End SECTION: concurrency + +jobs: + # === SECTION: distro-matrix (managed) === + # Source: template + config:github_actions.checkout (distro list is template literal by design) + distro-matrix: + # Clean-machine proof per distro: build the distro image (which runs the + # full project bootstrap at image-build time), then smoke the verb surface + # inside the built container. + if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + distro: [ubuntu, debian, fedora, alpine, arch] + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + submodules: false + - name: Build ${{ matrix.distro }} image + run: >- + docker build + -f tests/fixtures/ci/docker/${{ matrix.distro }}.Dockerfile + -t ci-matrix-${{ matrix.distro }} + . + - name: Bootstrap + verb smoke (${{ matrix.distro }}) + run: | + docker run --rm ci-matrix-${{ matrix.distro }} make help + docker run --rm ci-matrix-${{ matrix.distro }} make check + # End SECTION: distro-matrix + + # === SECTION: macos (managed) === + # Source: template + config:github_actions.checkout/setup-python/mise/setup-uv + macos: + if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }} + runs-on: macos-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + submodules: false + - name: Setup Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version-file: .python-version + - name: Install mise toolchain + uses: jdx/mise-action@dba19683ed58901619b14f395a24841710cb4925 # v4.1.0 + - name: Install uv + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + with: + enable-cache: true + - name: Bootstrap + run: make setup + - name: Verb surface + run: make help + # End SECTION: macos + + # === SECTION: windows (managed) === + # Source: template + config:github_actions.checkout/setup-python/mise/setup-uv + windows: + if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }} + runs-on: windows-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + submodules: false + - name: Setup Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version-file: .python-version + - name: Install mise toolchain + uses: jdx/mise-action@dba19683ed58901619b14f395a24841710cb4925 # v4.1.0 + - name: Install uv + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + with: + enable-cache: true + - name: Bootstrap + shell: bash + run: make setup + - name: Verb surface + shell: bash + run: make help + # End SECTION: windows diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bfa738595..8ac283eff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,44 +1,73 @@ -# Generated by flext_infra.github.workflows - DO NOT EDIT +# Generated by flext_infra codegen for flext-cli — DO NOT EDIT +# === SECTION: header (managed) === +# Source: template (base/.github/workflows/ci.yml.j2) +# Free: no +# End SECTION: header + name: CI -on: - pull_request: +# === SECTION: triggers (managed) === +# Source: operator CI policy — integration push = blocking CI only; +# main push/PR (non-draft) = blocking CI; draft and other branches = none. +"on": push: + branches: + - dev + - develop + - 0.12.0-dev + - main + pull_request: branches: - main - workflow_dispatch: + types: [opened, synchronize, reopened, ready_for_review] +# End SECTION: triggers +# === SECTION: permissions (managed) === +# Source: template (minimal read-only permissions) permissions: contents: read +# End SECTION: permissions jobs: + # === SECTION: ci job (managed) === + # Source: template + config:github_actions.* ci: name: ci + if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }} runs-on: ubuntu-latest timeout-minutes: 60 + env: + CI: Y steps: - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - submodules: recursive + submodules: false fetch-depth: 0 - - name: Setup Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 - with: - python-version: "3.13" + - name: Install mise toolchain + uses: jdx/mise-action@dba19683ed58901619b14f395a24841710cb4925 # v4.1.0 - name: Install uv - uses: astral-sh/setup-uv@e58605a9b6da7c637471fab8847a5e5a6b8df081 # v5 - with: - enable-cache: true + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + - - name: Setup (blocking) + - name: setup (blocking) run: make setup - - name: Check (blocking) + - name: gen (blocking) + run: make gen APPLY=Y + + - name: fmt (blocking) + run: make fmt APPLY=Y + + - name: fix (blocking) + run: make fix APPLY=Y + + - name: check (blocking) run: make check - - name: Test (advisory) - continue-on-error: true + - name: test (blocking) run: make test + + # End SECTION: ci job diff --git a/tests/fixtures/ci/docker/alpine.Dockerfile b/tests/fixtures/ci/docker/alpine.Dockerfile new file mode 100644 index 000000000..f7a3c6c97 --- /dev/null +++ b/tests/fixtures/ci/docker/alpine.Dockerfile @@ -0,0 +1,52 @@ +# Generated by `flext_infra codegen conform` for flext_cli. +# === SECTION: header (managed) === +# Source: template (base/tests/fixtures/ci/docker/alpine.Dockerfile.j2) +# Free: no +# End SECTION: header +# Clean-machine proof: project bootstrap + canonical make verbs on Alpine +# (musl, POSIX /bin/sh at runtime; bash installed for the project scripts). +FROM alpine:3.21 + +# === SECTION: base packages (managed) === +# Source: template (distro-specific package list) +RUN apk add --no-cache \ + bash ca-certificates curl git make build-base icu-dev icu-libs +# End SECTION: base packages + +# === SECTION: managed tool bootstrap (managed) === +# Source: config:python_version, template (installer URLs) +# mise installs the supported Python 3.13 family. +# uv is supplied by the managed environment without a project patch pin. +RUN curl -fsSL https://mise.run | sh +# uv is intentionally supplied by the caller environment; install it explicitly +# in clean-machine images so the project bootstrap can resolve dependencies. +RUN curl -fsSL https://astral.sh/uv/install.sh | sh +# tokei (and any future cargo-backed mise tool) needs a Rust toolchain. +RUN curl -fsSL https://sh.rustup.rs | sh -s -- -y --default-toolchain stable +# go is required for mise-managed beads (go:github.com/steveyegge/beads/cmd/bd). +RUN curl -fsSL https://go.dev/dl/go1.23.4.linux-amd64.tar.gz | tar -C /usr/local -xzf - \ + && ln -sf /usr/local/go/bin/go /usr/local/bin/go +ENV PATH="/usr/local/go/bin:/root/.local/bin:/root/.cargo/bin:/root/.local/share/mise/shims:${PATH}" +# End SECTION: managed tool bootstrap + +WORKDIR /workspace +COPY . . + +# === SECTION: mise install (managed) === +# Source: computed (reads .mise.toml from copied workspace) +RUN mise trust .mise.toml && mise install --yes +# End SECTION: mise install + +# === SECTION: bootstrap proof (managed) === +# Source: template (clean-machine bootstrap through the canonical verb) +# The image exists to PROVE that a clean machine can bootstrap this project +# with nothing but the declared toolchain. It therefore runs the canonical +# setup verb fail-closed: any non-zero status fails the build. An earlier +# revision wrapped this in `set +e` and soft-passed whenever the output +# mentioned uv.lock/flext-core, which turned the proof into a bypass -- a +# broken bootstrap still produced a green image. +RUN make setup +# End SECTION: bootstrap proof + +ENTRYPOINT [] +CMD ["/bin/bash", "-lc", "make help"] diff --git a/tests/fixtures/ci/docker/arch.Dockerfile b/tests/fixtures/ci/docker/arch.Dockerfile new file mode 100644 index 000000000..24abaffd0 --- /dev/null +++ b/tests/fixtures/ci/docker/arch.Dockerfile @@ -0,0 +1,54 @@ +# Generated by `flext_infra codegen conform` for flext_cli. +# === SECTION: header (managed) === +# Source: template (base/tests/fixtures/ci/docker/arch.Dockerfile.j2) +# Free: no +# End SECTION: header +# Clean-machine proof: project bootstrap + canonical make verbs on Arch Linux. +FROM archlinux:base + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +# === SECTION: base packages (managed) === +# Source: template (distro-specific package list) +RUN pacman -Syu --noconfirm --needed \ + bash ca-certificates curl git make base-devel icu \ + && pacman -Scc --noconfirm +# End SECTION: base packages + +# === SECTION: managed tool bootstrap (managed) === +# Source: config:python_version, template (installer URLs) +# mise installs the supported Python 3.13 family. +# uv is supplied by the managed environment without a project patch pin. +RUN curl -fsSL https://mise.run | sh +# uv is intentionally supplied by the caller environment; install it explicitly +# in clean-machine images so the project bootstrap can resolve dependencies. +RUN curl -fsSL https://astral.sh/uv/install.sh | sh +# tokei (and any future cargo-backed mise tool) needs a Rust toolchain. +RUN curl -fsSL https://sh.rustup.rs | sh -s -- -y --default-toolchain stable +# go is required for mise-managed beads (go:github.com/steveyegge/beads/cmd/bd). +RUN curl -fsSL https://go.dev/dl/go1.23.4.linux-amd64.tar.gz | tar -C /usr/local -xzf - \ + && ln -sf /usr/local/go/bin/go /usr/local/bin/go +ENV PATH="/usr/local/go/bin:/root/.local/bin:/root/.cargo/bin:/root/.local/share/mise/shims:${PATH}" +# End SECTION: managed tool bootstrap + +WORKDIR /workspace +COPY . . + +# === SECTION: mise install (managed) === +# Source: computed (reads .mise.toml from copied workspace) +RUN mise trust .mise.toml && mise install --yes +# End SECTION: mise install + +# === SECTION: bootstrap proof (managed) === +# Source: template (clean-machine bootstrap through the canonical verb) +# The image exists to PROVE that a clean machine can bootstrap this project +# with nothing but the declared toolchain. It therefore runs the canonical +# setup verb fail-closed: any non-zero status fails the build. An earlier +# revision wrapped this in `set +e` and soft-passed whenever the output +# mentioned uv.lock/flext-core, which turned the proof into a bypass -- a +# broken bootstrap still produced a green image. +RUN make setup +# End SECTION: bootstrap proof + +ENTRYPOINT [] +CMD ["make", "help"] diff --git a/tests/fixtures/ci/docker/debian.Dockerfile b/tests/fixtures/ci/docker/debian.Dockerfile new file mode 100644 index 000000000..c3db00466 --- /dev/null +++ b/tests/fixtures/ci/docker/debian.Dockerfile @@ -0,0 +1,55 @@ +# Generated by `flext_infra codegen conform` for flext_cli. +# === SECTION: header (managed) === +# Source: template (base/tests/fixtures/ci/docker/debian.Dockerfile.j2) +# Free: no +# End SECTION: header +# Clean-machine proof: project bootstrap + canonical make verbs on Debian. +FROM debian:bookworm-slim + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +# === SECTION: base packages (managed) === +# Source: template (distro-specific package list) +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + bash ca-certificates curl git make build-essential libicu-dev \ + && rm -rf /var/lib/apt/lists/* +# End SECTION: base packages + +# === SECTION: managed tool bootstrap (managed) === +# Source: config:python_version, template (installer URLs) +# mise installs the supported Python 3.13 family. +# uv is supplied by the managed environment without a project patch pin. +RUN curl -fsSL https://mise.run | sh +# uv is intentionally supplied by the caller environment; install it explicitly +# in clean-machine images so the project bootstrap can resolve dependencies. +RUN curl -fsSL https://astral.sh/uv/install.sh | sh +# tokei (and any future cargo-backed mise tool) needs a Rust toolchain. +RUN curl -fsSL https://sh.rustup.rs | sh -s -- -y --default-toolchain stable +# go is required for mise-managed beads (go:github.com/steveyegge/beads/cmd/bd). +RUN curl -fsSL https://go.dev/dl/go1.23.4.linux-amd64.tar.gz | tar -C /usr/local -xzf - \ + && ln -sf /usr/local/go/bin/go /usr/local/bin/go +ENV PATH="/usr/local/go/bin:/root/.local/bin:/root/.cargo/bin:/root/.local/share/mise/shims:${PATH}" +# End SECTION: managed tool bootstrap + +WORKDIR /workspace +COPY . . + +# === SECTION: mise install (managed) === +# Source: computed (reads .mise.toml from copied workspace) +RUN mise trust .mise.toml && mise install --yes +# End SECTION: mise install + +# === SECTION: bootstrap proof (managed) === +# Source: template (clean-machine bootstrap through the canonical verb) +# The image exists to PROVE that a clean machine can bootstrap this project +# with nothing but the declared toolchain. It therefore runs the canonical +# setup verb fail-closed: any non-zero status fails the build. An earlier +# revision wrapped this in `set +e` and soft-passed whenever the output +# mentioned uv.lock/flext-core, which turned the proof into a bypass -- a +# broken bootstrap still produced a green image. +RUN make setup +# End SECTION: bootstrap proof + +ENTRYPOINT [] +CMD ["make", "help"] diff --git a/tests/fixtures/ci/docker/fedora.Dockerfile b/tests/fixtures/ci/docker/fedora.Dockerfile new file mode 100644 index 000000000..cdd83030d --- /dev/null +++ b/tests/fixtures/ci/docker/fedora.Dockerfile @@ -0,0 +1,54 @@ +# Generated by `flext_infra codegen conform` for flext_cli. +# === SECTION: header (managed) === +# Source: template (base/tests/fixtures/ci/docker/fedora.Dockerfile.j2) +# Free: no +# End SECTION: header +# Clean-machine proof: project bootstrap + canonical make verbs on Fedora. +FROM fedora:41 + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +# === SECTION: base packages (managed) === +# Source: template (distro-specific package list) +RUN dnf install -y \ + bash ca-certificates curl git make gcc gcc-c++ libatomic libicu-devel \ + && dnf clean all +# End SECTION: base packages + +# === SECTION: managed tool bootstrap (managed) === +# Source: config:python_version, template (installer URLs) +# mise installs the supported Python 3.13 family. +# uv is supplied by the managed environment without a project patch pin. +RUN curl -fsSL https://mise.run | sh +# uv is intentionally supplied by the caller environment; install it explicitly +# in clean-machine images so the project bootstrap can resolve dependencies. +RUN curl -fsSL https://astral.sh/uv/install.sh | sh +# tokei (and any future cargo-backed mise tool) needs a Rust toolchain. +RUN curl -fsSL https://sh.rustup.rs | sh -s -- -y --default-toolchain stable +# go is required for mise-managed beads (go:github.com/steveyegge/beads/cmd/bd). +RUN curl -fsSL https://go.dev/dl/go1.23.4.linux-amd64.tar.gz | tar -C /usr/local -xzf - \ + && ln -sf /usr/local/go/bin/go /usr/local/bin/go +ENV PATH="/usr/local/go/bin:/root/.local/bin:/root/.cargo/bin:/root/.local/share/mise/shims:${PATH}" +# End SECTION: managed tool bootstrap + +WORKDIR /workspace +COPY . . + +# === SECTION: mise install (managed) === +# Source: computed (reads .mise.toml from copied workspace) +RUN mise trust .mise.toml && mise install --yes +# End SECTION: mise install + +# === SECTION: bootstrap proof (managed) === +# Source: template (clean-machine bootstrap through the canonical verb) +# The image exists to PROVE that a clean machine can bootstrap this project +# with nothing but the declared toolchain. It therefore runs the canonical +# setup verb fail-closed: any non-zero status fails the build. An earlier +# revision wrapped this in `set +e` and soft-passed whenever the output +# mentioned uv.lock/flext-core, which turned the proof into a bypass -- a +# broken bootstrap still produced a green image. +RUN make setup +# End SECTION: bootstrap proof + +ENTRYPOINT [] +CMD ["make", "help"] diff --git a/tests/fixtures/ci/docker/ubuntu.Dockerfile b/tests/fixtures/ci/docker/ubuntu.Dockerfile new file mode 100644 index 000000000..01e2ab82d --- /dev/null +++ b/tests/fixtures/ci/docker/ubuntu.Dockerfile @@ -0,0 +1,55 @@ +# Generated by `flext_infra codegen conform` for flext_cli. +# === SECTION: header (managed) === +# Source: template (base/tests/fixtures/ci/docker/ubuntu.Dockerfile.j2) +# Free: no +# End SECTION: header +# Clean-machine proof: project bootstrap + canonical make verbs on Ubuntu. +FROM ubuntu:24.04 + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +# === SECTION: base packages (managed) === +# Source: template (distro-specific package list) +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + bash ca-certificates curl git make build-essential libicu-dev \ + && rm -rf /var/lib/apt/lists/* +# End SECTION: base packages + +# === SECTION: managed tool bootstrap (managed) === +# Source: config:python_version, template (installer URLs) +# mise installs the supported Python 3.13 family. +# uv is supplied by the managed environment without a project patch pin. +RUN curl -fsSL https://mise.run | sh +# uv is intentionally supplied by the caller environment; install it explicitly +# in clean-machine images so the project bootstrap can resolve dependencies. +RUN curl -fsSL https://astral.sh/uv/install.sh | sh +# tokei (and any future cargo-backed mise tool) needs a Rust toolchain. +RUN curl -fsSL https://sh.rustup.rs | sh -s -- -y --default-toolchain stable +# go is required for mise-managed beads (go:github.com/steveyegge/beads/cmd/bd). +RUN curl -fsSL https://go.dev/dl/go1.23.4.linux-amd64.tar.gz | tar -C /usr/local -xzf - \ + && ln -sf /usr/local/go/bin/go /usr/local/bin/go +ENV PATH="/usr/local/go/bin:/root/.local/bin:/root/.cargo/bin:/root/.local/share/mise/shims:${PATH}" +# End SECTION: managed tool bootstrap + +WORKDIR /workspace +COPY . . + +# === SECTION: mise install (managed) === +# Source: computed (reads .mise.toml from copied workspace) +RUN mise trust .mise.toml && mise install --yes +# End SECTION: mise install + +# === SECTION: bootstrap proof (managed) === +# Source: template (clean-machine bootstrap through the canonical verb) +# The image exists to PROVE that a clean machine can bootstrap this project +# with nothing but the declared toolchain. It therefore runs the canonical +# setup verb fail-closed: any non-zero status fails the build. An earlier +# revision wrapped this in `set +e` and soft-passed whenever the output +# mentioned uv.lock/flext-core, which turned the proof into a bypass -- a +# broken bootstrap still produced a green image. +RUN make setup +# End SECTION: bootstrap proof + +ENTRYPOINT [] +CMD ["make", "help"] From 9bb827905987c3c6ebf63a1cc1d425d1eea61769 Mon Sep 17 00:00:00 2001 From: Marlon Costa Date: Mon, 3 Aug 2026 16:58:02 -0300 Subject: [PATCH 2/3] fix(ci): install managed Git hooks --- .github/scripts/install-git-hooks.sh | 109 +++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100755 .github/scripts/install-git-hooks.sh diff --git a/.github/scripts/install-git-hooks.sh b/.github/scripts/install-git-hooks.sh new file mode 100755 index 000000000..3619a0605 --- /dev/null +++ b/.github/scripts/install-git-hooks.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash +# Owner-Skill: .agents/skills/scripts-validation/SKILL.md +# install-git-hooks.sh — Install the Beads git hooks at the workspace root and +# apply the FLEXT agent-trailer guard. +# +# Canonical owner of git-hook provisioning for this workspace. Reproducible and +# idempotent: safe to run repeatedly and after every `bd hooks install`. +# +# Why the guard: +# FLEXT law (R5 / ai-hub agent-law §12) forbids agent attribution trailers by +# default. The Beads `prepare-commit-msg` shim's sole job is adding those +# trailers, so it must be gated behind an explicit opt-in: +# BD_ALLOW_AGENT_COMMIT_TRAILERS=1 +# `.github/scripts/check-beads-policy.sh` enforces the guard text is present +# in the installed hook; `make check WHAT=coordination` fails without it. +# +# Mechanism: +# `bd hooks install --chain` writes bd-managed sections between markers and +# preserves any content OUTSIDE those markers across installs/upgrades. This +# script re-applies bd's install, then injects the guard block above the bd +# `--- BEGIN BEADS INTEGRATION ---` marker so it survives future bd installs. +# +# Usage: +# make hooks +# .github/scripts/install-git-hooks.sh [--verbose] + +set -euo pipefail + +VERBOSE="${1:-}" +WORKSPACE_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +cd "${WORKSPACE_ROOT}" + +_log() { + if [[ "${VERBOSE}" == "--verbose" ]]; then + echo "[INFO] $*" + fi +} + +fail() { + printf 'install-git-hooks: %s\n' "$*" >&2 + exit 1 +} + +command -v pre-commit >/dev/null 2>&1 || fail "pre-commit is not installed; install it before provisioning hooks" +command -v bd >/dev/null 2>&1 || fail "bd is not installed; install Beads before provisioning hooks" + +# Why: install both staged workflow entry points before Beads chains its guard. +_log "Installing pre-commit and pre-push hooks at ${WORKSPACE_ROOT}" +pre-commit install -t pre-commit -t pre-push >/dev/null \ + || fail "pre-commit hook installation failed" +_log "Installing Beads git hooks (chained) at ${WORKSPACE_ROOT}" +bd hooks install --chain >/dev/null || fail "bd hooks install --chain failed" + +hook_path="$(git rev-parse --git-path hooks/prepare-commit-msg)" +[ -f "${hook_path}" ] || fail "prepare-commit-msg hook missing after bd hooks install" + +_log "Applying FLEXT agent-trailer guard to ${hook_path}" +GUARD_TOKEN="BD_ALLOW_AGENT_COMMIT_TRAILERS" python3 - "${hook_path}" <<'PY' +import os +import pathlib +import sys + +token = os.environ["GUARD_TOKEN"] +path = pathlib.Path(sys.argv[1]) +text = path.read_text() + +if token in text: + # Guard already present (idempotent): nothing to do. + sys.exit(0) + +guard = ( + "# --- BEGIN FLEXT AGENT-TRAILER GUARD ---\n" + "# Managed by .github/scripts/install-git-hooks.sh — do not hand-edit.\n" + "# FLEXT law (R5): prepare-commit-msg must NOT add agent attribution\n" + "# trailers unless the user opts in with BD_ALLOW_AGENT_COMMIT_TRAILERS=1.\n" + "# The Beads shim below only adds trailers, so gate it here.\n" + 'if [ "${BD_ALLOW_AGENT_COMMIT_TRAILERS:-0}" != "1" ]; then\n' + " exit 0\n" + "fi\n" + "# --- END FLEXT AGENT-TRAILER GUARD ---\n" +) + +lines = text.splitlines(keepends=True) +marker = "# --- BEGIN BEADS INTEGRATION" +insert_at = next( + (i for i, line in enumerate(lines) if line.startswith(marker)), + None, +) +if insert_at is None: + raise SystemExit( + "beads integration marker not found; cannot place guard deterministically" + ) + +# Insert the guard immediately before the bd-managed section (outside markers, +# so `bd hooks install` preserves it on future upgrades). +lines[insert_at:insert_at] = [guard] +path.write_text("".join(lines)) +PY + +grep -q 'BD_ALLOW_AGENT_COMMIT_TRAILERS' "${hook_path}" \ + || fail "guard token missing after injection" +grep -q 'bd hooks run prepare-commit-msg' "${hook_path}" \ + || fail "bd delegation missing; refusing to leave hook without beads integration" +[ -f "$(git rev-parse --git-path hooks/pre-commit)" ] \ + || fail "pre-commit hook missing after provisioning" +[ -f "$(git rev-parse --git-path hooks/pre-push)" ] \ + || fail "pre-push hook missing after provisioning" + +echo "install-git-hooks: prepare-commit-msg guarded (BD_ALLOW_AGENT_COMMIT_TRAILERS opt-in)" From 536925cf7d1a5db5767cb326e7445bb644b3a7ea Mon Sep 17 00:00:00 2001 From: Marlon Costa Date: Mon, 3 Aug 2026 17:16:29 -0300 Subject: [PATCH 3/3] fix(ci): promote hooks policy to main --- .github/workflows/ci.yml | 15 +++++++++++---- .pre-commit-config.yaml | 17 ++++++++++++++--- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ac283eff..856de137d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,11 @@ permissions: contents: read # End SECTION: permissions +# Why: one active run per workflow and branch prevents obsolete CI from consuming runners. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: # === SECTION: ci job (managed) === # Source: template + config:github_actions.* @@ -45,8 +50,10 @@ jobs: submodules: false fetch-depth: 0 + + - name: Install mise toolchain - uses: jdx/mise-action@dba19683ed58901619b14f395a24841710cb4925 # v4.1.0 + uses: jdx/mise-action@9e7f7633ff6f6d6048a9418a68d48f288f50eb14 # v4.2.3 - name: Install uv uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 @@ -56,13 +63,13 @@ jobs: run: make setup - name: gen (blocking) - run: make gen APPLY=Y + run: make gen WHAT=apply APPLY=Y - name: fmt (blocking) - run: make fmt APPLY=Y + run: make fmt WHAT=apply APPLY=Y - name: fix (blocking) - run: make fix APPLY=Y + run: make fix WHAT=apply APPLY=Y - name: check (blocking) run: make check diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9c08a65e9..b746da7c8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,11 +2,22 @@ repos: - repo: local hooks: - - id: flext-canonical-workflow - name: FLEXT canonical workflow + # Why: fast fixes run before each commit; expensive gates run before push. + - id: flext-pre-commit + name: FLEXT pre-commit workflow language: system entry: >- bash -eu -o pipefail -c - 'make gen APPLY=Y && make fmt APPLY=Y && make fix APPLY=Y && make check && make test' + 'make fmt APPLY=Y && make fix APPLY=Y' pass_filenames: false always_run: true + stages: [pre-commit] + - id: flext-pre-push + name: FLEXT pre-push workflow + language: system + entry: >- + bash -eu -o pipefail -c + 'make gen APPLY=Y && make check && make test' + pass_filenames: false + always_run: true + stages: [pre-push]