From e211c3a8ec1311259ebcc6a5fc915c9cf397b2e4 Mon Sep 17 00:00:00 2001 From: CMaintz Date: Fri, 25 Sep 2026 12:33:53 +0200 Subject: [PATCH] refactor!: prefix reusable workflows with _, rename tier0 -> _guards Group the 9 reusable workflows under a _ prefix (GitHub forbids subdirs in .github/workflows/, so a filename prefix is the only grouping GitHub allows) and give tier0 a functional name: _guards.yml (secret scan + ruleset-guard). Triggers stay unprefixed (lint-workflows, release-please, commitlint). Updated foundry-init's generated callers + CI mapping + the guards job name, the header usage examples, and README/OVERVIEW/FEATURES/DESIGN. Also corrected the stale no_var-job doc mention (folded into lint by the earlier change). BREAKING CHANGE: reusable-workflow paths changed. uses: .../java.yml@vX etc. must become .../_java.yml, and tier0.yml -> _guards.yml. @v1 keeps the old paths (that alias stays frozen); update the uses: path when moving to @v2. --- .../workflows/{autofix.yml => _autofix.yml} | 4 ++-- .../{bootstrap.yml => _bootstrap.yml} | 2 +- .github/workflows/{tier0.yml => _guards.yml} | 9 +++++---- .github/workflows/{java.yml => _java.yml} | 2 +- .github/workflows/{php.yml => _php.yml} | 2 +- ...ratchet-report.yml => _ratchet-report.yml} | 2 +- .../workflows/{semgrep.yml => _semgrep.yml} | 2 +- .github/workflows/{ts.yml => _ts.yml} | 2 +- .github/workflows/{web.yml => _web.yml} | 2 +- DESIGN.md | 4 ++-- FEATURES.md | 16 +++++++-------- OVERVIEW.md | 20 +++++++++---------- README.md | 20 +++++++++++-------- scripts/foundry-init.sh | 18 ++++++++--------- 14 files changed, 55 insertions(+), 50 deletions(-) rename .github/workflows/{autofix.yml => _autofix.yml} (96%) rename .github/workflows/{bootstrap.yml => _bootstrap.yml} (98%) rename .github/workflows/{tier0.yml => _guards.yml} (97%) rename .github/workflows/{java.yml => _java.yml} (99%) rename .github/workflows/{php.yml => _php.yml} (99%) rename .github/workflows/{ratchet-report.yml => _ratchet-report.yml} (97%) rename .github/workflows/{semgrep.yml => _semgrep.yml} (96%) rename .github/workflows/{ts.yml => _ts.yml} (99%) rename .github/workflows/{web.yml => _web.yml} (96%) diff --git a/.github/workflows/autofix.yml b/.github/workflows/_autofix.yml similarity index 96% rename from .github/workflows/autofix.yml rename to .github/workflows/_autofix.yml index cb47d90..81760e3 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/_autofix.yml @@ -1,14 +1,14 @@ # Reusable autofix — the "fix it for me" button. Add a label to a PR and CI runs # `mise run fix`, commits the result to the PR branch, and drops the label. # -# # .github/workflows/autofix.yml (caller) +# # .github/workflows/_autofix.yml (caller) # name: autofix # on: # pull_request: # types: [labeled] # jobs: # autofix: -# uses: CMaintz/foundry/.github/workflows/autofix.yml@v1 +# uses: CMaintz/foundry/.github/workflows/_autofix.yml@v1 # permissions: # contents: write # pull-requests: write diff --git a/.github/workflows/bootstrap.yml b/.github/workflows/_bootstrap.yml similarity index 98% rename from .github/workflows/bootstrap.yml rename to .github/workflows/_bootstrap.yml index 6c6fa8f..91cd440 100644 --- a/.github/workflows/bootstrap.yml +++ b/.github/workflows/_bootstrap.yml @@ -3,7 +3,7 @@ # # jobs: # bootstrap: -# uses: CMaintz/foundry/.github/workflows/bootstrap.yml@ +# uses: CMaintz/foundry/.github/workflows/_bootstrap.yml@ # with: # habit_hooks_plugin: habit-hooks-typescript # permissions: diff --git a/.github/workflows/tier0.yml b/.github/workflows/_guards.yml similarity index 97% rename from .github/workflows/tier0.yml rename to .github/workflows/_guards.yml index a58054e..9bceaed 100644 --- a/.github/workflows/tier0.yml +++ b/.github/workflows/_guards.yml @@ -1,11 +1,12 @@ -# Reusable Tier 0 — language-agnostic checks that run on every repo. +# Reusable "guards" — language-agnostic checks that run on every repo: secret scan + +# ruleset-guard. (Formerly tier0.yml.) # # jobs: -# tier0: -# uses: CMaintz/foundry/.github/workflows/tier0.yml@v1 +# guards: +# uses: CMaintz/foundry/.github/workflows/_guards.yml@v1 # # Nothing here knows what language the repo is written in. -name: tier0 +name: guards on: workflow_call: diff --git a/.github/workflows/java.yml b/.github/workflows/_java.yml similarity index 99% rename from .github/workflows/java.yml rename to .github/workflows/_java.yml index 8fbbc62..4361687 100644 --- a/.github/workflows/java.yml +++ b/.github/workflows/_java.yml @@ -2,7 +2,7 @@ # # jobs: # gate: -# uses: CMaintz/foundry/.github/workflows/java.yml@ +# uses: CMaintz/foundry/.github/workflows/_java.yml@ # with: # spotbugs: true # opt-in bytecode analysis # diff --git a/.github/workflows/php.yml b/.github/workflows/_php.yml similarity index 99% rename from .github/workflows/php.yml rename to .github/workflows/_php.yml index b375ca9..5f83aeb 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/_php.yml @@ -2,7 +2,7 @@ # # jobs: # gate: -# uses: CMaintz/foundry/.github/workflows/php.yml@ +# uses: CMaintz/foundry/.github/workflows/_php.yml@ # # Runs the six verbs a developer runs locally (`mise run gate`). PHP toolchain # comes from the repo's mise.toml; Composer is preinstalled on the runner. diff --git a/.github/workflows/ratchet-report.yml b/.github/workflows/_ratchet-report.yml similarity index 97% rename from .github/workflows/ratchet-report.yml rename to .github/workflows/_ratchet-report.yml index b2c35af..696a353 100644 --- a/.github/workflows/ratchet-report.yml +++ b/.github/workflows/_ratchet-report.yml @@ -4,7 +4,7 @@ # # jobs: # ratchet: -# uses: CMaintz/foundry/.github/workflows/ratchet-report.yml@ +# uses: CMaintz/foundry/.github/workflows/_ratchet-report.yml@ # with: # baselines: | # eslint-suppressions.json diff --git a/.github/workflows/semgrep.yml b/.github/workflows/_semgrep.yml similarity index 96% rename from .github/workflows/semgrep.yml rename to .github/workflows/_semgrep.yml index 916b718..08d9ea3 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/_semgrep.yml @@ -2,7 +2,7 @@ # # jobs: # sast: -# uses: CMaintz/foundry/.github/workflows/semgrep.yml@ +# uses: CMaintz/foundry/.github/workflows/_semgrep.yml@ # # CodeQL is the GitHub-native alternative but is free only on PUBLIC repos; # Semgrep is free and works on private repos too, so it's the portable default. diff --git a/.github/workflows/ts.yml b/.github/workflows/_ts.yml similarity index 99% rename from .github/workflows/ts.yml rename to .github/workflows/_ts.yml index 62e24e8..6dd6a6c 100644 --- a/.github/workflows/ts.yml +++ b/.github/workflows/_ts.yml @@ -2,7 +2,7 @@ # # jobs: # gate: -# uses: CMaintz/foundry/.github/workflows/ts.yml@v1 +# uses: CMaintz/foundry/.github/workflows/_ts.yml@v1 # # Runs the same six verbs a developer runs locally. If this and `mise run gate` # on a laptop ever disagree, that is a bug in the setup, not in the code. diff --git a/.github/workflows/web.yml b/.github/workflows/_web.yml similarity index 96% rename from .github/workflows/web.yml rename to .github/workflows/_web.yml index 8dbeffb..68720fd 100644 --- a/.github/workflows/web.yml +++ b/.github/workflows/_web.yml @@ -2,7 +2,7 @@ # # jobs: # web: -# uses: CMaintz/foundry/.github/workflows/web.yml@ +# uses: CMaintz/foundry/.github/workflows/_web.yml@ # with: { max_lines: 400 } # # File-length limits apply to markup and styles too — a 2000-line stylesheet is a diff --git a/DESIGN.md b/DESIGN.md index f3ed0eb..ebf3b2c 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -307,7 +307,7 @@ cadences, which is what justifies the split. C:\Users\akash\Projects\_foundry\ ← leading underscore: sorts above the IDE dirs, │ reads as "not an application" ├── foundry/ → github.com/CMaintz/foundry - │ ├── .github/workflows/ reusable workflow_call: tier0.yml, ts.yml, jvm.yml, php.yml + │ ├── .github/workflows/ reusable workflow_call: _guards.yml, _ts.yml, _java.yml, _php.yml │ ├── mise/ shared task templates per stack │ ├── presets/ eslint / tsconfig / ruff / phpstan base configs │ ├── habit-hooks/ config.toml presets per stack @@ -323,7 +323,7 @@ C:\Users\akash\Projects\_foundry\ ← leading underscore: sorts above the └── CONTRACT.md ◀── same file, kept in sync ``` -- `foundry` is consumed **by repos**, via `uses: CMaintz/foundry/.github/workflows/ts.yml@v1`. +- `foundry` is consumed **by repos**, via `uses: CMaintz/foundry/.github/workflows/_ts.yml@v1`. - `cmaintz-skills` is consumed **by the agent**, via `/plugin marketplace add CMaintz/cmaintz-skills`. **The seam is `CONTRACT.md`** — the verb interface of §3, copied verbatim into both. As long as both diff --git a/FEATURES.md b/FEATURES.md index c4b62d3..176622b 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -23,14 +23,14 @@ mechanism* (not just an example) → a per-language file (`mise/.toml`, | Workflow | Purpose | |---|---| -| `ts.yml` · `java.yml` · `php.yml` | Language gate — the six verbs, decomposed one-per-step with targeted failure summaries, + a structural-smells job (which prints a per-smell "what it means / fix toward" legend on failure). `java` adds opt-in `spotbugs` / `no_var`. node_modules / vendor / Gradle caching. | -| `tier0.yml` | Language-agnostic: secret scan (gitleaks) + `ruleset-guard`, both with remediation step-summaries. Merge-base–scoped. | -| `semgrep.yml` | SAST, diff-aware (`--baseline-commit`), pip-cached, pinnable. | -| `web.yml` | Max-file-length gate for HTML/CSS. | -| `bootstrap.yml` | Regenerate the habit-hooks snooze baseline on Linux (`--prune` to shrink), open a PR. | -| `ratchet-report.yml` | PR comment showing how the accepted-debt baselines moved. | -| `autofix.yml` | Label a PR `autofix` → runs `mise run fix`, commits + pushes the result. | -| `lint-workflows.yml` | actionlint over foundry's own workflows. | +| `_ts.yml` · `_java.yml` · `_php.yml` | Language gate — the six verbs, decomposed one-per-step with targeted failure summaries, + a structural-smells job (which prints a per-smell "what it means / fix toward" legend on failure). `_java.yml` adds an opt-in `spotbugs` job (no-`var` is folded into `lint`). node_modules / vendor / Gradle caching. | +| `_guards.yml` | Language-agnostic: secret scan (gitleaks) + `ruleset-guard`, both with remediation step-summaries. Merge-base–scoped. | +| `_semgrep.yml` | SAST, diff-aware (`--baseline-commit`), pip-cached, pinnable. | +| `_web.yml` | Max-file-length gate for HTML/CSS. | +| `_bootstrap.yml` | Regenerate the habit-hooks snooze baseline on Linux (`--prune` to shrink), open a PR. | +| `_ratchet-report.yml` | PR comment showing how the accepted-debt baselines moved. | +| `_autofix.yml` | Label a PR `autofix` → runs `mise run fix`, commits + pushes the result. | +| `lint-workflows.yml` | actionlint + shellcheck + typos over foundry's own repo (not reusable — no `_`). | ## mise verb templates (`mise/`) diff --git a/OVERVIEW.md b/OVERVIEW.md index 516072b..afc45f7 100644 --- a/OVERVIEW.md +++ b/OVERVIEW.md @@ -90,7 +90,7 @@ Three probabilistic *roles* are kept deliberately separate (people mush them): ## 5. The gate, job by job -The CI gate (`gate.yml`, or foundry's reusable `ts.yml` + `tier0.yml`) is four +The CI gate (`gate.yml`, or foundry's reusable `_ts.yml` + `_guards.yml`) is four jobs. CI is **100% deterministic on purpose** — no model, no API key, no secrets beyond the default token. @@ -164,7 +164,7 @@ lets `mise run fix` prune the baseline and ship that in the same PR as the fix. ### What counts as a gate-defining file — and who watches it Governance is split across three enforcement points by *when* they run — a -client-side hook (cmaintz-skills), the CI guard (`tier0.yml`), and the regenerator +client-side hook (cmaintz-skills), the CI guard (`_guards.yml`), and the regenerator (`bootstrap.yml`). This table is the single map of what's protected and how: | File | Controls | Protected by | @@ -173,7 +173,7 @@ client-side hook (cmaintz-skills), the CI guard (`tier0.yml`), and the regenerat | `eslint-suppressions.json` | lint baseline | same three | | `.jscpd.json` | which paths the duplication sensor scans | **ruleset-guard** — widening the ignore list alongside source needs the label | | `pmd/ruleset.xml`, thresholds, `mise.toml`, workflows | rule definitions / gate wiring | **ruleset-guard** — direction can't be proven, so any change alongside source needs the label | -| `ruleset_paths` regex | *which* files ruleset-guard treats as gate-defining (this table's first column) | the `tier0` input default, or a consumer's override — **keep it in sync when you add a guarded file** | +| `ruleset_paths` regex | *which* files ruleset-guard treats as gate-defining (this table's first column) | the `guards` input default, or a consumer's override — **keep it in sync when you add a guarded file** | The recurring failure mode: a *new* gate-defining file (like `.jscpd.json`) isn't added to `ruleset_paths`, so widening it slips past the guard. When you introduce @@ -250,7 +250,7 @@ work → habit-hooks flags a smell → agent fixes it → /learn decides: ## 10. Language coverage — where it actually is -Proven and built out: **TypeScript / Node** (`mise/ts.toml`, `ts.yml`, eslint + +Proven and built out: **TypeScript / Node** (`mise/ts.toml`, `_ts.yml`, eslint + tsc + vitest + habit-hooks-typescript). Reachable cheaply (habit-hooks already has detectors): **Java** (PMD), **PHP** @@ -318,10 +318,10 @@ radius* into separate workflow files, each composing foundry's reusable pieces: | File | Contains | Trigger | |---|---|---| -| `gate.yml` | the deterministic gate(s) — `ts.yml` / language gate per package | PR + push | +| `gate.yml` | the deterministic gate(s) — `_ts.yml` / language gate per package | PR + push | | `quality.yml` | structural smells (habit-hooks), ratchet checks | PR + push | -| `security.yml` | `tier0.yml` (secrets + ruleset-guard) + `semgrep.yml` | PR + push | -| `bootstrap.yml` | `bootstrap.yml` — baseline refresh | `workflow_dispatch` | +| `security.yml` | `_guards.yml` (secrets + ruleset-guard) + `_semgrep.yml` | PR + push | +| `bootstrap.yml` | `_bootstrap.yml` — baseline refresh | `workflow_dispatch` | | `deploy.yml` | image publish / release — **push-to-main only** | push | ```yaml @@ -329,10 +329,10 @@ radius* into separate workflow files, each composing foundry's reusable pieces: name: security on: { pull_request: {}, push: { branches: [main] } } jobs: - tier0: - uses: CMaintz/foundry/.github/workflows/tier0.yml@ + guards: + uses: CMaintz/foundry/.github/workflows/_guards.yml@ sast: - uses: CMaintz/foundry/.github/workflows/semgrep.yml@ + uses: CMaintz/foundry/.github/workflows/_semgrep.yml@ ``` Why split, not one file: `needs:` can't cross workflow files, so unrelated jobs diff --git a/README.md b/README.md index 3e0e7a2..8f0e291 100644 --- a/README.md +++ b/README.md @@ -48,20 +48,24 @@ Or wire it by hand — a `mise.toml` with the six verbs (see [templates/](./mise # .github/workflows/gate.yml jobs: gate: - uses: CMaintz/foundry/.github/workflows/java.yml@v1 + uses: CMaintz/foundry/.github/workflows/_java.yml@v1 ``` ### Reusable workflows +Reusable workflows are `_`-prefixed to group them in the file listing — GitHub requires +every workflow file at the top level of `.github/workflows/`, so a filename prefix is the +only grouping it allows. Consumers reference them by path (`uses: …/_java.yml@v1`). + | Workflow | What it runs | |---|---| -| `ts.yml` · `java.yml` · `php.yml` | the language gate (six verbs, decomposed one-per-step with targeted fix summaries) + structural smells (which print a per-smell "fix toward" legend on failure). `java` adds opt-in `spotbugs` / `no_var` jobs | -| `tier0.yml` | language-agnostic: secret scan + `ruleset-guard` | -| `semgrep.yml` | SAST, diff-aware (only new findings fail) | -| `web.yml` | max-file-length gate for HTML/CSS | -| `bootstrap.yml` | regenerate the habit-hooks snooze baseline on Linux, open a PR | -| `ratchet-report.yml` | PR comment showing how the accepted-debt baselines moved | -| `autofix.yml` | add an `autofix` label to a PR → runs `mise run fix`, commits + pushes the result | +| `_ts.yml` · `_java.yml` · `_php.yml` | the language gate (six verbs, decomposed one-per-step with targeted fix summaries) + structural smells (which print a per-smell "fix toward" legend on failure). `_java.yml` adds an opt-in `spotbugs` job (the no-`var` rule is folded into `lint`) | +| `_guards.yml` | language-agnostic: secret scan + `ruleset-guard` | +| `_semgrep.yml` | SAST, diff-aware (only new findings fail) | +| `_web.yml` | max-file-length gate for HTML/CSS | +| `_bootstrap.yml` | regenerate the habit-hooks snooze baseline on Linux, open a PR | +| `_ratchet-report.yml` | PR comment showing how the accepted-debt baselines moved | +| `_autofix.yml` | add an `autofix` label to a PR → runs `mise run fix`, commits + pushes the result | Split them across `gate.yml` / `quality.yml` / `security.yml` / `bootstrap.yml` (see [OVERVIEW.md](./OVERVIEW.md) §13). diff --git a/scripts/foundry-init.sh b/scripts/foundry-init.sh index 43a075f..a781d4d 100644 --- a/scripts/foundry-init.sh +++ b/scripts/foundry-init.sh @@ -22,9 +22,9 @@ RAW="https://raw.githubusercontent.com/$REPO/$REF" # HH = the habit-hooks preset basename (presets/habit-hooks/.toml). It tracks the # plugin/language name, so `ts` maps to `typescript` — the others match the stack. case "$STACK" in - ts) PLUGIN="habit-hooks-typescript"; CI="ts.yml"; HH="typescript" ;; - java) PLUGIN="habit-hooks-java"; CI="java.yml"; HH="java" ;; - php) PLUGIN="habit-hooks-php"; CI="php.yml"; HH="php" ;; + ts) PLUGIN="habit-hooks-typescript"; CI="_ts.yml"; HH="typescript" ;; + java) PLUGIN="habit-hooks-java"; CI="_java.yml"; HH="java" ;; + php) PLUGIN="habit-hooks-php"; CI="_php.yml"; HH="php" ;; kotlin|dotnet|python) PLUGIN=""; CI=""; HH="$STACK" ;; # mise template only; inline gate *) echo "unknown stack: $STACK" >&2; exit 2 ;; esac @@ -134,10 +134,10 @@ name: security on: { pull_request: {}, push: { branches: [main] } } concurrency: { group: security-\${{ github.ref }}, cancel-in-progress: true } jobs: - tier0: - uses: $REPO/.github/workflows/tier0.yml@$REF + guards: + uses: $REPO/.github/workflows/_guards.yml@$REF sast: - uses: $REPO/.github/workflows/semgrep.yml@$REF + uses: $REPO/.github/workflows/_semgrep.yml@$REF YAML write ".github/workflows/ratchet.yml" < Branches), require these checks: - Deterministic gate (gate.yml) - - Secret scan, Ruleset guard (security.yml / tier0) + - Secret scan, Ruleset guard (security.yml / _guards) - SAST (security.yml / semgrep) - Structural smells (once a baseline exists) Gotchas: required-check names must match the job name EXACTLY; do NOT require