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
1 change: 1 addition & 0 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ reviews:
# PRs targeting `dev` (not only the GitHub default branch, e.g. `main`) get automatic reviews.
base_branches:
- "^dev$"
- "^main$"
path_instructions:
- path: "src/specfact_cli/**/*.py"
instructions: |
Expand Down
111 changes: 89 additions & 22 deletions .github/workflows/pr-orchestrator.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,8 @@ name: PR Orchestrator - SpecFact CLI
on:
pull_request:
branches: [main, dev]
paths-ignore:
- "**/*.md"
- "**/*.mdc"
- "docs/**"
push:
branches: [main, dev]
paths-ignore:
- "**/*.md"
- "**/*.mdc"
- "docs/**"
workflow_dispatch:

concurrency:
Expand All @@ -33,6 +25,7 @@ jobs:
runs-on: ubuntu-latest
outputs:
code_changed: ${{ steps.out.outputs.code_changed }}
workflow_changed: ${{ steps.out.outputs.workflow_changed }}
skip_tests_dev_to_main: ${{ steps.out.outputs.skip_tests_dev_to_main }}
steps:
- uses: actions/checkout@v4
Expand All @@ -47,22 +40,50 @@ jobs:
- '!**/*.md'
- '!**/*.mdc'
- '!docs/**'
- '!.github/workflows/**'
workflow:
- '.github/workflows/**'
- id: out
env:
EVENT_NAME: ${{ github.event_name }}
PR_BASE_REF: ${{ github.event.pull_request.base.ref }}
PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
PR_BASE_REF="${PR_BASE_REF:-}"
PR_HEAD_REF="${PR_HEAD_REF:-}"
PR_BASE_SHA="${PR_BASE_SHA:-}"
PR_HEAD_SHA="${PR_HEAD_SHA:-}"
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
echo "code_changed=true" >> "$GITHUB_OUTPUT"
echo "workflow_changed=true" >> "$GITHUB_OUTPUT"
else
echo "code_changed=${{ steps.filter.outputs.code }}" >> "$GITHUB_OUTPUT"
echo "workflow_changed=${{ steps.filter.outputs.workflow }}" >> "$GITHUB_OUTPUT"
fi
SKIP_TESTS=false
if [ "$EVENT_NAME" = "pull_request" ] && [ "$PR_BASE_REF" = "main" ] && [ "$PR_HEAD_REF" = "dev" ]; then
echo "skip_tests_dev_to_main=true" >> "$GITHUB_OUTPUT"
else
echo "skip_tests_dev_to_main=false" >> "$GITHUB_OUTPUT"
PR_BASE_REV=""
PR_HEAD_REV=""
MERGE_BASE=""
if [ -n "$PR_BASE_SHA" ]; then
PR_BASE_REV=$(git rev-parse "${PR_BASE_SHA}^{commit}" 2>/dev/null || true)
fi
if [ -n "$PR_HEAD_SHA" ]; then
PR_HEAD_REV=$(git rev-parse "${PR_HEAD_SHA}^{commit}" 2>/dev/null || true)
fi
if [ -n "$PR_BASE_REV" ] && [ -n "$PR_HEAD_REV" ]; then
MERGE_BASE=$(git merge-base "$PR_BASE_REV" "$PR_HEAD_REV" 2>/dev/null || true)
fi
if [ -n "$PR_BASE_REV" ] && [ -n "$PR_HEAD_REV" ] && [ "$PR_BASE_REV" = "$PR_HEAD_REV" ] && [ "$MERGE_BASE" = "$PR_HEAD_REV" ]; then
SKIP_TESTS=true
echo "βœ… Proven release parity between dev and main; skipping duplicate validation."
else
echo "ℹ️ Release parity proof unavailable for devβ†’main PR; running required validation set."
fi
fi
echo "skip_tests_dev_to_main=${SKIP_TESTS}" >> "$GITHUB_OUTPUT"

verify-module-signatures:
name: Verify Module Signatures
Expand Down Expand Up @@ -99,6 +120,49 @@ jobs:
python scripts/verify-modules-signature.py --require-signature --enforce-version-bump --payload-from-filesystem
fi

workflow-lint:
name: Workflow Lint
needs: [changes]
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4

- name: Skip when no workflow changes are present
if: needs.changes.outputs.workflow_changed != 'true' && github.event_name != 'workflow_dispatch'
run: |
echo "βœ… No workflow changes detected; skipping workflow lint."

- name: Set up Python 3.12
if: needs.changes.outputs.workflow_changed == 'true' || github.event_name == 'workflow_dispatch'
uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: "pip"
cache-dependency-path: |
pyproject.toml

- name: Set up Go for actionlint
if: needs.changes.outputs.workflow_changed == 'true' || github.event_name == 'workflow_dispatch'
uses: actions/setup-go@v5
with:
go-version: "1.24"

- name: Install lint dependencies
if: needs.changes.outputs.workflow_changed == 'true' || github.event_name == 'workflow_dispatch'
run: |
python -m pip install --upgrade pip
pip install "hatch" "virtualenv<21"
go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.11
echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"

