# GitHub Integration ## Overview The `.github/` directory contains GitHub-specific configuration and automation for the Research Project Template. This includes continuous integration workflows, issue templates, PR templates, and other GitHub integrations that ensure code quality and collaborative development. **Human entry point:** [README.md](README.md) — GitHub overview, doc map, and [Agent & automation entry point](README.md#agent--automation-entry-point). **This file (`AGENTS.md`):** job names, paths, and thresholds for automation and agents. ## Publication **Title**: *A template/ approach to Reproducible Generative Research: Architecture and Ergonomics from Configuration through Publication* **DOI**: [10.5281/zenodo.19139090](https://doi.org/10.5281/zenodo.19139090) **Record**: [zenodo.org/records/19139090](https://zenodo.org/records/19139090) **License**: Apache 2.0 Public exemplar GitHub/Zenodo records are generated into [`.github/README.md`](README.md#published-exemplars--pipeline-productivity-advanced-provenance-and-autopoiesis) from [`../docs/_generated/publication_records.md`](../docs/_generated/publication_records.md). Refresh them with: ```bash uv run python scripts/docgen/publication_records.py --refresh-external ``` ## Directory Structure ```mermaid flowchart TB GH[.github/] GH --> META[AGENTS.md · README.md] GH --> DEP[dependabot.yml
Automated dependency updates] GH --> PRT[PULL_REQUEST_TEMPLATE.md] GH --> ITPL[ISSUE_TEMPLATE] GH --> WF[workflows] ITPL --> ITPL_DOCS[AGENTS.md · README.md] ITPL --> ITPL_F[config.yml · bug_report.md ·
feature_request.md · documentation.md] WF --> WF_DOCS[AGENTS.md · README.md] WF --> WF_CI[ci.yml
16 jobs — 2 conditional via detect-job outputs — fep-lean, setup-hook-windows-smoke — plus 1 scheduled-only — public-matrix-receipt] WF --> WF_STALE[stale.yml
Auto-label/close stale issues/PRs] WF --> WF_REL[release.yml
GitHub Releases on version tags] WF --> WF_DA[dependabot-automerge.yml
Auto-merge safe Dependabot PRs] classDef d fill:#0f172a,stroke:#0f172a,color:#fff classDef pkg fill:#1e3a8a,stroke:#0f172a,color:#fff classDef f fill:#0f766e,stroke:#0f172a,color:#fff class GH d class ITPL,WF pkg class META,DEP,PRT,ITPL_F,WF_DOCS,WF_CI,WF_STALE,WF_REL f ``` ## Continuous Integration (CI/CD) ### CI Pipeline (`workflows/ci.yml`) **Triggers:** push to `main`, pull requests targeting `main`, weekly scheduled run (Sunday midnight UTC), manual **`workflow_dispatch`** (no inputs). **Concurrency:** Running builds for the same ref are cancelled when a new commit arrives. **Pipeline jobs** (job ids in `ci.yml`; display names differ — use `name:` for branch protection): **16 jobs total; 2 are conditional on push/PR (fep-lean, setup-hook-windows-smoke), gated by the `detect` job's outputs (`needs.detect.outputs.*`) — NOT a job-level `hashFiles()` (that is invalid in a job `if:` and rejects the whole workflow at parse). 1 further job (`public-matrix-receipt`) runs only on the weekly schedule or manual dispatch.** | # | Job id | Display name (representative) | Depends on | Python | Runner | | --- | --- | --- | --- | --- | --- | | 1 | `detect` | Detect optional projects | — | — | ubuntu (always) | | 2 | `detect-projects` | Detect public capability matrix | — | manifest-owned 3.10 + 3.12 | ubuntu (always) | | 3 | `actionlint` | Actionlint | — | — | ubuntu (always) | | 4 | `lint` | Lint & Type Check | — | 3.12 | ubuntu | | 5 | `health` | Static Health Report | lint | 3.12 | ubuntu | | 6 | `verify-no-mocks` | Verify No Mocks Policy | — (parallel with `lint`) | 3.12 | ubuntu | | 7 | `setup-hook-windows-smoke` | Setup hook (Windows smoke) | verify-no-mocks, detect | 3.12 | windows · runs iff `needs.detect.outputs.setup_hook == 'true'` | | 8 | `test-infra` | Infra Tests (matrix) | verify-no-mocks | 3.10–3.13 | ubuntu (×3.10/3.11/3.12/3.13) + macOS (3.12 only) — 5 cells | | 9 | `test-regression` | Regression Tier (claim-binding pins) | verify-no-mocks | 3.12 | ubuntu | | 10 | `test-project` | Project Tests (per-project matrix) | verify-no-mocks, detect-projects | capability-manifest versions | ubuntu only — exact project/Python include matrix from `public_capabilities.py` | | 11 | `fep-lean` | fep_lean (gauss + lake) | verify-no-mocks, detect | 3.12 | ubuntu · runs iff `needs.detect.outputs.fep_lean == 'true'` | | 12 | `validate` | Validate Manuscripts | lint | 3.12 | ubuntu | | 13 | `security` | Security Scan | lint | 3.12 | ubuntu | | 14 | `docs-lint` | Documentation Lint | lint | 3.12 | ubuntu | | 15 | `performance` | Performance Check | test-infra + test-project | 3.12 | ubuntu | | 16 | `public-matrix-receipt` | Public Matrix Receipt (receipt-bearing full matrix) | — | 3.12 | ubuntu · schedule/manual only | **Lint job** also runs `uv run python -m infrastructure.skills check-all-exports` (MED5 `__all__` gate), `scripts/audit/check_tracked_generated_artifacts.py` (rejects generated outputs and local `.codegraph/` indexes), **`scripts/audit/check_template_drift.py --strict`** (exemplar doc/script drift against Layer-1 contracts), and **`scripts/audit/check_tracked_all.py`** — the **confidentiality guard** that fails CI if any path outside the public allowlists for `projects/`, `fonds/`, `rules/`, or `tools/` is git-tracked (this is a public repo; confidential/rotating resources are local-only). **`validate`** runs manuscript markdown validation (one dir per invocation, looped over `projects/*/manuscript/`), `scripts/docgen/api_reference.py --check`, and imports each `projects.{name}.src`. **`security`** runs blocking **`pip-audit`** (IDs from [`.github/pip-audit-ignore.txt`](pip-audit-ignore.txt), up to 3 retries on failure) and **`bandit -c bandit.yaml -r -ll`** over `infrastructure/`, `scripts/`, and `projects/`. Path exclusions (the non-rendered subfolders `projects/working/`, `projects/published/`, `projects/archive/`, `projects/other/`, plus `.venv`, `site-packages`, `.lake`, and the rotating research projects under `projects/active/`) live in [`bandit.yaml`](../bandit.yaml) (`exclude_dirs`). **Display name (branch protection):** the optional fep_lean job is reported as **`fep_lean (gauss + lake)`** (`ci.yml` `name:` on job id `fep-lean`). It runs only when the `detect` job sets `fep_lean == 'true'` (`if: needs.detect.outputs.fep_lean == 'true'`) — a job-level `hashFiles()` is **invalid** in a job `if:` and would reject the whole workflow at parse, which is why the `detect` job exists. When fep_lean lives under `projects/working/`, `detect` reports `false` and the job is skipped. Promote with `mv projects/working/fep_lean projects/active/fep_lean` to activate CI. **Branch protection must NOT mark the two conditional jobs (`fep-lean`, `setup-hook-windows-smoke`) as required** — they are skipped (not failed) when their project is absent, so requiring them would wedge every PR. Coverage is uploaded to **Codecov** after each test job (3.12/ubuntu-latest only). The `verify-no-mocks` job runs [`scripts/audit/verify_no_mocks.py`](../scripts/audit/verify_no_mocks.py) at the repository root (not under `.github/`). ### Stale Workflow (`workflows/stale.yml`) Runs daily. Issues → stale after 60 days, closed after 14 more. PRs → stale after 30 days, closed after 14 more. Exempt labels: `pinned`, `security`, `in-progress`, `blocked`, `do-not-close`. ### Release Workflow (`workflows/release.yml`) Triggered by `v*.*.*` tag pushes or manual dispatch with a tag. It resolves the requested tag before checkout, checks out that exact ref, proves `HEAD` matches the dereferenced tag commit, runs the root release contract, and then runs the bounded pipeline-smoke infra test lane plus no-mocks gate on the tagged SHA (executable test evidence without the per-commit full matrix), the fail-closed capability manifest, credential-free clean-export package/import smoke, and strict rendered publication audit across the canonical public roster. Those gates require valid source/package contracts, standalone imports, and current rendered co-snapshot receipts before distributions are built. The release uses `softprops/action-gh-release@v3.0.2`, writes a short git-log excerpt to `body_path`, and keeps **`generate_release_notes`** off so GitHub does not duplicate the body. ### Dependabot Automerge Workflow (`workflows/dependabot-automerge.yml`) Triggered by `pull_request_target` on Dependabot PRs only (`github.actor == 'dependabot[bot]'`). Enables GitHub native auto-merge (`gh pr merge --auto --squash`) for `semver-minor` and `semver-patch` updates. Major bumps are left for human review. The workflow never checks out or executes PR HEAD code — it only reads update metadata and calls the GitHub API — so the elevated `pull_request_target` trigger does not expose secrets to untrusted code. Auto-merge must also be enabled in repository settings (Settings → General → "Allow auto-merge"). ## Dependabot (`dependabot.yml`) [`dependabot.yml`](dependabot.yml) at the repository root: **GitHub Actions** (`package-ecosystem: github-actions`, `directory: /`) and **Python** (`package-ecosystem: pip`, `directory: /`) both read the root **`pyproject.toml`** / lockfile — compatible with **uv**. Weekly **Monday 09:00 UTC**, max **5** open PRs per ecosystem. Groups: **`actions-minor`** (Actions minor/patch), **`dev-tools`** and **`scientific-core`** (Python). ## Quality Gates | Gate | Threshold | | --- | --- | | Ruff lint | zero violations | | Ruff format | zero diffs | | mypy strict gate | zero errors across the generated public source scope | | Mock-framework lexical gate | zero prohibited imports/calls (`--inventory` debt remains advisory) | | Public capability parity | exact public roster and CI matrix; unique normalized package identities; complete moving-patch Python series; parseable source/tests; compiled/confined direct hydration; reason-bearing skips | | Infrastructure coverage | ≥ 60% | | Project coverage (per-project standalone) | ≥ 90% | | Combined-union public-project gate (`DEFAULT_FAIL_UNDER`) | ≥ 75% via local `scripts/pipeline/stage_01_test.py --project-only --all-projects --public-projects` (CI `test-project` enforces per-exemplar 90% only) | | pip-audit | zero known vulns not listed in `.github/pip-audit-ignore.txt` | | Bandit MEDIUM+ (`-c bandit.yaml`) | zero findings | | Mermaid diagrams render under `mmdc` | zero failures | | Markdown cross-links resolve on disk | zero broken links | | Permanent-template folders carry `AGENTS.md` + `README.md` | zero missing pairs | | `N Python (sub)packages` claims match reality | zero stale counts | | Rotating projects in long-lived docs are conditional | zero ghost references | | Import time | ≤ 5 s | | Module line count (`scripts/gates/module_line_count_check.py`) | warn ≥ 800 / fail ≥ 950 for `infrastructure/` + `scripts/`; warn ≥ 150 / fail ≥ 250 for `projects/*/scripts/` | ## Local Pre-Push Parity (`.pre-commit-config.yaml`) The repo ships a [`.pre-commit-config.yaml`](../.pre-commit-config.yaml) with **seven pre-push hooks** that mirror (or partially mirror) CI gates so pushes that would fail CI fail locally first: | Hook id | Mirrors CI step | Typical runtime | | --- | --- | :-: | | `pre-push-quick` | `verify-no-mocks` + `check_tracked_all` + `tests/infra_tests/git_hook_smoke/` | ~3 s | | `docs-contract-guard` | `check_template_drift.py --strict` + AGENTS personal-memory test | ~5 s | | `bandit-quick` | `security` job Bandit step (`-c bandit.yaml -r -ll`) | ~5–30 s | | `skills-check` | `infrastructure.skills check` (manifest freshness) | <1 s | | `operations-check` | `infrastructure.skills operations-check` (operation-manifest freshness) | <1 s | | `all-exports-check` | `infrastructure.skills check-all-exports` (lint job MED5 gate) | <1 s | | `skill-reachability-check` | docs-lint skill front-door and index completeness gate | <1 s | The lint job also runs the deterministic source-only publication audit across the generated public roster: ```bash uv run python -m infrastructure.validation.cli publication-audit --all-public --strict --format json ``` For release sign-off, add `--rendered` after running each exemplar's canonical pipeline; that mode makes artifact manifests, evidence registries, and figure registries blocking requirements. Subjective editorial findings remain `review_required` and are reported without weakening deterministic failures. The lint hooks (`ruff-ci`, `mypy-ci`) run on the **pre-commit** stage, not pre-push, to keep `git commit` fast. A separate manual-stage `bandit-low` hook provides a stricter LOW+MEDIUM+HIGH sweep against `bandit.yaml`'s allow-list — invoke with `pre-commit run --hook-stage manual bandit-low`. To run the full pre-push gate manually: ```bash pre-commit run --hook-stage pre-push --all-files ``` A new MEDIUM Bandit finding fails `git push` locally with the same scope and severity as the CI `security` job, so contributors hear it before CI does. ## Branch Protection (Recommended) Required checks must match the **`name:`** field of each job in [`workflows/ci.yml`](workflows/ci.yml). `main` is currently unprotected, so the contexts below are **illustrative**. Matrix jobs expand to one check per cell: - **`test-infra`** → **Infra Tests (``, Python ``)** — 5 cells: `ubuntu-latest × 3.10/3.11/3.12/3.13` plus `macos-latest × 3.12`. - **`test-project`** → **Project Tests (``, py``)** — one cell for every entry emitted by the validated capability manifest. Its current canonical versions are py3.10 and py3.12, ubuntu-latest only. Require the combinations you care about, or use GitHub rulesets that treat required checks flexibly. ```yaml required_status_checks: contexts: - "Lint & Type Check" - "Verify No Mocks Policy" - "Infra Tests (ubuntu-latest, Python 3.10)" - "Infra Tests (ubuntu-latest, Python 3.11)" - "Infra Tests (ubuntu-latest, Python 3.12)" - "Infra Tests (ubuntu-latest, Python 3.13)" - "Infra Tests (macos-latest, Python 3.12)" # test-project expands dynamically: "Project Tests (, py)" # for each live templates/template_* exemplar on py3.10 and py3.12. Examples: - "Project Tests (templates/template_active_inference, py3.12)" - "Project Tests (templates/template_code_project, py3.10)" # ... (one check per exemplar × {py3.10, py3.12}) # Optional: only when fep_lean job runs (skipped if no lean-toolchain file) # - "fep_lean (gauss + lake)" - "Static Health Report" - "Validate Manuscripts" - "Security Scan" - "Documentation Lint" - "Performance Check" required_pull_request_reviews: required_approving_review_count: 1 ``` Branch protection is an external GitHub control and must also require the `Regression Tier (claim-binding pins)` check. Changes under the sensitive workflow/configuration paths listed in [`sensitive-ownership.yaml`](sensitive-ownership.yaml) must receive the generated CODEOWNERS review. The current single-maintainer exceptions are documented in that policy file; they are risk disclosures, not permission to bypass the required status checks or sensitive-area review. ## Issue Templates | Template | Labels | Use for | | --- | --- | --- | | Bug Report | `bug`, `needs-triage` | Reproducible errors with log output | | Feature Request | `enhancement`, `needs-triage` | New capabilities and improvements | | Documentation Update | `documentation`, `needs-triage` | Incorrect or missing docs | Blank issues are disabled. General questions should go to **GitHub Discussions**. See [`ISSUE_TEMPLATE/AGENTS.md`](ISSUE_TEMPLATE/AGENTS.md) for local editing rules. ## Troubleshooting ```bash # Fix linting locally uv run python -m infrastructure.project.public_scope lint-paths | xargs uv run ruff check --fix uv run python -m infrastructure.project.public_scope lint-paths | xargs uv run ruff format # Run tests locally (safe macOS coverage lane; CI uses auto on Linux) COVERAGE_FILE=.coverage.infra uv run pytest tests/infra_tests/ \ -n 2 --dist loadscope --benchmark-disable \ --cov=infrastructure --cov-report=term-missing --cov-fail-under=60 \ --durations=10 \ -m "not requires_ollama and not requires_docker and not network and not slow and not bench and not benchmark and not performance" \ --timeout=120 # Uncached serial diagnostic oracle: remove the xdist flags above. uv sync --group public-exemplars COVERAGE_FILE=.coverage.project uv run python scripts/pipeline/stage_01_test.py --project-only --all-projects --public-projects --non-strict --include-slow uv run coverage xml -o coverage-project.xml # Security scan locally (mirror CI) IGNORE_ARGS=() while IFS= read -r raw; do [[ "$raw" =~ ^[[:space:]]*# ]] && continue; line="${raw%%#*}"; line="$(echo "$line" | xargs)"; [ -z "$line" ] || IGNORE_ARGS+=(--ignore-vuln "$line"); done < .github/pip-audit-ignore.txt uv run pip-audit "${IGNORE_ARGS[@]}" uv run bandit -c bandit.yaml -r -ll infrastructure/ scripts/ projects/ # Module line count (also in `uv run python -m infrastructure.core.health --gates=module-line-count`): uv run python scripts/gates/module_line_count_check.py # Strict LOW+MEDIUM+HIGH sweep against the documented allow-list: uv run bandit -c bandit.yaml -r --severity-level low infrastructure/ scripts/ # Check workflow status via GitHub CLI gh workflow list gh run list --workflow=CI --limit=5 gh run view --log ``` ## See Also - [`README.md`](README.md) — Contributor-oriented GitHub integration guide - [`workflows/AGENTS.md`](workflows/AGENTS.md) — Detailed CI/CD workflow documentation - [`../AGENTS.md`](../AGENTS.md) — Root system overview - [GitHub Actions Documentation](https://docs.github.com/en/actions) - [uv Documentation](https://docs.astral.sh/uv/)