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
69 changes: 66 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,75 @@
# Stop after the first failing hook so a broken Block 1 never runs Block 2 (code review + contract tests).
# Layout and stages mirror specfact-cli-modules; CLI adds Markdown + workflow lint + version hook.
fail_fast: true

repos:
- repo: local
hooks:
- id: specfact-smart-checks
name: SpecFact smart pre-commit checks
entry: scripts/pre-commit-smart-checks.sh
- id: verify-module-signatures
name: Verify module signatures (branch-aware; skip if no staged module tree changes)
entry: scripts/pre-commit-verify-modules.sh
language: script
pass_filenames: false
always_run: true

- id: check-version-sources
name: Check synchronized version sources
entry: hatch run check-version-sources
language: system
files: ^(pyproject\.toml|setup\.py|src/__init__\.py|src/specfact_cli/__init__\.py)$
pass_filenames: false

- id: cli-block1-format
name: "CLI Block 1 — format"
entry: ./scripts/pre-commit-quality-checks.sh block1-format
language: system
pass_filenames: false
always_run: true
verbose: true

- id: cli-block1-yaml
name: "CLI Block 1 — yaml-lint (when YAML staged)"
entry: ./scripts/pre-commit-quality-checks.sh block1-yaml
language: system
files: \.(yaml|yml)$
verbose: true

- id: cli-block1-markdown-fix
name: "CLI Block 1 — markdown auto-fix (when Markdown staged)"
entry: ./scripts/pre-commit-quality-checks.sh block1-markdown-fix
language: system
files: \.md$
verbose: true

- id: cli-block1-markdown-lint
name: "CLI Block 1 — markdownlint (when Markdown staged)"
entry: ./scripts/pre-commit-quality-checks.sh block1-markdown-lint
language: system
files: \.md$
verbose: true

- id: cli-block1-workflows
name: "CLI Block 1 — workflow lint (when workflows staged)"
entry: ./scripts/pre-commit-quality-checks.sh block1-workflows
language: system
files: ^\.github/workflows/.*\.(yaml|yml)$
verbose: true

- id: cli-block1-lint
name: "CLI Block 1 — lint (when Python staged)"
entry: ./scripts/pre-commit-quality-checks.sh block1-lint
language: system
files: \.(py|pyi)$
verbose: true

- id: cli-block2
name: "CLI Block 2 — code review + contract tests"
entry: ./scripts/pre-commit-quality-checks.sh block2
language: system
pass_filenames: false
always_run: true
verbose: true

- id: check-doc-frontmatter
name: Check documentation ownership frontmatter (enforced paths)
entry: hatch run doc-frontmatter-check
Expand Down
44 changes: 44 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,50 @@ All notable changes to this project will be documented in this file.

---

## [0.46.1] - 2026-04-14

### Added

- **`scripts/pre-commit-quality-checks.sh`**: modular Block 1/2 entrypoints (`block1-*`, `block2`, `all`) with
staged-file gates and Markdown auto-fix before lint (parity with `specfact-cli-modules` hook layout and
`fail_fast` behavior in `.pre-commit-config.yaml`).
- **`scripts/pre-commit-smart-checks.sh`**: back-compat shim that resolves the repository root (so copies under
`.git/hooks/pre-commit` still run the canonical quality script) and delegates to
`pre-commit-quality-checks.sh all`.

### Fixed

- **Pre-commit robustness**: `pre-commit-verify-modules.sh` fails closed on unexpected `sig_policy` output and on
`git diff --cached` errors; `pre-commit-quality-checks.sh` documents suppressed `contract-test-status` output,
deduplicates the contract-first script existence check, and treats `git diff` exit codes greater than 1 as errors in
`run_format_safety` (exit 1 means “has diff”, not failure); script tests use a fake `hatch`, tighter timeouts,
skip-path and `git diff --cached` failure coverage.
- **Legacy module verify path**: `scripts/pre-commit-verify-modules-signature.sh` is a small delegating shim to
`pre-commit-verify-modules.sh` for downstream hooks and mirrors; `run_module_signature_verification` prefers the
canonical script and falls back to the legacy path when only that file exists.
- **Pre-commit quality script**: staged Markdown detection includes `*.mdc`; Block 2 “safe change” no longer skips
review or contract tests for `pyproject.toml` / `setup.py` alone; markdown file lists avoid Bash 4 `mapfile` for
macOS Bash 3.2 compatibility.