- name: Run workflow lint
if: needs.changes.outputs.workflow_changed == 'true' || github.event_name == 'workflow_dispatch'
run: |
echo "πŸ” Running actionlint for workflow changes..."
hatch run lint-workflows

tests:
name: Tests (Python 3.12)
needs: [changes, verify-module-signatures]
Expand Down Expand Up @@ -219,7 +283,7 @@ jobs:
name: Compatibility (Python 3.11)
runs-on: ubuntu-latest
needs: [changes, verify-module-signatures]
if: needs.changes.outputs.skip_tests_dev_to_main != 'true'
if: needs.changes.outputs.code_changed == 'true' && needs.changes.outputs.skip_tests_dev_to_main != 'true'
permissions:
contents: read
steps:
Expand Down Expand Up @@ -258,9 +322,10 @@ jobs:
mkdir -p logs/compat-py311
COMPAT_LOG="logs/compat-py311/compat_$(date -u +%Y%m%d_%H%M%S).log"
{
hatch -e hatch-test.py3.11 run run -- -r fEw tests/unit tests/integration || echo "⚠️ Some tests failed (advisory)"
hatch -e hatch-test.py3.11 run run -- -r fEw tests/unit tests/integration
hatch -e hatch-test.py3.11 run xml || true
} 2>&1 | tee "$COMPAT_LOG"
exit "${PIPESTATUS[0]:-$?}"
- name: Upload compat-py311 logs
if: always()
uses: actions/upload-artifact@v4
Expand All @@ -273,7 +338,7 @@ jobs:
name: Contract-First CI
runs-on: ubuntu-latest
needs: [changes, verify-module-signatures]
if: needs.changes.outputs.skip_tests_dev_to_main != 'true'
if: needs.changes.outputs.code_changed == 'true' && needs.changes.outputs.skip_tests_dev_to_main != 'true'
permissions:
contents: read
steps:
Expand Down Expand Up @@ -313,8 +378,9 @@ jobs:
run: |
echo "πŸ” Validating runtime contracts..."
REPRO_LOG="logs/repro/repro_$(date -u +%Y%m%d_%H%M%S).log"
echo "Running specfact repro with required CrossHair... (log: $REPRO_LOG)"
hatch run specfact repro --verbose --crosshair-required --budget 120 2>&1 | tee "$REPRO_LOG" || echo "SpecFact repro found issues"
echo "Running contract-first validation with required CrossHair... (log: $REPRO_LOG)"
hatch run contract-test 2>&1 | tee "$REPRO_LOG"
exit "${PIPESTATUS[0]:-$?}"
- name: Upload repro logs
if: always()
uses: actions/upload-artifact@v4
Expand All @@ -334,7 +400,7 @@ jobs:
name: CLI Command Validation
runs-on: ubuntu-latest
needs: [changes, verify-module-signatures]
if: needs.changes.outputs.skip_tests_dev_to_main != 'true'
if: needs.changes.outputs.code_changed == 'true' && needs.changes.outputs.skip_tests_dev_to_main != 'true'
permissions:
contents: read
steps:
Expand All @@ -353,8 +419,8 @@ jobs:
- name: Validate CLI commands
run: |
echo "πŸ” Validating CLI commands..."
specfact --help || echo "⚠️ CLI not yet fully implemented"
echo "βœ… CLI validation complete (advisory)"
specfact --help
echo "βœ… CLI validation complete"

quality-gates:
name: Quality Gates (Advisory)
Expand Down Expand Up @@ -412,7 +478,7 @@ jobs:
name: Type Checking (basedpyright)
runs-on: ubuntu-latest
needs: [changes, verify-module-signatures]
if: needs.changes.outputs.skip_tests_dev_to_main != 'true'
if: needs.changes.outputs.code_changed == 'true' && needs.changes.outputs.skip_tests_dev_to_main != 'true'
permissions:
contents: read
steps:
Expand Down Expand Up @@ -447,7 +513,7 @@ jobs:
name: Linting (ruff, pylint)
runs-on: ubuntu-latest
needs: [changes, verify-module-signatures]
if: needs.changes.outputs.skip_tests_dev_to_main != 'true'
if: needs.changes.outputs.code_changed == 'true' && needs.changes.outputs.skip_tests_dev_to_main != 'true'
permissions:
contents: read
steps:
Expand Down Expand Up @@ -477,7 +543,8 @@ jobs:
python -m basedpyright --pythonpath "$(python -c 'import sys; print(sys.executable)')"
ruff check .
pylint src tests tools
} 2>&1 | tee "$LINT_LOG" || echo "⚠️ Linting incomplete"
} 2>&1 | tee "$LINT_LOG"
exit "${PIPESTATUS[0]:-$?}"
- name: Upload lint logs
if: always()
uses: actions/upload-artifact@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sign-modules.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ on:

