Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "forge",
"version": "1.21.0",
"version": "1.23.1",
"description": "Automate Python CI/CD and code-quality standards — deterministic CLIs + a drop-in pre-commit hook, runnable with or without an AI agent. This optional Claude Code plugin orchestrates them; the gate is the CLI, never the model.",
"author": {
"name": "Jean Simonnet",
Expand Down
12 changes: 8 additions & 4 deletions .claude/skills/promote/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ description: Forge-only — open a dev→main promotion PR when a MINOR or MAJOR

# Promote dev → main (forge-only)

> **Spec:** [`docs/release-process.md`](../../../docs/release-process.md) is the
> source of truth for the versioning + promotion model and its invariant→test
> contract. This skill is the *operational* runbook for that model.

Opens a `dev → main` promotion PR after a MINOR (`Y+1, Z→0`) or
MAJOR (`X+1, Y→0, Z→0`) bump to `.claude-plugin/plugin.json` lands on
`dev`. PATCH-only bumps (`Z+1`) do NOT trigger promotion — `dev`
accumulates patches between minor releases per CLAUDE.md "plugin
manifest version is rolling-next."
accumulates patches between minor releases per
`docs/release-process.md` §1 (rolling-next).

**Scope: forge repo only.** Lives at
`.claude/skills/promote/SKILL.md` (project-local, not shipped via the
Expand Down Expand Up @@ -62,7 +66,7 @@ Promotion pending — promote these in order (2):

Skip entirely when it reports **"Up to date — nothing to promote"**
(`main`'s minor ≥ `dev`'s minor; patch differences accumulate on `dev`
between releases per CLAUDE.md "rolling-next"), or when a promotion PR
between releases per `docs/release-process.md` §1), or when a promotion PR
(base `main`) is already open (Step 2).

Set `$NEW` to the **first** (lowest) listed release and promote that one.
Expand Down Expand Up @@ -112,7 +116,7 @@ gh pr create --base main --head "release/v$NEW" \
$(git log --oneline origin/main..release/v$NEW)

## After merge
- [ ] Tag handling per CLAUDE.md \"Dual-track tag cadence\": \`/next\` (\`forge-next-prep --tag\`) already created \`v$NEW\` on the dev commit. Confirm the tag exists; follow that section for any main-side tagging.
- [ ] Tag handling per \`docs/release-process.md\` §2 (dual-track tag cadence): \`/next\` (\`forge-next-prep --tag\`) already created \`v$NEW\` on the dev commit. Confirm the tag exists; follow that section for any main-side tagging.
- [ ] If more minors remain behind, promote the next one (repeat from Step 1).
"
```
Expand Down
116 changes: 116 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Changelog

Notable changes to forge, by release on **`main`**.

forge's slow channel (`@main`) ships **minor releases only** — patches
accumulate on `dev` between minors and fold into the next minor's
promotion. Pin `@main` to track the entries below; pin `@dev` for every
patch. Each entry corresponds to one `dev → main` promotion.

**Reading this as a forge consumer.** You're usually jumping several
minors at once: read every entry newer than your current version, top to
bottom, and read each **⚠️ Upgrade notes** lane first — that's the
actions your repo may need (breaking changes, config, new mandatory
behavior). Releases without that lane are additive or internal and need
nothing from you.

**Format.** Per release: an optional **⚠️ Upgrade notes** lane, then
change groups by conventional-commit type (**Features / Fixes / Refactor
/ Tooling / Docs / Chore**) mirroring the promotion squash message.
Follows [Keep a Changelog](https://keepachangelog.com/) in spirit;
versions follow forge's rolling-next convention.

## v1.21.0 — 2026-06-12

### Features
- Require a `Requires:` line atop every issue (FOUNDATION convention).

### Refactor / Tooling
- Promotion model: a dedicated `release/vX.Y.Z` branch is now required
(never a direct `dev → main` merge), with staged catch-up one minor at
a time, surfaced by the new read-only `forge-next-prep
--promotion-status` CLI.
- Remove dead `tomllib` import guards now that the Python floor is 3.11.

## v1.20.0 — 2026-06-12

### ⚠️ Upgrade notes
- **Python floor raised to 3.11.** `forge-scripts` no longer installs on
Python 3.10 (it uses `datetime.UTC` / `tomllib`, both 3.11+ stdlib).
Move your repo and CI to Python ≥ 3.11 before upgrading forge.
- **Slow-tests CI recipe changed.** If you adopt the slow-tests report,
pass `--durations` explicitly on the pytest command —
`pytest --durations=25 --durations-min=1.0 | tee code_health/pytest.log`.
A bare `pytest` yields an empty report: the durations flags live in
forge's *own* `pyproject.toml`, not yours.

### Features
- `forge-slow-tests-report` CLI: parses pytest `--durations`, merges
across batches, and ranks the slowest tests — a read-only reporter for
CI and local runs (#29).
- Raise the Python floor to 3.11 — `requires-python >= 3.11`, ruff target
`py311` (#29).

### Tests / Docs
- Test-doc audit fixes; document the dev tag cadence; the CI recipe now
passes `--durations` explicitly so the slow-tests report works in any
consumer repo regardless of its pytest config (#27, #29).

## v1.19.0 — 2026-06-12

### Features
- Consumer hook-extension directories — `post-merge.d` / `post-checkout.d`
run consumer `*.sh` scripts after the managed hook (sorted,
failure-tolerant, interactive-only, and surviving hook refresh).
Additive and opt-in; drop scripts in those dirs to use it.

## v1.18.0 — 2026-06-12

### ⚠️ Upgrade notes
- **New `block_branch_deletion` hook.** Claude Code agents can no longer
delete a protected remote branch (`base_branch` / `dev_branch`). No
action unless you relied on an agent doing that — run the delete
yourself with `! …` instead.

### Features
- `block_branch_deletion` hook — blocks agents from deleting protected
remote branches.

## v1.17.0 — 2026-06-12

### ⚠️ Upgrade notes
- **Hook-version sidecar.** Managed git hooks now read their version from
a per-clone `.githooks/.forge-hook-version` file (keeps tracked
`.githooks/*` byte-stable across bumps). Add `.githooks/.forge-hook-version`
to your `.gitignore` — the installer does not write the ignore rule for
you.
- **Two new foundation agents** — `forge:test-advisor` + `forge:test-writer`
become available after `/plugin update forge@forge` + `/reload-plugins`.

### Features
- Add the `forge:test-advisor` + `forge:test-writer` foundation agents
and the testing-documentation policy they enforce (fixtures excluded
from `Args`, structured mock docs, Null-Objects-over-Mock; interrogate
`ignore-nested-functions` + ruff `D417` in tests) — 12 foundation
agents total.
- Per-clone conda env name via `.conda_env_name`, so parallel forge
clones each get their own environment (opt-in: drop a `.conda_env_name`
file at the repo root).

### Fixes
- `forge-post-merge` now accepts git's squash-flag positional argument
(it had been exiting 2 on every merge, killing the drift check and the
hook self-refresh).
- Store the git-hook version in a gitignored sidecar so tracked
`.githooks/*` stay byte-stable across version bumps.

### Docs / Chore
- Complete the README CLI and pre-commit reference tables.
- Share forge-standard CI permissions; allow `-D` for merged branches.

## v1.16.1 — 2026-06-11

### Chore
- Initial published artifacts: git hooks, `docs/api-digest.md`, and
`docs/cli-reference.md` generated at forge 1.16.1; README refreshed
around the guardrails thesis.
13 changes: 6 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,12 @@
## Forge-specific rules

- **Version derivation**: pip package version comes from the latest git tag via setuptools-scm. There is no manual `version = "x.y.z"` in `pyproject.toml`. Release flow: `git tag vX.Y.Z && git push origin vX.Y.Z`.
- **Plugin manifest version is rolling-next**: `.claude-plugin/plugin.json["version"]` always names the version about to be released. `step_plugin_version` in `forge-precommit` enforces `plugin.json > latest_tag` on every commit (skipped on the release commit itself). Workflow:
1. On the release PR, set `plugin.json["version"]` to the version about to be tagged (e.g. `"1.1.2"`).
2. Merge the PR, then `git tag vX.Y.Z` at the merge commit. The guard skips that single commit (`HEAD` == tag commit).
3. Immediately after tagging, the next PR must bump `plugin.json` to `"X.Y.(Z+1)"` (or a higher minor/major) — otherwise commits fail the guard.
- **Dual-track tag cadence** (disambiguates "the merge commit" in step 2 above for forge's `base_branch=main` / `dev_branch=dev` model): **tag `dev` `vX.Y.Z` after *every* merge to dev.** This is the dev-channel release and is mandatory — `forge-next-prep --tag` (run by `/next` Phase 1) does it automatically when `plugin.json` is ahead of the latest tag, so **never drop `--tag`**. `main` is advanced and tagged **only at the minor-boundary promotion** (`/promote`), where the minor `vX.Y.0` is (re-)tagged on main's squash commit. Net: `@dev` consumers receive every version (patch + minor); `@main` receives minors only. **Skipping a promotion does NOT mean skipping the dev tag** — the per-merge dev tag and the dev→main promotion are independent steps. (Caveat: because promotion squashes, a minor re-tagged onto main's squash commit is not reachable from dev's history; that is expected — `@dev` resolves the tag on the dev commit, `@main` resolves it on the main commit.)
- **`/promote` skill (forge-only)**: lives at `.claude/skills/promote/SKILL.md` — project-local, **not** shipped via the forge plugin. After merging a PR to `dev` that bumped `plugin.json` past a minor boundary (MINOR or MAJOR), invoke `/promote` to open the `dev → main` promotion PR with a release-summary squash message. Forge-private because the dev/main two-branch model + rolling-next convention are specific to forge; consumer plugin authors may follow trunk-based, gitflow, or other release models.
- **Semver policy for the plugin bump** — when the next-PR bump in step 3 above lands, choose the increment deliberately, not reflexively. The plugin's public surface is: every CLI in `[project.scripts]`, every agent name + canonical Output shape under `agents/`, every Claude Code hook under `claude-hooks/`, every skill under `skills/`, every FOUNDATION rule a consumer can rely on.
- **Release process — versioning, tag cadence, and `dev → main` promotion** are specified in **[`docs/release-process.md`](docs/release-process.md)**, the single source of truth + the **invariant→test contract**. Do not restate its mechanics elsewhere; point here. Operational summary:
- `.claude-plugin/plugin.json["version"]` is **rolling-next** — always names the version about to be released; `step_plugin_version` enforces `plugin.json > latest_tag` on every commit (skipped when `HEAD` reproduces a tagged release's tree). After tagging `vX.Y.Z`, the next PR must bump it.
- Tag `dev` `vX.Y.Z` after **every** merge to dev (`forge-next-prep --tag`, run by `/next` — **never drop `--tag`**); tag `main` **only** at the minor-boundary promotion. `@dev` gets every version, `@main` minors only.
- Promote `dev → main` **one minor at a time** via a `release/vX.Y.Z` branch (never `--head dev`) — the **`/promote`** skill (`.claude/skills/promote/SKILL.md`, forge-only, not shipped via the plugin).
- **Every versioning/promotion invariant names an enforcing test in the spec.** Do NOT change that code without the matching spec test staying green — e.g. the guard skips on *any* tagged-release tree (not just the latest), required for staged catch-up, locked by `test_main_skips_when_head_reproduces_older_tag`. This contract exists because #43 silently broke that behavior with no test to catch it.
- **Semver policy for the plugin bump** — when the post-tag rolling-next bump PR lands (see [`docs/release-process.md`](docs/release-process.md) §1), choose the increment deliberately, not reflexively. The plugin's public surface is: every CLI in `[project.scripts]`, every agent name + canonical Output shape under `agents/`, every Claude Code hook under `claude-hooks/`, every skill under `skills/`, every FOUNDATION rule a consumer can rely on.
- **PATCH (Z+1)** — internal-only changes: bug fix in an existing CLI / agent / hook with no behavior change visible to consumers; refactor with identical externals; doc typo fix; non-blocking new audit check; CLAUDE.md edits that affect only forge contributors.
- **MINOR (Y+1, Z→0)** — additive, backward-compatible: new CLI, new agent, new hook, new skill, new mandatory rule in FOUNDATION or `_TEMPLATE.md` that does NOT break existing consumer artifacts (e.g. a new reporter-agent header that older agents simply lack — audit flags it but does not refuse a commit), new mode/flag on an existing CLI or agent, new pre-commit step that self-skips when not opted in.
- **MAJOR (X+1, Y→0, Z→0)** — breaking: CLI renamed / removed / argument-incompatible; agent renamed / removed / Output shape changed in a way that breaks existing parsers (e.g. `pr-manager`); hook semantics inverted (allow → block); FOUNDATION rule promoted from soft to blocking; manifest layout change. Any consumer upgrade that requires action beyond `forge-upgrade` is MAJOR.
Expand Down
32 changes: 27 additions & 5 deletions FOUNDATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,11 @@ cheaper than reverting.
divergence first: `git fetch origin && git log origin/<branch>`.
- **NEVER add Claude/AI attribution** in commits, PRs, or merge messages
(no `Co-Authored-By`, no `Generated with Claude`, no AI references).
- **NEVER push directly to `main`**. Always create a feature branch first.
The `block_protected_branches` hook enforces this for agents.
- **NEVER push directly to a protected branch** (`base_branch` /
`dev_branch`, default `main` / `dev`). Always create a feature branch
first. The `block_protected_branches` hook enforces this for agents —
defaulting to the same `main` + `dev` set as the sibling
`block_branch_deletion` hook.
- **NEVER merge PRs autonomously.** Merging is the user's decision — produce
the squash-merge message and wrap-up comment, then stop. The
`block_pr_merge` hook enforces this for agents (blocks `gh pr merge` and
Expand Down Expand Up @@ -481,9 +484,28 @@ layer would be redundant with ruff.
every standard interrogate key (threshold, `exclude`, `ignore-*`
flags). The foundation default threshold is `fail-under = 90`;
tighten per §4 ("threshold = current passing baseline. Raise over
time."). Opt into badge generation with
`[tool.forge.docstring_coverage] badge = true` — writes
`.badges/DocstringCoverage.svg` for README embedding.
time.").

Forge reads interrogate's **native** section directly and does **not**
wrap it: re-exposing a third-party tool's whole config surface under a
forge namespace (plus a key-name mapping to maintain) is a needless
wrapper — the tool's own section is the right home, exactly as forge
reads `ruff.toml` rather than copying it. Only keys interrogate has no
concept of live under `[tool.forge.docstring_coverage]`: `badge = true`
(writes `.badges/DocstringCoverage.svg`) and `paths` (a per-tool scan-root
override that otherwise defaults to the repo-wide layout
`[tool.forge].source_dirs + test_dirs`). **Project layout** is itself a
`[tool.forge]` single-ground-truth: `source_dirs` (default `["src"]`) and
`test_dirs` (default `["tests"]`) — split source-vs-test so a source-only
tool doesn't pull test dirs in — so every layout-aware tool reads the
repo's roots from one place. **Config-home rule:** a forge tool that
wraps a third-party library reads the library's native config section
directly; only forge-specific keys are namespaced under
`[tool.forge.<tool>]`. `forge-config --list` enumerates every
`[tool.forge.*]` key forge reads and names the native sections (like
`[tool.interrogate]`) it reads too — so the config surface is
discoverable without doc-hunting, and `install-forge-bootstrap` surfaces
it as a post-install nudge.

### Testing documentation standards

Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@ edit `.githooks/pre-commit` directly. No plugin system, no config file.

| Category | Items |
|---|---|
| **CLIs** (pip package, no Claude required) | `install-forge-bootstrap` (one-shot umbrella), `forge-upgrade` (two-phase upgrade flow), `forge-precommit` (full sequence dispatcher), `fix-forge-ruff` (ruff phase), `verify-forge-docstrings`, `verify-forge-docstring-coverage`, `verify-forge-repo-structure`, `verify-forge-test-naming`, `verify-forge-manifest`, `verify-forge-plugin-version`, `verify-forge-cli-wiring`, `forge-continuation-append`, `forge-next-prep`, `install-forge-labels`, `forge-doctor`, `install-forge-githooks`, `install-forge-claude-md` |
| **CLIs** (pip package, no Claude required) | `install-forge-bootstrap` (one-shot umbrella), `forge-upgrade` (two-phase upgrade flow), `forge-precommit` (full sequence dispatcher), `fix-forge-ruff` (ruff phase), `verify-forge-docstrings`, `verify-forge-docstring-coverage`, `verify-forge-repo-structure`, `verify-forge-test-naming`, `verify-forge-manifest`, `verify-forge-plugin-version`, `verify-forge-cli-wiring`, `forge-continuation-append`, `forge-next-prep`, `forge-config` (config reference + setup advisor — see [`docs/configuration.md`](docs/configuration.md)), `install-forge-labels`, `forge-doctor`, `install-forge-githooks`, `install-forge-claude-md` |
| **Audit-pack CLIs** (pip package, optional `[audit]` extras) | `forge-audit-dup`, `forge-audit-deps`, `forge-audit-suppressions`, `forge-audit-orphans`, `forge-audit-data`, `forge-audit-claims`, `forge-audit-agents` (non-blocking template-conformance audit), `forge-audit-all` — see [`docs/audit-pack.md`](docs/audit-pack.md) |
| **Git hooks** (drop-in, no Claude required) | `.githooks/pre-commit` (dispatcher), `.githooks/post-merge` + `.githooks/post-checkout` (auto-warn on FOUNDATION.md drift) |
| **Process docs** | `docs/security.md`, `docs/audit-pack.md`, `docs/cli-reference.md` (generated CLI reference), `docs/api-digest.md` (generated index of all top-level functions/classes, public API + internal helpers); foundation engineering principles at `FOUNDATION.md` |
| **Claude Code plugin** (optional) | Agents (`pr-manager`, `precommit-fixer`, `git-commit-push`, `design-checker`, `docs-types-checker`, `security-checker`, `issue-triage`, `perf-optimizer`, `weekly-summary`, `knowledge-search`, `test-advisor`, `test-writer`); skills (`commit`, `pr`, `next`, `triage`, `weekly`, `fix`, `review`); Claude Code hooks (`block_protected_branches`, `block_force_push`, `block_pr_merge`, `block_branch_deletion`, `block_no_verify`, `block_install_deps`, `block_claude_attribution`, `block_continuation_delete`, `block_protected_files`, `check_commit_format`, `check_foundation_sync`, `warn_pr_checks`, `block_raw_ruff`, `block_raw_git`) |
| **Claude Code plugin** (optional) | Agents (`pr-manager`, `precommit-fixer`, `git-commit-push`, `design-checker`, `docs-types-checker`, `security-checker`, `issue-triage`, `perf-optimizer`, `weekly-summary`, `knowledge-search`, `test-advisor`, `test-writer`); skills (`commit`, `pr`, `next`, `triage`, `weekly`, `fix`, `review`, `test`); Claude Code hooks (`block_protected_branches`, `block_force_push`, `block_pr_merge`, `block_branch_deletion`, `block_no_verify`, `block_install_deps`, `block_claude_attribution`, `block_continuation_delete`, `block_protected_files`, `check_commit_format`, `check_foundation_sync`, `warn_pr_checks`, `block_raw_ruff`, `block_raw_git`) |

Everything in the first three rows is **Claude-independent** — works
from any shell, CI, or IDE.
Expand Down Expand Up @@ -164,10 +164,11 @@ order. Idempotent — re-run safely after every forge upgrade.
| 5 | `forge-gen-cli-reference` | `docs/cli-reference.md` (generated from each CLI's `--help`) |
| 6 | `forge-audit-deps --tree` | `code_health/audit_deps_tree.log` (dependency tree) |
| 7 | `forge-doctor` | Verifies the install |
| 8 | `forge-config` | Post-install nudge: what `[tool.forge.*]` config forge reads + what to set ([`docs/configuration.md`](docs/configuration.md)) |

Flags: `--check` (dry-run), `--skip <slug>` (repeatable; slugs are
`githooks`, `claude-md`, `labels`, `api-digest`, `cli-reference`,
`audit-deps`, `doctor`), `--strict` (abort on first failure; default is
`audit-deps`, `doctor`, `config`), `--strict` (abort on first failure; default is
continue-on-fail).

Want to run an installer on its own? Each step is also a standalone CLI
Expand Down
Loading
Loading