### Changed

- **Module verify (pre-commit)**: branch-aware policy via `scripts/pre-commit-verify-modules.sh` and
`scripts/git-branch-module-signature-flag.sh` — on `main`, run `verify-modules-signature.py` with
`--require-signature`; on other branches (including detached `HEAD`), omit that flag so the verifier stays in
checksum-only mode (there is no `--allow-unsigned` CLI). Skips when no staged paths under `modules/` or
`src/specfact_cli/modules/`; when the check runs it always passes `--payload-from-filesystem` and
`--enforce-version-bump`.
Comment thread
djm81 marked this conversation as resolved.
- **`scripts/pre-commit-quality-checks.sh`**: staged file enumeration uses
`git diff --cached --diff-filter=ACMR` (no deleted paths), stricter `set -euo pipefail`, portable Markdown
invocation (no GNU `xargs -r`), and safe iteration for “safe change” detection and version-source checks;
pre-commit wrapper scripts are not exempt from Block 2 when staged.
- **Docs / OpenSpec**: `docs/reference/module-security.md`, `docs/guides/module-signing-and-key-rotation.md`,
`docs/guides/publishing-modules.md`, and `docs/agent-rules/50-quality-gates-and-review.md` now describe
branch-aware verify vs strict `--require-signature`, and clarify that `--allow-unsigned` applies to
`sign-modules.py` only; `openspec/changes/marketplace-06-ci-module-signing/` artifacts updated to match.

Comment thread
djm81 marked this conversation as resolved.
---

## [0.46.0] - 2026-04-13

### Added
Expand Down
16 changes: 14 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,23 @@ hatch test --cover -v

### Pre-commit Checks

Local hooks use **`fail_fast: true`** and a **modular layout** aligned with `specfact-cli-modules`:
branch-aware module verify (skip if no staged module tree changes; on `main` pass `--require-signature`
to `verify-modules-signature.py`, elsewhere omit it for checksum-only mode) → sync version files when those paths are staged → format (always) →
YAML / Markdown / workflow lint when matching paths are staged → **`hatch run lint`** when Python
is staged → Block 2 (scoped code review + contract tests, with a safe-change short-circuit for
docs-only and similar commits). See `.pre-commit-config.yaml` and `scripts/pre-commit-quality-checks.sh`.

```bash
# Install repo hooks
# Install framework hooks (recommended; matches CI-style stages)
pre-commit install

# Optional: copy raw script into .git/hooks/pre-commit (runs full `all` pipeline via shim)
scripts/setup-git-hooks.sh

# Manual full pipeline (same as shim)
hatch run pre-commit-checks

# Format code
hatch run format

Expand All @@ -99,7 +111,7 @@ hatch run contract-test-full
The supported local hook path is the repo-owned smart-check wrapper installed by the commands
above. It keeps local semantics aligned with CI:

- Merge-blocking local gates: module signature verification, formatter safety, Markdown/YAML checks,
- Merge-blocking local gates: module signature verification (branch-aware; see `scripts/pre-commit-verify-modules.sh`), formatter safety, Markdown/YAML checks,
workflow lint for staged workflow changes, and contract-test fast feedback when code changes.
- Review gate behavior: `specfact code review run` reviews staged Python files and blocks the
commit only on `FAIL`. `PASS_WITH_ADVISORY` remains green but still prints the JSON report path for
Expand Down
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ uvx specfact-cli code review run --path . --scope full
**Sample output:**

```text
SpecFact CLI - v0.46.0
SpecFact CLI - v0.46.1