jobs:
verify:
name: Verify module signatures
name: Verify Module Signatures
runs-on: ubuntu-latest
permissions:
contents: read
Expand Down
15 changes: 4 additions & 11 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
repos:
- repo: local
hooks:
- id: verify-module-signatures
name: Verify module signatures and version bumps
entry: hatch run ./scripts/verify-modules-signature.py --require-signature --enforce-version-bump
language: system
- 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
- id: check-doc-frontmatter
Expand All @@ -14,10 +14,3 @@ repos:
files: ^(docs/.*\.md|docs/\.doc-frontmatter-enforced|USAGE-FAQ\.md)$
pass_filenames: false
always_run: false
- id: specfact-code-review-gate
name: Run code review gate on staged Python files
entry: hatch run python scripts/pre_commit_code_review.py
language: system
files: \.pyi?$
# Show summary + copy-paste lines on success; pre-commit hides hook output otherwise.
verbose: true
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<!-- markdownlint-configure-file { "MD024": { "siblings_only": true } } -->
# Changelog

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
Expand Down Expand Up @@ -508,7 +509,6 @@ All notable changes to this project will be documented in this file.
- Bundle ID candidate derivation no longer falls back to the manifest filename stem (`bundle.yaml` -> `bundle`), preventing false rejection of valid explicit `bundle:<id>` tags.
- OpenSpec change order/archive tracking was synchronized for Wave 1 closure (`verification-01-wave1-delta-closure`) and related archived status markers.

---
## [0.34.0] - 2026-02-18

### Added
Expand Down
18 changes: 15 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,19 @@ hatch run lint
hatch run contract-test-full
```

The repo-owned pre-commit flow now also runs `specfact code review run` on
staged Python files and blocks commits only when the review verdict is
blocking.
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,
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
remediation in Copilot/Cursor.
- Advisory review surfaces: CodeRabbit and other PR review comments remain advisory unless a branch
protection rule explicitly promotes a check to required.
- Workflow linting requires `actionlint` on `PATH` or a Docker-enabled environment. CI installs a
pinned `actionlint` release explicitly; local contributors should install it globally, for example
with `go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.11`.

## Contributor License Agreement (CLA)

Expand Down Expand Up @@ -199,13 +209,15 @@ The repository README, `docs/index.md`, and other first-contact surfaces must pr
first-contact story.

When editing those surfaces, make sure a new visitor can quickly answer:

- **What is SpecFact?**
- **Why does it exist?**
- **Why should I use it?**
- **What do I get?**
- **How do I get started?**

Keep the hierarchy in this order:

1. Product identity
2. Why it exists
3. User value
Expand Down
6 changes: 5 additions & 1 deletion docs/contributing/docs-sync.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ exempt: false
exempt_reason: ""
---

# Documentation ownership and frontmatter
## Documentation ownership and frontmatter

Core documentation uses **YAML frontmatter** for Jekyll (layout, title, permalink) and for **ownership** fields that drive the `scripts/check_doc_frontmatter.py` checker.

Expand Down Expand Up @@ -46,6 +46,10 @@ The enforced-path file accepts glob patterns matched against repo-relative Markd
`docs/` and root docs such as `USAGE-FAQ.md`; blank lines and lines starting with `#` are ignored by
the checker.

Docs-only PRs rely on the dedicated `Docs Review` workflow as the required documentation gate. Its
cross-site link check remains advisory because the published site can lag deployment, while
frontmatter validation and docs test suites remain merge-blocking for docs-owned changes.

## Troubleshooting

- **Missing `doc_owner`**: add the field and a sensible `tracks` list for the code or specs this page describes.
Expand Down
32 changes: 20 additions & 12 deletions docs/modules/code-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Install and use the official specfact-code-review module scaffold.
permalink: /modules/code-review/
---

# Code Review Module
## Code Review Module

The `nold-ai/specfact-code-review` module extends `specfact code` with a governed `review` subgroup for structured review execution, scoring, and reporting.

Expand Down Expand Up @@ -102,25 +102,27 @@ The scaffolded `ReviewReport` envelope carries these fields:

## Pre-Commit Review Gate

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

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

```yaml
repos:
- repo: local
hooks:
- id: specfact-code-review-gate
name: Run code review gate on staged Python files
entry: hatch run python scripts/pre_commit_code_review.py
language: system
files: \.pyi?$
# Needed so you still see hook output when the gate passes (pre-commit hides it otherwise).
verbose: true
- 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 helper script scopes the gate to staged Python files only and then runs:
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
then runs:

```bash
specfact code review run --json --out .specfact/code-review.json <staged-python-files>
Expand Down Expand Up @@ -158,6 +160,12 @@ Commit behavior:
- `PASS_WITH_ADVISORY` keeps the commit green
- `FAIL` blocks the commit

Repository gate taxonomy:

- Local smart-check wrapper: merge-blocking for its enforced local checks.
- `specfact code review run`: advisory unless it returns `FAIL`; `PASS_WITH_ADVISORY` stays commit-green.
- CodeRabbit review comments/status: advisory review assistance, not a merge-blocking branch-protection gate by themselves.

To install the repo-owned hook flow:

```bash
Expand Down
Loading
Loading