From 3bc9470034ce2aa4ffa74913f5e976727ffd3919 Mon Sep 17 00:00:00 2001 From: omit-test Date: Thu, 16 Apr 2026 21:23:08 +0200 Subject: [PATCH 01/14] Updating and archiving finalized changes --- .../.openspec.yaml | 0 .../TDD_EVIDENCE.md | 0 .../design.md | 0 .../proposal.md | 0 .../specs/code-review-bug-finding/spec.md | 0 .../code-review-tool-dependencies/spec.md | 0 .../specs/contract-runner/spec.md | 0 .../specs/review-cli-contracts/spec.md | 0 .../specs/review-run-command/spec.md | 0 .../specs/sidecar-route-extraction/spec.md | 0 .../tasks.md | 0 .../.openspec.yaml | 0 .../CHANGE_VALIDATION.md | 0 .../TDD_EVIDENCE.md | 0 .../design.md | 0 .../proposal.md | 0 .../specs/agent-governance-loading/spec.md | 0 .../specs/github-hierarchy-cache/spec.md | 0 .../tasks.md | 8 +- .../.openspec.yaml | 0 .../TDD_EVIDENCE.md | 0 .../design.md | 0 .../proposal.md | 0 .../specs/ci-integration/spec.md | 0 .../ci-module-signing-on-approval/spec.md | 0 .../tasks.md | 6 +- .../specs/agent-governance-loading/spec.md | 117 ++++++++++++++++++ openspec/specs/ci-integration/spec.md | 55 ++++++++ .../ci-module-signing-on-approval/spec.md | 68 ++++++++++ .../specs/code-review-bug-finding/spec.md | 57 +++++++++ .../code-review-tool-dependencies/spec.md | 70 +++++++++++ openspec/specs/contract-runner/spec.md | 26 +++- openspec/specs/github-hierarchy-cache/spec.md | 25 +++- openspec/specs/review-cli-contracts/spec.md | 39 ++++++ openspec/specs/review-run-command/spec.md | 104 ++++++++++++++++ .../specs/sidecar-route-extraction/spec.md | 43 +++++++ 36 files changed, 604 insertions(+), 14 deletions(-) rename openspec/changes/{code-review-bug-finding-and-sidecar-venv-fix => archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix}/.openspec.yaml (100%) rename openspec/changes/{code-review-bug-finding-and-sidecar-venv-fix => archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix}/TDD_EVIDENCE.md (100%) rename openspec/changes/{code-review-bug-finding-and-sidecar-venv-fix => archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix}/design.md (100%) rename openspec/changes/{code-review-bug-finding-and-sidecar-venv-fix => archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix}/proposal.md (100%) rename openspec/changes/{code-review-bug-finding-and-sidecar-venv-fix => archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix}/specs/code-review-bug-finding/spec.md (100%) rename openspec/changes/{code-review-bug-finding-and-sidecar-venv-fix => archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix}/specs/code-review-tool-dependencies/spec.md (100%) rename openspec/changes/{code-review-bug-finding-and-sidecar-venv-fix => archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix}/specs/contract-runner/spec.md (100%) rename openspec/changes/{code-review-bug-finding-and-sidecar-venv-fix => archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix}/specs/review-cli-contracts/spec.md (100%) rename openspec/changes/{code-review-bug-finding-and-sidecar-venv-fix => archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix}/specs/review-run-command/spec.md (100%) rename openspec/changes/{code-review-bug-finding-and-sidecar-venv-fix => archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix}/specs/sidecar-route-extraction/spec.md (100%) rename openspec/changes/{code-review-bug-finding-and-sidecar-venv-fix => archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix}/tasks.md (100%) rename openspec/changes/{governance-04-deterministic-agent-governance-loading => archive/2026-04-16-governance-04-deterministic-agent-governance-loading}/.openspec.yaml (100%) rename openspec/changes/{governance-04-deterministic-agent-governance-loading => archive/2026-04-16-governance-04-deterministic-agent-governance-loading}/CHANGE_VALIDATION.md (100%) rename openspec/changes/{governance-04-deterministic-agent-governance-loading => archive/2026-04-16-governance-04-deterministic-agent-governance-loading}/TDD_EVIDENCE.md (100%) rename openspec/changes/{governance-04-deterministic-agent-governance-loading => archive/2026-04-16-governance-04-deterministic-agent-governance-loading}/design.md (100%) rename openspec/changes/{governance-04-deterministic-agent-governance-loading => archive/2026-04-16-governance-04-deterministic-agent-governance-loading}/proposal.md (100%) rename openspec/changes/{governance-04-deterministic-agent-governance-loading => archive/2026-04-16-governance-04-deterministic-agent-governance-loading}/specs/agent-governance-loading/spec.md (100%) rename openspec/changes/{governance-04-deterministic-agent-governance-loading => archive/2026-04-16-governance-04-deterministic-agent-governance-loading}/specs/github-hierarchy-cache/spec.md (100%) rename openspec/changes/{governance-04-deterministic-agent-governance-loading => archive/2026-04-16-governance-04-deterministic-agent-governance-loading}/tasks.md (95%) rename openspec/changes/{marketplace-06-ci-module-signing => archive/2026-04-16-marketplace-06-ci-module-signing}/.openspec.yaml (100%) rename openspec/changes/{marketplace-06-ci-module-signing => archive/2026-04-16-marketplace-06-ci-module-signing}/TDD_EVIDENCE.md (100%) rename openspec/changes/{marketplace-06-ci-module-signing => archive/2026-04-16-marketplace-06-ci-module-signing}/design.md (100%) rename openspec/changes/{marketplace-06-ci-module-signing => archive/2026-04-16-marketplace-06-ci-module-signing}/proposal.md (100%) rename openspec/changes/{marketplace-06-ci-module-signing => archive/2026-04-16-marketplace-06-ci-module-signing}/specs/ci-integration/spec.md (100%) rename openspec/changes/{marketplace-06-ci-module-signing => archive/2026-04-16-marketplace-06-ci-module-signing}/specs/ci-module-signing-on-approval/spec.md (100%) rename openspec/changes/{marketplace-06-ci-module-signing => archive/2026-04-16-marketplace-06-ci-module-signing}/tasks.md (96%) create mode 100644 openspec/specs/agent-governance-loading/spec.md create mode 100644 openspec/specs/ci-integration/spec.md create mode 100644 openspec/specs/ci-module-signing-on-approval/spec.md create mode 100644 openspec/specs/code-review-bug-finding/spec.md create mode 100644 openspec/specs/code-review-tool-dependencies/spec.md create mode 100644 openspec/specs/sidecar-route-extraction/spec.md diff --git a/openspec/changes/code-review-bug-finding-and-sidecar-venv-fix/.openspec.yaml b/openspec/changes/archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix/.openspec.yaml similarity index 100% rename from openspec/changes/code-review-bug-finding-and-sidecar-venv-fix/.openspec.yaml rename to openspec/changes/archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix/.openspec.yaml diff --git a/openspec/changes/code-review-bug-finding-and-sidecar-venv-fix/TDD_EVIDENCE.md b/openspec/changes/archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix/TDD_EVIDENCE.md similarity index 100% rename from openspec/changes/code-review-bug-finding-and-sidecar-venv-fix/TDD_EVIDENCE.md rename to openspec/changes/archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix/TDD_EVIDENCE.md diff --git a/openspec/changes/code-review-bug-finding-and-sidecar-venv-fix/design.md b/openspec/changes/archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix/design.md similarity index 100% rename from openspec/changes/code-review-bug-finding-and-sidecar-venv-fix/design.md rename to openspec/changes/archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix/design.md diff --git a/openspec/changes/code-review-bug-finding-and-sidecar-venv-fix/proposal.md b/openspec/changes/archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix/proposal.md similarity index 100% rename from openspec/changes/code-review-bug-finding-and-sidecar-venv-fix/proposal.md rename to openspec/changes/archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix/proposal.md diff --git a/openspec/changes/code-review-bug-finding-and-sidecar-venv-fix/specs/code-review-bug-finding/spec.md b/openspec/changes/archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix/specs/code-review-bug-finding/spec.md similarity index 100% rename from openspec/changes/code-review-bug-finding-and-sidecar-venv-fix/specs/code-review-bug-finding/spec.md rename to openspec/changes/archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix/specs/code-review-bug-finding/spec.md diff --git a/openspec/changes/code-review-bug-finding-and-sidecar-venv-fix/specs/code-review-tool-dependencies/spec.md b/openspec/changes/archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix/specs/code-review-tool-dependencies/spec.md similarity index 100% rename from openspec/changes/code-review-bug-finding-and-sidecar-venv-fix/specs/code-review-tool-dependencies/spec.md rename to openspec/changes/archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix/specs/code-review-tool-dependencies/spec.md diff --git a/openspec/changes/code-review-bug-finding-and-sidecar-venv-fix/specs/contract-runner/spec.md b/openspec/changes/archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix/specs/contract-runner/spec.md similarity index 100% rename from openspec/changes/code-review-bug-finding-and-sidecar-venv-fix/specs/contract-runner/spec.md rename to openspec/changes/archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix/specs/contract-runner/spec.md diff --git a/openspec/changes/code-review-bug-finding-and-sidecar-venv-fix/specs/review-cli-contracts/spec.md b/openspec/changes/archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix/specs/review-cli-contracts/spec.md similarity index 100% rename from openspec/changes/code-review-bug-finding-and-sidecar-venv-fix/specs/review-cli-contracts/spec.md rename to openspec/changes/archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix/specs/review-cli-contracts/spec.md diff --git a/openspec/changes/code-review-bug-finding-and-sidecar-venv-fix/specs/review-run-command/spec.md b/openspec/changes/archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix/specs/review-run-command/spec.md similarity index 100% rename from openspec/changes/code-review-bug-finding-and-sidecar-venv-fix/specs/review-run-command/spec.md rename to openspec/changes/archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix/specs/review-run-command/spec.md diff --git a/openspec/changes/code-review-bug-finding-and-sidecar-venv-fix/specs/sidecar-route-extraction/spec.md b/openspec/changes/archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix/specs/sidecar-route-extraction/spec.md similarity index 100% rename from openspec/changes/code-review-bug-finding-and-sidecar-venv-fix/specs/sidecar-route-extraction/spec.md rename to openspec/changes/archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix/specs/sidecar-route-extraction/spec.md diff --git a/openspec/changes/code-review-bug-finding-and-sidecar-venv-fix/tasks.md b/openspec/changes/archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix/tasks.md similarity index 100% rename from openspec/changes/code-review-bug-finding-and-sidecar-venv-fix/tasks.md rename to openspec/changes/archive/2026-04-16-code-review-bug-finding-and-sidecar-venv-fix/tasks.md diff --git a/openspec/changes/governance-04-deterministic-agent-governance-loading/.openspec.yaml b/openspec/changes/archive/2026-04-16-governance-04-deterministic-agent-governance-loading/.openspec.yaml similarity index 100% rename from openspec/changes/governance-04-deterministic-agent-governance-loading/.openspec.yaml rename to openspec/changes/archive/2026-04-16-governance-04-deterministic-agent-governance-loading/.openspec.yaml diff --git a/openspec/changes/governance-04-deterministic-agent-governance-loading/CHANGE_VALIDATION.md b/openspec/changes/archive/2026-04-16-governance-04-deterministic-agent-governance-loading/CHANGE_VALIDATION.md similarity index 100% rename from openspec/changes/governance-04-deterministic-agent-governance-loading/CHANGE_VALIDATION.md rename to openspec/changes/archive/2026-04-16-governance-04-deterministic-agent-governance-loading/CHANGE_VALIDATION.md diff --git a/openspec/changes/governance-04-deterministic-agent-governance-loading/TDD_EVIDENCE.md b/openspec/changes/archive/2026-04-16-governance-04-deterministic-agent-governance-loading/TDD_EVIDENCE.md similarity index 100% rename from openspec/changes/governance-04-deterministic-agent-governance-loading/TDD_EVIDENCE.md rename to openspec/changes/archive/2026-04-16-governance-04-deterministic-agent-governance-loading/TDD_EVIDENCE.md diff --git a/openspec/changes/governance-04-deterministic-agent-governance-loading/design.md b/openspec/changes/archive/2026-04-16-governance-04-deterministic-agent-governance-loading/design.md similarity index 100% rename from openspec/changes/governance-04-deterministic-agent-governance-loading/design.md rename to openspec/changes/archive/2026-04-16-governance-04-deterministic-agent-governance-loading/design.md diff --git a/openspec/changes/governance-04-deterministic-agent-governance-loading/proposal.md b/openspec/changes/archive/2026-04-16-governance-04-deterministic-agent-governance-loading/proposal.md similarity index 100% rename from openspec/changes/governance-04-deterministic-agent-governance-loading/proposal.md rename to openspec/changes/archive/2026-04-16-governance-04-deterministic-agent-governance-loading/proposal.md diff --git a/openspec/changes/governance-04-deterministic-agent-governance-loading/specs/agent-governance-loading/spec.md b/openspec/changes/archive/2026-04-16-governance-04-deterministic-agent-governance-loading/specs/agent-governance-loading/spec.md similarity index 100% rename from openspec/changes/governance-04-deterministic-agent-governance-loading/specs/agent-governance-loading/spec.md rename to openspec/changes/archive/2026-04-16-governance-04-deterministic-agent-governance-loading/specs/agent-governance-loading/spec.md diff --git a/openspec/changes/governance-04-deterministic-agent-governance-loading/specs/github-hierarchy-cache/spec.md b/openspec/changes/archive/2026-04-16-governance-04-deterministic-agent-governance-loading/specs/github-hierarchy-cache/spec.md similarity index 100% rename from openspec/changes/governance-04-deterministic-agent-governance-loading/specs/github-hierarchy-cache/spec.md rename to openspec/changes/archive/2026-04-16-governance-04-deterministic-agent-governance-loading/specs/github-hierarchy-cache/spec.md diff --git a/openspec/changes/governance-04-deterministic-agent-governance-loading/tasks.md b/openspec/changes/archive/2026-04-16-governance-04-deterministic-agent-governance-loading/tasks.md similarity index 95% rename from openspec/changes/governance-04-deterministic-agent-governance-loading/tasks.md rename to openspec/changes/archive/2026-04-16-governance-04-deterministic-agent-governance-loading/tasks.md index fb59c887..ba39e7d2 100644 --- a/openspec/changes/governance-04-deterministic-agent-governance-loading/tasks.md +++ b/openspec/changes/archive/2026-04-16-governance-04-deterministic-agent-governance-loading/tasks.md @@ -7,7 +7,7 @@ - [x] 1.3 In the worktree: `hatch env create` and `hatch run dev-deps` so `specfact` CLI is available for code-review dogfood tasks. - [x] 1.4 Pre-flight from worktree: `hatch run smart-test-status` and `hatch run contract-test-status` (or full quick sanity per AGENTS.md if those targets differ). - [x] 1.5 Run `openspec validate governance-04-deterministic-agent-governance-loading --strict` and capture output in `CHANGE_VALIDATION.md`; fix artifact issues until green. -- [ ] 1.6 After PR merges: `git worktree remove`, `git branch -d`, `git worktree prune` for the feature branch; remove worktree-local `.venv` if unused. +- [x] 1.6 After PR merges: `git worktree remove`, `git branch -d`, `git worktree prune` for the feature branch; remove worktree-local `.venv` if unused. ## 2. Spec-first and test-first preparation @@ -28,12 +28,12 @@ ## 4. Validation and documentation - [x] 4.1 Run quality gates from the worktree until green: `hatch run format`, `hatch run type-check`, `hatch run lint`, `hatch run yaml-lint`, `hatch run contract-test`, `hatch run smart-test`, `hatch run test` (add signature verify if any `module-package.yaml` / registry payload changes). -- [ ] 4.2 **SpecFact code review JSON**: ensure `.specfact/code-review.json` exists and is fresh per `openspec/config.yaml` rules; remediate all findings or document a rare justified exception in the proposal; record commands and timestamp in `TDD_EVIDENCE.md`. +- [x] 4.2 **SpecFact code review JSON**: ensure `.specfact/code-review.json` exists and is fresh per `openspec/config.yaml` rules; remediate all findings or document a rare justified exception in the proposal; record commands and timestamp in `TDD_EVIDENCE.md`. - [x] 4.3 If contributor-facing docs under `docs/` must mention the new layout (e.g. onboarding, nav, frontmatter schema), update them without breaking Jekyll front matter or `documentation-url-contract.md` permalinks. - [x] 4.4 Re-run `openspec validate governance-04-deterministic-agent-governance-loading --strict` and update `CHANGE_VALIDATION.md`. ## 5. Delivery - [x] 5.1 Refresh `TDD_EVIDENCE.md` with passing-after commands and timestamps. -- [ ] 5.2 Open a PR from `feature/governance-04-deterministic-agent-governance-loading` to `dev` with summary linking modules issue, #163, #494, and #178. -- [ ] 5.3 After merge, run `openspec archive governance-04-deterministic-agent-governance-loading` from repo root (no manual folder moves) and confirm **openspec/CHANGE_ORDER.md** reflects archived status. +- [x] 5.2 Open a PR from `feature/governance-04-deterministic-agent-governance-loading` to `dev` with summary linking modules issue, #163, #494, and #178. +- [x] 5.3 After merge, run `openspec archive governance-04-deterministic-agent-governance-loading` from repo root (no manual folder moves) and confirm **openspec/CHANGE_ORDER.md** reflects archived status. diff --git a/openspec/changes/marketplace-06-ci-module-signing/.openspec.yaml b/openspec/changes/archive/2026-04-16-marketplace-06-ci-module-signing/.openspec.yaml similarity index 100% rename from openspec/changes/marketplace-06-ci-module-signing/.openspec.yaml rename to openspec/changes/archive/2026-04-16-marketplace-06-ci-module-signing/.openspec.yaml diff --git a/openspec/changes/marketplace-06-ci-module-signing/TDD_EVIDENCE.md b/openspec/changes/archive/2026-04-16-marketplace-06-ci-module-signing/TDD_EVIDENCE.md similarity index 100% rename from openspec/changes/marketplace-06-ci-module-signing/TDD_EVIDENCE.md rename to openspec/changes/archive/2026-04-16-marketplace-06-ci-module-signing/TDD_EVIDENCE.md diff --git a/openspec/changes/marketplace-06-ci-module-signing/design.md b/openspec/changes/archive/2026-04-16-marketplace-06-ci-module-signing/design.md similarity index 100% rename from openspec/changes/marketplace-06-ci-module-signing/design.md rename to openspec/changes/archive/2026-04-16-marketplace-06-ci-module-signing/design.md diff --git a/openspec/changes/marketplace-06-ci-module-signing/proposal.md b/openspec/changes/archive/2026-04-16-marketplace-06-ci-module-signing/proposal.md similarity index 100% rename from openspec/changes/marketplace-06-ci-module-signing/proposal.md rename to openspec/changes/archive/2026-04-16-marketplace-06-ci-module-signing/proposal.md diff --git a/openspec/changes/marketplace-06-ci-module-signing/specs/ci-integration/spec.md b/openspec/changes/archive/2026-04-16-marketplace-06-ci-module-signing/specs/ci-integration/spec.md similarity index 100% rename from openspec/changes/marketplace-06-ci-module-signing/specs/ci-integration/spec.md rename to openspec/changes/archive/2026-04-16-marketplace-06-ci-module-signing/specs/ci-integration/spec.md diff --git a/openspec/changes/marketplace-06-ci-module-signing/specs/ci-module-signing-on-approval/spec.md b/openspec/changes/archive/2026-04-16-marketplace-06-ci-module-signing/specs/ci-module-signing-on-approval/spec.md similarity index 100% rename from openspec/changes/marketplace-06-ci-module-signing/specs/ci-module-signing-on-approval/spec.md rename to openspec/changes/archive/2026-04-16-marketplace-06-ci-module-signing/specs/ci-module-signing-on-approval/spec.md diff --git a/openspec/changes/marketplace-06-ci-module-signing/tasks.md b/openspec/changes/archive/2026-04-16-marketplace-06-ci-module-signing/tasks.md similarity index 96% rename from openspec/changes/marketplace-06-ci-module-signing/tasks.md rename to openspec/changes/archive/2026-04-16-marketplace-06-ci-module-signing/tasks.md index 5a7fff30..93cc1279 100644 --- a/openspec/changes/marketplace-06-ci-module-signing/tasks.md +++ b/openspec/changes/archive/2026-04-16-marketplace-06-ci-module-signing/tasks.md @@ -5,7 +5,7 @@ - [x] 1.1 Create `feature/marketplace-06-ci-module-signing` in a dedicated worktree from `origin/dev`; run pre-flight status checks. - [x] 1.2 ~~Create a GitHub User Story issue~~ Issue created: [specfact-cli-modules#185](https://github.com/nold-ai/specfact-cli-modules/issues/185); `proposal.md` Source Tracking updated. Paired core issue: [specfact-cli#500](https://github.com/nold-ai/specfact-cli/issues/500). *(done)* -- [ ] 1.3 Confirm `SPECFACT_MODULE_PRIVATE_SIGN_KEY` and `SPECFACT_MODULE_PRIVATE_SIGN_KEY_PASSPHRASE` +- [x] 1.3 Confirm `SPECFACT_MODULE_PRIVATE_SIGN_KEY` and `SPECFACT_MODULE_PRIVATE_SIGN_KEY_PASSPHRASE` are set as repository secrets in `specfact-cli-modules` (should already be present via publish-modules.yml). *(human)* @@ -73,5 +73,5 @@ require `--require-signature`). PR: [specfact-cli-modules#188](https://github.com/nold-ai/specfact-cli-modules/pull/188). - [x] 6.2 Link the PR to the GitHub issue created in 1.2 and to the paired specfact-cli PR. *(Closes #185 in PR body; link specfact-cli PR manually when it exists.)* -- [ ] 6.3 After merge: remove the worktree, delete the local branch, run `git worktree prune`. -- [ ] 6.4 Record cleanup completion in `TDD_EVIDENCE.md`. +- [x] 6.3 After merge: remove the worktree, delete the local branch, run `git worktree prune`. +- [x] 6.4 Record cleanup completion in `TDD_EVIDENCE.md`. diff --git a/openspec/specs/agent-governance-loading/spec.md b/openspec/specs/agent-governance-loading/spec.md new file mode 100644 index 00000000..05767591 --- /dev/null +++ b/openspec/specs/agent-governance-loading/spec.md @@ -0,0 +1,117 @@ +# agent-governance-loading Specification + +## Purpose +TBD - created by archiving change governance-04-deterministic-agent-governance-loading. Update Purpose after archive. +## Requirements +### Requirement: Compact AGENTS bootstrap contract + +The repository SHALL keep `AGENTS.md` as the mandatory bootstrap governance surface, but it SHALL remain compact and SHALL delegate long-form operational detail to canonical rule artifacts. + +#### Scenario: Session bootstrap reads compact governance contract + +- **WHEN** an agent starts work in the repository +- **THEN** it reads `AGENTS.md` first +- **AND** `AGENTS.md` defines a mandatory bootstrap sequence rather than embedding the full long-form governance corpus +- **AND** the bootstrap sequence requires loading `docs/agent-rules/INDEX.md` +- **AND** the bootstrap sequence requires loading the canonical non-negotiable checklist before code implementation work + +#### Scenario: AGENTS stays compact while preserving enforcement + +- **WHEN** repository governance is updated +- **THEN** `AGENTS.md` SHALL retain only the bootstrap contract, invariant governance rules, and canonical references needed for startup +- **AND** detailed workflow, validation, or finalization guidance SHALL live in dedicated rule artifacts rather than being duplicated inline + +### Requirement: Deterministic rule index and loading semantics + +The repository SHALL provide a canonical rule index that deterministically decides which governance rule files must be loaded for a given task. + +#### Scenario: Always-load rule subset + +- **WHEN** an agent loads the governance rule index +- **THEN** the index SHALL identify a mandatory always-load subset +- **AND** that subset SHALL include the non-negotiable checklist +- **AND** the index SHALL define the order in which always-load rules are processed + +#### Scenario: Applicability-based rule loading + +- **WHEN** a task involves worktree management, OpenSpec change selection, GitHub issue coordination, TDD gating, quality verification, module signature or registry release work, or finalization +- **THEN** the index SHALL map those task signals to specific `docs/agent-rules/*.md` files +- **AND** the index SHALL define which rule files are required versus optional for that task class +- **AND** the loading decision SHALL not depend on heuristic file-name guessing alone + +#### Scenario: Deterministic precedence + +- **WHEN** governance instructions overlap across `AGENTS.md`, the rule index, rule files, and change-local artifacts +- **THEN** the repository SHALL define a single precedence order for which instruction wins +- **AND** the precedence order SHALL include explicit handling for direct user override where repository governance permits it + +### Requirement: Governance rule files use machine-readable frontmatter + +Every dedicated governance rule artifact SHALL include machine-readable frontmatter that defines how and when the rule applies. + +#### Scenario: Required frontmatter fields are present + +- **WHEN** a file under `docs/agent-rules/` is intended to govern agent behavior +- **THEN** it SHALL include frontmatter fields for rule identity, applicability, priority, blocking semantics, and stop conditions +- **AND** it SHALL declare whether the file is always loaded +- **AND** it SHALL declare whether user interaction is required when the rule blocks progress + +#### Scenario: Frontmatter drives deterministic behavior + +- **WHEN** an agent evaluates a rule file with frontmatter +- **THEN** it can determine from metadata whether the rule is mandatory for the current task +- **AND** it can determine whether the rule requires a hard stop, read-only continuation, or interactive clarification +- **AND** it does not need to infer those semantics solely from unstructured prose + +### Requirement: Governance must define explicit stop and continue behavior + +The governance system SHALL define explicit blocking behavior for stale changes, concurrency ambiguity, missing metadata, and TDD gate violations. + +#### Scenario: Blocking ambiguity requires user clarification + +- **WHEN** a selected change is stale, superseded, ambiguous, or linked to GitHub work already in progress +- **THEN** the applicable rule SHALL require the agent to stop implementation work +- **AND** the rule SHALL state that the ambiguity must be surfaced to the user for clarification +- **AND** the rule SHALL define whether read-only investigation may continue while implementation remains blocked + +#### Scenario: TDD gate remains non-bypassable in compact governance + +- **WHEN** a task changes behavior in code +- **THEN** the applicable rule SHALL still require spec updates, test creation, failing-test evidence, implementation, and passing evidence in that order +- **AND** compact governance SHALL not weaken or omit that sequence + +### Requirement: Public GitHub work must pass metadata completeness checks + +The governance system SHALL define explicit readiness checks for linked GitHub change issues before implementation proceeds for public repository work. + +#### Scenario: Parent and dependency metadata must be complete + +- **WHEN** an agent prepares to implement a publicly tracked change with a linked GitHub issue +- **THEN** the applicable governance rules SHALL require verifying the issue's parent relationship, blockers, and blocked-by relationships against current repository GitHub reality +- **AND** the parent lookup SHALL use the local hierarchy cache first and refresh the cache when repository-defined freshness rules require it +- **AND** implementation SHALL not proceed if the required parent or dependency metadata is missing or ambiguous + +#### Scenario: Labels and project assignment must be complete + +- **WHEN** an agent prepares to implement a publicly tracked change with a linked GitHub issue +- **THEN** the applicable governance rules SHALL require verifying that the issue has the required labels and project assignment for repository workflow completeness +- **AND** implementation SHALL not proceed until that metadata is complete or the user explicitly directs an allowed exception path + +#### Scenario: Live GitHub issue state can block implementation + +- **WHEN** an agent prepares to implement a publicly tracked change with a linked GitHub issue +- **AND** the issue is already marked `in progress` +- **THEN** the governance rules SHALL treat that state as a concurrency ambiguity +- **AND** the agent SHALL stop implementation work and ask the user to clarify whether the change is already being worked in another session +- **AND** the rules SHALL define whether only read-only investigation may continue while implementation remains blocked + +### Requirement: Canonical aliases prevent instruction drift + +Repository instruction surfaces other than `AGENTS.md` SHALL reference the canonical governance rule system instead of embedding duplicate long-form policy text. + +#### Scenario: Alias instruction surfaces stay synchronized + +- **WHEN** a contributor reads another repository instruction surface such as `CLAUDE.md`, `.cursorrules`, `.github/copilot-instructions.md`, or generated IDE guidance +- **THEN** the surface SHALL reference the canonical rule system for governance semantics +- **AND** it SHALL avoid copying long-form governance content that could drift from the canonical source + diff --git a/openspec/specs/ci-integration/spec.md b/openspec/specs/ci-integration/spec.md new file mode 100644 index 00000000..f1109782 --- /dev/null +++ b/openspec/specs/ci-integration/spec.md @@ -0,0 +1,55 @@ +# ci-integration Specification + +## Purpose +TBD - created by archiving change marketplace-06-ci-module-signing. Update Purpose after archive. +## Requirements +### Requirement: pr-orchestrator skips signature requirement for dev-targeting events + +The `verify-module-signatures` job in `pr-orchestrator.yml` SHALL NOT enforce `--require-signature` +for pull requests or pushes targeting `dev`; it SHALL enforce `--require-signature` only for +`main`-targeting events. + +#### Scenario: Feature-to-dev PR with unsigned package manifests + +- **WHEN** a pull request targets `dev` +- **AND** the PR contains package manifest changes with checksum-only integrity blocks +- **THEN** the `verify-module-signatures` job SHALL pass without `--require-signature` +- **AND** all downstream jobs (`quality`, `contract-tests`, etc.) SHALL not be blocked + +#### Scenario: Dev-to-main PR with unsigned manifests (before approval) + +- **WHEN** a pull request targets `main` +- **AND** one or more `packages/*/module-package.yaml` files lack a valid signature +- **THEN** the `verify-module-signatures` job SHALL fail with `--require-signature` +- **AND** the PR SHALL be blocked from merging + +#### Scenario: Dev-to-main PR after CI signing commit + +- **WHEN** a pull request targets `main` +- **AND** the CI `sign-modules-on-approval` workflow has committed signed manifests to the PR branch +- **THEN** the `verify-module-signatures` job SHALL pass with `--require-signature` +- **AND** the PR SHALL be unblocked (subject to other required checks) + +#### Scenario: Push to main post-merge + +- **WHEN** a commit is pushed to `main` (post-merge) +- **THEN** the `verify-module-signatures` job SHALL run with `--require-signature` +- **AND** fail if any `packages/*/module-package.yaml` lacks a valid signature + +### Requirement: pre-commit verify mirrors pr-orchestrator signature policy + +The repository pre-commit hook that runs `verify-modules-signature.py` SHALL apply the same branch rule as the `verify-module-signatures` CI job: omit `--require-signature` unless the integration target is `main` (local branch `main`, or `GITHUB_BASE_REF=main` on pull request events in Actions). + +#### Scenario: Commit on a feature branch without a signing key + +- **WHEN** a developer commits on a branch other than `main` +- **AND** manifests satisfy checksum and version-bump policy but lack a valid signature +- **THEN** the pre-commit signature hook SHALL pass (no `--require-signature`) +- **AND** the developer SHALL remain unblocked until a `main`-targeting flow enforces signatures in CI + +#### Scenario: Commit on main requires signatures + +- **WHEN** a developer commits on branch `main` +- **AND** any `packages/*/module-package.yaml` lacks a valid signature under `--require-signature` +- **THEN** the pre-commit signature hook SHALL fail + diff --git a/openspec/specs/ci-module-signing-on-approval/spec.md b/openspec/specs/ci-module-signing-on-approval/spec.md new file mode 100644 index 00000000..1448dc9e --- /dev/null +++ b/openspec/specs/ci-module-signing-on-approval/spec.md @@ -0,0 +1,68 @@ +# ci-module-signing-on-approval Specification + +## Purpose +TBD - created by archiving change marketplace-06-ci-module-signing. Update Purpose after archive. +## Requirements +### Requirement: Sign packages manifests on PR approval + +The system SHALL automatically sign changed `packages/*/module-package.yaml` manifests using CI +secrets when a pull request targeting `dev` or `main` is approved, and SHALL commit the signed +manifests back to the PR branch. + +#### Scenario: PR to dev approved with package module changes + +- **WHEN** a pull request targeting `dev` is approved by a reviewer +- **AND** the PR contains changes to one or more files under `packages/` +- **THEN** the CI signing workflow SHALL discover all `packages/*/module-package.yaml` manifests + whose payload changed on the PR branch since the merge-base with `origin/dev` (not merely + divergent from the moving `origin/dev` tip) +- **AND** SHALL sign them using `SPECFACT_MODULE_PRIVATE_SIGN_KEY` and + `SPECFACT_MODULE_PRIVATE_SIGN_KEY_PASSPHRASE` +- **AND** SHALL commit the updated manifests back to the PR branch + +#### Scenario: PR to main approved with package module changes + +- **WHEN** a pull request targeting `main` is approved +- **AND** the PR contains changes to one or more files under `packages/` +- **THEN** the CI signing workflow SHALL sign all changed manifests relative to the merge-base + between the PR head and `origin/main` +- **AND** SHALL commit the signed manifests back to the PR branch before merge + +#### Scenario: PR approved with no package changes + +- **WHEN** a pull request is approved +- **AND** no files under `packages/` have changed relative to the base branch +- **THEN** the CI signing workflow SHALL exit cleanly with no commit + +#### Scenario: Missing signing secret + +- **WHEN** the signing workflow triggers on approval +- **AND** `SPECFACT_MODULE_PRIVATE_SIGN_KEY` is empty or unset, or `SPECFACT_MODULE_PRIVATE_SIGN_KEY_PASSPHRASE` is empty or unset +- **THEN** the workflow SHALL fail before checkout/signing with a clear error naming which secret(s) are missing +- **AND** SHALL NOT commit partial changes + +#### Scenario: Fork PR is out of scope for automated signing + +- **WHEN** a pull request targets `dev` or `main` but the head branch lives in a fork + (`head.repo` differs from the base repository) +- **THEN** the signing workflow SHALL NOT run (the default `GITHUB_TOKEN` cannot push to the + contributor fork; maintainers sign or merge via same-repo branches instead) + +### Requirement: Manifest discovery covers packages directory + +The signing workflow SHALL discover module manifests from the `packages/` directory tree, not only +from `src/specfact_cli/modules/` or `modules/` (which do not exist in this repository). + +#### Scenario: Sign only changed packages manifests + +- **WHEN** the signing workflow runs with changes across multiple packages +- **AND** only a subset of packages have payload changes +- **THEN** only the changed `packages/*/module-package.yaml` files SHALL be signed and committed +- **AND** unchanged package manifests SHALL NOT be modified + +#### Scenario: Sign workflow produces idempotent output + +- **WHEN** the signing workflow runs twice on the same package payload +- **THEN** the resulting `integrity:` block SHALL be byte-for-byte identical +- **AND** the second run SHALL produce no git diff and SHALL skip the commit + diff --git a/openspec/specs/code-review-bug-finding/spec.md b/openspec/specs/code-review-bug-finding/spec.md new file mode 100644 index 00000000..28f5b13e --- /dev/null +++ b/openspec/specs/code-review-bug-finding/spec.md @@ -0,0 +1,57 @@ +# code-review-bug-finding Specification + +## Purpose +TBD - created by archiving change code-review-bug-finding-and-sidecar-venv-fix. Update Purpose after archive. +## Requirements +### Requirement: Semgrep bug-finding rules pass + +The system SHALL run a second semgrep pass using a `bugs.yaml` config alongside +the existing `clean_code.yaml` pass. The `bugs.yaml` config SHALL cover Python +security and correctness patterns. When `bugs.yaml` is absent the pass SHALL be +silently skipped without error. + +#### Scenario: bugs.yaml present — security findings emitted + +- **WHEN** `.semgrep/bugs.yaml` exists in the bundle +- **AND** `run_semgrep_bugs` is called on Python files matching a bug rule +- **THEN** `ReviewFinding` records are returned with `category="security"` or `category="clean_code"` +- **AND** findings reference the matched rule id from `bugs.yaml` + +#### Scenario: bugs.yaml absent — pass is a no-op + +- **WHEN** no `.semgrep/bugs.yaml` file is discoverable +- **AND** `run_semgrep_bugs` is called +- **THEN** no finding is returned for the missing bugs pass +- **AND** no exception propagates to the caller + +#### Scenario: bugs.yaml findings are included in the JSON report + +- **WHEN** `specfact code review run --json` is executed on a file matching a bug rule +- **THEN** the output JSON contains findings from both the clean-code and bug-finding passes +- **AND** `run_review` wires `run_semgrep_bugs` alongside the existing `run_semgrep` pass + +### Requirement: CrossHair bug-hunt mode + +The system SHALL support a `--bug-hunt` flag on `specfact code review run` that +increases the CrossHair per-path timeout to 10 seconds and the total CrossHair +timeout to 120 seconds. Without `--bug-hunt` the existing timeouts SHALL remain +unchanged. + +#### Scenario: --bug-hunt increases CrossHair timeouts + +- **WHEN** `specfact code review run --bug-hunt --json ` is executed +- **THEN** CrossHair runs with `--per_path_timeout 10` +- **AND** the total CrossHair subprocess timeout is 120 seconds + +#### Scenario: Default run uses original CrossHair timeouts + +- **WHEN** `specfact code review run --json ` is executed without `--bug-hunt` +- **THEN** CrossHair runs with `--per_path_timeout 2` +- **AND** the total CrossHair subprocess timeout is 30 seconds + +#### Scenario: --bug-hunt is composable with --scope and --out + +- **WHEN** `specfact code review run --bug-hunt --scope full --json --out report.json` is executed +- **THEN** the command completes without error +- **AND** the output JSON is written to `report.json` + diff --git a/openspec/specs/code-review-tool-dependencies/spec.md b/openspec/specs/code-review-tool-dependencies/spec.md new file mode 100644 index 00000000..04b4ac46 --- /dev/null +++ b/openspec/specs/code-review-tool-dependencies/spec.md @@ -0,0 +1,70 @@ +# code-review-tool-dependencies Specification + +## Purpose +TBD - created by archiving change code-review-bug-finding-and-sidecar-venv-fix. Update Purpose after archive. +## Requirements +### Requirement: pip_dependencies cover all external review tools + +The `specfact-code-review` bundle manifest (`packages/specfact-code-review/module-package.yaml`) SHALL declare, under `pip_dependencies`, every PyPI distribution required so that **all external** tools invoked by the default `run_review` pipeline (including the TDD gate and CrossHair) can run in a normal bundle install. + +#### Scenario: Manifest includes core CLI tools + +- **WHEN** a maintainer inspects `module-package.yaml` +- **THEN** `pip_dependencies` includes packages that provide the `ruff`, `radon`, `semgrep`, `basedpyright`, `pylint`, and `crosshair` CLIs on `PATH` after install +- **AND** `pytest` and `pytest-cov` are listed for the targeted test / coverage subprocess + +#### Scenario: New runner adds a new external executable + +- **WHEN** a new subprocess-backed review step is added to the pipeline +- **THEN** the change updates `pip_dependencies` and the canonical tool map (see design D9) in the same delivery + +### Requirement: Runtime detection skips missing tools with a clear tool_error + +Before each external tool subprocess runs, the implementation SHALL verify the tool is available. If the executable is not found on `PATH` (or pytest cannot be launched as today’s gate requires), the implementation SHALL **not** invoke that tool and SHALL return **exactly one** `ReviewFinding` per skipped tool. + +#### Scenario: Missing Ruff executable produces a skip finding + +- **GIVEN** `ruff` is not on `PATH` +- **WHEN** `run_ruff` is executed for a non-empty file list +- **THEN** no `ruff` subprocess is started +- **AND** exactly one finding is returned with `category="tool_error"` and `tool="ruff"` +- **AND** the message states that review checks for `ruff` were **skipped** because it is **not installed** or unavailable, and names the pip package to install (e.g. `ruff`) + +#### Scenario: Missing Semgrep does not raise + +- **GIVEN** `semgrep` is not on `PATH` +- **WHEN** the semgrep review step runs +- **THEN** the step returns a single skip finding as above for `semgrep` +- **AND** no uncaught exception propagates + +#### Scenario: AST clean-code pass requires no extra CLI dependency + +- **WHEN** `run_ast_clean_code` runs +- **THEN** it does not depend on a `pip_dependencies` CLI entry (stdlib / in-package Python only) + +#### Scenario: TDD gate reports pytest unavailable clearly + +- **GIVEN** `pytest` (or coverage support) is missing such that the targeted test subprocess cannot run +- **WHEN** `_evaluate_tdd_gate` would run tests +- **THEN** findings include a `tool_error` for `pytest` (or the agreed tool id) with a skip / not-installed style message referencing `pytest` and/or `pytest-cov` + +### Requirement: No misleading errors when the binary is absent + +If a tool executable is missing, runners SHALL NOT surface generic failures such as “Unable to parse JSON output” that imply the tool ran but returned bad data. + +#### Scenario: Ruff missing is not reported as a parse failure + +- **GIVEN** `ruff` is absent +- **WHEN** `run_ruff` completes +- **THEN** the user-visible message indicates the tool was **skipped** / **not installed**, not a JSON parse error from Ruff output + +### Requirement: Automated guard for manifest vs tool map + +The repository SHALL include an automated check (unit test or small validation script invoked by the normal test suite) that fails if `module-package.yaml` `pip_dependencies` omits any package from the canonical tool → pip map for the default pipeline. + +#### Scenario: Drift fails CI + +- **GIVEN** the canonical map lists a pip package for an active runner +- **WHEN** that package is removed from `pip_dependencies` without updating the map +- **THEN** `hatch run test` (or the chosen gate) fails with a clear assertion message + diff --git a/openspec/specs/contract-runner/spec.md b/openspec/specs/contract-runner/spec.md index 2b0da54c..56f31ec5 100644 --- a/openspec/specs/contract-runner/spec.md +++ b/openspec/specs/contract-runner/spec.md @@ -5,17 +5,28 @@ TBD - created by archiving change code-review-04-contract-test-runners. Update P ## Requirements ### Requirement: icontract Decorator AST Scan and CrossHair Fast Pass -The system SHALL AST-scan changed Python files for public functions missing -`@require` / `@ensure` decorators, and run CrossHair with a 2-second per-path timeout -for counterexample discovery. +The system SHALL AST-scan reviewed Python files for public functions missing +`@require` / `@ensure` decorators, and run CrossHair with a configurable +per-path timeout for counterexample discovery. When no icontract usage is +detected in the reviewed files' package/repo scan roots, `MISSING_ICONTRACT` findings SHALL be +suppressed entirely for that run. -#### Scenario: Public function without icontract decorators produces a contracts finding +#### Scenario: Public function without icontract decorators produces a contracts finding when icontract is in use - **GIVEN** a Python file with a public function lacking icontract decorators +- **AND** at least one file in the reviewed batch's package/repo scan roots imports from `icontract` - **WHEN** `run_contract_check(files=[...])` is called - **THEN** a `ReviewFinding` is returned with `category="contracts"` and `severity="warning"` +#### Scenario: MISSING_ICONTRACT suppressed when no icontract usage detected + +- **GIVEN** the package/repo scan roots for the reviewed Python files contain no `from icontract import` or + `import icontract` statements +- **WHEN** `run_contract_check(files=[...])` is called +- **THEN** no `MISSING_ICONTRACT` findings are returned +- **AND** CrossHair still runs on the files + #### Scenario: Decorated public function produces no contracts finding - **GIVEN** a Python file with a public function decorated with both `@require` and @@ -45,3 +56,10 @@ for counterexample discovery. - **AND** no exception propagates to the caller - **AND** CrossHair unavailability does not produce a blocking finding +#### Scenario: Bug-hunt mode uses extended CrossHair timeouts + +- **GIVEN** `run_contract_check(files=[...], bug_hunt=True)` is called +- **WHEN** CrossHair executes +- **THEN** CrossHair runs with `--per_path_timeout 10` +- **AND** the subprocess timeout is 120 seconds + diff --git a/openspec/specs/github-hierarchy-cache/spec.md b/openspec/specs/github-hierarchy-cache/spec.md index 2a13536e..8c8c1032 100644 --- a/openspec/specs/github-hierarchy-cache/spec.md +++ b/openspec/specs/github-hierarchy-cache/spec.md @@ -3,9 +3,7 @@ ## Purpose This capability standardizes a **repo-local, deterministic GitHub Epic/Feature hierarchy cache** for SpecFact modules and paired `specfact-cli` workflows. It defines how contributors sync hierarchy metadata from GitHub into ignored `.specfact/backlog/` files, how fingerprints detect unchanged trees, and how governance (for example `AGENTS.md` and `docs/agent-rules/`) MUST consult that cache before manual GitHub parent lookups. It builds on the archived OpenSpec change **`governance-03-github-hierarchy-cache`**; shipped behavior and drift against `openspec/**/*.md` remain governed by `openspec/CHANGE_ORDER.md` and the modules release checklist. - ## Requirements - ### Requirement: Repository hierarchy cache sync The repository SHALL provide a deterministic sync mechanism that retrieves GitHub Epic and Feature issues for the current repository and writes a local hierarchy cache under ignored `.specfact/backlog/`. @@ -29,7 +27,28 @@ Repository governance instructions SHALL direct contributors and agents to consu #### Scenario: Cache-first governance guidance -- **WHEN** a contributor reads `AGENTS.md` for GitHub issue setup guidance +- **WHEN** a contributor reads `AGENTS.md` or `openspec/config.yaml` for GitHub issue setup guidance - **THEN** the instructions tell them to consult the local hierarchy cache first - **AND** the instructions define when the sync script must be rerun to refresh stale hierarchy metadata - **AND** the instructions state that the cache is local ephemeral state and must not be committed + +#### Scenario: Session bootstrap refreshes missing or stale cache + +- **WHEN** an agent starts a governance-sensitive session that depends on GitHub hierarchy metadata +- **AND** the local hierarchy cache is missing or stale according to repository-defined freshness rules +- **THEN** the bootstrap guidance SHALL require rerunning the hierarchy cache sync script before continuing with issue-parenting or blocker-resolution work +- **AND** the compact governance flow SHALL treat the refresh as part of deterministic startup rather than an optional later reminder + +#### Scenario: State reuse is scoped to the current repository + +- **WHEN** the local hierarchy cache state file contains a matching hierarchy fingerprint +- **BUT** the state metadata belongs to a different repository or does not identify the repository at all +- **THEN** the sync logic SHALL regenerate the markdown cache instead of incorrectly short-circuiting on fingerprint equality alone +- **AND** the resulting cache SHALL render the current repository identity deterministically + +#### Scenario: CLI reports refresh failures clearly + +- **WHEN** the hierarchy cache sync script encounters a runtime or filesystem error during refresh +- **THEN** the CLI entrypoint SHALL emit a clear failure message to stderr +- **AND** it SHALL exit non-zero so bootstrap and governance flows do not silently continue on an untrusted cache state + diff --git a/openspec/specs/review-cli-contracts/spec.md b/openspec/specs/review-cli-contracts/spec.md index 22627efe..8230a8e1 100644 --- a/openspec/specs/review-cli-contracts/spec.md +++ b/openspec/specs/review-cli-contracts/spec.md @@ -38,3 +38,42 @@ The modules repository SHALL keep CLI review scenarios aligned with the expanded - **THEN** advisory checklist findings are represented without changing the base report schema - **AND** unknown clean-code category names are rejected by the scenario contract layer +### Requirement: Review-run CLI scenarios cover enforcement mode, bug-hunt, focus facets, and severity level + +The modules repository SHALL extend `tests/cli-contracts/specfact-code-review-run.scenarios.yaml` so contract tests exercise the new `specfact code review run` flags together with existing scope and JSON output behaviour. + +#### Scenario: Scenarios cover shadow versus enforce exit behaviour + +- **GIVEN** `tests/cli-contracts/specfact-code-review-run.scenarios.yaml` +- **WHEN** it is validated after this change +- **THEN** it includes at least one scenario asserting `--mode shadow` yields process success (exit `0`) while JSON still reports a failing verdict when findings warrant it +- **AND** it includes a control scenario showing `--mode enforce` (or default) preserves non-zero exit on blocking failures + +#### Scenario: Scenarios cover --bug-hunt in shadow and enforce modes + +- **GIVEN** `tests/cli-contracts/specfact-code-review-run.scenarios.yaml` +- **WHEN** it is validated after this change +- **THEN** it includes at least one scenario with `--bug-hunt --mode shadow` +- **AND** it includes at least one scenario with `--bug-hunt --mode enforce` + +#### Scenario: Scenarios cover --focus facets + +- **GIVEN** the same scenario file +- **WHEN** it is validated +- **THEN** it includes coverage for `--focus` union behaviour (e.g. `source` + `docs`) and for `--focus tests` narrowing the file set +- **AND** it includes coverage for `--bug-hunt` composed with `--focus` + +#### Scenario: Scenarios cover --level filtering + +- **GIVEN** the same scenario file +- **WHEN** it is validated +- **THEN** it includes at least one scenario where `--level error` removes warnings from the JSON `findings` list +- **AND** it includes coverage for `--bug-hunt` composed with `--level error` + +#### Scenario: Scenarios cover invalid flag combinations + +- **GIVEN** the same scenario file +- **WHEN** it is validated +- **THEN** it includes an error-path scenario for `--focus` combined with `--include-tests` or `--exclude-tests` +- **AND** `--bug-hunt` remains composable with test-selection flags + diff --git a/openspec/specs/review-run-command/spec.md b/openspec/specs/review-run-command/spec.md index a65bdf8c..09dc3a88 100644 --- a/openspec/specs/review-run-command/spec.md +++ b/openspec/specs/review-run-command/spec.md @@ -82,3 +82,107 @@ The bundle SHALL run the expanded clean-code analysis set as part of the governe - **THEN** the report includes an advisory checklist finding - **AND** the checklist finding does not create a new command surface +### Requirement: --bug-hunt flag on review run command + +The `specfact code review run` command SHALL accept a `--bug-hunt` flag that +enables extended CrossHair timeouts and is composable with all existing flags. + +#### Scenario: --bug-hunt flag accepted without error + +- **GIVEN** `specfact code review run --bug-hunt --json ` is executed +- **WHEN** the command parses its arguments +- **THEN** the command proceeds without a CLI argument error +- **AND** `ReviewRunRequest.bug_hunt` is `True` + +#### Scenario: --bug-hunt flag absent defaults to False + +- **GIVEN** `specfact code review run --json ` is executed without `--bug-hunt` +- **WHEN** the command parses its arguments +- **THEN** `ReviewRunRequest.bug_hunt` is `False` +- **AND** CrossHair uses the standard 2-second per-path timeout + +### Requirement: --mode shadow and --mode enforce + +The `specfact code review run` command SHALL accept `--mode shadow` or `--mode enforce`. + +#### Scenario: Default mode is enforce + +- **GIVEN** `specfact code review run` is invoked without `--mode` +- **WHEN** the command parses its arguments +- **THEN** enforcement behaves as today: `ci_exit_code` reflects blocking findings + +#### Scenario: Shadow mode never returns a failing process exit + +- **GIVEN** a review run that would yield `ci_exit_code == 1` under enforce semantics +- **WHEN** `specfact code review run --mode shadow` completes +- **THEN** the process exit code is `0` +- **AND** `ReviewReport.ci_exit_code` in JSON is `0` +- **AND** `overall_verdict` still reflects the computed verdict (including `FAIL` when applicable) + +#### Scenario: Enforce mode matches legacy exit behaviour + +- **GIVEN** the same findings payload as today for a failing run +- **WHEN** `specfact code review run --mode enforce` completes +- **THEN** process exit and `ci_exit_code` match the pre-change `enforce` default + +#### Scenario: --mode composes with --bug-hunt and --json + +- **WHEN** `specfact code review run --bug-hunt --mode shadow --json --out report.json` is executed +- **THEN** the command parses successfully +- **AND** CrossHair uses bug-hunt timeouts +- **AND** the process exits `0` even if findings would fail under enforce semantics + +### Requirement: Repeatable --focus for source, tests, and docs + +The command SHALL accept repeated `--focus` options with values `source`, `tests`, and `docs`. When at least one `--focus` is present, the reviewed Python file set SHALL be the intersection of the scope-resolved files with the **union** of the selected facets: + +- `tests`: files where `tests` appears in the path’s directory components (same rule as existing test detection). +- `docs`: Python files where `docs` appears in the path’s directory components. +- `source`: Python files that match neither the `tests` nor the `docs` facet. + +#### Scenario: --focus tests restricts to test paths + +- **GIVEN** a repository with both `src/app.py` and `tests/test_app.py` in scope +- **WHEN** `specfact code review run --scope full --focus tests --json` runs +- **THEN** only files under the `tests` facet are analyzed + +#### Scenario: Union of multiple focuses + +- **GIVEN** scope includes `src/a.py`, `tests/t.py`, and `docs/conf.py` +- **WHEN** `specfact code review run --scope full --focus source --focus docs --json` runs +- **THEN** `tests/t.py` is excluded +- **AND** `src/a.py` and `docs/conf.py` are included + +#### Scenario: --focus conflicts with --include-tests + +- **WHEN** `specfact code review run --focus source --include-tests` is parsed +- **THEN** the CLI rejects the combination with a clear error + +#### Scenario: --focus conflicts with --exclude-tests + +- **WHEN** `specfact code review run --focus tests --exclude-tests` is parsed +- **THEN** the CLI rejects the combination with a clear error + +### Requirement: --level error and --level warning + +The command SHALL accept `--level error` or `--level warning` to filter findings **before** scoring and verdict. + +#### Scenario: --level error drops warnings and info + +- **GIVEN** a run that produces both `warning` and `error` severity findings +- **WHEN** `specfact code review run --level error --json` completes +- **THEN** the JSON `findings` list contains only `severity == "error"` items +- **AND** score and verdict are computed from that filtered list + +#### Scenario: --level warning retains errors and warnings + +- **GIVEN** a run that produces `info`, `warning`, and `error` findings +- **WHEN** `specfact code review run --level warning --json` completes +- **THEN** the JSON `findings` list contains no `severity == "info"` items +- **AND** score and verdict are computed from the filtered list + +#### Scenario: Omitted --level keeps all severities + +- **WHEN** `specfact code review run --json` runs without `--level` +- **THEN** all severities appear in output as they do today + diff --git a/openspec/specs/sidecar-route-extraction/spec.md b/openspec/specs/sidecar-route-extraction/spec.md new file mode 100644 index 00000000..502df1a4 --- /dev/null +++ b/openspec/specs/sidecar-route-extraction/spec.md @@ -0,0 +1,43 @@ +# sidecar-route-extraction Specification + +## Purpose +TBD - created by archiving change code-review-bug-finding-and-sidecar-venv-fix. Update Purpose after archive. +## Requirements +### Requirement: Framework extractors exclude .specfact from scan paths + +All sidecar framework extractors (FastAPI, Flask, Django, DRF) SHALL exclude +`.specfact/` directories from Python file discovery. This prevents the sidecar's +own installed venv and workspace artefacts from being scanned as application +source. + +#### Scenario: FastAPI extractor ignores .specfact/venv + +- **GIVEN** a repo with a `.specfact/venv/` directory containing FastAPI source +- **WHEN** the FastAPI extractor runs route extraction on that repo +- **THEN** no routes are extracted from files under `.specfact/` +- **AND** only routes from application source files are returned + +#### Scenario: Flask extractor ignores .specfact/venv + +- **GIVEN** a repo with a `.specfact/venv/` directory containing Flask source +- **WHEN** the Flask extractor runs route extraction on that repo +- **THEN** no routes are extracted from files under `.specfact/` + +#### Scenario: Django extractor ignores .specfact/venv + +- **GIVEN** a repo with a `.specfact/venv/` directory containing Django source +- **WHEN** the Django extractor runs route extraction on that repo +- **THEN** no routes are extracted from files under `.specfact/` + +#### Scenario: Other standard exclusions also apply + +- **WHEN** any framework extractor scans a repo +- **THEN** files under `.git/`, `__pycache__/`, and `node_modules/` are also excluded +- **AND** legitimate application source outside these directories is not affected + +#### Scenario: Route count reflects real application routes only + +- **GIVEN** gpt-researcher repo with 19 real FastAPI routes and `.specfact/venv` installed +- **WHEN** sidecar validation runs route extraction +- **THEN** routes extracted is approximately 19, not 25,947 + From 86ae3ac776c1757d3f373d4194b58de7e6b0bc7b Mon Sep 17 00:00:00 2001 From: omit-test Date: Thu, 16 Apr 2026 21:46:34 +0200 Subject: [PATCH 02/14] Validate bundle docs links against browser URL when paths use parent segments. Filesystem-first resolution for ../ links matched markdown under docs/ while published permalinks under /bundles/ resolve differently in the browser, which hid broken cross-tree and cross-bundle links. Require published-route checks only for that combination, fix affected bundle overview links to use root-absolute /bundles/... and /reference/... targets, and add regression tests. Made-with: Cursor --- docs/bundles/code-review/overview.md | 6 +- docs/bundles/code-review/run.md | 2 +- docs/bundles/codebase/overview.md | 4 +- docs/bundles/govern/overview.md | 2 +- docs/bundles/project/overview.md | 4 +- docs/bundles/spec/overview.md | 4 +- .../TDD_EVIDENCE.md | 8 + scripts/docs_site_validation.py | 43 +++++ ...est_docs_site_validation_link_agreement.py | 162 ++++++++++++++++++ 9 files changed, 224 insertions(+), 11 deletions(-) create mode 100644 tests/unit/scripts/test_docs_site_validation_link_agreement.py diff --git a/docs/bundles/code-review/overview.md b/docs/bundles/code-review/overview.md index 2df3a93a..1c8f42a7 100644 --- a/docs/bundles/code-review/overview.md +++ b/docs/bundles/code-review/overview.md @@ -12,7 +12,7 @@ expertise_level: [beginner, intermediate] The **Code Review** bundle (`nold-ai/specfact-code-review`) extends the shared **`specfact code`** command group with **`review`** workflows: governed review runs, **reward ledger** history, and **house-rules** skill management. -Use it together with the [Codebase](../codebase/overview/) bundle (`import`, `analyze`, `drift`, `validate`, `repro`) on the same `code` surface. +Use it together with the [Codebase](/bundles/codebase/overview/) bundle (`import`, `analyze`, `drift`, `validate`, `repro`) on the same `code` surface. ## Prerequisites @@ -63,5 +63,5 @@ specfact code review rules show --help - [Code review run](../run/) - [Code review ledger](../ledger/) - [Code review rules](../rules/) -- [Code review module](../../modules/code-review/) -- [Codebase bundle overview](../codebase/overview/) — import, drift, validation, repro +- [Code review module](/modules/code-review/) +- [Codebase bundle overview](/bundles/codebase/overview/) — import, drift, validation, repro diff --git a/docs/bundles/code-review/run.md b/docs/bundles/code-review/run.md index e1b3cada..519c5604 100644 --- a/docs/bundles/code-review/run.md +++ b/docs/bundles/code-review/run.md @@ -71,4 +71,4 @@ The review pipeline uses rules, skills, and policy payloads shipped with the ins - [Code review ledger](/bundles/code-review/ledger/) - [Code review rules](/bundles/code-review/rules/) -- [Code review module guide](../../modules/code-review/) +- [Code review module guide](/modules/code-review/) diff --git a/docs/bundles/codebase/overview.md b/docs/bundles/codebase/overview.md index b961d5c1..625abec5 100644 --- a/docs/bundles/codebase/overview.md +++ b/docs/bundles/codebase/overview.md @@ -12,7 +12,7 @@ expertise_level: [beginner, intermediate] The **Codebase** bundle (`nold-ai/specfact-codebase`) mounts under `specfact code` alongside the Code Review bundle. It focuses on **brownfield import**, **contract coverage analysis**, **drift detection**, **sidecar validation**, and **reproducible validation suites**. -For automated review runs (Ruff, Semgrep, ledger, rules), see [Code Review](../code-review/overview/) — also on the `code` command group. +For automated review runs (Ruff, Semgrep, ledger, rules), see [Code Review](/bundles/code-review/overview/) — also on the `code` command group. ## Prerequisites @@ -28,7 +28,7 @@ For automated review runs (Ruff, Semgrep, ledger, rules), see [Code Review](../c | `specfact code import` (default) | Import a repository into a project bundle (`from-code` behavior; see `--help`) | | `specfact code import from-bridge` | Import from an external bridge/export flow | -Advanced import topics: [Project import command features](../project/import-migration/) (cross-bundle). +Advanced import topics: [Project import command features](/bundles/project/import-migration/) (cross-bundle). ### `analyze` — structure and contracts diff --git a/docs/bundles/govern/overview.md b/docs/bundles/govern/overview.md index add1fee4..3e7675b8 100644 --- a/docs/bundles/govern/overview.md +++ b/docs/bundles/govern/overview.md @@ -47,4 +47,4 @@ specfact govern patch apply --help - [Govern enforce](../enforce/) - [Govern patch](../patch/) -- [Command reference](../../reference/commands/) — nested `govern` commands +- [Command reference](/reference/commands/) — nested `govern` commands diff --git a/docs/bundles/project/overview.md b/docs/bundles/project/overview.md index 18e1f8bd..fe8d16af 100644 --- a/docs/bundles/project/overview.md +++ b/docs/bundles/project/overview.md @@ -16,7 +16,7 @@ The **Project** bundle (`nold-ai/specfact-project`) manages SpecFact **project b - SpecFact CLI and a repository with `.specfact/` layout - Bundle installed: `specfact module install nold-ai/specfact-project` -- For backlog-linked flows: install [Backlog](../backlog/overview/) and link a provider +- For backlog-linked flows: install [Backlog](/bundles/backlog/overview/) and link a provider ## Command families @@ -76,7 +76,7 @@ Use the top-level group (`specfact sync --help`). ## Related: codebase import -Brownfield **code import** (`specfact code import`, `specfact import …`) lives in the [Codebase](../codebase/overview/) bundle; it often feeds project bundles. See [Import command features](../import-migration/) for behavior that spans both bundles. +Brownfield **code import** (`specfact code import`, `specfact import …`) lives in the [Codebase](/bundles/codebase/overview/) bundle; it often feeds project bundles. See [Import command features](../import-migration/) for behavior that spans both bundles. ## Bundle-owned prompts and plan templates diff --git a/docs/bundles/spec/overview.md b/docs/bundles/spec/overview.md index 2e7f220c..8e91bb78 100644 --- a/docs/bundles/spec/overview.md +++ b/docs/bundles/spec/overview.md @@ -71,8 +71,8 @@ specfact spec generate contracts --help ## See also -- [Command reference](../../reference/commands/) — bundle-to-command mapping +- [Command reference](/reference/commands/) — bundle-to-command mapping - [Spec validate and backward compatibility](/bundles/spec/validate/) - [Generate Specmatic tests](/bundles/spec/generate-tests/) - [Run a mock server](/bundles/spec/mock/) -- [Contract testing workflow](../../guides/contract-testing-workflow/) +- [Contract testing workflow](/contract-testing-workflow/) diff --git a/openspec/changes/docs-15-code-review-validation-guardrails/TDD_EVIDENCE.md b/openspec/changes/docs-15-code-review-validation-guardrails/TDD_EVIDENCE.md index ec711534..bc00a89c 100644 --- a/openspec/changes/docs-15-code-review-validation-guardrails/TDD_EVIDENCE.md +++ b/openspec/changes/docs-15-code-review-validation-guardrails/TDD_EVIDENCE.md @@ -17,3 +17,11 @@ ## SpecFact code review - `hatch run specfact code review run --json --out .specfact/code-review.json --scope changed` — passing (2026-04-15); evidence at `.specfact/code-review.json`. + +## Follow-up: bundle permalink vs. `..` links (2026-04-16) + +- `hatch run pytest tests/unit/scripts/test_docs_site_validation_link_agreement.py tests/unit/docs/test_docs_review.py::test_authored_internal_docs_links_resolve_to_published_docs_targets -q` — passing. +- `python scripts/check-docs-commands.py` — passing (no findings). +- `hatch run format`, `hatch run lint`, `hatch run type-check` — passing. +- `hatch run contract-test` — passing (624 tests). +- `hatch run specfact code review run --json --out /tmp/code-review-docs15.json scripts/docs_site_validation.py tests/unit/scripts/test_docs_site_validation_link_agreement.py` — **PASS** (`overall_verdict` PASS, `ci_exit_code` 0; report outside gitignored `.specfact/` for inspection). diff --git a/scripts/docs_site_validation.py b/scripts/docs_site_validation.py index a11c3858..4ed87041 100644 --- a/scripts/docs_site_validation.py +++ b/scripts/docs_site_validation.py @@ -3,6 +3,11 @@ Used by ``scripts/check-docs-commands.py``, unit tests, and CI. Resolves Markdown links relative to each page's *published* permalink (browser semantics), not the repository source path. + +For pages whose ``permalink`` is under ``/bundles/``, links whose path +contains parent segments (``..``) are validated against both filesystem +resolution and browser URL resolution, because bundle permalinks do not +mirror the ``docs/bundles//`` directory depth. """ from __future__ import annotations @@ -345,6 +350,30 @@ def _resolve_http_or_opaque(parsed, ctx: DocsLinkResolutionContext) -> tuple[Pat return None +def _published_route_label(ctx: DocsLinkResolutionContext, doc_path: Path) -> str: + """Human-readable route or path for mismatch diagnostics.""" + resolved = doc_path.resolve() + route = ctx.path_to_route.get(resolved) + if route is not None: + return route + try: + return str(resolved.relative_to(ctx.docs_root)) + except ValueError: + return str(resolved) + + +def _link_path_has_parent_traversal(raw_link: str) -> bool: + """True when the link path walks up with ``..`` (repo vs browser divergence risk).""" + stripped = _normalize_jekyll_relative_url(raw_link.strip()) + path_only = urlparse(stripped).path + return ".." in Path(unquote(path_only)).parts + + +def _published_route_is_under_bundles(published_page_route: str) -> bool: + """Bundle overview pages use ``permalink`` paths that diverge from the ``docs/`` tree.""" + return normalize_route(published_page_route).startswith("/bundles/") + + @beartype @require( lambda source, ctx, published_page_route, raw_link: ( @@ -381,6 +410,20 @@ def resolve_internal_link_hybrid( if fs_first: fs_target, fs_err = _resolve_filesystem_relative(source, ctx, raw_link) if fs_target is not None and fs_err is None: + if _link_path_has_parent_traversal(raw_link) and _published_route_is_under_bundles(published_page_route): + pub_target, pub_err = _resolve_published_relative_url(published_page_route, target_path, fragment, ctx) + if pub_err is not None: + return None, pub_err + if pub_target is None: + return fs_target, None + if pub_target.resolve() != fs_target.resolve(): + want = _published_route_label(ctx, fs_target) + got = _published_route_label(ctx, pub_target) + return None, ( + "repository-relative link matches a markdown file on disk, but browsers " + f"resolve it to a different site route than `{want}` (permalink-relative " + f"navigation targets `{got}`); use a root-absolute path such as `{want}`" + ) return fs_target, None pub_target, pub_err = _resolve_published_relative_url(published_page_route, target_path, fragment, ctx) diff --git a/tests/unit/scripts/test_docs_site_validation_link_agreement.py b/tests/unit/scripts/test_docs_site_validation_link_agreement.py new file mode 100644 index 00000000..174bc797 --- /dev/null +++ b/tests/unit/scripts/test_docs_site_validation_link_agreement.py @@ -0,0 +1,162 @@ +"""Tests for published vs filesystem agreement in ``docs_site_validation``.""" + +from __future__ import annotations + +import sys +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[3] +_SCRIPTS_DIR = REPO_ROOT / "scripts" +if str(_SCRIPTS_DIR) not in sys.path: + sys.path.insert(0, str(_SCRIPTS_DIR)) + +import docs_site_validation as dsv # noqa: E402 + + +def _ctx_for(docs: Path) -> dsv.DocsLinkResolutionContext: + route_to_path, path_to_route = dsv.build_route_mappings(docs) + return dsv.DocsLinkResolutionContext(docs, route_to_path, path_to_route, dsv.MODULES_DOCS_HOST) + + +def test_hybrid_rejects_repo_relative_when_disk_and_browser_targets_diverge(tmp_path: Path) -> None: + """``../..`` from a deep ``/bundles/.../`` permalink must not pass on filesystem match alone.""" + docs = tmp_path / "docs" + (docs / "bundles" / "deep").mkdir(parents=True) + (docs / "guides").mkdir(parents=True) + (docs / "bundles" / "deep" / "page.md").write_text( + """--- +layout: default +title: Deep page +permalink: /bundles/deep/here/ +--- +[bad](../../guides/target/) +""", + encoding="utf-8", + ) + (docs / "guides" / "target.md").write_text( + """--- +layout: default +title: Target +permalink: /guides/target/ +--- +""", + encoding="utf-8", + ) + ctx = _ctx_for(docs) + source = docs / "bundles" / "deep" / "page.md" + target, err = dsv.resolve_internal_link_hybrid( + source=source, + ctx=ctx, + published_page_route="/bundles/deep/here/", + raw_link="../../guides/target/", + ) + assert target is None + assert err is not None + assert "missing published route" in err or "root-absolute" in err + + +def test_hybrid_accepts_root_absolute_when_target_exists(tmp_path: Path) -> None: + docs = tmp_path / "docs" + (docs / "bundles" / "deep").mkdir(parents=True) + (docs / "guides").mkdir(parents=True) + (docs / "bundles" / "deep" / "page.md").write_text( + """--- +layout: default +title: Deep page +permalink: /bundles/deep/here/ +--- +[ok](/guides/target/) +""", + encoding="utf-8", + ) + (docs / "guides" / "target.md").write_text( + """--- +layout: default +title: Target +permalink: /guides/target/ +--- +""", + encoding="utf-8", + ) + ctx = _ctx_for(docs) + source = docs / "bundles" / "deep" / "page.md" + target, err = dsv.resolve_internal_link_hybrid( + source=source, + ctx=ctx, + published_page_route="/bundles/deep/here/", + raw_link="/guides/target/", + ) + assert err is None + assert target == (docs / "guides" / "target.md").resolve() + + +def test_parent_traversal_outside_bundles_keeps_filesystem_first_semantics(tmp_path: Path) -> None: + """``../`` from non-``/bundles/`` permalinks must not require published agreement (legacy).""" + docs = tmp_path / "docs" + (docs / "adapters").mkdir(parents=True) + (docs / "guides").mkdir(parents=True) + (docs / "adapters" / "page.md").write_text( + """--- +layout: default +title: Adapter page +permalink: /adapters/page/ +--- +[guide](../guides/target.md) +""", + encoding="utf-8", + ) + (docs / "guides" / "target.md").write_text( + """--- +layout: default +title: Guide +permalink: /guides/target/ +--- +""", + encoding="utf-8", + ) + ctx = _ctx_for(docs) + source = docs / "adapters" / "page.md" + target, err = dsv.resolve_internal_link_hybrid( + source=source, + ctx=ctx, + published_page_route="/adapters/page/", + raw_link="../guides/target.md", + ) + assert err is None + assert target == (docs / "guides" / "target.md").resolve() + + +def test_hybrid_uses_published_semantics_when_filesystem_misses(tmp_path: Path) -> None: + """Sibling ``../run/`` style links often miss on disk but are valid on the site URL.""" + docs = tmp_path / "docs" + (docs / "bundles" / "cr").mkdir(parents=True) + (docs / "bundles" / "cr" / "overview.md").write_text( + """--- +layout: default +title: Overview +permalink: /bundles/cr/here/ +--- +[run](../run/) +""", + encoding="utf-8", + ) + (docs / "bundles" / "cr" / "run.md").write_text( + """--- +layout: default +title: Run +permalink: /bundles/cr/run/ +--- +""", + encoding="utf-8", + ) + ctx = _ctx_for(docs) + source = docs / "bundles" / "cr" / "overview.md" + target, err = dsv.resolve_internal_link_hybrid( + source=source, + ctx=ctx, + published_page_route="/bundles/cr/here/", + raw_link="../run/", + ) + assert err is None + assert target == (docs / "bundles" / "cr" / "run.md").resolve() From 95c7b39e9518cd5857e796a93e545bb1a1240309 Mon Sep 17 00:00:00 2001 From: omit-test Date: Thu, 16 Apr 2026 21:52:25 +0200 Subject: [PATCH 03/14] OpenSpec: fix docs-15 delta spec heading levels for strict validate. Use canonical ## MODIFIED Requirements, ### Requirement, and #### Scenario blocks so openspec validate --strict passes and the change can be archived. Made-with: Cursor --- .../TDD_EVIDENCE.md | 1 + .../specs/bundle-overview-pages/spec.md | 14 ++++---- .../modules-docs-command-validation/spec.md | 35 +++++++++++++------ .../specs/modules-docs-publishing/spec.md | 14 ++++---- .../modules-pre-commit-quality-parity/spec.md | 14 ++++---- .../specs/review-run-command/spec.md | 14 ++++---- 6 files changed, 57 insertions(+), 35 deletions(-) diff --git a/openspec/changes/docs-15-code-review-validation-guardrails/TDD_EVIDENCE.md b/openspec/changes/docs-15-code-review-validation-guardrails/TDD_EVIDENCE.md index bc00a89c..6cef774f 100644 --- a/openspec/changes/docs-15-code-review-validation-guardrails/TDD_EVIDENCE.md +++ b/openspec/changes/docs-15-code-review-validation-guardrails/TDD_EVIDENCE.md @@ -9,6 +9,7 @@ ## OpenSpec - `openspec validate docs-15-code-review-validation-guardrails --strict` — passing. +- 2026-04-16: Spec delta headers normalized (`## MODIFIED Requirements`, `### Requirement:`, `#### Scenario:`) under `openspec/changes/docs-15-code-review-validation-guardrails/specs/**/spec.md` so strict validation and future `openspec archive` succeed; `openspec validate docs-15-code-review-validation-guardrails --strict` — passing. ## Format / lint diff --git a/openspec/changes/docs-15-code-review-validation-guardrails/specs/bundle-overview-pages/spec.md b/openspec/changes/docs-15-code-review-validation-guardrails/specs/bundle-overview-pages/spec.md index 556ca3b0..71c0dd67 100644 --- a/openspec/changes/docs-15-code-review-validation-guardrails/specs/bundle-overview-pages/spec.md +++ b/openspec/changes/docs-15-code-review-validation-guardrails/specs/bundle-overview-pages/spec.md @@ -1,26 +1,28 @@ -# ADDED Requirements +# Bundle overview pages -## Requirement: Bundle overview links SHALL resolve as published URLs +## MODIFIED Requirements + +### Requirement: Bundle overview links SHALL resolve as published URLs Bundle overview pages SHALL use links that resolve correctly from the published overview permalink route, including "See also", prerequisite, deep-dive, and related-bundle links. -### Scenario: Code Review overview links to run page +#### Scenario: Code Review overview links to run page - **WHEN** the Code Review overview page is published at `/bundles/code-review/overview/` - **THEN** its "Code review run" link resolves to `/bundles/code-review/run/` - **AND** the link does not resolve to `/bundles/code-review/overview/run/` -### Scenario: Cross-bundle overview link resolves +#### Scenario: Cross-bundle overview link resolves - **WHEN** a bundle overview page links to another bundle overview page - **THEN** the link resolves to the target bundle's canonical published overview route - **AND** docs validation fails if the link resolves to a route nested under the source overview page -## Requirement: Bundle overview-related links SHALL be covered by docs validation tests +### Requirement: Bundle overview-related links SHALL be covered by docs validation tests The bundle overview docs test suite SHALL include coverage that fails when any overview page contains a body link that is valid by source-file path but broken under published permalink semantics. -### Scenario: Source-valid but published-broken link is rejected +#### Scenario: Source-valid but published-broken link is rejected - **WHEN** an overview page links to a sibling page using a source-file-relative shorthand that would publish below the overview permalink - **THEN** the overview link test reports the generated public route mismatch diff --git a/openspec/changes/docs-15-code-review-validation-guardrails/specs/modules-docs-command-validation/spec.md b/openspec/changes/docs-15-code-review-validation-guardrails/specs/modules-docs-command-validation/spec.md index ecb7a834..62cddbcf 100644 --- a/openspec/changes/docs-15-code-review-validation-guardrails/specs/modules-docs-command-validation/spec.md +++ b/openspec/changes/docs-15-code-review-validation-guardrails/specs/modules-docs-command-validation/spec.md @@ -1,54 +1,67 @@ -# ADDED Requirements +# Modules docs command validation -## Requirement: Docs validation SHALL validate published-route body links +## MODIFIED Requirements + +### Requirement: Docs validation SHALL validate published-route body links The modules docs validation command SHALL validate internal links in authored page bodies using the page's published permalink route as the URL base, and SHALL fail when a link resolves to a route that is not backed by a published page or an accepted redirect route. -### Scenario: Overview relative link fails under published route semantics +#### Scenario: Overview relative link fails under published route semantics - **WHEN** a page with permalink `/bundles/code-review/overview/` contains a body link `run/` - **THEN** docs validation resolves the link as `/bundles/code-review/overview/run/` - **AND** docs validation reports a `published-link` finding when that route is not published or redirected - **AND** the validation command exits non-zero -### Scenario: Published-route-safe link passes +#### Scenario: Published-route-safe link passes - **WHEN** a page with permalink `/bundles/code-review/overview/` links to `/bundles/code-review/run/` - **THEN** docs validation resolves the link to the published Code Review run page - **AND** no `published-link` finding is emitted for that link -## Requirement: Docs validation SHALL reject incomplete published page front matter +### Requirement: Docs validation SHALL reject incomplete published page front matter The modules docs validation command SHALL reject published Markdown pages whose front matter is missing required route and display metadata, including `layout`, `title`, and `permalink`, unless the page has an explicit documented exemption recognized by the validator. -### Scenario: Redirect page missing title fails +#### Scenario: Redirect page missing title fails - **WHEN** a published Markdown redirect page has `layout` and `permalink` but no `title` - **THEN** docs validation reports a `frontmatter` finding for the missing `title` - **AND** the validation command exits non-zero -### Scenario: Complete published page passes front matter validation +#### Scenario: Complete published page passes front matter validation - **WHEN** a published Markdown page defines `layout`, `title`, and `permalink` - **THEN** docs validation accepts the page front matter - **AND** no `frontmatter` finding is emitted for that page -## Requirement: Docs validation SHALL expose stable finding categories +### Requirement: Docs validation SHALL expose stable finding categories The modules docs validation command SHALL emit stable category names for each class of documentation defect so CI logs, pre-commit output, and tests can assert category coverage without matching brittle prose. -### Scenario: Multiple docs defect categories are reported together +#### Scenario: Multiple docs defect categories are reported together - **WHEN** docs validation finds an unknown command example, a broken published route link, and incomplete front matter - **THEN** the output includes `command`, `published-link`, and `frontmatter` categories - **AND** the validation command exits non-zero after reporting all discovered docs findings -## Requirement: Docs validation SHALL detect docs build dependency drift +### Requirement: Docs validation SHALL detect docs build dependency drift The modules docs validation workflow SHALL include a docs build dependency health check that fails when the checked-in Jekyll dependency lock cannot be installed for the docs site. -### Scenario: Stale Gemfile lock fails docs dependency validation +#### Scenario: Stale Gemfile lock fails docs dependency validation - **WHEN** the docs dependency install command cannot resolve a locked gem version from the configured sources - **THEN** the docs workflow reports a `docs-build-dependency` failure - **AND** Pages publication does not proceed as healthy + +### Requirement: Bundle permalink pages SHALL validate parent-segment links against browser routes + +For pages whose canonical published route is under `/bundles/`, docs validation SHALL treat Markdown links whose path contains parent-directory segments (`..`) as unsafe unless the filesystem-resolved target file matches the target resolved from the page permalink using browser URL rules. + +#### Scenario: Deep bundle overview rejects filesystem-only match for `../../` links + +- **WHEN** a bundle overview page is published under `/bundles//overview/` (or another deep `/bundles/` permalink) +- **AND** its body uses a `../` or `../../` link that reaches a markdown file on disk but resolves to a different or missing public route +- **THEN** docs validation reports a `published-link` finding (missing route or route mismatch) +- **AND** the validation command exits non-zero diff --git a/openspec/changes/docs-15-code-review-validation-guardrails/specs/modules-docs-publishing/spec.md b/openspec/changes/docs-15-code-review-validation-guardrails/specs/modules-docs-publishing/spec.md index c067c0fc..1e305958 100644 --- a/openspec/changes/docs-15-code-review-validation-guardrails/specs/modules-docs-publishing/spec.md +++ b/openspec/changes/docs-15-code-review-validation-guardrails/specs/modules-docs-publishing/spec.md @@ -1,26 +1,28 @@ -# ADDED Requirements +# Modules docs publishing -## Requirement: Docs publishing SHALL validate generated-site readiness before deploy +## MODIFIED Requirements + +### Requirement: Docs publishing SHALL validate generated-site readiness before deploy The docs publishing workflow SHALL run docs dependency installation, Jekyll build, and generated-site validation before uploading or deploying the Pages artifact. -### Scenario: Dependency install failure blocks Pages artifact +#### Scenario: Dependency install failure blocks Pages artifact - **WHEN** `bundle install` fails for the docs site - **THEN** the docs publishing workflow fails before `jekyll build` - **AND** no Pages artifact is uploaded from that run -### Scenario: Generated site contains broken internal link +#### Scenario: Generated site contains broken internal link - **WHEN** the generated `_site` HTML contains an internal `modules.specfact.io` link whose route is not present in the generated site or redirect set - **THEN** generated-site validation reports the broken route - **AND** the docs publishing workflow fails before deployment -## Requirement: Docs review CI SHALL run the same deterministic docs validators as local checks +### Requirement: Docs review CI SHALL run the same deterministic docs validators as local checks The docs review workflow SHALL run the deterministic docs validators used by local pre-commit, plus the docs unit tests, so PR and local validation enforce the same defect categories. -### Scenario: Docs-only pull request has broken published link +#### Scenario: Docs-only pull request has broken published link - **WHEN** a pull request changes only Markdown files under `docs/` - **THEN** the docs review workflow runs published-route link validation diff --git a/openspec/changes/docs-15-code-review-validation-guardrails/specs/modules-pre-commit-quality-parity/spec.md b/openspec/changes/docs-15-code-review-validation-guardrails/specs/modules-pre-commit-quality-parity/spec.md index 8d74260f..e467772a 100644 --- a/openspec/changes/docs-15-code-review-validation-guardrails/specs/modules-pre-commit-quality-parity/spec.md +++ b/openspec/changes/docs-15-code-review-validation-guardrails/specs/modules-pre-commit-quality-parity/spec.md @@ -1,26 +1,28 @@ -# ADDED Requirements +# Modules pre-commit quality parity -## Requirement: Docs-only pre-commit changes SHALL run docs validation before safe bypass +## MODIFIED Requirements + +### Requirement: Docs-only pre-commit changes SHALL run docs validation before safe bypass The modules repo pre-commit helper SHALL run deterministic docs validation for staged docs-only changes before skipping code-specific review and contract-test stages. -### Scenario: Docs-only commit with broken link fails pre-commit +#### Scenario: Docs-only commit with broken link fails pre-commit - **WHEN** only docs files are staged and one staged docs page introduces a broken published-route link - **THEN** pre-commit runs docs validation - **AND** pre-commit fails before reporting the change as safe -### Scenario: Docs-only commit with valid docs skips code-specific checks +#### Scenario: Docs-only commit with valid docs skips code-specific checks - **WHEN** only docs files are staged and docs validation passes - **THEN** pre-commit may skip code review and contract-test stages - **AND** pre-commit reports that docs validation passed before applying the safe-change bypass -## Requirement: Pre-commit and CI docs gates SHALL share validation categories +### Requirement: Pre-commit and CI docs gates SHALL share validation categories The local pre-commit docs gate and CI docs review workflow SHALL report the same docs validation categories for matching defects. -### Scenario: Same broken docs route reports same category locally and in CI +#### Scenario: Same broken docs route reports same category locally and in CI - **WHEN** a docs change introduces a broken generated public route - **THEN** local pre-commit reports a `published-link` finding diff --git a/openspec/changes/docs-15-code-review-validation-guardrails/specs/review-run-command/spec.md b/openspec/changes/docs-15-code-review-validation-guardrails/specs/review-run-command/spec.md index a92c3afd..e0fb1744 100644 --- a/openspec/changes/docs-15-code-review-validation-guardrails/specs/review-run-command/spec.md +++ b/openspec/changes/docs-15-code-review-validation-guardrails/specs/review-run-command/spec.md @@ -1,26 +1,28 @@ -# ADDED Requirements +# Review run command (docs) -## Requirement: Code Review run docs SHALL cover the public option surface +## MODIFIED Requirements + +### Requirement: Code Review run docs SHALL cover the public option surface The Code Review run documentation SHALL describe every supported public `specfact code review run` option that affects targeting, output, exit behavior, analysis depth, or filtering. -### Scenario: Newly added review options are documented +#### Scenario: Newly added review options are documented - **WHEN** the `specfact code review run` Typer command exposes `--bug-hunt`, `--mode`, `--focus`, and `--level` - **THEN** the Code Review run guide documents those options in its key option table or equivalent option section - **AND** docs validation fails if any of those public options are missing from the run guide -### Scenario: Invalid option combinations are documented +#### Scenario: Invalid option combinations are documented - **WHEN** the command rejects combinations such as positional files with `--scope` or `--path`, or `--focus` with `--include-tests` - **THEN** the Code Review docs describe the invalid combination behavior - **AND** the docs include a user-facing alternative for the supported targeting style aligned with the public **`run`** signature (**`files: list[Path]`**): pass explicit **positional files** (file paths) for a fixed review set, or use **`--scope`** / **`--path`** (without positional files) to auto-discover targets from the repo -## Requirement: Code Review docs SHALL stay aligned with review behavior +### Requirement: Code Review docs SHALL stay aligned with review behavior The Code Review docs SHALL describe current review run behavior for JSON output, shadow/enforce mode, progress output, focus filtering, severity filtering, bug-hunt budgets, and test inclusion semantics. -### Scenario: Docs parity check detects missing behavior section +#### Scenario: Docs parity check detects missing behavior section - **WHEN** the command implementation includes a public behavior that affects output, exit code, target selection, or analysis cost - **THEN** docs parity validation checks that the behavior is represented in the Code Review run docs From 940028be3ab9a8edad29458fd7363edc11306e4f Mon Sep 17 00:00:00 2001 From: omit-test Date: Thu, 16 Apr 2026 21:58:35 +0200 Subject: [PATCH 04/14] Docs: expand code review run examples and clarify Python vs site docs. Add worked examples for shadow mode, JSON output paths, --focus facets, folder-scoped --path runs, --level, --bug-hunt, noise and interactive flags. Document that run reviews .py/.pyi only and point Markdown validation to check-docs-commands. Fix bundle overview cross-links; link module notes to the bundle run guide for copy-pastable recipes. Made-with: Cursor --- docs/bundles/code-review/run.md | 53 +++++++++++++++++++ docs/modules/code-review.md | 5 ++ .../TDD_EVIDENCE.md | 5 ++ 3 files changed, 63 insertions(+) diff --git a/docs/bundles/code-review/run.md b/docs/bundles/code-review/run.md index 519c5604..8c96464f 100644 --- a/docs/bundles/code-review/run.md +++ b/docs/bundles/code-review/run.md @@ -16,6 +16,8 @@ expertise_level: [intermediate, advanced] The command prints **progress** to the terminal (spinner/status while the pipeline prepares and runs). With **`--json`**, it writes a machine-readable **`ReviewReport`** JSON file (defaulting to **`review-report.json`** in the working directory when **`--out`** is omitted). +The pipeline reviews **`.py`** and **`.pyi`** only. The **`--focus docs`** facet selects Python files whose path contains a **`docs/`** directory segment (for example tooling beside the Jekyll site), not Markdown documentation pages. For published-site link, front matter, and command-example checks on the modules docs tree, run **`python scripts/check-docs-commands.py`** in this repository (see CI and contributing docs). + ## Command - `specfact code review run [FILES...]` @@ -55,12 +57,63 @@ The Typer entrypoint validates **review flags** first: it raises **`typer.BadPar ## Examples +### Auto-discovered scope (omit positional files) + ```bash +# Tracked + untracked changes; tests excluded by default for auto-scope specfact code review run --scope changed + +# Same, with bug-hunt heuristics on the discovered file set +specfact code review run --scope changed --bug-hunt + +# Full index, limited to one package (repeat --path for more repo-relative prefixes) specfact code review run --scope full --path packages/specfact-code-review + +# Package sources plus that package’s unit tests +specfact code review run --scope full --path packages/specfact-code-review --path tests/unit/specfact_code_review + +# Warnings dropped before scoring (affects JSON, verdict text, and ci_exit_code) +specfact code review run --scope changed --level warning + +# Longer CrossHair budgets for exploratory bug-hunt pass (with explicit files) +specfact code review run --bug-hunt --json --out /tmp/review-bughunt.json packages/specfact-code-review/src/specfact_code_review/run/commands.py +``` + +### Shadow mode and JSON to a file + +**`--mode shadow`** runs the full toolchain but forces process exit code **`0`** and JSON **`ci_exit_code`** **`0`** so callers can ingest reports without failing a step; **`overall_verdict`** still reflects the real outcome. + +```bash +specfact code review run --scope changed --mode shadow --json --out /tmp/review-report.json +``` + +### `--focus` facets (repeatable) + +Use **`--focus`** with **`source`**, **`tests`**, and/or **`docs`** (union of facets, then intersect with scope). Do not combine **`--focus`** with **`--include-tests`** or **`--exclude-tests`**. + +```bash +specfact code review run --scope changed --focus tests +specfact code review run --scope full --path packages/specfact-code-review --focus source +specfact code review run --scope full --focus docs +``` + +### Positional files (explicit Python paths) + +Do not pass **`--scope`** or **`--path`** when **`FILES...`** are present. + +```bash specfact code review run --json --out /tmp/review-report.json packages/specfact-code-review/src/specfact_code_review/run/commands.py specfact code review run --score-only packages/specfact-code-review/src/specfact_code_review/run/commands.py specfact code review run --fix packages/specfact-code-review/src/specfact_code_review/run/commands.py +specfact code review run --no-tests packages/specfact-code-review/src/specfact_code_review/run/commands.py +``` + +### Noise and interactive test inclusion + +```bash +specfact code review run --scope changed --include-noise +specfact code review run --scope changed --suppress-noise +specfact code review run --scope changed --interactive ``` ## Bundle-owned resources diff --git a/docs/modules/code-review.md b/docs/modules/code-review.md index ce498abe..10acd867 100644 --- a/docs/modules/code-review.md +++ b/docs/modules/code-review.md @@ -101,6 +101,11 @@ specfact code review run --scope full --path packages/specfact-code-review specfact code review run --scope changed --path packages/specfact-code-review --path tests/unit/specfact_code_review ``` +Copy-pastable recipes for **shadow mode**, **JSON `--out`**, **`--focus`** +(`source` / `tests` / `docs` Python only), **noise flags**, and **interactive** +test prompts live in the [Code review run](/bundles/code-review/run/) bundle +guide (same Typer surface as this section). + Positional `FILES...` cannot be mixed with **`--scope`** or **`--path`** (see **Invalid combinations** above). diff --git a/openspec/changes/docs-15-code-review-validation-guardrails/TDD_EVIDENCE.md b/openspec/changes/docs-15-code-review-validation-guardrails/TDD_EVIDENCE.md index 6cef774f..a5ea8e80 100644 --- a/openspec/changes/docs-15-code-review-validation-guardrails/TDD_EVIDENCE.md +++ b/openspec/changes/docs-15-code-review-validation-guardrails/TDD_EVIDENCE.md @@ -19,6 +19,11 @@ - `hatch run specfact code review run --json --out .specfact/code-review.json --scope changed` — passing (2026-04-15); evidence at `.specfact/code-review.json`. +## Code Review run guide examples (2026-04-16) + +- Expanded `docs/bundles/code-review/run.md` with worked examples (auto-scope, `--path`, shadow + `--json` + `--out`, `--focus`, positional files, `--level`, `--bug-hunt`, noise/interactive); clarified Python-only scope vs Markdown docs validation (`scripts/check-docs-commands.py`). Cross-links on bundle overview fixed to root-absolute routes. `docs/modules/code-review.md` points readers to the bundle guide for recipes. +- `hatch run pytest tests/unit/docs/test_code_review_docs_parity.py …` and `python scripts/check-docs-commands.py` — passing. + ## Follow-up: bundle permalink vs. `..` links (2026-04-16) - `hatch run pytest tests/unit/scripts/test_docs_site_validation_link_agreement.py tests/unit/docs/test_docs_review.py::test_authored_internal_docs_links_resolve_to_published_docs_targets -q` — passing. From e2ee98360fdd68d8eedf26eb6be56db6989db07a Mon Sep 17 00:00:00 2001 From: omit-test Date: Thu, 16 Apr 2026 22:16:09 +0200 Subject: [PATCH 05/14] fix(code-review): coerce pylint line 0 and empty messages for ReviewFinding. Pylint JSON can use line 0 for file-scoped messages; ReviewFinding requires line >= 1, so Pydantic ValidationError was raised, caught as ValueError, and the whole pylint step collapsed into a single tool_error. Normalize line to at least 1, substitute a placeholder for blank messages, strip stdout before JSON parse, and add regression tests. Made-with: Cursor --- .../specfact-code-review/module-package.yaml | 5 +- .../tools/pylint_runner.py | 32 ++++++++--- .../tools/test_pylint_runner.py | 55 +++++++++++++++++++ 3 files changed, 82 insertions(+), 10 deletions(-) diff --git a/packages/specfact-code-review/module-package.yaml b/packages/specfact-code-review/module-package.yaml index 7483d4f0..723188d7 100644 --- a/packages/specfact-code-review/module-package.yaml +++ b/packages/specfact-code-review/module-package.yaml @@ -1,5 +1,5 @@ name: nold-ai/specfact-code-review -version: 0.47.9 +version: 0.47.10 commands: - code tier: official @@ -23,5 +23,4 @@ description: Official SpecFact code review bundle package. category: codebase bundle_group_command: code integrity: - checksum: sha256:64d68f426a6140fdff3ceffd51c3960fb36745a040068722ae37271dc378659e - signature: PcP7OeNMkZqTtRpRBkpr2v2od0RX7fM4VCKXyWAYyP7bBRsKyqyRJI5oxX/0BEIWPt29o6y4t93WyJisWgWKBQ== + checksum: sha256:61d8c322b10fadbc91a5148eda0ef605c9b03252a13afe31156132e496dc09fd diff --git a/packages/specfact-code-review/src/specfact_code_review/tools/pylint_runner.py b/packages/specfact-code-review/src/specfact_code_review/tools/pylint_runner.py index af333ff4..1bfc9224 100644 --- a/packages/specfact-code-review/src/specfact_code_review/tools/pylint_runner.py +++ b/packages/specfact-code-review/src/specfact_code_review/tools/pylint_runner.py @@ -44,6 +44,25 @@ def _category_for_message_id(message_id: str) -> Literal["architecture", "style" return "style" +def _coerce_pylint_line(raw: object) -> int: + """Pylint may emit ``0`` for file- or config-scoped messages; ``ReviewFinding`` requires ``line >= 1``.""" + if raw is None or isinstance(raw, bool): + return 1 + if isinstance(raw, int): + return raw if raw >= 1 else 1 + if isinstance(raw, float): + as_int = int(raw) + return as_int if as_int >= 1 else 1 + return 1 + + +def _coerce_pylint_message(raw: object) -> str: + """Pylint occasionally emits an empty ``msg``; governed findings require non-blank text.""" + if isinstance(raw, str) and raw.strip(): + return raw + return "(pylint provided no message text)" + + def _finding_from_item(item: object, *, allowed_paths: set[str]) -> ReviewFinding | None: if not isinstance(item, dict): raise ValueError("pylint finding must be an object") @@ -57,12 +76,8 @@ def _finding_from_item(item: object, *, allowed_paths: set[str]) -> ReviewFindin message_id = item["message-id"] if not isinstance(message_id, str): raise ValueError("pylint message-id must be a string") - line = item["line"] - if not isinstance(line, int): - raise ValueError("pylint line must be an integer") - message = item["message"] - if not isinstance(message, str): - raise ValueError("pylint message must be a string") + line = _coerce_pylint_line(item.get("line")) + message = _coerce_pylint_message(item.get("message")) return ReviewFinding( category=_category_for_message_id(message_id), @@ -77,7 +92,10 @@ def _finding_from_item(item: object, *, allowed_paths: set[str]) -> ReviewFindin def _payload_from_output(stdout: str) -> list[object]: - payload = json.loads(stdout) + stripped = stdout.strip() + if not stripped: + return [] + payload = json.loads(stripped) if not isinstance(payload, list): raise ValueError("pylint output must be a list") return payload diff --git a/tests/unit/specfact_code_review/tools/test_pylint_runner.py b/tests/unit/specfact_code_review/tools/test_pylint_runner.py index b981f14a..528cdcd6 100644 --- a/tests/unit/specfact_code_review/tools/test_pylint_runner.py +++ b/tests/unit/specfact_code_review/tools/test_pylint_runner.py @@ -97,6 +97,61 @@ def test_run_pylint_returns_tool_error_on_parse_error(tmp_path: Path, monkeypatc assert findings[0].tool == "pylint" +def test_run_pylint_coerces_line_zero_to_one(tmp_path: Path, monkeypatch: MonkeyPatch) -> None: + file_path = tmp_path / "target.py" + payload = [ + { + "message-id": "C0301", + "path": str(file_path), + "line": 0, + "message": "line too long", + } + ] + monkeypatch.setattr(subprocess, "run", Mock(return_value=completed_process("pylint", stdout=json.dumps(payload)))) + + findings = run_pylint([file_path]) + + assert len(findings) == 1 + assert findings[0].line == 1 + + +def test_run_pylint_coerces_empty_message_text(tmp_path: Path, monkeypatch: MonkeyPatch) -> None: + file_path = tmp_path / "target.py" + payload = [ + { + "message-id": "C0301", + "path": str(file_path), + "line": 3, + "message": "", + } + ] + monkeypatch.setattr(subprocess, "run", Mock(return_value=completed_process("pylint", stdout=json.dumps(payload)))) + + findings = run_pylint([file_path]) + + assert len(findings) == 1 + assert findings[0].message == "(pylint provided no message text)" + + +def test_run_pylint_parses_json_with_surrounding_whitespace(tmp_path: Path, monkeypatch: MonkeyPatch) -> None: + file_path = tmp_path / "target.py" + payload = [ + { + "message-id": "W0702", + "path": str(file_path), + "line": 7, + "message": "No exception type(s) specified", + } + ] + stdout = "\n " + json.dumps(payload) + " \n" + monkeypatch.setattr(subprocess, "run", Mock(return_value=completed_process("pylint", stdout=stdout, returncode=16))) + + findings = run_pylint([file_path]) + + assert len(findings) == 1 + assert findings[0].rule == "W0702" + + def test_run_pylint_returns_tool_error_for_invalid_payload_item(tmp_path: Path, monkeypatch: MonkeyPatch) -> None: file_path = tmp_path / "target.py" payload = [{"path": str(file_path), "line": 7, "message": "No exception type(s) specified"}] From 097111c3d243e2eb04e0f20c9b2a83a099f4d8b4 Mon Sep 17 00:00:00 2001 From: omit-test Date: Thu, 16 Apr 2026 22:22:37 +0200 Subject: [PATCH 06/14] docs(code-review): correct --level example and table for review filter. --level warning still includes warnings in scoring; only --level error drops warnings. Align run guide example and module notes with runner behavior. Made-with: Cursor --- docs/bundles/code-review/run.md | 6 +++--- docs/modules/code-review.md | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/bundles/code-review/run.md b/docs/bundles/code-review/run.md index 8c96464f..e8b406b6 100644 --- a/docs/bundles/code-review/run.md +++ b/docs/bundles/code-review/run.md @@ -31,7 +31,7 @@ The pipeline reviews **`.py`** and **`.pyi`** only. The **`--focus docs`** facet | `--include-tests`, `--exclude-tests` | Control whether changed test files participate in auto-scope review | | `--focus ` | Limit auto-discovered scope to **`source`**, **`tests`**, and/or **`docs`** (repeatable); mutually exclusive with `--include-tests` / `--exclude-tests` | | `--mode shadow\|enforce` | **`shadow`** surfaces findings without failing the exit code for policy violations; **`enforce`** applies normal gating (default **`enforce`**) | -| `--level error\|warning` | Optional reporting level override for the review run | +| `--level error\|warning` | Optional reporting level override before scoring: **`error`** keeps errors only (drops warnings and info); **`warning`** keeps errors and warnings (drops info only); omit to keep all severities (JSON, verdict, and `ci_exit_code` use the filtered list) | | `--bug-hunt` | Enable exploratory / bug-hunt style heuristics in the review pipeline | | `--include-noise`, `--suppress-noise` | Keep or suppress known low-signal findings | | `--json` | Emit a `ReviewReport` JSON file | @@ -72,8 +72,8 @@ specfact code review run --scope full --path packages/specfact-code-review # Package sources plus that package’s unit tests specfact code review run --scope full --path packages/specfact-code-review --path tests/unit/specfact_code_review -# Warnings dropped before scoring (affects JSON, verdict text, and ci_exit_code) -specfact code review run --scope changed --level warning +# Errors only before scoring — warnings and info omitted from JSON, verdict, and ci_exit_code +specfact code review run --scope changed --level error # Longer CrossHair budgets for exploratory bug-hunt pass (with explicit files) specfact code review run --bug-hunt --json --out /tmp/review-bughunt.json packages/specfact-code-review/src/specfact_code_review/run/commands.py diff --git a/docs/modules/code-review.md b/docs/modules/code-review.md index 10acd867..8661a851 100644 --- a/docs/modules/code-review.md +++ b/docs/modules/code-review.md @@ -44,9 +44,10 @@ Options (aligned with `specfact code review run --help`): full toolchain and preserves `overall_verdict` in JSON, but forces `ci_exit_code` and the process exit code to `0` so CI or hooks can log signal without blocking -- `--level error|warning`: drop findings below the chosen severity **before** - scoring and report construction so JSON, tables, score, verdict, and - `ci_exit_code` all match the filtered list. Omit to keep all severities +- `--level error|warning`: filter findings **before** scoring so JSON, tables, + score, verdict, and `ci_exit_code` match the filtered list: **`error`** + keeps errors only (warnings and info dropped); **`warning`** keeps errors and + warnings (info dropped). Omit to keep all severities - `--bug-hunt`: enable the bug-hunt pass (CrossHair uses longer budgets: per-path timeout **10s**, subprocess budget **120s**; other tools keep normal speed) - `--include-noise` / `--suppress-noise`: include or suppress known low-signal From 0035bec83ab2bbf128d11a1e6470c3c8647ec310 Mon Sep 17 00:00:00 2001 From: omit-test Date: Thu, 16 Apr 2026 22:35:03 +0200 Subject: [PATCH 07/14] fix(code-review): treat empty pylint stdout as parse failure. Blank or whitespace-only stdout no longer maps to zero findings; raise with stdout, stderr preview, and returncode so run_pylint surfaces a tool_error. Adds regression tests for empty and whitespace-only stdout. Made-with: Cursor --- .../specfact-code-review/module-package.yaml | 4 +- .../tools/pylint_runner.py | 15 +++++-- .../tools/test_pylint_runner.py | 40 +++++++++++++++++++ 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/packages/specfact-code-review/module-package.yaml b/packages/specfact-code-review/module-package.yaml index 723188d7..324ae8ec 100644 --- a/packages/specfact-code-review/module-package.yaml +++ b/packages/specfact-code-review/module-package.yaml @@ -1,5 +1,5 @@ name: nold-ai/specfact-code-review -version: 0.47.10 +version: 0.47.11 commands: - code tier: official @@ -23,4 +23,4 @@ description: Official SpecFact code review bundle package. category: codebase bundle_group_command: code integrity: - checksum: sha256:61d8c322b10fadbc91a5148eda0ef605c9b03252a13afe31156132e496dc09fd + checksum: sha256:02ef18238482a3fb01e5c3bee60e800c718a9a4a20e17b98ee8de566e7ec6790 diff --git a/packages/specfact-code-review/src/specfact_code_review/tools/pylint_runner.py b/packages/specfact-code-review/src/specfact_code_review/tools/pylint_runner.py index 1bfc9224..a906c914 100644 --- a/packages/specfact-code-review/src/specfact_code_review/tools/pylint_runner.py +++ b/packages/specfact-code-review/src/specfact_code_review/tools/pylint_runner.py @@ -91,10 +91,15 @@ def _finding_from_item(item: object, *, allowed_paths: set[str]) -> ReviewFindin ) -def _payload_from_output(stdout: str) -> list[object]: +def _payload_from_output(stdout: str, *, stderr: str, returncode: int | None) -> list[object]: stripped = stdout.strip() if not stripped: - return [] + err = stderr + if len(err) > 4096: + err = f"{err[:4096]}... ({len(stderr)} chars total)" + raise ValueError( + f"pylint produced no JSON on stdout; {stdout=!r}, stderr={err!r}, {returncode=}", + ) payload = json.loads(stripped) if not isinstance(payload, list): raise ValueError("pylint output must be a list") @@ -137,7 +142,11 @@ def run_pylint(files: list[Path]) -> list[ReviewFinding]: check=False, timeout=30, ) - payload = _payload_from_output(result.stdout) + payload = _payload_from_output( + result.stdout, + stderr=result.stderr, + returncode=result.returncode, + ) except (OSError, ValueError, json.JSONDecodeError, subprocess.TimeoutExpired) as exc: return [tool_error(tool="pylint", file_path=files[0], message=f"Unable to parse pylint output: {exc}")] diff --git a/tests/unit/specfact_code_review/tools/test_pylint_runner.py b/tests/unit/specfact_code_review/tools/test_pylint_runner.py index 528cdcd6..a7fda05b 100644 --- a/tests/unit/specfact_code_review/tools/test_pylint_runner.py +++ b/tests/unit/specfact_code_review/tools/test_pylint_runner.py @@ -97,6 +97,46 @@ def test_run_pylint_returns_tool_error_on_parse_error(tmp_path: Path, monkeypatc assert findings[0].tool == "pylint" +def test_run_pylint_empty_stdout_is_tool_error(tmp_path: Path, monkeypatch: MonkeyPatch) -> None: + file_path = tmp_path / "target.py" + monkeypatch.setattr( + subprocess, + "run", + Mock( + return_value=completed_process( + "pylint", + stdout="", + stderr="config error: missing plugin", + returncode=2, + ), + ), + ) + + findings = run_pylint([file_path]) + + assert len(findings) == 1 + assert findings[0].category == "tool_error" + assert findings[0].tool == "pylint" + assert "stdout=''" in findings[0].message + assert "config error" in findings[0].message + assert "returncode=2" in findings[0].message + + +def test_run_pylint_whitespace_only_stdout_is_tool_error(tmp_path: Path, monkeypatch: MonkeyPatch) -> None: + file_path = tmp_path / "target.py" + monkeypatch.setattr( + subprocess, + "run", + Mock(return_value=completed_process("pylint", stdout=" \n\t ", stderr="", returncode=1)), + ) + + findings = run_pylint([file_path]) + + assert len(findings) == 1 + assert findings[0].category == "tool_error" + assert "pylint produced no JSON on stdout" in findings[0].message + + def test_run_pylint_coerces_line_zero_to_one(tmp_path: Path, monkeypatch: MonkeyPatch) -> None: file_path = tmp_path / "target.py" payload = [ From 58f5828f4a35ae62efdf933e55a83fc91456930a Mon Sep 17 00:00:00 2001 From: omit-test Date: Thu, 16 Apr 2026 22:45:14 +0200 Subject: [PATCH 08/14] fix(code-review): truncate stdout preview in pylint empty-output errors. Mirror stderr truncation for huge stdout when pylint emits no JSON, and add a unit test for the long stderr branch. Made-with: Cursor --- .../specfact-code-review/module-package.yaml | 4 +-- .../tools/pylint_runner.py | 5 +++- .../tools/test_pylint_runner.py | 29 +++++++++++++++++++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/packages/specfact-code-review/module-package.yaml b/packages/specfact-code-review/module-package.yaml index 324ae8ec..ba1d3228 100644 --- a/packages/specfact-code-review/module-package.yaml +++ b/packages/specfact-code-review/module-package.yaml @@ -1,5 +1,5 @@ name: nold-ai/specfact-code-review -version: 0.47.11 +version: 0.47.12 commands: - code tier: official @@ -23,4 +23,4 @@ description: Official SpecFact code review bundle package. category: codebase bundle_group_command: code integrity: - checksum: sha256:02ef18238482a3fb01e5c3bee60e800c718a9a4a20e17b98ee8de566e7ec6790 + checksum: sha256:4bf2b79687947ecc4734eb9bd7ae976fd9500189315f4e50534eb84402d6277f diff --git a/packages/specfact-code-review/src/specfact_code_review/tools/pylint_runner.py b/packages/specfact-code-review/src/specfact_code_review/tools/pylint_runner.py index a906c914..d2a0a9db 100644 --- a/packages/specfact-code-review/src/specfact_code_review/tools/pylint_runner.py +++ b/packages/specfact-code-review/src/specfact_code_review/tools/pylint_runner.py @@ -94,11 +94,14 @@ def _finding_from_item(item: object, *, allowed_paths: set[str]) -> ReviewFindin def _payload_from_output(stdout: str, *, stderr: str, returncode: int | None) -> list[object]: stripped = stdout.strip() if not stripped: + out = stdout + if len(out) > 4096: + out = f"{out[:4096]}... ({len(stdout)} chars total)" err = stderr if len(err) > 4096: err = f"{err[:4096]}... ({len(stderr)} chars total)" raise ValueError( - f"pylint produced no JSON on stdout; {stdout=!r}, stderr={err!r}, {returncode=}", + f"pylint produced no JSON on stdout; stdout={out!r}, stderr={err!r}, {returncode=}", ) payload = json.loads(stripped) if not isinstance(payload, list): diff --git a/tests/unit/specfact_code_review/tools/test_pylint_runner.py b/tests/unit/specfact_code_review/tools/test_pylint_runner.py index a7fda05b..d4b7b70f 100644 --- a/tests/unit/specfact_code_review/tools/test_pylint_runner.py +++ b/tests/unit/specfact_code_review/tools/test_pylint_runner.py @@ -137,6 +137,35 @@ def test_run_pylint_whitespace_only_stdout_is_tool_error(tmp_path: Path, monkeyp assert "pylint produced no JSON on stdout" in findings[0].message +def test_run_pylint_empty_stdout_truncates_long_stderr(tmp_path: Path, monkeypatch: MonkeyPatch) -> None: + file_path = tmp_path / "target.py" + unique_tail = "UNIQUE_STDERR_TAIL_FOR_TRUNCATION_TEST" + long_stderr = "e" * 4096 + unique_tail + assert len(long_stderr) > 4096 + monkeypatch.setattr( + subprocess, + "run", + Mock( + return_value=completed_process( + "pylint", + stdout="", + stderr=long_stderr, + returncode=3, + ), + ), + ) + + findings = run_pylint([file_path]) + + assert len(findings) == 1 + assert findings[0].category == "tool_error" + assert findings[0].tool == "pylint" + assert unique_tail not in findings[0].message + assert "... (" in findings[0].message + assert "chars total)" in findings[0].message + assert f"... ({len(long_stderr)} chars total)" in findings[0].message + + def test_run_pylint_coerces_line_zero_to_one(tmp_path: Path, monkeypatch: MonkeyPatch) -> None: file_path = tmp_path / "target.py" payload = [ From a92a29835cc18e11bb1e3287874c89fbb346ce95 Mon Sep 17 00:00:00 2001 From: omit-test Date: Thu, 16 Apr 2026 22:54:04 +0200 Subject: [PATCH 09/14] test(code-review): cover pylint line and message coercion edge cases. Add tests for negative line numbers and whitespace-only pylint messages. Made-with: Cursor --- .../tools/test_pylint_runner.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/unit/specfact_code_review/tools/test_pylint_runner.py b/tests/unit/specfact_code_review/tools/test_pylint_runner.py index d4b7b70f..e8916e98 100644 --- a/tests/unit/specfact_code_review/tools/test_pylint_runner.py +++ b/tests/unit/specfact_code_review/tools/test_pylint_runner.py @@ -202,6 +202,42 @@ def test_run_pylint_coerces_empty_message_text(tmp_path: Path, monkeypatch: Monk assert findings[0].message == "(pylint provided no message text)" +def test_run_pylint_coerces_negative_line_to_one(tmp_path: Path, monkeypatch: MonkeyPatch) -> None: + file_path = tmp_path / "target.py" + payload = [ + { + "message-id": "C0301", + "path": str(file_path), + "line": -5, + "message": "line too long", + } + ] + monkeypatch.setattr(subprocess, "run", Mock(return_value=completed_process("pylint", stdout=json.dumps(payload)))) + + findings = run_pylint([file_path]) + + assert len(findings) == 1 + assert findings[0].line == 1 + + +def test_run_pylint_coerces_whitespace_only_message(tmp_path: Path, monkeypatch: MonkeyPatch) -> None: + file_path = tmp_path / "target.py" + payload = [ + { + "message-id": "C0301", + "path": str(file_path), + "line": 3, + "message": " \t\n ", + } + ] + monkeypatch.setattr(subprocess, "run", Mock(return_value=completed_process("pylint", stdout=json.dumps(payload)))) + + findings = run_pylint([file_path]) + + assert len(findings) == 1 + assert findings[0].message == "(pylint provided no message text)" + + def test_run_pylint_parses_json_with_surrounding_whitespace(tmp_path: Path, monkeypatch: MonkeyPatch) -> None: file_path = tmp_path / "target.py" payload = [ From afb14efd9c47f8419eb051fb2f7d25dcd9baebed Mon Sep 17 00:00:00 2001 From: omit-test Date: Thu, 16 Apr 2026 22:57:44 +0200 Subject: [PATCH 10/14] docs(agent-rules): require --bug-hunt on manual specfact code review runs. Document the flag in the quality gate checklist, repository command block, and clean-code gate notes so agents default to CrossHair bug-hunt budgets outside pre-commit. Made-with: Cursor --- docs/agent-rules/20-repository-context.md | 5 +++-- docs/agent-rules/50-quality-gates-and-review.md | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/agent-rules/20-repository-context.md b/docs/agent-rules/20-repository-context.md index d771d9ee..2cce7f08 100644 --- a/docs/agent-rules/20-repository-context.md +++ b/docs/agent-rules/20-repository-context.md @@ -13,7 +13,7 @@ tracks: - packages/** - registry/index.json - pyproject.toml -last_reviewed: 2026-04-12 +last_reviewed: 2026-04-16 exempt: false exempt_reason: "" id: agent-rules-repository-context @@ -50,7 +50,8 @@ hatch run verify-modules-signature --payload-from-filesystem --enforce-version-b hatch run contract-test hatch run smart-test hatch run test -hatch run specfact code review run --json --out .specfact/code-review.json +# manual code review: always include --bug-hunt (CrossHair longer budgets; see bundle docs) +hatch run specfact code review run --bug-hunt --json --out .specfact/code-review.json ``` ## Architecture diff --git a/docs/agent-rules/50-quality-gates-and-review.md b/docs/agent-rules/50-quality-gates-and-review.md index 8582b653..38c2cc35 100644 --- a/docs/agent-rules/50-quality-gates-and-review.md +++ b/docs/agent-rules/50-quality-gates-and-review.md @@ -13,7 +13,7 @@ tracks: - scripts/pre_commit_code_review.py - scripts/verify-modules-signature.py - docs/agent-rules/** -last_reviewed: 2026-04-12 +last_reviewed: 2026-04-16 exempt: false exempt_reason: "" id: agent-rules-quality-gates-and-review @@ -47,7 +47,7 @@ depends_on: 7. `hatch run contract-test` 8. `hatch run smart-test` 9. `hatch run test` -10. `hatch run specfact code review run --json --out .specfact/code-review.json` (full-repo scope when required: add `--scope full`; machine-readable evidence lives at `.specfact/code-review.json` and unresolved findings block merge unless an explicit exception is documented) +10. `hatch run specfact code review run --bug-hunt --json --out .specfact/code-review.json` (always pass **`--bug-hunt`** on manual runs so CrossHair uses bug-hunt timeouts; full-repo scope when required: add **`--scope full`**; machine-readable evidence lives at `.specfact/code-review.json` and unresolved findings block merge unless an explicit exception is documented) ## Pre-commit order @@ -66,7 +66,7 @@ Run the full pipeline manually with `./scripts/pre-commit-quality-checks.sh` or ## Clean-code review gate -The repository enforces the clean-code charter through `specfact code review run`. Zero regressions in `naming`, `kiss`, `yagni`, `dry`, and `solid` are required before merge. +The repository enforces the clean-code charter through `specfact code review run`. When agents or developers invoke the review manually (outside the pre-commit helper), include **`--bug-hunt`** so the contract runner gives CrossHair the longer bug-hunt budgets documented in the code-review bundle. Zero regressions in `naming`, `kiss`, `yagni`, `dry`, and `solid` are required before merge. ## Module signature gate From 840bc36faa3b2b6823ca434a091b078d78a96bd9 Mon Sep 17 00:00:00 2001 From: omit-test Date: Thu, 16 Apr 2026 23:02:18 +0200 Subject: [PATCH 11/14] test(code-review): strengthen pylint runner assertions for line and payload. Assert valid lines are preserved for W0702 and validate full finding fields when JSON is wrapped in surrounding whitespace. Made-with: Cursor --- tests/unit/specfact_code_review/tools/test_pylint_runner.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/unit/specfact_code_review/tools/test_pylint_runner.py b/tests/unit/specfact_code_review/tools/test_pylint_runner.py index e8916e98..58bcf83e 100644 --- a/tests/unit/specfact_code_review/tools/test_pylint_runner.py +++ b/tests/unit/specfact_code_review/tools/test_pylint_runner.py @@ -34,6 +34,7 @@ def test_run_pylint_maps_bare_except_to_architecture(tmp_path: Path, monkeypatch assert findings[0].category == "architecture" assert findings[0].severity == "warning" assert findings[0].rule == "W0702" + assert findings[0].line == 7 assert_tool_run(run_mock, ["pylint", "--output-format", "json", str(file_path)]) @@ -255,6 +256,9 @@ def test_run_pylint_parses_json_with_surrounding_whitespace(tmp_path: Path, monk assert len(findings) == 1 assert findings[0].rule == "W0702" + assert findings[0].line == 7 + assert findings[0].message == "No exception type(s) specified" + assert findings[0].category == "architecture" def test_run_pylint_returns_tool_error_for_invalid_payload_item(tmp_path: Path, monkeypatch: MonkeyPatch) -> None: From de318580757ce8ce138644332f66aac73c1154dc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 21:10:57 +0000 Subject: [PATCH 12/14] chore(registry): publish changed modules [skip ci] --- .../specfact-code-review/module-package.yaml | 1 + registry/index.json | 6 +++--- .../modules/specfact-code-review-0.47.12.tar.gz | Bin 0 -> 37581 bytes .../specfact-code-review-0.47.12.tar.gz.sha256 | 1 + .../specfact-code-review-0.47.12.tar.sig | 1 + 5 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 registry/modules/specfact-code-review-0.47.12.tar.gz create mode 100644 registry/modules/specfact-code-review-0.47.12.tar.gz.sha256 create mode 100644 registry/signatures/specfact-code-review-0.47.12.tar.sig diff --git a/packages/specfact-code-review/module-package.yaml b/packages/specfact-code-review/module-package.yaml index ba1d3228..914b1b48 100644 --- a/packages/specfact-code-review/module-package.yaml +++ b/packages/specfact-code-review/module-package.yaml @@ -24,3 +24,4 @@ category: codebase bundle_group_command: code integrity: checksum: sha256:4bf2b79687947ecc4734eb9bd7ae976fd9500189315f4e50534eb84402d6277f + signature: 8ZIQZvhMaaaXvEJwjePJu4lgWb2YBsDmXe3edwWbboa/a+oxkyv5NYuJWeojEvBx+pmxq2RMerDR/QvuZYMjDQ== diff --git a/registry/index.json b/registry/index.json index f9461119..910aab7a 100644 --- a/registry/index.json +++ b/registry/index.json @@ -78,9 +78,9 @@ }, { "id": "nold-ai/specfact-code-review", - "latest_version": "0.47.9", - "download_url": "modules/specfact-code-review-0.47.9.tar.gz", - "checksum_sha256": "8471e41b3567021834229c970365f537e1ac1ebe3c174c64a5a21304105eacd9", + "latest_version": "0.47.12", + "download_url": "modules/specfact-code-review-0.47.12.tar.gz", + "checksum_sha256": "8ff0261849057113629289b82c674ae20594d3df21c33c5932468a8169c9274b", "core_compatibility": ">=0.44.0,<1.0.0", "tier": "official", "publisher": { diff --git a/registry/modules/specfact-code-review-0.47.12.tar.gz b/registry/modules/specfact-code-review-0.47.12.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..3b8e47ad6a34c6d59765eee2aa19a38c571ea2b5 GIT binary patch literal 37581 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 literal 0 HcmV?d00001 diff --git a/registry/modules/specfact-code-review-0.47.12.tar.gz.sha256 b/registry/modules/specfact-code-review-0.47.12.tar.gz.sha256 new file mode 100644 index 00000000..2bee1d4c --- /dev/null +++ b/registry/modules/specfact-code-review-0.47.12.tar.gz.sha256 @@ -0,0 +1 @@ +8ff0261849057113629289b82c674ae20594d3df21c33c5932468a8169c9274b diff --git a/registry/signatures/specfact-code-review-0.47.12.tar.sig b/registry/signatures/specfact-code-review-0.47.12.tar.sig new file mode 100644 index 00000000..04ac39f2 --- /dev/null +++ b/registry/signatures/specfact-code-review-0.47.12.tar.sig @@ -0,0 +1 @@ +8ZIQZvhMaaaXvEJwjePJu4lgWb2YBsDmXe3edwWbboa/a+oxkyv5NYuJWeojEvBx+pmxq2RMerDR/QvuZYMjDQ== From 30ec4b777c46b84956789012ae40cc487d4fc630 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 21:21:12 +0000 Subject: [PATCH 13/14] chore(modules): auto-sign module manifests --- packages/specfact-code-review/module-package.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/specfact-code-review/module-package.yaml b/packages/specfact-code-review/module-package.yaml index 914b1b48..48e91a00 100644 --- a/packages/specfact-code-review/module-package.yaml +++ b/packages/specfact-code-review/module-package.yaml @@ -1,5 +1,5 @@ name: nold-ai/specfact-code-review -version: 0.47.12 +version: 0.47.13 commands: - code tier: official @@ -23,5 +23,5 @@ description: Official SpecFact code review bundle package. category: codebase bundle_group_command: code integrity: - checksum: sha256:4bf2b79687947ecc4734eb9bd7ae976fd9500189315f4e50534eb84402d6277f - signature: 8ZIQZvhMaaaXvEJwjePJu4lgWb2YBsDmXe3edwWbboa/a+oxkyv5NYuJWeojEvBx+pmxq2RMerDR/QvuZYMjDQ== + checksum: sha256:5345c5f7d453481edc033ebb64f5b38d3d8ebdcd8a8c6ae1fcd879d5136dc919 + signature: UyRN+72IHFSxnU1woW5XJwuBRd1eZQrgKT4XODi2LAhI7G/RgY2SRCrde1fugYuE9Rc4Wx8kNMdjzah01DoPAA== From 2041a36fc80e4d91296a06f6c7226d4d6f30a00b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 21:22:24 +0000 Subject: [PATCH 14/14] chore(registry): publish changed modules [skip ci] --- registry/index.json | 6 +++--- .../modules/specfact-code-review-0.47.13.tar.gz | Bin 0 -> 37548 bytes .../specfact-code-review-0.47.13.tar.gz.sha256 | 1 + .../specfact-code-review-0.47.13.tar.sig | 1 + 4 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 registry/modules/specfact-code-review-0.47.13.tar.gz create mode 100644 registry/modules/specfact-code-review-0.47.13.tar.gz.sha256 create mode 100644 registry/signatures/specfact-code-review-0.47.13.tar.sig diff --git a/registry/index.json b/registry/index.json index 910aab7a..017606a3 100644 --- a/registry/index.json +++ b/registry/index.json @@ -78,9 +78,9 @@ }, { "id": "nold-ai/specfact-code-review", - "latest_version": "0.47.12", - "download_url": "modules/specfact-code-review-0.47.12.tar.gz", - "checksum_sha256": "8ff0261849057113629289b82c674ae20594d3df21c33c5932468a8169c9274b", + "latest_version": "0.47.13", + "download_url": "modules/specfact-code-review-0.47.13.tar.gz", + "checksum_sha256": "618fa5dea59e1a38bb7173b2d906d55897c896af8f6a97ce1d502def0afa5f9e", "core_compatibility": ">=0.44.0,<1.0.0", "tier": "official", "publisher": { diff --git a/registry/modules/specfact-code-review-0.47.13.tar.gz b/registry/modules/specfact-code-review-0.47.13.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..789ae1c6f4bb894dc1639afa58092aaae87a3756 GIT binary patch literal 37548 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 Ysh|3(pZckv`dR(?f7^F>J^+{l0Q6(D_5c6? literal 0 HcmV?d00001 diff --git a/registry/modules/specfact-code-review-0.47.13.tar.gz.sha256 b/registry/modules/specfact-code-review-0.47.13.tar.gz.sha256 new file mode 100644 index 00000000..7e4b65ef --- /dev/null +++ b/registry/modules/specfact-code-review-0.47.13.tar.gz.sha256 @@ -0,0 +1 @@ +618fa5dea59e1a38bb7173b2d906d55897c896af8f6a97ce1d502def0afa5f9e diff --git a/registry/signatures/specfact-code-review-0.47.13.tar.sig b/registry/signatures/specfact-code-review-0.47.13.tar.sig new file mode 100644 index 00000000..fdc29ce7 --- /dev/null +++ b/registry/signatures/specfact-code-review-0.47.13.tar.sig @@ -0,0 +1 @@ +UyRN+72IHFSxnU1woW5XJwuBRd1eZQrgKT4XODi2LAhI7G/RgY2SRCrde1fugYuE9Rc4Wx8kNMdjzah01DoPAA==