Running Ruff checks...
Running Radon complexity checks...
Expand Down Expand Up @@ -80,16 +80,24 @@ It exists because delivery drifts in predictable ways:

## Add SpecFact to your workflow

**Pre-commit hook**
### Pre-commit

This repository uses a **modular** local hook layout (parity with `specfact-cli-modules`: `fail_fast`,
separate verify / format / YAML / Markdown / workflow / lint / Block 2 hooks). Copy
[`.pre-commit-config.yaml`](.pre-commit-config.yaml) from this repo and run `pre-commit install`.

For a **single-hook** setup in downstream repos, keep using the stable id and script shim:

```yaml
- repo: https://github.com/nold-ai/specfact-cli
rev: v0.46.0
rev: v0.46.1
hooks:
- id: specfact-smart-checks
```

**GitHub Actions**
The shim runs `scripts/pre-commit-quality-checks.sh all` (full pipeline including module verify).

### GitHub Actions

```yaml
- name: SpecFact Gate
Expand Down
17 changes: 13 additions & 4 deletions docs/agent-rules/50-quality-gates-and-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ tracks:
- scripts/pre_commit_code_review.py
- scripts/verify-modules-signature.py
- docs/agent-rules/**
last_reviewed: 2026-04-10
last_reviewed: 2026-04-14
exempt: false
exempt_reason: ""
id: agent-rules-quality-gates-and-review
Expand Down Expand Up @@ -59,10 +59,19 @@ The repository enforces the clean-code charter through `specfact code review run

## Module signature gate

Before PR creation, every change that affects signed module assets or manifests must pass:
Every change that affects signed module assets or bundled manifests must satisfy verification **before
the change reaches `main`**.

- **Local / feature branches**: pre-commit may run `verify-modules-signature.py` **without**
`--require-signature` (checksum-only) when only `dev` or a feature branch is checked out — see
`scripts/pre-commit-verify-modules.sh` and `scripts/git-branch-module-signature-flag.sh`.
- **Before merging to `main` or when validating release readiness**, run strict verification:

```bash
hatch run ./scripts/verify-modules-signature.py --require-signature
hatch run ./scripts/verify-modules-signature.py --require-signature --enforce-version-bump
```

If verification fails because module contents changed, re-sign the affected manifests and bump the module version before re-running verification.
If verification fails because module contents changed, re-sign the affected manifests and bump the
module version before re-running verification. Note: `verify-modules-signature.py` has **no**
`--allow-unsigned` flag; checksum-only mode is “omit `--require-signature`”. The `--allow-unsigned`
option on **`sign-modules.py`** is only for local test signing.
14 changes: 7 additions & 7 deletions docs/agent-rules/70-release-commit-and-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,33 +35,33 @@ depends_on:
- agent-rules-quality-gates-and-review
---

# Agent release, commit, and docs rules
## Agent release, commit, and docs rules

## Versioning
### Versioning

- Keep version updates in sync across `pyproject.toml`, `setup.py`, and `src/specfact_cli/__init__.py`.
- **Automated check:** Before tagging or publishing, run `hatch run check-version-sources` (or `python scripts/check_version_sources.py`). It exits non-zero with a clear diff if `pyproject.toml`, `setup.py`, `src/__init__.py`, and `src/specfact_cli/__init__.py` disagree. The **Tests** job in `.github/workflows/pr-orchestrator.yml` runs the same script so mismatches fail CI. Pre-commit runs it whenever a version file is staged (see `scripts/pre-commit-smart-checks.sh`) instead of treating version-only commits as “safe” without verification.
- **Automated check:** Before tagging or publishing, run `hatch run check-version-sources` (or `python scripts/check_version_sources.py`). It exits non-zero with a clear diff if `pyproject.toml`, `setup.py`, `src/__init__.py`, and `src/specfact_cli/__init__.py` disagree. The **Tests** job in `.github/workflows/pr-orchestrator.yml` runs the same script so mismatches fail CI. Pre-commit runs it whenever a version file is staged (see the `check-version-sources` hook in `.pre-commit-config.yaml`) instead of treating version-only commits as “safe” without verification.
- `hatch run release` is reserved for maintainers to chain `check-version-sources` before manual release steps; extend that script if you add more release automation.
- `feature/*` branches imply a minor bump, `bugfix/*` and `hotfix/*` imply a patch bump, and major bumps require explicit confirmation.

