build: migrate dev tooling to uv + widen Python matrix to 3.11/3.12 - #95
Conversation
Mirrors clauditor's build setup. uv becomes the canonical dev install (`uv sync --dev`); `uv.lock` is committed; CI runs uv-managed steps across a 3-Python matrix. `pip install -e ".[dev]"` is retained for back-compat — `[project.optional-dependencies].dev` and the new `[dependency-groups].dev` are kept in sync. - `pyproject.toml`: add `[dependency-groups].dev` (PEP 735); only delta vs the pip-extra is `build>=1.2,<2` for the wheel_smoke marker. - `.github/workflows/ci.yml`: replace setup-python + pip install with `astral-sh/setup-uv@v8.1.0` (SHA-pinned per ci-supply-chain.md) + `uv sync --dev`; wrap lint/test steps with `uv run`. Matrix runs ruff + pytest on all three versions; pyright gated to the matrix floor (3.11, matching pyright.pythonVersion); codecov upload gated to the matrix ceiling (3.13) so coverage doesn't double-upload. - `.github/workflows/publish.yml`: `python -m build` → `uv build` in both publish-testpypi and publish-pypi jobs. - `.gitignore`: drop `uv.lock` from the ignore list (bark scaffolding artifact comment is stale — the canonical lock is committed now). - `CLAUDE.md` / `CONTRIBUTING.md` / `README.md` / `docs/cli-ops.md` / `docs/codecov-ops.md` / `docs/manifest-loader-ops.md`: switch copy-pasteable dev commands to `uv sync --dev` + `uv run …`. - `.claude/rules/python-build.md`: replace "Editable install (zsh-safe)" with a uv-managed dev environment section; refresh issue-#46 floor-pinning to describe the matrix. - `.claude/rules/ci-supply-chain.md`: add `astral-sh/setup-uv` to the SHA-pinning example list; graduate DEC-003 from single-Python to a 3-Python matrix with the pyright / codecov gating rationale. Local validation: `uv run ruff check .` / `uv run ruff format --check .` / `uv run pyright` / `uv run pytest` all clean under Python 3.13; `uv run --python 3.11 pytest --no-cov` and `--python 3.12` both 1830/1830 pass. wheel_smoke passes under `uv run`. (Six pre-existing WSL2-only symlink-loop test failures on 3.13 unaffected; pass in GHA.) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review (4)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis PR systematically migrates the project from pip-based development and CI to uv-managed tooling. The CI matrix narrows to Python 3.11/3.12 only (deferring 3.13 with documented catch sites), conditional steps gate Pyright to 3.11 and Codecov uploads to 3.12, and all documentation is updated to reflect uv workflows with pip fallbacks. Changesuv Tooling Migration
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~35 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
CI exposed six 3.13-only test failures under tests/_common/, tests/diff/, tests/grade/, tests/manifest/. Root cause: Python 3.13 changed Path.resolve() to raise OSError(errno.ELOOP) on cyclic symlinks instead of RuntimeError. SignalForge's _canonicalise_path and the manifest loader's path canonicalisation catch RuntimeError only — on 3.13 the cycle now escapes as OSError or short-circuits to a different typed error before the loop check fires. Fixing it touches production code (signalforge/_common/path_safety.py and signalforge/manifest/loader.py) plus four test fixtures, which is substantial scope creep for a tooling-migration PR. Narrow the matrix to 3.11 / 3.12 (both green: 1830 passed locally and in CI) and track the 3.13 work in a follow-up issue. - ci.yml: matrix python-version: ["3.11", "3.12"]; codecov-upload gate flips from 3.13 → 3.12 (matrix ceiling). Comment block records the reason and points at the follow-up. - CLAUDE.md / CONTRIBUTING.md / docs/cli-ops.md / python-build.md / ci-supply-chain.md: every "3.11 / 3.12 / 3.13" reference now reads "3.11 / 3.12"; ci-supply-chain.md and python-build.md add a "3.13 is deferred" paragraph naming the catch-site fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
@CodeRabbit review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Pull request overview
Migrates the repo’s Python dev/CI tooling to uv (committing uv.lock and running CI steps under uv run) and expands CI from a single interpreter to a 3.11 / 3.12 matrix, with documentation updated to match the new workflows.
Changes:
- Switch CI to
astral-sh/setup-uv+uv sync --dev, and run ruff/pyright/pytest underuv runacross a 3.11/3.12 matrix (pyright on 3.11 only; codecov upload on 3.12 only). - Add
[dependency-groups].dev(PEP 735) as the uv-native dev dependency source of truth, retaining[project.optional-dependencies].devforpip install -e ".[dev]"back-compat. - Update publish workflow to build via
uv build, and refresh contributor/docs guidance to prefer uv.
Reviewed changes
Copilot reviewed 11 out of 13 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Updates install instructions to prefer uv sync --dev with a pip back-compat note. |
| pyproject.toml | Adds [dependency-groups].dev (includes build) while retaining pip’s [project.optional-dependencies].dev. |
| uv.lock | New committed lockfile to make dev/CI resolution reproducible. |
| .gitignore | Stops ignoring uv.lock so it can be committed. |
| .github/workflows/ci.yml | Migrates CI to uv and expands to Python 3.11/3.12 matrix with gated pyright/codecov steps. |
| .github/workflows/publish.yml | Migrates release build from python -m build to uv build and installs uv via setup-uv. |
| CONTRIBUTING.md | Updates contributor setup/validation commands to uv equivalents. |
| CLAUDE.md | Updates canonical validation command to uv-based workflow. |
| docs/cli-ops.md | Updates dev install instructions to uv sync and notes committed lock usage. |
| docs/codecov-ops.md | Updates local coverage/validation commands to uv equivalents. |
| docs/manifest-loader-ops.md | Updates fixture regen guidance to reference the dev group + uv sync. |
| .claude/rules/python-build.md | Documents uv-managed dev env + matrix floor/ceiling conventions. |
| .claude/rules/ci-supply-chain.md | Updates CI supply-chain/matrix guidance (but contains a Codecov gating inconsistency). |
Comments suppressed due to low confidence (1)
.github/workflows/publish.yml:61
- Same as above for the PyPI job:
setup-uvhas nopython-version, souv buildmay run under a moving default Python from the runner image. Pin the Python version explicitly (e.g., 3.11) for deterministic release artifacts.
- name: Set up uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
- name: Build sdist + wheel
run: uv build
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.claude/rules/ci-supply-chain.md:
- Line 78: The rule text currently instructs to "Gate on `matrix.python-version
== '3.13'`" but CI actually gates uploads on 3.12; update that mention to
"matrix.python-version == '3.12'". Locate the string `matrix.python-version ==
'3.13'` in the rule text (in the ci-supply-chain rule) and replace the version
token '3.13' with '3.12' so the documentation matches current CI behavior.
In @.github/workflows/ci.yml:
- Around line 32-33: The checkout step using
actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 should be updated to
disable credential persistence by adding persist-credentials: false to the step;
modify the Checkout step (the block that uses actions/checkout...) to include
the key persist-credentials with value false so the GITHUB_TOKEN is not written
into local git config during this read-only CI job.
In @.github/workflows/publish.yml:
- Around line 35-36: The Checkout steps that use actions/checkout (the steps
with uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5) need to
include persist-credentials: false; update both occurrences of the "Checkout"
step in the publish workflow to add the key persist-credentials: false under the
step so the checkout does not persist the GITHUB_TOKEN in git config.
In `@docs/cli-ops.md`:
- Around line 41-45: Clarify that the pip fallback is not fully equivalent to
the uv path: update the paragraph around 'uv sync --dev' and the fallback 'pip
install -e ".[dev]"' to add a short note stating that '[dependency-groups].dev'
(used by uv sync --dev) includes the 'build' tool and possibly other uv-only
deltas, so pip users should not assume identical tooling coverage; reference the
symbols 'uv sync --dev', 'pip install -e ".[dev]"', '[dependency-groups].dev',
and 'build' in the note so readers can see exactly which parts differ.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 6ef1e077-3d4c-46b1-9fa5-f7b2968b66e2
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (12)
.claude/rules/ci-supply-chain.md.claude/rules/python-build.md.github/workflows/ci.yml.github/workflows/publish.yml.gitignoreCLAUDE.mdCONTRIBUTING.mdREADME.mddocs/cli-ops.mddocs/codecov-ops.mddocs/manifest-loader-ops.mdpyproject.toml
💤 Files with no reviewable changes (1)
- .gitignore
Five fixes from PR #95 review (all real issues, no false positives): - `.claude/rules/ci-supply-chain.md`: codecov-gate example said `matrix.python-version == '3.13'` (stale from the 3.13 matrix); current CI gates on `'3.12'`. Updated and noted the flip-back when issue #96 lands. [coderabbit/copilot — same finding, threads 1+3] - `.github/workflows/ci.yml`: `actions/checkout` now sets `persist-credentials: false` — CI is read-only; least-privilege for the GITHUB_TOKEN. [coderabbit — thread 4] - `.github/workflows/publish.yml`: same `persist-credentials: false` on both `Checkout` steps; same justification (build job doesn't push to git). [coderabbit — thread 5] - `.github/workflows/publish.yml`: `astral-sh/setup-uv` now pins `python-version: "3.11"` on both jobs for release-build reproducibility. Without it, `uv build` picks whatever `ubuntu-latest` happens to ship. [copilot — thread 2] - `docs/cli-ops.md`: clarify that the `pip install -e ".[dev]"` fallback omits the `build` package (uv-only delta in `[dependency-groups].dev` powering wheel_smoke). Pip users shouldn't assume identical tooling coverage. [coderabbit — thread 6] Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR Review SummaryAll 6 review threads addressed in commit Fixed (5 items)
False positives (0)All review comments were actionable; no false positives to document. |
Branch discipline: test releases are now cut from `dev` (was "any branch") and full releases from `main` (unchanged). The publish.yml prerelease-flag routing is unchanged — it's the mechanism; the branch is the maintainer discipline the skill enforces in pre-flight. dev is the in-development line feeding TestPyPI; main is the released line feeding PyPI. Also refreshes the skill for the uv migration (PRs #95/#97): - validation command → `uv sync --dev && uv run ruff/pyright/pytest` - `python -m build` → `uv build` (both build blocks + PR-body string) - allowed-tools gains `Bash(uv *)`; compatibility frontmatter updated - left the TestPyPI clean-room `pip install` smoke test as-is (it deliberately installs the published artifact in a throwaway venv) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 93: silence pydantic UserWarning for LLMRequest.schema field shadow (#94) * 93: silence pydantic UserWarning for LLMRequest.schema field shadow The `schema` field name on `LLMRequest` is part of the documented audit-log contract (safety-layer.md DEC-014), but it shadows Pydantic v1's deprecated `BaseModel.schema` method, triggering a UserWarning at class-creation time that surfaces on every fresh import / CLI invocation. Wrap the class definition in `warnings.catch_warnings()` with a targeted filter — narrowest blast radius; no global filter mutation, the contract stays unchanged. Adds a subprocess-import regression test under `-W error::UserWarning` so future shape changes can't silently reintroduce it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 93: document Pydantic field-name-shadow suppression pattern Compounding update from PR #94: capture the `catch_warnings()` scope-to-class convention so the next field-name-shadow case (audit-log contract + Pydantic v1 BaseModel attribute collision) doesn't get "fixed" by renaming or a global filter. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 93: address PR review feedback - models.py: relax warning-filter message from exact literal to regex (`Field name "schema".*shadows.*`) so future Pydantic message-wording drift still gets caught. Drops the experimental `module=` qualifier (over-narrowing — filter stopped matching the live warning). - test_models.py: add `timeout=10` to the subprocess import smoke, matching the precedent in `tests/cli/test_subprocess_smoke.py` so a pathological import hang fails fast instead of stalling the suite. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: reconcile README + docs with current v0.1 codebase (#90) Audited the root README and docs/ ops files against the shipped code: - demo profiles.yml: add maximum_bytes_billed: 1000000000 so the README quick start actually works — the demo's materialised-sample CTAS over the full ~2.27M-row bikeshare_trips source exceeds the adapter's 100 MB default cap. Parity-test docstring updated to document the new rewrite. - README: add the kept-uncertain tier to the expected-output section (four-tier taxonomy was undocumented), complete the generate/lint flag lists in the CLI section, mention lint --model. - docs/audits.md: fix stale audit_schema_version values (safety 1 -> 3, prune 1 -> 2) bumped by issues #54/#55. - docs/e2e-smoke-test.md: fix stale cross-reference claiming the README quick start does a manual profile rewrite — it uses signalforge init-demo. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * build: migrate dev tooling to uv + widen Python matrix to 3.11/3.12 (#95) * build: migrate dev tooling to uv + widen Python matrix to 3.11/3.12/3.13 Mirrors clauditor's build setup. uv becomes the canonical dev install (`uv sync --dev`); `uv.lock` is committed; CI runs uv-managed steps across a 3-Python matrix. `pip install -e ".[dev]"` is retained for back-compat — `[project.optional-dependencies].dev` and the new `[dependency-groups].dev` are kept in sync. - `pyproject.toml`: add `[dependency-groups].dev` (PEP 735); only delta vs the pip-extra is `build>=1.2,<2` for the wheel_smoke marker. - `.github/workflows/ci.yml`: replace setup-python + pip install with `astral-sh/setup-uv@v8.1.0` (SHA-pinned per ci-supply-chain.md) + `uv sync --dev`; wrap lint/test steps with `uv run`. Matrix runs ruff + pytest on all three versions; pyright gated to the matrix floor (3.11, matching pyright.pythonVersion); codecov upload gated to the matrix ceiling (3.13) so coverage doesn't double-upload. - `.github/workflows/publish.yml`: `python -m build` → `uv build` in both publish-testpypi and publish-pypi jobs. - `.gitignore`: drop `uv.lock` from the ignore list (bark scaffolding artifact comment is stale — the canonical lock is committed now). - `CLAUDE.md` / `CONTRIBUTING.md` / `README.md` / `docs/cli-ops.md` / `docs/codecov-ops.md` / `docs/manifest-loader-ops.md`: switch copy-pasteable dev commands to `uv sync --dev` + `uv run …`. - `.claude/rules/python-build.md`: replace "Editable install (zsh-safe)" with a uv-managed dev environment section; refresh issue-#46 floor-pinning to describe the matrix. - `.claude/rules/ci-supply-chain.md`: add `astral-sh/setup-uv` to the SHA-pinning example list; graduate DEC-003 from single-Python to a 3-Python matrix with the pyright / codecov gating rationale. Local validation: `uv run ruff check .` / `uv run ruff format --check .` / `uv run pyright` / `uv run pytest` all clean under Python 3.13; `uv run --python 3.11 pytest --no-cov` and `--python 3.12` both 1830/1830 pass. wheel_smoke passes under `uv run`. (Six pre-existing WSL2-only symlink-loop test failures on 3.13 unaffected; pass in GHA.) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: drop 3.13 from matrix; document path-safety follow-up CI exposed six 3.13-only test failures under tests/_common/, tests/diff/, tests/grade/, tests/manifest/. Root cause: Python 3.13 changed Path.resolve() to raise OSError(errno.ELOOP) on cyclic symlinks instead of RuntimeError. SignalForge's _canonicalise_path and the manifest loader's path canonicalisation catch RuntimeError only — on 3.13 the cycle now escapes as OSError or short-circuits to a different typed error before the loop check fires. Fixing it touches production code (signalforge/_common/path_safety.py and signalforge/manifest/loader.py) plus four test fixtures, which is substantial scope creep for a tooling-migration PR. Narrow the matrix to 3.11 / 3.12 (both green: 1830 passed locally and in CI) and track the 3.13 work in a follow-up issue. - ci.yml: matrix python-version: ["3.11", "3.12"]; codecov-upload gate flips from 3.13 → 3.12 (matrix ceiling). Comment block records the reason and points at the follow-up. - CLAUDE.md / CONTRIBUTING.md / docs/cli-ops.md / python-build.md / ci-supply-chain.md: every "3.11 / 3.12 / 3.13" reference now reads "3.11 / 3.12"; ci-supply-chain.md and python-build.md add a "3.13 is deferred" paragraph naming the catch-site fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: address CodeRabbit + Copilot review feedback Five fixes from PR #95 review (all real issues, no false positives): - `.claude/rules/ci-supply-chain.md`: codecov-gate example said `matrix.python-version == '3.13'` (stale from the 3.13 matrix); current CI gates on `'3.12'`. Updated and noted the flip-back when issue #96 lands. [coderabbit/copilot — same finding, threads 1+3] - `.github/workflows/ci.yml`: `actions/checkout` now sets `persist-credentials: false` — CI is read-only; least-privilege for the GITHUB_TOKEN. [coderabbit — thread 4] - `.github/workflows/publish.yml`: same `persist-credentials: false` on both `Checkout` steps; same justification (build job doesn't push to git). [coderabbit — thread 5] - `.github/workflows/publish.yml`: `astral-sh/setup-uv` now pins `python-version: "3.11"` on both jobs for release-build reproducibility. Without it, `uv build` picks whatever `ubuntu-latest` happens to ship. [copilot — thread 2] - `docs/cli-ops.md`: clarify that the `pip install -e ".[dev]"` fallback omits the `build` package (uv-only delta in `[dependency-groups].dev` powering wheel_smoke). Pip users shouldn't assume identical tooling coverage. [coderabbit — thread 6] Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: publish MkDocs Material site to GitHub Pages on push to main (#97) * docs: publish MkDocs Material site to GitHub Pages on push to main Mirrors clauditor's docs-publishing setup. The published site at https://wjduenow.github.io/SignalForge/ redeploys on every push to main; the gh-pages branch is generated, not authored. - `mkdocs.yml`: Material theme with light/dark toggle, search + include-markdown plugins, mermaid via pymdownx.superfences, exclude_docs: research/ (internal analysis stays off the site), edit_uri: edit/dev/docs/ (doc edits land on dev like every other PR; main is the release line). Nav: Home + CLI Reference + Pipeline Stages (manifest/warehouse/safety/draft/prune/grade/diff) + Audits & Sidecars + e2e + coverage. - `docs/index.md`: 4-line include-markdown stub pulling in the root README. Mirrors clauditor verbatim — README stays the canonical authored home doc; site home auto-syncs on every build. - `.github/workflows/ci.yml`: new `docs:` job gated on `github.ref == 'refs/heads/main' && github.event_name == 'push'` with job-scoped `permissions: contents: write` (workflow default stays `contents: read`). Uses pinned `astral-sh/setup-uv@v8.1.0` with `python-version: "3.11"` (matches publish.yml floor); runs `uv run mkdocs gh-deploy --force --no-history`. Deliberately does NOT set `persist-credentials: false` on the checkout step — `gh-deploy` needs the persisted GITHUB_TOKEN to push to gh-pages. - `pyproject.toml`: add `mkdocs-material>=9.0` and `mkdocs-include-markdown-plugin>=6.0` to `[dependency-groups].dev` only. Pip contributors don't need to build docs; this stays a uv-only delta (alongside `build` for wheel_smoke). - `.gitignore`: ignore `site/` (mkdocs build output). - `README.md`: docs badge top-of-file. - `CLAUDE.md` / `CONTRIBUTING.md`: one-liner pointing at the published site and the deploy contract. - `.claude/rules/docs-publishing.md` (new): full deploy contract — trigger rule, include-markdown pattern, edit_uri choice, persist-credentials exception, exclude_docs convention, local build recipe, when to update mkdocs.yml vs the docs themselves, first-time GH Pages setup the maintainer does once. Local validation: `uv run mkdocs build` succeeds (warnings on repo-internal links to plans/super/ and .claude/rules/ are expected and matched in clauditor — those aren't part of the published site). `uv run ruff check .` / `ruff format --check .` clean. 1823/1823 tests pass on 3.13 (6 pre-existing #96 failures unrelated). Post-merge maintainer step (once): Settings → Pages → source = "Deploy from a branch", branch = gh-pages, folder = / (root). The first push to main after this merges lands the gh-pages branch; toggle Pages on right after. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: address PR #97 review — split docs dep group + PR-time build gate Two Copilot suggestions, both real wins: - Extract `[dependency-groups].docs` (mkdocs-material + mkdocs-include-markdown-plugin only). Both CI docs jobs now `uv run --only-group docs ...` so they pull just MkDocs + plugins, not the heavy dev set (dbt-core, pyright, pytest). `dev` includes `docs` via `{include-group = "docs"}` so `uv sync --dev` still gives contributors everything. [thread: ci.yml deploy pulled --dev] - Add a read-only `docs-build` job that runs on every PR + push (`uv run --only-group docs mkdocs build`, no write perms). Catches a broken mkdocs.yml / plugin config / include-markdown syntax error at PR time instead of silently merging and only failing the post-merge gh-pages deploy. [thread: no pre-merge docs build signal] The third thread (Home page "Edit this page" link points at the docs/index.md include stub rather than README.md) is an accepted limitation of the include-markdown pattern — Material has no per-page edit-button hide without a template override, and clauditor's identical setup accepts it. The stub a contributor lands on literally documents where the real content lives. Documented in the review summary; no code change. `.claude/rules/docs-publishing.md` updated: two-job structure, the docs dependency-group split, and the "new plugin goes in docs not dev" convention. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * release-manager: cut TestPyPI from dev, PyPI from main + uv refresh Branch discipline: test releases are now cut from `dev` (was "any branch") and full releases from `main` (unchanged). The publish.yml prerelease-flag routing is unchanged — it's the mechanism; the branch is the maintainer discipline the skill enforces in pre-flight. dev is the in-development line feeding TestPyPI; main is the released line feeding PyPI. Also refreshes the skill for the uv migration (PRs #95/#97): - validation command → `uv sync --dev && uv run ruff/pyright/pytest` - `python -m build` → `uv build` (both build blocks + PR-body string) - allowed-tools gains `Bash(uv *)`; compatibility frontmatter updated - left the TestPyPI clean-room `pip install` smoke test as-is (it deliberately installs the published artifact in a throwaway venv) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: bump to 0.1.0rc3 for test release * release-manager: pin clean-room smoke test to Python >=3.11 The Step 5 TestPyPI smoke test used a bare `python -m venv`, which picks up whatever `python3` is on PATH — on a host where that's 3.10, pip silently ignores the new release (requires-python >=3.11) and only the pre-floor 0.1.0rc1 installs, producing a confusing "Could not find a version that satisfies the requirement" right after a green publish. Surfaced cutting v0.1.0rc3: the first clean-room attempt used the system 3.10 and rejected rc3/rc2 as "Requires-Python >=3.11". Fix: provision the interpreter explicitly via `uv venv --python 3.11` (the requires-python floor — verifies installability on the minimum supported version; uv auto-fetches 3.11 if absent) and `uv pip install --python <venv>`. Also documents how to distinguish this from the separate TestPyPI Fastly-cache propagation lag. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: disable setup-uv cache (enable-cache: false) on all steps CodeRabbit flagged on PR #98: setup-uv's default persists the uv cache to the GitHub Actions Cache. On a public repo, pull_request runs share a cache scope with the base branch, so a fork PR can poison an entry a later trusted run restores — a cross-trust-boundary cache-poisoning vector. Set `enable-cache: false` explicitly on every setup-uv step: ci.yml (lint-test, docs-build, docs-deploy) + publish.yml (testpypi, pypi). Runs are short enough that the lost cache is negligible; the publish/deploy steps especially must never restore a poisoned cache into a published artifact or the gh-pages deploy. Documents the convention in .claude/rules/ci-supply-chain.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 93: silence pydantic UserWarning for LLMRequest.schema field shadow (#94) * 93: silence pydantic UserWarning for LLMRequest.schema field shadow The `schema` field name on `LLMRequest` is part of the documented audit-log contract (safety-layer.md DEC-014), but it shadows Pydantic v1's deprecated `BaseModel.schema` method, triggering a UserWarning at class-creation time that surfaces on every fresh import / CLI invocation. Wrap the class definition in `warnings.catch_warnings()` with a targeted filter — narrowest blast radius; no global filter mutation, the contract stays unchanged. Adds a subprocess-import regression test under `-W error::UserWarning` so future shape changes can't silently reintroduce it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 93: document Pydantic field-name-shadow suppression pattern Compounding update from PR #94: capture the `catch_warnings()` scope-to-class convention so the next field-name-shadow case (audit-log contract + Pydantic v1 BaseModel attribute collision) doesn't get "fixed" by renaming or a global filter. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 93: address PR review feedback - models.py: relax warning-filter message from exact literal to regex (`Field name "schema".*shadows.*`) so future Pydantic message-wording drift still gets caught. Drops the experimental `module=` qualifier (over-narrowing — filter stopped matching the live warning). - test_models.py: add `timeout=10` to the subprocess import smoke, matching the precedent in `tests/cli/test_subprocess_smoke.py` so a pathological import hang fails fast instead of stalling the suite. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: reconcile README + docs with current v0.1 codebase (#90) Audited the root README and docs/ ops files against the shipped code: - demo profiles.yml: add maximum_bytes_billed: 1000000000 so the README quick start actually works — the demo's materialised-sample CTAS over the full ~2.27M-row bikeshare_trips source exceeds the adapter's 100 MB default cap. Parity-test docstring updated to document the new rewrite. - README: add the kept-uncertain tier to the expected-output section (four-tier taxonomy was undocumented), complete the generate/lint flag lists in the CLI section, mention lint --model. - docs/audits.md: fix stale audit_schema_version values (safety 1 -> 3, prune 1 -> 2) bumped by issues #54/#55. - docs/e2e-smoke-test.md: fix stale cross-reference claiming the README quick start does a manual profile rewrite — it uses signalforge init-demo. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * build: migrate dev tooling to uv + widen Python matrix to 3.11/3.12 (#95) * build: migrate dev tooling to uv + widen Python matrix to 3.11/3.12/3.13 Mirrors clauditor's build setup. uv becomes the canonical dev install (`uv sync --dev`); `uv.lock` is committed; CI runs uv-managed steps across a 3-Python matrix. `pip install -e ".[dev]"` is retained for back-compat — `[project.optional-dependencies].dev` and the new `[dependency-groups].dev` are kept in sync. - `pyproject.toml`: add `[dependency-groups].dev` (PEP 735); only delta vs the pip-extra is `build>=1.2,<2` for the wheel_smoke marker. - `.github/workflows/ci.yml`: replace setup-python + pip install with `astral-sh/setup-uv@v8.1.0` (SHA-pinned per ci-supply-chain.md) + `uv sync --dev`; wrap lint/test steps with `uv run`. Matrix runs ruff + pytest on all three versions; pyright gated to the matrix floor (3.11, matching pyright.pythonVersion); codecov upload gated to the matrix ceiling (3.13) so coverage doesn't double-upload. - `.github/workflows/publish.yml`: `python -m build` → `uv build` in both publish-testpypi and publish-pypi jobs. - `.gitignore`: drop `uv.lock` from the ignore list (bark scaffolding artifact comment is stale — the canonical lock is committed now). - `CLAUDE.md` / `CONTRIBUTING.md` / `README.md` / `docs/cli-ops.md` / `docs/codecov-ops.md` / `docs/manifest-loader-ops.md`: switch copy-pasteable dev commands to `uv sync --dev` + `uv run …`. - `.claude/rules/python-build.md`: replace "Editable install (zsh-safe)" with a uv-managed dev environment section; refresh issue-#46 floor-pinning to describe the matrix. - `.claude/rules/ci-supply-chain.md`: add `astral-sh/setup-uv` to the SHA-pinning example list; graduate DEC-003 from single-Python to a 3-Python matrix with the pyright / codecov gating rationale. Local validation: `uv run ruff check .` / `uv run ruff format --check .` / `uv run pyright` / `uv run pytest` all clean under Python 3.13; `uv run --python 3.11 pytest --no-cov` and `--python 3.12` both 1830/1830 pass. wheel_smoke passes under `uv run`. (Six pre-existing WSL2-only symlink-loop test failures on 3.13 unaffected; pass in GHA.) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: drop 3.13 from matrix; document path-safety follow-up CI exposed six 3.13-only test failures under tests/_common/, tests/diff/, tests/grade/, tests/manifest/. Root cause: Python 3.13 changed Path.resolve() to raise OSError(errno.ELOOP) on cyclic symlinks instead of RuntimeError. SignalForge's _canonicalise_path and the manifest loader's path canonicalisation catch RuntimeError only — on 3.13 the cycle now escapes as OSError or short-circuits to a different typed error before the loop check fires. Fixing it touches production code (signalforge/_common/path_safety.py and signalforge/manifest/loader.py) plus four test fixtures, which is substantial scope creep for a tooling-migration PR. Narrow the matrix to 3.11 / 3.12 (both green: 1830 passed locally and in CI) and track the 3.13 work in a follow-up issue. - ci.yml: matrix python-version: ["3.11", "3.12"]; codecov-upload gate flips from 3.13 → 3.12 (matrix ceiling). Comment block records the reason and points at the follow-up. - CLAUDE.md / CONTRIBUTING.md / docs/cli-ops.md / python-build.md / ci-supply-chain.md: every "3.11 / 3.12 / 3.13" reference now reads "3.11 / 3.12"; ci-supply-chain.md and python-build.md add a "3.13 is deferred" paragraph naming the catch-site fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: address CodeRabbit + Copilot review feedback Five fixes from PR #95 review (all real issues, no false positives): - `.claude/rules/ci-supply-chain.md`: codecov-gate example said `matrix.python-version == '3.13'` (stale from the 3.13 matrix); current CI gates on `'3.12'`. Updated and noted the flip-back when issue #96 lands. [coderabbit/copilot — same finding, threads 1+3] - `.github/workflows/ci.yml`: `actions/checkout` now sets `persist-credentials: false` — CI is read-only; least-privilege for the GITHUB_TOKEN. [coderabbit — thread 4] - `.github/workflows/publish.yml`: same `persist-credentials: false` on both `Checkout` steps; same justification (build job doesn't push to git). [coderabbit — thread 5] - `.github/workflows/publish.yml`: `astral-sh/setup-uv` now pins `python-version: "3.11"` on both jobs for release-build reproducibility. Without it, `uv build` picks whatever `ubuntu-latest` happens to ship. [copilot — thread 2] - `docs/cli-ops.md`: clarify that the `pip install -e ".[dev]"` fallback omits the `build` package (uv-only delta in `[dependency-groups].dev` powering wheel_smoke). Pip users shouldn't assume identical tooling coverage. [coderabbit — thread 6] Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: publish MkDocs Material site to GitHub Pages on push to main (#97) * docs: publish MkDocs Material site to GitHub Pages on push to main Mirrors clauditor's docs-publishing setup. The published site at https://wjduenow.github.io/SignalForge/ redeploys on every push to main; the gh-pages branch is generated, not authored. - `mkdocs.yml`: Material theme with light/dark toggle, search + include-markdown plugins, mermaid via pymdownx.superfences, exclude_docs: research/ (internal analysis stays off the site), edit_uri: edit/dev/docs/ (doc edits land on dev like every other PR; main is the release line). Nav: Home + CLI Reference + Pipeline Stages (manifest/warehouse/safety/draft/prune/grade/diff) + Audits & Sidecars + e2e + coverage. - `docs/index.md`: 4-line include-markdown stub pulling in the root README. Mirrors clauditor verbatim — README stays the canonical authored home doc; site home auto-syncs on every build. - `.github/workflows/ci.yml`: new `docs:` job gated on `github.ref == 'refs/heads/main' && github.event_name == 'push'` with job-scoped `permissions: contents: write` (workflow default stays `contents: read`). Uses pinned `astral-sh/setup-uv@v8.1.0` with `python-version: "3.11"` (matches publish.yml floor); runs `uv run mkdocs gh-deploy --force --no-history`. Deliberately does NOT set `persist-credentials: false` on the checkout step — `gh-deploy` needs the persisted GITHUB_TOKEN to push to gh-pages. - `pyproject.toml`: add `mkdocs-material>=9.0` and `mkdocs-include-markdown-plugin>=6.0` to `[dependency-groups].dev` only. Pip contributors don't need to build docs; this stays a uv-only delta (alongside `build` for wheel_smoke). - `.gitignore`: ignore `site/` (mkdocs build output). - `README.md`: docs badge top-of-file. - `CLAUDE.md` / `CONTRIBUTING.md`: one-liner pointing at the published site and the deploy contract. - `.claude/rules/docs-publishing.md` (new): full deploy contract — trigger rule, include-markdown pattern, edit_uri choice, persist-credentials exception, exclude_docs convention, local build recipe, when to update mkdocs.yml vs the docs themselves, first-time GH Pages setup the maintainer does once. Local validation: `uv run mkdocs build` succeeds (warnings on repo-internal links to plans/super/ and .claude/rules/ are expected and matched in clauditor — those aren't part of the published site). `uv run ruff check .` / `ruff format --check .` clean. 1823/1823 tests pass on 3.13 (6 pre-existing #96 failures unrelated). Post-merge maintainer step (once): Settings → Pages → source = "Deploy from a branch", branch = gh-pages, folder = / (root). The first push to main after this merges lands the gh-pages branch; toggle Pages on right after. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: address PR #97 review — split docs dep group + PR-time build gate Two Copilot suggestions, both real wins: - Extract `[dependency-groups].docs` (mkdocs-material + mkdocs-include-markdown-plugin only). Both CI docs jobs now `uv run --only-group docs ...` so they pull just MkDocs + plugins, not the heavy dev set (dbt-core, pyright, pytest). `dev` includes `docs` via `{include-group = "docs"}` so `uv sync --dev` still gives contributors everything. [thread: ci.yml deploy pulled --dev] - Add a read-only `docs-build` job that runs on every PR + push (`uv run --only-group docs mkdocs build`, no write perms). Catches a broken mkdocs.yml / plugin config / include-markdown syntax error at PR time instead of silently merging and only failing the post-merge gh-pages deploy. [thread: no pre-merge docs build signal] The third thread (Home page "Edit this page" link points at the docs/index.md include stub rather than README.md) is an accepted limitation of the include-markdown pattern — Material has no per-page edit-button hide without a template override, and clauditor's identical setup accepts it. The stub a contributor lands on literally documents where the real content lives. Documented in the review summary; no code change. `.claude/rules/docs-publishing.md` updated: two-job structure, the docs dependency-group split, and the "new plugin goes in docs not dev" convention. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * release-manager: cut TestPyPI from dev, PyPI from main + uv refresh Branch discipline: test releases are now cut from `dev` (was "any branch") and full releases from `main` (unchanged). The publish.yml prerelease-flag routing is unchanged — it's the mechanism; the branch is the maintainer discipline the skill enforces in pre-flight. dev is the in-development line feeding TestPyPI; main is the released line feeding PyPI. Also refreshes the skill for the uv migration (PRs #95/#97): - validation command → `uv sync --dev && uv run ruff/pyright/pytest` - `python -m build` → `uv build` (both build blocks + PR-body string) - allowed-tools gains `Bash(uv *)`; compatibility frontmatter updated - left the TestPyPI clean-room `pip install` smoke test as-is (it deliberately installs the published artifact in a throwaway venv) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: bump to 0.1.0rc3 for test release * release-manager: pin clean-room smoke test to Python >=3.11 The Step 5 TestPyPI smoke test used a bare `python -m venv`, which picks up whatever `python3` is on PATH — on a host where that's 3.10, pip silently ignores the new release (requires-python >=3.11) and only the pre-floor 0.1.0rc1 installs, producing a confusing "Could not find a version that satisfies the requirement" right after a green publish. Surfaced cutting v0.1.0rc3: the first clean-room attempt used the system 3.10 and rejected rc3/rc2 as "Requires-Python >=3.11". Fix: provision the interpreter explicitly via `uv venv --python 3.11` (the requires-python floor — verifies installability on the minimum supported version; uv auto-fetches 3.11 if absent) and `uv pip install --python <venv>`. Also documents how to distinguish this from the separate TestPyPI Fastly-cache propagation lag. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: disable setup-uv cache (enable-cache: false) on all steps CodeRabbit flagged on PR #98: setup-uv's default persists the uv cache to the GitHub Actions Cache. On a public repo, pull_request runs share a cache scope with the base branch, so a fork PR can poison an entry a later trusted run restores — a cross-trust-boundary cache-poisoning vector. Set `enable-cache: false` explicitly on every setup-uv step: ci.yml (lint-test, docs-build, docs-deploy) + publish.yml (testpypi, pypi). Runs are short enough that the lost cache is negligible; the publish/deploy steps especially must never restore a poisoned cache into a published artifact or the gh-pages deploy. Documents the convention in .claude/rules/ci-supply-chain.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: begin 0.2.0.dev0 (#100) * docs: README install instructions for the live PyPI release signalforge-dbt 0.1.0 is on PyPI, so lead the install with `pip install signalforge-dbt` instead of the clone+uv-sync path. - Status line: "Not yet on PyPI" → "Live on PyPI — pip install signalforge-dbt". - Quick start §1: pip install first + `signalforge --version` verify; note the distribution-vs-import name; add `uv tool install` / `pipx` as the isolated-CLI alternative; demote the clone + `uv sync --dev` path to a contributing pointer at CONTRIBUTING.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * #104: ingest external dbt schema.yml tests into CandidateSchema (#106) * Add super plan for #104: ingest external dbt schema.yml tests Library-only seam (signalforge.ingest.read_schema -> IngestResult). prune-existing CLI subcommand split to fast-follow #105. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Mark #104 plan published (PR #106) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Devolve #104 plan to beads (epic bd_1-scaffolding-ky3, 8 tasks) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ky3.1: scaffold signalforge.ingest package + IngestError hierarchy + exit-code lockstep US-001 of issue #104. Adds the signalforge.ingest subpackage scaffold and its six-class typed-error hierarchy (DEC-001), wired into the CLI four-tier exit-code taxonomy and the 7th AST scan. - src/signalforge/ingest/__init__.py: package init re-exporting the IngestError surface. - src/signalforge/ingest/errors.py: IngestError base (remediation + ↳ Remediation rendering, repr-safe values) plus five concretes — IngestSchemaNotFoundError / IngestSchemaParseError / IngestSchemaTooLargeError (tier 1) and IngestModelNotFoundError / IngestAnchorContractError (tier 2). - cli/_helpers.py: every concrete mapped in _EXCEPTION_TO_EXIT_CODE at its tier; IngestError registered as fallback tier-1 (dual-registration). - tests/test_audit_completeness.py: IngestError added to _EXCEPTION_MAPPING_EXCLUDED_BASES; Scan-7 module count bumped 10 -> 11. - tests/cli/test_exit_codes.py: synthetic-construction branches for the (size, limit) and (violations,) constructor shapes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ky3.2: typed ingest result models (IngestResult, SkippedTest, SkipReason) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ky3.4: anchor-contract validator (fail-loud, collect-all) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ky3.3: dbt test-entry parser (pure mapping) Pure parse_test_entry(entry, *, column) mapping a single dbt schema.yml test entry to a CandidateTest or a structured SkippedTest. Handles bare strings, single-key dicts with inline and arguments:-nested args (DEC-006), config-key tolerance, malformed-supported skips, custom / namespaced skips, and best-effort ref()/source() unwrap in relationships.to (DEC-009). No logging, no I/O, not part of the public surface. 19-test matrix added (TDD). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ky3.5: read_schema orchestrator Implement the public ingest entry point read_schema(schema, model, *, project_dir=None) -> IngestResult tying together the parser, anchor validator, and result models. Steps: str-vs-Path input contract (Path = file, symlink-hardened via _common.path_safety; str = raw YAML), size-cap before yaml.safe_load (DEC-005), model-block selection by name (DEC-006), union+dedupe tests/data_tests (DEC-008), description="" default (DEC-010), whole-file anchor check. No logging (stage-0 reader). Re-export read_schema from the package __init__ as the public surface. Adds the dbt-codegen-shaped fixture and 13 tests: happy path (supported + skipped tests, dedupe, ref() unwrap, both test keys), str-content input, and each error path (model-not-found, malformed YAML, oversize, anchor violation, missing path), plus the disabled-prune acceptance bonus. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ky3.6: docs/ingest-ops.md operational reference + mkdocs nav Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ky3.7: Quality gate — fix bugs from code review - parser: model-level supported test (column=None) now routes to a structured malformed skip instead of raising a Pydantic ValidationError out of read_schema. - reader: accepted_values dedupe key now sorts values so reordered value sets collapse (DEC-008 "sorted-args" contract). - parser: clarify _extract_args docstring for non-dict `arguments`. - tests: regression coverage for both fixes (+3 ingest tests). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ky3.8: Patterns & Memory — ingest-layer rule + CLAUDE.md + cli-layer count - New .claude/rules/ingest-layer.md distilling the #104 conventions (str-vs-Path contract, fail-loud anchor vs skip-record taxonomy, no-logging stage-0, safe-YAML size cap, dbt syntax tolerance, 11th errors.py exit-code lockstep, deferred #105 CLI). - CLAUDE.md: #104 shipped-issue entry + public-API surface entry. - cli-layer.md: scan-7 count 10->11; IngestError joins DemoError as a span-tier base (frozenset-only, no fallback) — and aligned _helpers.py by removing the IngestError tier-1 fallback to match that precedent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * #104: Address PR review feedback (Copilot) - errors.py: correct IngestError docstring — base is frozenset-only (no fallback tier), concretes span tiers 1 and 2 like DemoError. - __init__.py: refresh module docstring — the full library seam (reader, parser, anchor, models) ships in #104, not "typed-error surface only". - parser.py: _extract_args now drops the structural `arguments` key from inline args so a non-mapping `arguments:` can't leak (+ regression test). - docs/ingest-ops.md: fix usage snippet to use WarehouseAdapter.from_profile (the public factory; there is no top-level from_profile) and pass an un-entered adapter to prune_tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * #105: signalforge prune-existing CLI subcommand (#107) * Add super plan for #105: signalforge prune-existing CLI subcommand Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Mark plan phase published (PR #107) * Mark plan devolved; record beads manifest (epic bd_1-scaffolding-ad1) * bd_1-scaffolding-ad1.1: Hoist bare-name model resolver to _helpers #105 US-001 / DEC-008. Move the bare-name model resolver from lint.py to signalforge.cli._helpers as _resolve_model_by_key(manifest, key) -> Model so the future prune-existing subcommand shares one resolver rather than copy-pasting the body. lint.py keeps a thin _resolve_model_for_lint wrapper that delegates. Identical behaviour: unique_id/file-path via Manifest.get_model; bare-name via iter_models with multi-match disambiguation + disabled-model exclusion + the same ModelNotFoundError messages. No behaviour change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ad1.2: Add prune-existing add_parser + stub handler US-002 of #105 — contract surface (DEC-001, DEC-002, DEC-003). Adds src/signalforge/cli/prune_existing.py with add_parser registering the prune-existing subcommand and full flag set (positional <model>, required --schema, --project-dir/--manifest/--profiles-dir, --scope/--sample-strategy/--format choice flags, --dry-run, and the --quiet/--verbose/--no-color observability triad). Drops --mode/--write/ --min-score/--select/--estimate per DEC-002/DEC-003. cmd_prune_existing is a stub returning 0 (full ingest->prune->diff body lands in US-003). Registered in signalforge.cli.__init__._build_parser. Help strings are surface 1 of the 5-surface parity contract. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ad1.5: Add prune-existing --help subprocess smoke Extends the @pytest.mark.cli_subprocess gate to the prune-existing subcommand (#105 US-005), mirroring the existing generate/lint/version/ init-demo --help smokes. Asserts returncode 0, the unique --schema flag in stdout, and the no-traceback floor on stderr. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ad1.3: Implement cmd_prune_existing orchestrator (ingest -> prune -> diff) US-003 of #105. Replaces the US-002 stub with the real ingest -> prune -> diff orchestrator (no draft, no grade, no LLM call), inside one try/except Exception boundary (DEC-016 — no traceback ever leaks). - Resolve project_dir (mirror generate: --project-dir absolute assertion, walk-up default); --no-color/--profiles-dir env mutation (DEC-023). - Resolve <model> via _resolve_model_by_key (bare-name / unique_id / file-path). Canonicalise --schema via canonicalise_user_path (DEC-005). - read_schema(Path, model, project_dir=...) so ingest typed errors fire and route via the pre-registered exit-code table (DEC-006 — no bespoke CliPruneExisting* wrappers). - Skipped-test report to stderr: grouped summary + --verbose per-item detail, suppressed by --quiet (DEC-007). print_stderr, not _LOGGER. - PruneConfig --scope/--sample-strategy overrides via model_validate (DEC-002). _make_warehouse_adapter test seam (DEC-009); prune_tests owns the with-adapter block (un-entered adapter). - Feed the external schema.yml text as render_diff existing_schema with grading_report=None -> kept/kept-uncertain/dropped, never flagged (DEC-004); --dry-run -> write_sidecar=False. render_to_text -> stdout. - 3-stage progress (1/3 ingest, 2/3 prune, 3/3 diff) via the existing emit_progress_* helpers, generalised with a total= kwarg (default 5). Adds an Austin-aligned schema.yml fixture whose four supported test types reference real stg_bikeshare_trips columns plus two unsupported tests for the skip path. Tests cover happy path, skipped report (+ --verbose + --quiet), each error-path exit-code tier with a no-traceback floor, --dry-run no-disk, --format json, and bare-name vs unique_id resolution, via a FakeBigQueryClient-backed BigQueryAdapter on the patch seam. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ad1.4: Add cli-ops prune-existing section + 5-surface parity test US-004 of #105. Documents the `signalforge prune-existing` subcommand in docs/cli-ops.md (synopsis, flag-reference table, four-tier exit codes, skipped-test summary stderr shape, read-only/no-`--write` note, the unified-diff-against-your-file framing + cosmetic-reformatting caveat, the no-grading -> never-flagged note, and a worked example). Bumps the subcommand count from four to six. Adds tests/cli/test_5_surface_parity_prune_existing.py mirroring test_5_surface_parity_select.py: asserts the flag set (--schema, --scope, --sample-strategy, --dry-run, prune-existing) and the read-only intent appear consistently across argparse help, prune_existing.py help/docstrings, docs/cli-ops.md, and the plan's DEC list. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ad1.6: Quality gate — fix bugs from code review - prune engine: short-circuit empty candidate before warehouse contact (all-skipped schema.yml no longer materialises a temp table for zero tests); regression test on the default materialised+sample path. - prune-existing: scrub \n/\r/\t in the --verbose skipped-test report so an operator YAML value can't inject a fake bullet line (mirrors format_batch_summary's control-char scrub). - test: assert the exact kept/dropped mix (7/3) in the --format json test so a prune keep-everything/drop-everything regression fails loudly (testing-signal.md). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ad1.6: Quality gate — fix CodeRabbit finding (plan ref) Correct the prune-existing subprocess-smoke docstring to cite plans/super/105-prune-existing-cli.md (was 104). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ad1.7: Patterns & Memory — record #105 conventions - ingest-layer.md: prune-existing shipped (was deferred); record the no-bespoke-wrappers (DEC-006), --mode-inert (DEC-002), read-only (DEC-003/004) findings. - cli-layer.md: bare-name resolver hoist done in #105; new section on "library errors already mapped -> no per-class wrappers" + no-LLM-stage flag-audit + read-only conventions. - prune-engine.md: empty-candidate short-circuit (no warehouse contact). - CLAUDE.md: add #105 to shipped issues + CLI surface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * #105: Address PR review feedback (Copilot + CodeRabbit) - prune_existing.py: refresh stale module docstring (no longer a "stub"; the orchestrator is implemented) and note the no-bespoke-wrappers seam. - prune/engine.py: call _maybe_emit_kept_rate_warning at the new empty-candidate return site (no-op via total==0 early-return) to honour the "every return site" contract (#51). - docs/cli-ops.md: subcommand count six -> five; avoid a line starting with "#104" (heading parse); trim leading spaces inside the Remediation inline code span. - plans/super/105-prune-existing-cli.md: markdownlint — language on the fenced block, avoid "#104" line start. --profiles-dir non-canonicalisation is intentional (mirrors generate; dbt places profiles.yml outside the project) — documented as a false positive on the thread. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: surface ingest + prune-existing (#104/#105) in user docs (#108) The external-test ingest layer (#104) and the prune-existing CLI (#105) only appeared in their own ops pages; the README — the published home page and product pitch — never mentioned them, and ingest-ops.md still said the CLI didn't exist. README: - "Why this exists" / "What it does": SignalForge now prunes ANY generator's tests, not just its own drafts. - "How it works": add the second (no-LLM) entry path diagram. - New "Prune the tests you already have" cookbook with a runnable example + an availability callout (prune-existing is the v0.2 dev line, not yet on the v0.1 PyPI release; install-from-source command). - CLI list: four -> five subcommands; prune-existing flags; v0.2 marker. - Roadmap: note prune-existing on the v0.2 row. Docs: - ingest-ops.md: fix stale "no prune-existing CLI yet" wording (it shipped in #105); cross-link the CLI reference. - cli-ops.md: cross-link the supported/skipped test taxonomy in ingest-ops.md. Verified: mkdocs build clean; all in-page anchors resolve. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * #96: Python 3.13 compatibility for the path-safety layer (#109) * #96: Python 3.13 compatibility for the path-safety layer Python 3.13 (gh-108958) changed `Path.resolve()` to raise `OSError(errno.ELOOP)` instead of `RuntimeError` on cyclic symlinks, and made `strict=False` resolution stop raising on a cycle entirely (it returns a best-effort unresolved path). The three loop-guard sites caught only `RuntimeError`, so on 3.13 a symlink cycle either escaped as a raw OSError or slipped silently past the containment check. Fix every loop-guard site to resolve the input path `strict=True` first (falling back to `strict=False` only for a genuinely missing target) and to catch both `RuntimeError` (<= 3.12) and `OSError(errno.ELOOP)` (>= 3.13): - `signalforge/_common/path_safety.py` - `signalforge/manifest/loader.py::_canonicalise_path` - `signalforge/demo/__init__.py::copy_demo` The demo test's symlink-cycle case no longer skips on Linux/WSL2 and now asserts the cause is `RuntimeError | OSError`. Re-add 3.13 to the CI matrix as the ceiling; flip the codecov upload gate 3.12 -> 3.13. Update the deferral prose in CLAUDE.md, CONTRIBUTING.md, python-build.md, and ci-supply-chain.md. Validated on 3.11 / 3.12 / 3.13: 1923 passed; ruff + format + pyright clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * #96: keep the manifest non-dict-root guard live (pyright unreachable fix) `raw: dict[str, Any] = json.load(fh)` asserted a type json.load doesn't guarantee, so pyright treated the `isinstance(loaded, dict)` root-shape guard as dead code. Load as `Any`, narrow to `dict[str, Any]` after the check so the guard stays live. Add a test (`non_dict_root.json` — valid JSON array root) that exercises the now-reachable branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * #96: narrow strict-resolve fallback to missing-target only (PR review) Copilot review: the `except OSError` fallback to `resolve(strict=False)` was too broad — it downgraded ANY non-ELOOP OSError (e.g. PermissionError) to best-effort resolution, contradicting the "fallback only for a missing target" intent and potentially weakening the containment check. Narrow the fallback to `FileNotFoundError` / `NotADirectoryError`; re-raise every other OSError. Applied to all three loop-guard sites (_common/path_safety, manifest/loader, demo). Tighten the demo docstring to state the strict-then-fallback behaviour explicitly. Add a regression test asserting a PermissionError on the input path propagates rather than being swallowed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * #96: close codecov patch-coverage gaps on the path-safety fix The codecov upload runs from the 3.13 leg, so the `except RuntimeError` arms (which only execute on Python <= 3.12) and the new non-ELOOP re-raise arms showed as uncovered. Address honestly: - Add regression tests for the non-ELOOP re-raise arms (PermissionError on the input path AND project_dir for path_safety; input path for loader; dest for demo) — these encode the Copilot fix and are reachable on every interpreter. - `# pragma: no cover` the `except RuntimeError` arms (<=3.12-only cycle signal; exercised on the 3.11/3.12 CI legs, structurally unreachable on the 3.13 upload leg) and loader's project_dir handler block (defensive — `load()` passes an already-resolved project_dir, mirroring the tested _common.path_safety helper). Patch coverage on the touched files is now 100% on the 3.13 upload leg. Validated on 3.11 / 3.12 / 3.13: 1928 passed; ruff + format + pyright clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: steer pre-release gated-marker runs to the matrix ceiling (3.13) (#110) The gated markers (bigquery / anthropic / cli_subprocess / e2e / wheel_smoke) carry no Python-version pin and run on whatever interpreter `uv run` resolves; no CI job runs them. Following #96 (3.13 is now the matrix ceiling), document that maintainers should run the packaging/entry-point markers (wheel_smoke, cli_subprocess) under `uv run --python 3.13`, while the interpreter-invariant live-service markers stay single-version to avoid multiplying paid API calls. CONTRIBUTING.md § Pre-release coverage audit + testing-signal.md § Known gap. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: release 0.2.0 * test: close 0.2.0 patch-coverage gaps in ingest + prune-existing (#112) * test: close 0.2.0 patch-coverage gaps in ingest + prune-existing Brings ingest/parser.py and ingest/reader.py to 100% and cli/prune_existing.py to 96% on the default (non-gated) test run, raising the 0.2.0 release PR's codecov patch coverage from 92.44%. - parser: ref()/source() non-quoted + arity edges, non-dict bodies, multi-key + non-str/dict entries, model-level relationships skip - reader: path-containment failure, unreadable file (POSIX, non-root), non-dict / nameless column entries ignored - prune-existing: walk-up project-dir resolution (success + failure), --no-color env mutation, empty skipped-report guard Residual: prune_existing.py:270 (real WarehouseAdapter.from_profile, gated-only) and :447-454 (--profiles-dir resolve error, flaky to trigger) are documented coverage-policy exclusions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test: assert diff.json sidecar durability in prune-existing success tests Addresses CodeRabbit review on #112: the three new success-path tests now also assert the default-on .signalforge/diff.json sidecar is written (DEC-016 e2e convention — exit 0 + no traceback + durable sidecar). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: address CodeRabbit findings on ingest + prune-existing (#104/#105) (#113) #3 (ingest reader): enforce the DEC-005 size cap from stat().st_size BEFORE reading file bytes for a Path input, so an oversize schema.yml is rejected without first being slurped into memory. The post-resolve length check stays as the str-path enforcement + backstop. #2 (prune-existing --dry-run docs): correct cli-ops.md — --dry-run suppresses the .signalforge/diff.json sidecar, but the fail-closed .signalforge/prune.jsonl audit is STILL written (every prune run leaves a durable receipt — the cross-stage invariant; mirrors generate). Pinned by strengthening test_dry_run_writes_no_sidecar to assert prune.jsonl is present. (#1, env-var restore, is left as-is — DEC-023 'mutate, don't restore' is the deliberate, consistent pattern across all subcommands.) reader.py back to 100% coverage; full suite 1945 passed. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: README install instructions for the live PyPI release (#102) signalforge-dbt 0.1.0 is on PyPI, so lead the install with `pip install signalforge-dbt` instead of the clone+uv-sync path. - Status line: "Not yet on PyPI" → "Live on PyPI — pip install signalforge-dbt". - Quick start §1: pip install first + `signalforge --version` verify; note the distribution-vs-import name; add `uv tool install` / `pipx` as the isolated-CLI alternative; demote the clone + `uv sync --dev` path to a contributing pointer at CONTRIBUTING.md. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: release 0.2.0 (#111) * 93: silence pydantic UserWarning for LLMRequest.schema field shadow (#94) * 93: silence pydantic UserWarning for LLMRequest.schema field shadow The `schema` field name on `LLMRequest` is part of the documented audit-log contract (safety-layer.md DEC-014), but it shadows Pydantic v1's deprecated `BaseModel.schema` method, triggering a UserWarning at class-creation time that surfaces on every fresh import / CLI invocation. Wrap the class definition in `warnings.catch_warnings()` with a targeted filter — narrowest blast radius; no global filter mutation, the contract stays unchanged. Adds a subprocess-import regression test under `-W error::UserWarning` so future shape changes can't silently reintroduce it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 93: document Pydantic field-name-shadow suppression pattern Compounding update from PR #94: capture the `catch_warnings()` scope-to-class convention so the next field-name-shadow case (audit-log contract + Pydantic v1 BaseModel attribute collision) doesn't get "fixed" by renaming or a global filter. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 93: address PR review feedback - models.py: relax warning-filter message from exact literal to regex (`Field name "schema".*shadows.*`) so future Pydantic message-wording drift still gets caught. Drops the experimental `module=` qualifier (over-narrowing — filter stopped matching the live warning). - test_models.py: add `timeout=10` to the subprocess import smoke, matching the precedent in `tests/cli/test_subprocess_smoke.py` so a pathological import hang fails fast instead of stalling the suite. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: reconcile README + docs with current v0.1 codebase (#90) Audited the root README and docs/ ops files against the shipped code: - demo profiles.yml: add maximum_bytes_billed: 1000000000 so the README quick start actually works — the demo's materialised-sample CTAS over the full ~2.27M-row bikeshare_trips source exceeds the adapter's 100 MB default cap. Parity-test docstring updated to document the new rewrite. - README: add the kept-uncertain tier to the expected-output section (four-tier taxonomy was undocumented), complete the generate/lint flag lists in the CLI section, mention lint --model. - docs/audits.md: fix stale audit_schema_version values (safety 1 -> 3, prune 1 -> 2) bumped by issues #54/#55. - docs/e2e-smoke-test.md: fix stale cross-reference claiming the README quick start does a manual profile rewrite — it uses signalforge init-demo. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * build: migrate dev tooling to uv + widen Python matrix to 3.11/3.12 (#95) * build: migrate dev tooling to uv + widen Python matrix to 3.11/3.12/3.13 Mirrors clauditor's build setup. uv becomes the canonical dev install (`uv sync --dev`); `uv.lock` is committed; CI runs uv-managed steps across a 3-Python matrix. `pip install -e ".[dev]"` is retained for back-compat — `[project.optional-dependencies].dev` and the new `[dependency-groups].dev` are kept in sync. - `pyproject.toml`: add `[dependency-groups].dev` (PEP 735); only delta vs the pip-extra is `build>=1.2,<2` for the wheel_smoke marker. - `.github/workflows/ci.yml`: replace setup-python + pip install with `astral-sh/setup-uv@v8.1.0` (SHA-pinned per ci-supply-chain.md) + `uv sync --dev`; wrap lint/test steps with `uv run`. Matrix runs ruff + pytest on all three versions; pyright gated to the matrix floor (3.11, matching pyright.pythonVersion); codecov upload gated to the matrix ceiling (3.13) so coverage doesn't double-upload. - `.github/workflows/publish.yml`: `python -m build` → `uv build` in both publish-testpypi and publish-pypi jobs. - `.gitignore`: drop `uv.lock` from the ignore list (bark scaffolding artifact comment is stale — the canonical lock is committed now). - `CLAUDE.md` / `CONTRIBUTING.md` / `README.md` / `docs/cli-ops.md` / `docs/codecov-ops.md` / `docs/manifest-loader-ops.md`: switch copy-pasteable dev commands to `uv sync --dev` + `uv run …`. - `.claude/rules/python-build.md`: replace "Editable install (zsh-safe)" with a uv-managed dev environment section; refresh issue-#46 floor-pinning to describe the matrix. - `.claude/rules/ci-supply-chain.md`: add `astral-sh/setup-uv` to the SHA-pinning example list; graduate DEC-003 from single-Python to a 3-Python matrix with the pyright / codecov gating rationale. Local validation: `uv run ruff check .` / `uv run ruff format --check .` / `uv run pyright` / `uv run pytest` all clean under Python 3.13; `uv run --python 3.11 pytest --no-cov` and `--python 3.12` both 1830/1830 pass. wheel_smoke passes under `uv run`. (Six pre-existing WSL2-only symlink-loop test failures on 3.13 unaffected; pass in GHA.) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: drop 3.13 from matrix; document path-safety follow-up CI exposed six 3.13-only test failures under tests/_common/, tests/diff/, tests/grade/, tests/manifest/. Root cause: Python 3.13 changed Path.resolve() to raise OSError(errno.ELOOP) on cyclic symlinks instead of RuntimeError. SignalForge's _canonicalise_path and the manifest loader's path canonicalisation catch RuntimeError only — on 3.13 the cycle now escapes as OSError or short-circuits to a different typed error before the loop check fires. Fixing it touches production code (signalforge/_common/path_safety.py and signalforge/manifest/loader.py) plus four test fixtures, which is substantial scope creep for a tooling-migration PR. Narrow the matrix to 3.11 / 3.12 (both green: 1830 passed locally and in CI) and track the 3.13 work in a follow-up issue. - ci.yml: matrix python-version: ["3.11", "3.12"]; codecov-upload gate flips from 3.13 → 3.12 (matrix ceiling). Comment block records the reason and points at the follow-up. - CLAUDE.md / CONTRIBUTING.md / docs/cli-ops.md / python-build.md / ci-supply-chain.md: every "3.11 / 3.12 / 3.13" reference now reads "3.11 / 3.12"; ci-supply-chain.md and python-build.md add a "3.13 is deferred" paragraph naming the catch-site fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: address CodeRabbit + Copilot review feedback Five fixes from PR #95 review (all real issues, no false positives): - `.claude/rules/ci-supply-chain.md`: codecov-gate example said `matrix.python-version == '3.13'` (stale from the 3.13 matrix); current CI gates on `'3.12'`. Updated and noted the flip-back when issue #96 lands. [coderabbit/copilot — same finding, threads 1+3] - `.github/workflows/ci.yml`: `actions/checkout` now sets `persist-credentials: false` — CI is read-only; least-privilege for the GITHUB_TOKEN. [coderabbit — thread 4] - `.github/workflows/publish.yml`: same `persist-credentials: false` on both `Checkout` steps; same justification (build job doesn't push to git). [coderabbit — thread 5] - `.github/workflows/publish.yml`: `astral-sh/setup-uv` now pins `python-version: "3.11"` on both jobs for release-build reproducibility. Without it, `uv build` picks whatever `ubuntu-latest` happens to ship. [copilot — thread 2] - `docs/cli-ops.md`: clarify that the `pip install -e ".[dev]"` fallback omits the `build` package (uv-only delta in `[dependency-groups].dev` powering wheel_smoke). Pip users shouldn't assume identical tooling coverage. [coderabbit — thread 6] Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: publish MkDocs Material site to GitHub Pages on push to main (#97) * docs: publish MkDocs Material site to GitHub Pages on push to main Mirrors clauditor's docs-publishing setup. The published site at https://wjduenow.github.io/SignalForge/ redeploys on every push to main; the gh-pages branch is generated, not authored. - `mkdocs.yml`: Material theme with light/dark toggle, search + include-markdown plugins, mermaid via pymdownx.superfences, exclude_docs: research/ (internal analysis stays off the site), edit_uri: edit/dev/docs/ (doc edits land on dev like every other PR; main is the release line). Nav: Home + CLI Reference + Pipeline Stages (manifest/warehouse/safety/draft/prune/grade/diff) + Audits & Sidecars + e2e + coverage. - `docs/index.md`: 4-line include-markdown stub pulling in the root README. Mirrors clauditor verbatim — README stays the canonical authored home doc; site home auto-syncs on every build. - `.github/workflows/ci.yml`: new `docs:` job gated on `github.ref == 'refs/heads/main' && github.event_name == 'push'` with job-scoped `permissions: contents: write` (workflow default stays `contents: read`). Uses pinned `astral-sh/setup-uv@v8.1.0` with `python-version: "3.11"` (matches publish.yml floor); runs `uv run mkdocs gh-deploy --force --no-history`. Deliberately does NOT set `persist-credentials: false` on the checkout step — `gh-deploy` needs the persisted GITHUB_TOKEN to push to gh-pages. - `pyproject.toml`: add `mkdocs-material>=9.0` and `mkdocs-include-markdown-plugin>=6.0` to `[dependency-groups].dev` only. Pip contributors don't need to build docs; this stays a uv-only delta (alongside `build` for wheel_smoke). - `.gitignore`: ignore `site/` (mkdocs build output). - `README.md`: docs badge top-of-file. - `CLAUDE.md` / `CONTRIBUTING.md`: one-liner pointing at the published site and the deploy contract. - `.claude/rules/docs-publishing.md` (new): full deploy contract — trigger rule, include-markdown pattern, edit_uri choice, persist-credentials exception, exclude_docs convention, local build recipe, when to update mkdocs.yml vs the docs themselves, first-time GH Pages setup the maintainer does once. Local validation: `uv run mkdocs build` succeeds (warnings on repo-internal links to plans/super/ and .claude/rules/ are expected and matched in clauditor — those aren't part of the published site). `uv run ruff check .` / `ruff format --check .` clean. 1823/1823 tests pass on 3.13 (6 pre-existing #96 failures unrelated). Post-merge maintainer step (once): Settings → Pages → source = "Deploy from a branch", branch = gh-pages, folder = / (root). The first push to main after this merges lands the gh-pages branch; toggle Pages on right after. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: address PR #97 review — split docs dep group + PR-time build gate Two Copilot suggestions, both real wins: - Extract `[dependency-groups].docs` (mkdocs-material + mkdocs-include-markdown-plugin only). Both CI docs jobs now `uv run --only-group docs ...` so they pull just MkDocs + plugins, not the heavy dev set (dbt-core, pyright, pytest). `dev` includes `docs` via `{include-group = "docs"}` so `uv sync --dev` still gives contributors everything. [thread: ci.yml deploy pulled --dev] - Add a read-only `docs-build` job that runs on every PR + push (`uv run --only-group docs mkdocs build`, no write perms). Catches a broken mkdocs.yml / plugin config / include-markdown syntax error at PR time instead of silently merging and only failing the post-merge gh-pages deploy. [thread: no pre-merge docs build signal] The third thread (Home page "Edit this page" link points at the docs/index.md include stub rather than README.md) is an accepted limitation of the include-markdown pattern — Material has no per-page edit-button hide without a template override, and clauditor's identical setup accepts it. The stub a contributor lands on literally documents where the real content lives. Documented in the review summary; no code change. `.claude/rules/docs-publishing.md` updated: two-job structure, the docs dependency-group split, and the "new plugin goes in docs not dev" convention. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * release-manager: cut TestPyPI from dev, PyPI from main + uv refresh Branch discipline: test releases are now cut from `dev` (was "any branch") and full releases from `main` (unchanged). The publish.yml prerelease-flag routing is unchanged — it's the mechanism; the branch is the maintainer discipline the skill enforces in pre-flight. dev is the in-development line feeding TestPyPI; main is the released line feeding PyPI. Also refreshes the skill for the uv migration (PRs #95/#97): - validation command → `uv sync --dev && uv run ruff/pyright/pytest` - `python -m build` → `uv build` (both build blocks + PR-body string) - allowed-tools gains `Bash(uv *)`; compatibility frontmatter updated - left the TestPyPI clean-room `pip install` smoke test as-is (it deliberately installs the published artifact in a throwaway venv) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: bump to 0.1.0rc3 for test release * release-manager: pin clean-room smoke test to Python >=3.11 The Step 5 TestPyPI smoke test used a bare `python -m venv`, which picks up whatever `python3` is on PATH — on a host where that's 3.10, pip silently ignores the new release (requires-python >=3.11) and only the pre-floor 0.1.0rc1 installs, producing a confusing "Could not find a version that satisfies the requirement" right after a green publish. Surfaced cutting v0.1.0rc3: the first clean-room attempt used the system 3.10 and rejected rc3/rc2 as "Requires-Python >=3.11". Fix: provision the interpreter explicitly via `uv venv --python 3.11` (the requires-python floor — verifies installability on the minimum supported version; uv auto-fetches 3.11 if absent) and `uv pip install --python <venv>`. Also documents how to distinguish this from the separate TestPyPI Fastly-cache propagation lag. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: disable setup-uv cache (enable-cache: false) on all steps CodeRabbit flagged on PR #98: setup-uv's default persists the uv cache to the GitHub Actions Cache. On a public repo, pull_request runs share a cache scope with the base branch, so a fork PR can poison an entry a later trusted run restores — a cross-trust-boundary cache-poisoning vector. Set `enable-cache: false` explicitly on every setup-uv step: ci.yml (lint-test, docs-build, docs-deploy) + publish.yml (testpypi, pypi). Runs are short enough that the lost cache is negligible; the publish/deploy steps especially must never restore a poisoned cache into a published artifact or the gh-pages deploy. Documents the convention in .claude/rules/ci-supply-chain.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: begin 0.2.0.dev0 (#100) * docs: README install instructions for the live PyPI release signalforge-dbt 0.1.0 is on PyPI, so lead the install with `pip install signalforge-dbt` instead of the clone+uv-sync path. - Status line: "Not yet on PyPI" → "Live on PyPI — pip install signalforge-dbt". - Quick start §1: pip install first + `signalforge --version` verify; note the distribution-vs-import name; add `uv tool install` / `pipx` as the isolated-CLI alternative; demote the clone + `uv sync --dev` path to a contributing pointer at CONTRIBUTING.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * #104: ingest external dbt schema.yml tests into CandidateSchema (#106) * Add super plan for #104: ingest external dbt schema.yml tests Library-only seam (signalforge.ingest.read_schema -> IngestResult). prune-existing CLI subcommand split to fast-follow #105. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Mark #104 plan published (PR #106) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Devolve #104 plan to beads (epic bd_1-scaffolding-ky3, 8 tasks) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ky3.1: scaffold signalforge.ingest package + IngestError hierarchy + exit-code lockstep US-001 of issue #104. Adds the signalforge.ingest subpackage scaffold and its six-class typed-error hierarchy (DEC-001), wired into the CLI four-tier exit-code taxonomy and the 7th AST scan. - src/signalforge/ingest/__init__.py: package init re-exporting the IngestError surface. - src/signalforge/ingest/errors.py: IngestError base (remediation + ↳ Remediation rendering, repr-safe values) plus five concretes — IngestSchemaNotFoundError / IngestSchemaParseError / IngestSchemaTooLargeError (tier 1) and IngestModelNotFoundError / IngestAnchorContractError (tier 2). - cli/_helpers.py: every concrete mapped in _EXCEPTION_TO_EXIT_CODE at its tier; IngestError registered as fallback tier-1 (dual-registration). - tests/test_audit_completeness.py: IngestError added to _EXCEPTION_MAPPING_EXCLUDED_BASES; Scan-7 module count bumped 10 -> 11. - tests/cli/test_exit_codes.py: synthetic-construction branches for the (size, limit) and (violations,) constructor shapes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ky3.2: typed ingest result models (IngestResult, SkippedTest, SkipReason) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ky3.4: anchor-contract validator (fail-loud, collect-all) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ky3.3: dbt test-entry parser (pure mapping) Pure parse_test_entry(entry, *, column) mapping a single dbt schema.yml test entry to a CandidateTest or a structured SkippedTest. Handles bare strings, single-key dicts with inline and arguments:-nested args (DEC-006), config-key tolerance, malformed-supported skips, custom / namespaced skips, and best-effort ref()/source() unwrap in relationships.to (DEC-009). No logging, no I/O, not part of the public surface. 19-test matrix added (TDD). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ky3.5: read_schema orchestrator Implement the public ingest entry point read_schema(schema, model, *, project_dir=None) -> IngestResult tying together the parser, anchor validator, and result models. Steps: str-vs-Path input contract (Path = file, symlink-hardened via _common.path_safety; str = raw YAML), size-cap before yaml.safe_load (DEC-005), model-block selection by name (DEC-006), union+dedupe tests/data_tests (DEC-008), description="" default (DEC-010), whole-file anchor check. No logging (stage-0 reader). Re-export read_schema from the package __init__ as the public surface. Adds the dbt-codegen-shaped fixture and 13 tests: happy path (supported + skipped tests, dedupe, ref() unwrap, both test keys), str-content input, and each error path (model-not-found, malformed YAML, oversize, anchor violation, missing path), plus the disabled-prune acceptance bonus. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ky3.6: docs/ingest-ops.md operational reference + mkdocs nav Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ky3.7: Quality gate — fix bugs from code review - parser: model-level supported test (column=None) now routes to a structured malformed skip instead of raising a Pydantic ValidationError out of read_schema. - reader: accepted_values dedupe key now sorts values so reordered value sets collapse (DEC-008 "sorted-args" contract). - parser: clarify _extract_args docstring for non-dict `arguments`. - tests: regression coverage for both fixes (+3 ingest tests). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ky3.8: Patterns & Memory — ingest-layer rule + CLAUDE.md + cli-layer count - New .claude/rules/ingest-layer.md distilling the #104 conventions (str-vs-Path contract, fail-loud anchor vs skip-record taxonomy, no-logging stage-0, safe-YAML size cap, dbt syntax tolerance, 11th errors.py exit-code lockstep, deferred #105 CLI). - CLAUDE.md: #104 shipped-issue entry + public-API surface entry. - cli-layer.md: scan-7 count 10->11; IngestError joins DemoError as a span-tier base (frozenset-only, no fallback) — and aligned _helpers.py by removing the IngestError tier-1 fallback to match that precedent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * #104: Address PR review feedback (Copilot) - errors.py: correct IngestError docstring — base is frozenset-only (no fallback tier), concretes span tiers 1 and 2 like DemoError. - __init__.py: refresh module docstring — the full library seam (reader, parser, anchor, models) ships in #104, not "typed-error surface only". - parser.py: _extract_args now drops the structural `arguments` key from inline args so a non-mapping `arguments:` can't leak (+ regression test). - docs/ingest-ops.md: fix usage snippet to use WarehouseAdapter.from_profile (the public factory; there is no top-level from_profile) and pass an un-entered adapter to prune_tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * #105: signalforge prune-existing CLI subcommand (#107) * Add super plan for #105: signalforge prune-existing CLI subcommand Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Mark plan phase published (PR #107) * Mark plan devolved; record beads manifest (epic bd_1-scaffolding-ad1) * bd_1-scaffolding-ad1.1: Hoist bare-name model resolver to _helpers #105 US-001 / DEC-008. Move the bare-name model resolver from lint.py to signalforge.cli._helpers as _resolve_model_by_key(manifest, key) -> Model so the future prune-existing subcommand shares one resolver rather than copy-pasting the body. lint.py keeps a thin _resolve_model_for_lint wrapper that delegates. Identical behaviour: unique_id/file-path via Manifest.get_model; bare-name via iter_models with multi-match disambiguation + disabled-model exclusion + the same ModelNotFoundError messages. No behaviour change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ad1.2: Add prune-existing add_parser + stub handler US-002 of #105 — contract surface (DEC-001, DEC-002, DEC-003). Adds src/signalforge/cli/prune_existing.py with add_parser registering the prune-existing subcommand and full flag set (positional <model>, required --schema, --project-dir/--manifest/--profiles-dir, --scope/--sample-strategy/--format choice flags, --dry-run, and the --quiet/--verbose/--no-color observability triad). Drops --mode/--write/ --min-score/--select/--estimate per DEC-002/DEC-003. cmd_prune_existing is a stub returning 0 (full ingest->prune->diff body lands in US-003). Registered in signalforge.cli.__init__._build_parser. Help strings are surface 1 of the 5-surface parity contract. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ad1.5: Add prune-existing --help subprocess smoke Extends the @pytest.mark.cli_subprocess gate to the prune-existing subcommand (#105 US-005), mirroring the existing generate/lint/version/ init-demo --help smokes. Asserts returncode 0, the unique --schema flag in stdout, and the no-traceback floor on stderr. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ad1.3: Implement cmd_prune_existing orchestrator (ingest -> prune -> diff) US-003 of #105. Replaces the US-002 stub with the real ingest -> prune -> diff orchestrator (no draft, no grade, no LLM call), inside one try/except Exception boundary (DEC-016 — no traceback ever leaks). - Resolve project_dir (mirror generate: --project-dir absolute assertion, walk-up default); --no-color/--profiles-dir env mutation (DEC-023). - Resolve <model> via _resolve_model_by_key (bare-name / unique_id / file-path). Canonicalise --schema via canonicalise_user_path (DEC-005). - read_schema(Path, model, project_dir=...) so ingest typed errors fire and route via the pre-registered exit-code table (DEC-006 — no bespoke CliPruneExisting* wrappers). - Skipped-test report to stderr: grouped summary + --verbose per-item detail, suppressed by --quiet (DEC-007). print_stderr, not _LOGGER. - PruneConfig --scope/--sample-strategy overrides via model_validate (DEC-002). _make_warehouse_adapter test seam (DEC-009); prune_tests owns the with-adapter block (un-entered adapter). - Feed the external schema.yml text as render_diff existing_schema with grading_report=None -> kept/kept-uncertain/dropped, never flagged (DEC-004); --dry-run -> write_sidecar=False. render_to_text -> stdout. - 3-stage progress (1/3 ingest, 2/3 prune, 3/3 diff) via the existing emit_progress_* helpers, generalised with a total= kwarg (default 5). Adds an Austin-aligned schema.yml fixture whose four supported test types reference real stg_bikeshare_trips columns plus two unsupported tests for the skip path. Tests cover happy path, skipped report (+ --verbose + --quiet), each error-path exit-code tier with a no-traceback floor, --dry-run no-disk, --format json, and bare-name vs unique_id resolution, via a FakeBigQueryClient-backed BigQueryAdapter on the patch seam. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ad1.4: Add cli-ops prune-existing section + 5-surface parity test US-004 of #105. Documents the `signalforge prune-existing` subcommand in docs/cli-ops.md (synopsis, flag-reference table, four-tier exit codes, skipped-test summary stderr shape, read-only/no-`--write` note, the unified-diff-against-your-file framing + cosmetic-reformatting caveat, the no-grading -> never-flagged note, and a worked example). Bumps the subcommand count from four to six. Adds tests/cli/test_5_surface_parity_prune_existing.py mirroring test_5_surface_parity_select.py: asserts the flag set (--schema, --scope, --sample-strategy, --dry-run, prune-existing) and the read-only intent appear consistently across argparse help, prune_existing.py help/docstrings, docs/cli-ops.md, and the plan's DEC list. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ad1.6: Quality gate — fix bugs from code review - prune engine: short-circuit empty candidate before warehouse contact (all-skipped schema.yml no longer materialises a temp table for zero tests); regression test on the default materialised+sample path. - prune-existing: scrub \n/\r/\t in the --verbose skipped-test report so an operator YAML value can't inject a fake bullet line (mirrors format_batch_summary's control-char scrub). - test: assert the exact kept/dropped mix (7/3) in the --format json test so a prune keep-everything/drop-everything regression fails loudly (testing-signal.md). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ad1.6: Quality gate — fix CodeRabbit finding (plan ref) Correct the prune-existing subprocess-smoke docstring to cite plans/super/105-prune-existing-cli.md (was 104). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bd_1-scaffolding-ad1.7: Patterns & Memory — record #105 conventions - ingest-layer.md: prune-existing shipped (was deferred); record the no-bespoke-wrappers (DEC-006), --mode-inert (DEC-002), read-only (DEC-003/004) findings. - cli-layer.md: bare-name resolver hoist done in #105; new section on "library errors already mapped -> no per-class wrappers" + no-LLM-stage flag-audit + read-only conventions. - prune-engine.md: empty-candidate short-circuit (no warehouse contact). - CLAUDE.md: add #105 to shipped issues + CLI surface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * #105: Address PR review feedback (Copilot + CodeRabbit) - prune_existing.py: refresh stale module docstring (no longer a "stub"; the orchestrator is implemented) and note the no-bespoke-wrappers seam. - prune/engine.py: call _maybe_emit_kept_rate_warning at the new empty-candidate return site (no-op via total==0 early-return) to honour the "every return site" contract (#51). - docs/cli-ops.md: subcommand count six -> five; avoid a line starting with "#104" (heading parse); trim leading spaces inside the Remediation inline code span. - plans/super/105-prune-existing-cli.md: markdownlint — language on the fenced block, avoid "#104" line start. --profiles-dir non-canonicalisation is intentional (mirrors generate; dbt places profiles.yml outside the project) — documented as a false positive on the thread. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: surface ingest + prune-existing (#104/#105) in user docs (#108) The external-test ingest layer (#104) and the prune-existing CLI (#105) only appeared in their own ops pages; the README — the published home page and product pitch — never mentioned them, and ingest-ops.md still said the CLI didn't exist. README: - "Why this exists" / "What it does": SignalForge now prunes ANY generator's tests, not just its own drafts. - "How it works": add the second (no-LLM) entry path diagram. - New "Prune the tests you already have" cookbook with a runnable example + an availability callout (prune-existing is the v0.2 dev line, not yet on the v0.1 PyPI release; install-from-source command). - CLI list: four -> five subcommands; prune-existing flags; v0.2 marker. - Roadmap: note prune-existing on the v0.2 row. Docs: - ingest-ops.md: fix stale "no prune-existing CLI yet" wording (it shipped in #105); cross-link the CLI reference. - cli-ops.md: cross-link the supported/skipped test taxonomy in ingest-ops.md. Verified: mkdocs build clean; all in-page anchors resolve. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * #96: Python 3.13 compatibility for the path-safety layer (#109) * #96: Python 3.13 compatibility for the path-safety layer Python 3.13 (gh-108958) changed `Path.resolve()` to raise `OSError(errno.ELOOP)` instead of `RuntimeError` on cyclic symlinks, and made `strict=False` resolution stop raising on a cycle entirely (it returns a best-effort unresolved path). The three loop-guard sites caught only `RuntimeError`, so on 3.13 a symlink cycle either escaped as a raw OSError or slipped silently past the containment check. Fix every loop-guard site to resolve the input path `strict=True` first (falling back to `strict=False` only for a genuinely missing target) and to catch both `RuntimeError` (<= 3.12) and `OSError(errno.ELOOP)` (>= 3.13): - `signalforge/_common/path_safety.py` - `signalforge/manifest/loader.py::_canonicalise_path` - `signalforge/demo/__init__.py::copy_demo` The demo test's symlink-cycle case no longer skips on Linux/WSL2 and now asserts the cause is `RuntimeError | OSError`. Re-add 3.13 to the CI matrix as the ceiling; flip the codecov upload gate 3.12 -> 3.13. Update the deferral prose in CLAUDE.md, CONTRIBUTING.md, python-build.md, and ci-supply-chain.md. Validated on 3.11 / 3.12 / 3.13: 1923 passed; ruff + format + pyright clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * #96: keep the manifest non-dict-root guard live (pyright unreachable fix) `raw: dict[str, Any] = json.load(fh)` asserted a type json.load doesn't guarantee, so pyright treated the `isinstance(loaded, dict)` root-shape guard as dead code. Load as `Any`, narrow to `dict[str, Any]` after the check so the guard stays live. Add a test (`non_dict_root.json` — valid JSON array root) that exercises the now-reachable branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * #96: narrow strict-resolve fallback to missing-target only (PR review) Copilot review: the `except OSError` fallback to `resolve(strict=False)` was too broad — it downgraded ANY non-ELOOP OSError (e.g. PermissionError) to best-effort resolution, contradicting the "fallback only for a missing target" intent and potentially weakening the containment check. Narrow the fallback to `FileNotFoundError` / `NotADirectoryError`; re-raise every other OSError. Applied to all three loop-guard sites (_common/path_safety, manifest/loader, demo). Tighten the demo docstring to state the strict-then-fallback behaviour explicitly. Add a regression test asserting a PermissionError on the input path propagates rather than being swallowed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * #96: close codecov patch-coverage gaps on the path-safety fix The codecov upload runs from the 3.13 leg, so the `except RuntimeError` arms (which only execute on Python <= 3.12) and the new non-ELOOP re-raise arms showed as uncovered. Address honestly: - Add regression tests for the non-ELOOP re-raise arms (PermissionError on the input path AND project_dir for path_safety; input path for loader; dest for demo) — these encode the Copilot fix and are reachable on every interpreter. - `# pragma: no cover` the `except RuntimeError` arms (<=3.12-only cycle signal; exercised on the 3.11/3.12 CI legs, structurally unreachable on the 3.13 upload leg) and loader's project_dir handler block (defensive — `load()` passes an already-resolved project_dir, mirroring the tested _common.path_safety helper). Patch coverage on the touched files is now 100% on the 3.13 upload leg. Validated on 3.11 / 3.12 / 3.13: 1928 passed; ruff + format + pyright clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: steer pre-release gated-marker runs to the matrix ceiling (3.13) (#110) The gated markers (bigquery / anthropic / cli_subprocess / e2e / wheel_smoke) carry no Python-version pin and run on whatever interpreter `uv run` resolves; no CI job runs them. Following #96 (3.13 is now the matrix ceiling), document that maintainers should run the packaging/entry-point markers (wheel_smoke, cli_subprocess) under `uv run --python 3.13`, while the interpreter-invariant live-service markers stay single-version to avoid multiplying paid API calls. CONTRIBUTING.md § Pre-release coverage audit + testing-signal.md § Known gap. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: release 0.2.0 * test: close 0.2.0 patch-coverage gaps in ingest + prune-existing (#112) * test: close 0.2.0 patch-coverage gaps in ingest + prune-existing Brings ingest/parser.py and ingest/reader.py to 100% and cli/prune_existing.py to 96% on the default (non-gated) test run, raising the 0.2.0 release PR's codecov patch coverage from 92.44%. - parser: ref()/source() non-quoted + arity edges, non-dict bodies, multi-key + non-str/dict entries, model-level relationships skip - reader: path-containment failure, unreadable file (POSIX, non-root), non-dict / nameless column entries ignored - prune-existing: walk-up project-dir resolution (success + failure), --no-color env mutation, empty skipped-report guard Residual: prune_existing.py:270 (real WarehouseAdapter.from_profile, gated-only) and :447-454 (--profiles-dir resolve error, flaky to trigger) are documented coverage-policy exclusions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test: assert diff.json sidecar durability in prune-existing success tests Addresses CodeRabbit review on #112: the three new success-path tests now also assert the default-on .signalforge/diff.json sidecar is written (DEC-016 e2e convention — exit 0 + no traceback + durable sidecar). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: address CodeRabbit findings on ingest + prune-existing (#104/#105) (#113) #3 (ingest reader): enforce the DEC-005 size cap from stat().st_size BEFORE reading file bytes for a Path input, so an oversize schema.yml is rejected without first being slurped into memory. The post-resolve length check stays as the str-path enforcement + backstop. #2 (prune-existing --dry-run docs): correct cli-ops.md — --dry-run suppresses the .signalforge/diff.json sidecar, but the fail-closed .signalforge/prune.jsonl audit is STILL written (every prune run leaves a durable receipt — the cross-stage invariant; mirrors generate). Pinned by strengthening test_dry_run_writes_no_sidecar to assert prune.jsonl is present. (#1, env-var restore, is left as-is — DEC-023 'mutate, don't restore' is the deliberate, consistent pattern across all subcommands.) reader.py back to 100% coverage; full suite 1945 passed. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Mirrors clauditor's build setup as the first of two PRs (PR B will add mkdocs / GitHub Pages docs publishing on top of this).
uv sync --dev;uv.lockis committed; CI runs every step underuv run.pip install -e ".[dev]"retained for back-compat:[project.optional-dependencies].devis kept in sync with the new[dependency-groups].dev(PEP 735). The only delta isbuild>=1.2,<2in the uv group for thewheel_smokemarker.Path.resolve()to raiseOSError(errno.ELOOP)instead ofRuntimeErroron cyclic symlinks; six tests undertests/_common/,tests/diff/,tests/grade/,tests/manifest/rely on the 3.11/3.12 shape. Filed as Python 3.13 compatibility for path-safety layer #96.astral-sh/setup-uv@08807647… # v8.1.0SHA-pinned perci-supply-chain.md.publish.yml:python -m build→uv buildin both publish-testpypi and publish-pypi jobs.Files touched
pyproject.toml,uv.lock(new),.gitignore(drop staleuv.lockignore).github/workflows/ci.yml,.github/workflows/publish.ymlCLAUDE.md,CONTRIBUTING.md,README.mddocs/cli-ops.md,docs/codecov-ops.md,docs/manifest-loader-ops.md.claude/rules/python-build.md(uv-managed dev env section; refresh issue-scaffolding: reconcile pyright pythonVersion with requires-python #46 floor-pinning for the matrix; note 3.13 deferral).claude/rules/ci-supply-chain.md(add setup-uv to SHA-pin examples; graduate DEC-003 single-Python rule; note 3.13 deferral)Rule updates
python-build.md§ "Editable install (zsh-safe)" → § "uv-managed dev environment (uv migration)"; § "Python version" updated to describe the matrix with floor-pinning of pyright.ci-supply-chain.md§ "Single Python version for early milestones" → § "Python matrix: 3.11 / 3.12 (uv migration)".Test plan
uv sync --devclean from scratch on a wiped.venv/uv run ruff check .cleanuv run ruff format --check .cleanuv run pyright— 0 errors / 0 warningsuv run pytest— 1823 passed locally on 3.13 (6 known failures gated by Python 3.13 compatibility for path-safety layer #96 — they pass on 3.11/3.12)uv run --python 3.11 pytest --no-cov— 1830/1830 passuv run --python 3.12 pytest --no-cov— 1830/1830 passuv run pytest -m wheel_smoke --no-cov— 2/2 passFollow-up
#96 — Python 3.13 compatibility for path-safety layer (gate to re-adding 3.13 to the matrix).
🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
Chores
Documentation