## Changelog
### Changelog

- Update `CHANGELOG.md` in the same commit as the version bump.
- Follow Keep a Changelog sections: `Added`, `Changed`, `Fixed`, `Removed`, `Security`.

## Commits
### Commits

- Use Conventional Commits.
- If signed commits fail in a non-interactive shell, stage files and hand the exact `git commit -S -m "<message>"` command to the user instead of bypassing signing.

## Documentation and README
### Documentation and README

- Keep docs current with every user-facing behavior change.
- Preserve all Jekyll frontmatter on docs edits.
- Update navigation when adding or moving pages.
- Keep `README.md` and the docs landing page aligned with what SpecFact actually does.

## Internal wiki (sibling `specfact-cli-internal`)
### Internal wiki (sibling `specfact-cli-internal`)

After **merging** changes that affect OpenSpec or GitHub-linked planning, and when a sibling `specfact-cli-internal` checkout is available, run the wiki scripts only after **`cd` into that internal repo** so the working directory matches what the scripts expect (running from `specfact-cli` or elsewhere will break them). From this repo’s root, for example:

Expand Down
29 changes: 25 additions & 4 deletions docs/guides/module-signing-and-key-rotation.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,35 @@ With explicit public key file:
python scripts/verify-modules-signature.py --require-signature --public-key-file resources/keys/module-signing-public.pem
```

Checksum and version discipline without requiring signatures (same tool; omit the flag):

```bash
hatch run python scripts/verify-modules-signature.py --enforce-version-bump --payload-from-filesystem
```

Do not pass `--allow-unsigned` to `verify-modules-signature.py` — it is not a supported argument there.
Use `python scripts/sign-modules.py --allow-unsigned …` only when you intentionally want checksum-only
**signing** for local tests.

## Pre-commit (bundled modules in this repository)

If you use `pre-commit` or `scripts/setup-git-hooks.sh`, commits that stage changes under `modules/` or
`src/specfact_cli/modules/` run `scripts/pre-commit-verify-modules.sh`. That script adds
`--require-signature` only when the current branch is `main`; on other branches (including detached
`HEAD`) it runs checksum-only verification so commits do not require a local private key.

## CI Enforcement

`pr-orchestrator.yml` contains a strict gate:
`pr-orchestrator.yml` runs job `verify-module-signatures` with a **branch-aware** policy:

- Job: `verify-module-signatures`
- Command: `python scripts/verify-modules-signature.py --require-signature`
- PRs and pushes targeting **`main`**: `verify-modules-signature.py` is invoked **with**
`--require-signature` (plus `--enforce-version-bump --payload-from-filesystem` and PR base comparison
as configured in the workflow).
- PRs and pushes targeting **`dev`**: the same script runs **without** `--require-signature`
(checksum-only), matching local feature-branch development.

This runs on PR/push for `dev` and `main` and fails the pipeline if module signatures/checksums are missing or stale.
The pipeline fails if checksums or version-bump rules are violated, or if `main`-targeting events lack
valid signatures when required.

## Rotation Procedure

Expand Down
5 changes: 4 additions & 1 deletion docs/guides/publishing-modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,10 @@ metadata.

- Bump module `version` in `module-package.yaml` whenever payload or manifest content changes; keep versions immutable for published artifacts.
- Use `namespace/name` for any module you publish to a registry.
- Run `scripts/verify-modules-signature.py --require-signature` (or your registry’s policy) before releasing.
- Before releasing from a protected branch, run strict verification, e.g.
`scripts/verify-modules-signature.py --require-signature` (checksum-only is the default when that
flag is omitted — see [Module signing and key rotation](module-signing-and-key-rotation.md)). Follow
your registry’s policy if stricter.
- Prefer `--download-base-url` and `--index-fragment` when integrating with a custom registry index.

## See also
Expand Down
32 changes: 12 additions & 20 deletions docs/modules/code-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,26 +102,18 @@ The scaffolded `ReviewReport` envelope carries these fields:

## Pre-Commit Review Gate

This repository wires `specfact code review run` into the smart pre-commit wrapper before a commit
is considered green.

The supported local hook entry lives in `.pre-commit-config.yaml`:

```yaml
repos:
- repo: local
hooks:
- id: specfact-smart-checks
name: SpecFact smart pre-commit checks
entry: scripts/pre-commit-smart-checks.sh
language: script
pass_filenames: false
always_run: true
```

The wrapper calls `scripts/pre_commit_code_review.py` only when staged Python files are present,
alongside the repo's other local required gates (module signatures, formatter safety, Markdown/YAML
checks, workflow lint when relevant, and contract-test fast feedback). The review helper itself
This repository wires `specfact code review run` into **Block 2** of the modular pre-commit pipeline
(`scripts/pre-commit-quality-checks.sh block2`), configured in `.pre-commit-config.yaml` alongside
hooks that mirror `specfact-cli-modules` (module verify, format, staged YAML/Markdown/workflow checks,
`hatch run lint` when Python is staged, then code review + contract tests).

Downstream copies can either use the full modular config from this repo or a single hook
`specfact-smart-checks` pointing at `scripts/pre-commit-smart-checks.sh` (shim → `pre-commit-quality-checks.sh all`).

Block 2 calls `scripts/pre_commit_code_review.py` with staged paths under `src/`, `scripts/`,
`tools/`, `tests/`, and `openspec/changes/` (non-Python paths are filtered inside the helper),
after Block 1 gates (module signatures, formatter safety, Markdown/YAML/workflow checks, and full
`hatch run lint` when `.py` is staged). The review helper itself
then runs:

```bash
Expand Down
15 changes: 12 additions & 3 deletions docs/reference/module-security.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,18 @@ Module packages carry **publisher** and **integrity** metadata so installation,
- **CI secrets**:
- `SPECFACT_MODULE_PRIVATE_SIGN_KEY`
- `SPECFACT_MODULE_PRIVATE_SIGN_KEY_PASSPHRASE`
- **Verification command**:
- `scripts/verify-modules-signature.py --require-signature --enforce-version-bump`
- `--version-check-base <git-ref>` can be used in CI PR comparisons.
- **Verification command** (`verify-modules-signature.py`):
- **Strict** (signatures required): `--require-signature --enforce-version-bump` (and optional
`--payload-from-filesystem`, `--version-check-base <git-ref>` in CI).
- **Checksum-only** (default when `--require-signature` is omitted): still enforces payload
checksums and, with `--enforce-version-bump`, version discipline — useful on feature branches and
for dev-targeting CI without local signing keys.
- There is **no** `--allow-unsigned` on this verifier; that flag exists on **`sign-modules.py`**
for explicit test-only signing without a key.
- **Pre-commit** (this repo): when staged paths exist under `modules/` or `src/specfact_cli/modules/`,
`scripts/pre-commit-verify-modules.sh` runs the verifier with `--enforce-version-bump` and
`--payload-from-filesystem`, adding `--require-signature` only on `main` (see
`scripts/git-branch-module-signature-flag.sh`).

## Public key and key rotation

Expand Down
Loading
Loading