Skip to content

12: v0.1.0 release plumbing + rc1 (#12) - #34

Merged
wjduenow merged 23 commits into
mainfrom
release/0.1.0
May 11, 2026
Merged

12: v0.1.0 release plumbing + rc1 (#12)#34
wjduenow merged 23 commits into
mainfrom
release/0.1.0

Conversation

@wjduenow

@wjduenow wjduenow commented May 11, 2026

Copy link
Copy Markdown
Owner

Summary

Wires up the v0.1.0 release: PyPI publish workflow (clauditor-style), CHANGELOG, PR template, distribution rename to `signalforge-dbt`, and a `0.1.0rc1` cut for a test.pypi.org dry run before the real publish.

Mirrors clauditor's flow: GitHub Release `published` event triggers `publish.yml`; the prerelease checkbox routes to test.pypi.org vs prod pypi.org via separate trusted-publisher environments.

Distribution name is `signalforge-dbt` because the bare `signalforge` on PyPI is held by an unrelated DSP package. Import name and CLI command remain `signalforge` — no source-code surface changes.

This PR also includes a one-time merge commit aligning `main` (previously orphan, Initial commit only) with `dev`'s v0.1 history so the release PR can open.

Closes #12 once the follow-up (rc1 → 0.1.0 + dev → 0.2.0.dev0) lands.

Changes

  • `pyproject.toml` — `name = "signalforge-dbt"`
  • `src/signalforge/init.py` — `0.1.0.dev0` → `0.1.0rc1`
  • `CHANGELOG.md` (new) — v0.1 line items drawn from CLAUDE.md's "Repository status"
  • `.github/workflows/publish.yml` (new) — release-published trigger, two jobs (`publish-testpypi` / `publish-pypi`) gated on `event.release.prerelease`. Actions SHA-pinned per `ci-supply-chain.md`. Builds via `python -m build` to match the existing pip-based `ci.yml`.
  • `.github/PULL_REQUEST_TEMPLATE.md` (new) — minimal template with the canonical validation checklist
  • `README.md` / `docs/cli-ops.md` / `CLAUDE.md` — install snippets + rename rationale so future agents don't drift back to `signalforge`

Release sequence after this PR merges

  1. Add pending trusted publisher on test.pypi.org: project=`signalforge-dbt`, owner=`wjduenow`, repo=`SignalForge`, workflow=`publish.yml`, environment=`testpypi`.
  2. Create GitHub Release: tag=`v0.1.0rc1`, target=`main`, check "set as pre-release". `publish.yml` → test.pypi.org.
  3. Validate: `pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ signalforge-dbt==0.1.0rc1` in a clean venv; `signalforge --version`.
  4. Follow-up PR: bump `version` to `0.1.0`.
  5. Add pending trusted publisher on pypi.org (same fields, environment=`pypi`).
  6. Create GitHub Release: tag=`v0.1.0`, target=`main`, uncheck pre-release. `publish.yml` → prod PyPI.
  7. PR-B: branch from `dev`, bump `version` → `0.2.0.dev0`, PR → `dev`.

Test plan

  • `ruff check .` + `ruff format --check .` (all checks passed)
  • `pyright` (0 errors, 0 warnings, 0 informations)
  • `pytest` (1463 passed, 13 deselected; coverage 95.32%)
  • `python -m build` produces `signalforge_dbt-0.1.0rc1-{tar.gz,whl}`
  • `pip install -e ".[dev]"` resolves the new dist name; `import signalforge` works
  • Trusted publisher configured on test.pypi.org (manual)
  • Prerelease v0.1.0rc1 created from main; workflow run succeeds
  • `pip install signalforge-dbt==0.1.0rc1` from test.pypi.org installs cleanly

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation
    • Added comprehensive operational guides, rulebooks, research reports, and runbooks covering all pipeline layers, ops, and contributor guidance.
  • New Features
    • Added interactive release and review skills plus evaluation specs for automated checks.
  • Chores
    • Added CI and publish workflows, repository settings, Python build conventions, scaffolding files (README, CHANGELOG, .gitignore, PR template).
  • Tests
    • Added guidance and gating for end-to-end BigQuery-backed smoke tests and stricter pytest conventions.

Review Change Stack

wjduenow and others added 22 commits April 26, 2026 18:37
Pre-alpha design document. Establishes the value proposition (LLM-drafted
dbt artifacts pruned against real warehouse data), the differentiator
(quality eval in the loop, reusing clauditor's grading methodology), the
v0.1-v1.0 roadmap, and the design principles.
Architecture stays warehouse-agnostic; adapters plug in behind a thin
sampling/profiling interface. BigQuery picked as v0.1 target for its
generous sampled-read pricing and INFORMATION_SCHEMA.JOBS history.
Snowflake, Databricks, Postgres, Redshift moved to v0.2+.
Sets up per-repo policy for AI tooling: synced commands/agents and
non-maintainer skills stay local-only, only release-manager and
review-agentskills-spec ship with the repo. Pattern mirrors clauditor.

Includes the super-plan document for issue #1 (project scaffolding) so
planning work survives across worktrees.
* Trim bark/beads scaffolding additions to match repo policy

- CLAUDE.md: replace mandatory beads block with a short
  availability note. The original asserted bd as the only allowed
  task tracker and prescribed a fixed end-of-session push workflow,
  which conflicts with /super-plan and Claude Code's task tools.
- AGENTS.md: same trim; keep the non-interactive shell guidance.
- .claude/settings.json: track. Just bd-prime hooks for SessionStart
  and PreCompact — no user-specific paths.
- .gitignore: ignore .claude/plugins/ (install/cache state).


* Plan #1: Phases 2-3 complete (architecture review + decisions)

- Architecture review surfaces 8 concerns, 0 blockers (CI permissions,
  action SHA-pinning, smoke test signal, hatchling wheel target,
  CONTRIBUTING scope, README drift, version value).
- R1-R6 resolved as DEC-009..DEC-014.
- Filed #13 to track the open question of long-term beads ↔
  /super-plan integration; link recorded in the plan doc.
- Add .beads/ to .gitignore — bark's bd init landed it in the
  original checkout, not the worktree; ignoring prevents accidental
  commits if it appears in a future checkout too.


* Plan #1: Phase 4 — story breakdown (US-001..US-007)

Seven stories: five implementation (foundation, lint+type, tests,
CI, docs), one quality gate, one patterns-and-memory. Each story
traces to specific DEC-### entries and lists explicit files,
acceptance criteria, and dependencies.

Validation command set: pip install -e .[dev] && ruff check . &&
pyright && pytest.


* Plan #1: phase published (PR #14)


* Plan #1: phase devolved (bd epic bd_1-scaffolding-mxk + 7 tasks)

Created the bd issue graph: 1 epic, 7 tasks (US-001..US-007), 11
blocks edges. bd ready returns only US-001 — the rest are correctly
blocked.

Discovered (and recorded) that bd is worktree-aware via 'bd context':
auto-discovers the canonical .beads/ at the main checkout from any
worktree CWD. No symlink or BD_REPO_DIR env var needed; obsoletes
the option-C plan.


* bd_1-scaffolding-mxk.1: Add pyproject.toml + src layout + __version__

Hatchling backend with dynamic version sourced from
src/signalforge/__init__.py. Explicit wheel packages declaration
(DEC-011) since hatchling does not reliably auto-find src layout.
Dev deps: ruff, pyright, pytest (DEC-004).

Closes US-001 of #1.


* bd_1-scaffolding-mxk.2: Add ruff + pyright config

Per DEC-002: line-length 100, target-version py310, rule set
E/F/W/I/UP/B/SIM, pyright in standard mode with src+tests roots
and Python 3.11 (matches CI). Empty [tool.ruff.format] accepts
defaults.

Closes US-002 of #1.


* bd_1-scaffolding-mxk.5: Add CONTRIBUTING.md + README v0.1 status callout

Lean CONTRIBUTING (DEC-012): branch policy, local dev commands,
license reminder, explicit out-of-scope note for bark/super-plan/bd
(tracked in #13). README gets a sharper v0.1 status block above the
quick-start (DEC-013) so readers do not try a 'pip install signalforge'
that fails today.

Closes US-005 of #1.


* bd_1-scaffolding-mxk.3: Add tests/ + smoke test + pytest config

Smoke test imports signalforge and asserts __version__ matches
PEP 440 shape (DEC-010 — no assert True; the test must be capable
of failing). pytest config sets testpaths=['tests'] and
addopts='-ra --strict-markers' so unknown markers error out
loudly instead of being silently accepted.

Closes US-003 of #1.


* bd_1-scaffolding-mxk.4: Add GitHub Actions CI workflow

DEC-009: actions/checkout and actions/setup-python pinned to commit
SHA with trailing version comment. Top-level permissions scoped to
contents:read. Concurrency cancels superseded runs on the same ref.
DEC-003: Python pinned to 3.11 (single version for v0.1; widen later).

Triggers: PRs into dev, pushes to main. Job runs ruff check, ruff
format check, pyright, pytest in series — same commands contributors
run locally per CONTRIBUTING.md.

Closes US-004 of #1.


* bd_1-scaffolding-mxk.6: Quality gate — fix bugs from code review

Quote pip install extras for zsh users (CONTRIBUTING.md, README.md):
'pip install -e .[dev]' is a glob in zsh and fails with 'no matches
found'. Quoting works in both bash and zsh. Caught by QG pass 3.

Fix README v0.1 prose drift: line 49 still said 'Snowflake adapter'
after commit 243a709 switched v0.1 to BigQuery, contradicting the
roadmap table on the next line. Caught by QG pass 4.

Closes US-006 of #1.


* bd_1-scaffolding-mxk.7: Add .claude/rules/* and CLAUDE.md validation cmd

Capture the conventions established by issue #1 so future
/super-plan runs in this repo discover them automatically:

- python-build.md: Hatchling + src layout + dynamic version (DEC-011);
  the "wheel target packages declaration is non-negotiable" lesson.
- ci-supply-chain.md: SHA-pinning, scoped permissions, concurrency
  cancel-in-progress (DEC-003, DEC-009).
- testing-signal.md: no assert-True tests; pytest 9 needs both
  addopts=--strict-markers AND strict_markers=true ini setting,
  because the option does not propagate to getini (DEC-010, US-003).

CLAUDE.md gains a 'Validation' section with the canonical four-step
command shared between local dev and CI; 'Repository status' now
reflects what shipped instead of "only README and LICENSE".

Implemented by orchestrator after the worker hit a permission gap on
.claude/ writes inside the worktree (filed for the next ralph run to
include .claude/** in worker scope).

Closes US-007 of #1.


* Address Copilot PR #14 review comments

- testing-signal.md: replace "(CLAUDE.md §1)" with explicit reference to
  the "Architectural commitments" heading (CLAUDE.md has no numbered §s).
- test_smoke.py: rewrite the misleading comment — the test does not
  exercise wheel packaging (editable install goes via .pth, not the
  wheel target). Describe what is actually verified: idempotent import.
- .github/workflows/ci.yml: quote ".[dev]" to match the documented
  zsh-safe convention even though Actions runs bash.
- README.md: "follow-up ticket of v0.1" → "follow-up v0.1 ticket".
- AGENTS.md: drop `bd dolt push` from the quick reference and replace
  with a comment pointing to #13 (the dolt remote URL is not wired up
  in this repo, so the command would fail).
- plans/super/1-project-scaffolding.md:
  - Replace local absolute paths in Meta, Phase 2 housekeeping note,
    and Beads Manifest with machine-agnostic descriptions.
  - Update Phase from "devolved" to "implemented" (all 7 stories
    merged; PR ready for review).
  - Align the Detailed Breakdown's stated validation command with the
    canonical CLAUDE.md §Validation recipe (quoted extras + ruff
    format check).
  - Fix US-003 acceptance criteria: 3 tests with their actual names
    (was "1 test"; old name placeholder was wrong); add the pytest 9
    strict-markers ini-vs-addopts caveat inline.

Validation green: ruff check, ruff format --check, pyright, pytest
(3 passed).


---------
* Add super plan for #2 (manifest loader) and vendored dbt research

- plans/super/2-manifest-loader.md: full /super-plan output (Phases 1-4)
  with 17 decisions and 9 stories (US-001..US-009 incl. Quality Gate
  and Patterns & Memory).
- docs/research/: pinned snapshots of the seven dbt-prefixed research
  files from clauditor's gitignored docs/temp/, so contributors outside
  the maintainer's machine can read the references the plan cites
  (DEC-006).

No code yet -- this is the planning PR. Implementation lands per the
beads task graph after approval.


* Fill in Beads Manifest for #2 plan (Phase 7 devolve)

Epic bd_1-scaffolding-28p + 9 tasks created with 10 dependency edges
matching the story graph. bd ready returns only .1 (US-001).


* bd_1-scaffolding-28p.1: US-001 — add Pydantic v2 + dbt-core deps and pytest markers

- pydantic>=2.5,<3 as the first runtime dep (DEC-001).
- dbt-core>=1.8,<2 in [dev] for fixture regeneration (DEC-009).
- pytest markers unit/integration/error declared (DEC-015).


* bd_1-scaffolding-28p.3: US-003 — manifest errors module (TDD)

- src/signalforge/manifest/{__init__.py, errors.py}: seven-class hierarchy
  rooted at ManifestError, with remediation: str rendered in __str__
  (DEC-013, DEC-014).
- tests/manifest/test_errors.py: ≥6 fail-capable unit tests covering
  hierarchy, default remediation, override, and base-class catching.


* bd_1-scaffolding-28p.2: US-002 — test fixtures (small × 4 schemas, medium, 5 error paths)

- tests/fixtures/dbt_project_small/: 4-5 model dbt project + manifest_v9/10/11/12.json (DEC-012).
- tests/fixtures/dbt_project_medium/: ~50-model synthesised project + manifest_v12.json.
- tests/fixtures/error_paths/: malformed/missing_version_url/unsupported_v99/disabled_only/empty_raw_code.
- tests/fixtures/regenerate.sh + README.md (DEC-009 multi-version regen recipe).


* bd_1-scaffolding-28p.4: US-004 — manifest Pydantic models module (TDD)

- src/signalforge/manifest/models.py: Manifest, Model, Column, Ref, Config,
  DependsOn — frozen + extra=ignore + populate_by_name. Validators on
  unique_id (must start "model.") and raw_code (strip-to-None) per DEC-016.
  columns_list property on Model. Nested Config per DEC-011.
- tests/manifest/test_models.py: TDD tests parametrised across the four
  committed v9/v10/v11/v12 small manifests + extra="forbid" drift detector.


* bd_1-scaffolding-28p.5: US-005 — manifest loader module (TDD)

- src/signalforge/manifest/loader.py: load(), _detect_version,
  _canonicalise_path with .resolve()+is_relative_to() symlink hardening
  (DEC-007), MAX_MANIFEST_BYTES soft warning (DEC-008), manifest_path
  override (DEC-010), get_model/iter_models/schema_version free functions
  backed by lazily-built unique_id and file-path indexes.
- src/signalforge/manifest/models.py: thin method wrappers on Manifest
  delegating to loader free functions (deferred import, no cycle).
- tests/manifest/test_loader.py: >=16 fail-capable tests including the 7
  Phase 2 regression tests, symlink hardening, 200MB warning, all error
  paths.


* bd_1-scaffolding-28p.6: US-006 — public __init__.py re-exports

- src/signalforge/manifest/__init__.py: re-exports load, Manifest, Model,
  and all error classes; declares __all__ (DEC-017).
- tests/manifest/test_public_api.py: programmatic importability check,
  Pydantic isinstance checks, error-hierarchy preservation, and
  explicit guards against accidental promotion of internals.


* bd_1-scaffolding-28p.7: US-007 — documentation (research index, ops guide, CONTRIBUTING)

- docs/research/README.md: snapshot framing for the seven vendored dbt
  research files (DEC-006).
- docs/manifest-loader-ops.md: memory profile table, soft 200MB warning
  posture (DEC-008), multi-version regen cross-link (DEC-009/DEC-012),
  supported-version table, error-class quick reference.
- CONTRIBUTING.md: Test markers + Regenerating fixtures subsections
  (DEC-015 / DEC-009).
- README.md: v0.1 status callout updated to reflect library-first delivery.


* bd_1-scaffolding-28p.8: Quality gate — fix bugs from code-reviewer passes 1-4

Pass 2 (adversarial) surfaced two real bugs in the path-hardening logic;
both are now fixed with regression tests that fail before the fix.

1. Default target/manifest.json bypassed _canonicalise_path. A symlink at
   target/manifest.json -> /etc/passwd would have been silently followed.
   Fix: route the default path through _canonicalise_path the same way
   the manifest_path override is handled. (DEC-007 hardening, applies
   uniformly.)

2. Path.resolve() raises RuntimeError on symlink cycles regardless of
   strict=. _canonicalise_path leaked that bare RuntimeError instead of
   the typed ModelPathOutsideProjectError our hierarchy promises. Fix:
   wrap both resolve() calls in try/except RuntimeError and re-raise
   with the same remediation surface.

Three new fail-capable regression tests:
- test_default_manifest_path_symlink_escape_is_rejected
- test_symlink_loop_in_default_path_is_rejected
- test_symlink_loop_in_explicit_manifest_path_is_rejected

Pass 3 (test correctness) flagged one marker mis-mark:
test_oversize_manifest_emits_warning was @pytest.mark.unit but reads a
real fixture file; corrected to @pytest.mark.integration.

Validation: ruff/format/pyright all green; pytest 67 passed
(64 → 67, +3 regression tests).


* bd_1-scaffolding-28p.9: US-009 — patterns & memory

- .claude/rules/manifest-readers.md (new): Pydantic v2 frozen+extra=ignore
  for external-format readers; the three symlink-resolution traps caught
  by issue #2's pass-2 review (Path.relative_to vs resolve, RuntimeError
  on cycles, default paths must be canonicalised too); ManifestError +
  remediation: str pattern; no logging/metrics in stage-0 modules.
- .claude/rules/testing-signal.md: appended sections on fixture
  regeneration via ephemeral uvx and the extra="forbid" drift detector
  pattern (DEC-005, DEC-009, DEC-012, DEC-017).
- CLAUDE.md: marked issue #2 shipped; added "Public API surface (v0.1)"
  section listing signalforge.manifest as the first stable surface;
  cross-link to docs/manifest-loader-ops.md.

Auto-memory entry added (outside the repo): the editable-install race
that bit US-002's worker-in-worktree pattern — orchestrators must
reinstall from the merged feature worktree before validating.


* Address Copilot review comments on PR #15

- .claude/rules/manifest-readers.md:33: example used PathOutsideProjectError
  (doesn't exist); align with the real ModelPathOutsideProjectError so
  contributors copy/pasting the rule into a future external-format reader
  pick up the correct symbol.
- src/signalforge/manifest/loader.py:35-41: module docstring claimed
  get_model caches a "unique_id -> Model" index. The actual implementation
  caches only path-based indexes (manifest.nodes is the unique_id lookup).
  Docstring updated to match.
- plans/super/2-manifest-loader.md:7,521: replace absolute maintainer
  worktree paths (/home/wesd/...) with portable placeholders / generic
  prose so the doc isn't workstation-specific.


---------
* 3: BigQuery warehouse adapter (plan)

Super plan for issue #3 — BigQuery warehouse adapter with sampling +
dialect helpers. Covers Discovery + Architecture Review + Refinement
Log + Detailed Breakdown.

Phase 1 — DEC-001..012 lock the subpackage layout, ABC shape, sampling
strategy, and ops surface.

Phase 2 — six parallel reviews (security/performance/data-model/API/
observability/testing) surfaced 9 blockers + 18 concerns. No findings
invalidate the Phase-1 shape.

Phase 3 — DEC-013..028 resolve every blocker and concern. Notable:
PartitionFilter ADT replaces raw SQL strings (B2); use_query_cache=False
to preserve determinism (B3); DbtProfileTarget extra="forbid" + auth
validator to make ADC fallback loud (B5); WarehouseAdapter.from_profile
factory (B7); TestResult.explanation() to anchor explainable diffs (B8).

Phase 4 — 14 stories, architecture-ordered. ~80 unit tests
enumerated; 6 integration tests gated by SF_RUN_BQ.

* 3: devolve plan to beads (epic + 14 tasks)

Phase 5 → 7. Epic bd_1-scaffolding-8xk with 14 typed task children:
US-001..US-012 implementation, US-013 Quality Gate, US-014 Patterns &
Memory. Dependency graph wired so only US-001 is initially ready;
predecessors unlock as they close.

Plan doc Beads Manifest section now lists every bead ID and its deps.
Phase: published → devolved.

* bd_1-scaffolding-8xk.1: US-001 — Wire BigQuery deps + pytest bigquery marker

Add google-cloud-bigquery and PyYAML runtime deps, types-PyYAML dev dep,
register the `bigquery` pytest marker, and gate it from default collection
via `-m 'not bigquery'`.

Refs DEC-010, DEC-021 in plans/super/3-bigquery-adapter.md.

* bd_1-scaffolding-8xk.2: US-002 — dbt profile YAML fixtures

Hand-author six dbt profile fixtures under tests/fixtures/profiles/ for
US-005's profile-loader tests:

- bigquery_oauth.yml — minimal valid ADC profile (the v0.1 happy path).
- bigquery_service_account.yml — non-oauth method; drives
  UnsupportedAuthMethodError (DEC-017).
- multi_target.yml — two outputs; drives the target= override test.
- missing_target.yml — target: dev with only prod defined; drives
  ProfileTargetNotFoundError.
- dbt_project.yml — minimal project file for project-root resolution.
- dbt_bigquery_drift_v1_9.yml — every documented dbt-bigquery 1.9 oauth
  field; drives the extra="forbid" strict-model drift detector.

Mirrors the dbt-bigquery 1.9 docs and is bumped manually when dbt-bigquery
releases a new minor — no regeneration script (DEC-009, DEC-017).

Update tests/fixtures/README.md with a new "Profiles" section explaining
the regeneration trigger, the source URL, and the hand-author rationale.

All six files load cleanly via yaml.safe_load. Validation passes: ruff,
ruff format, pyright (0/0/0), pytest (67 passed).

* bd_1-scaffolding-8xk.3: US-003 — Warehouse errors module (DEC-026, DEC-022)

Implements `signalforge.warehouse.errors`: a 16-class typed exception
hierarchy rooted at `WarehouseError`, mirroring the manifest layer's
remediation pattern. Every error carries a class-level
`default_remediation` that the base `__str__` renders on a separate
`↳ Remediation:` line — the explainable-diffs commitment, applied at
the warehouse-adapter failure surface.

DEC-022: user-supplied strings (table names, identifiers, profile
fields, paths) route through a private `_format_value` helper that
quotes via `repr()` so adversarial input cannot smuggle special
characters into log viewers / stack traces.

DEC-026: 15 typed subclasses listed in the plan are implemented;
`ProfileTargetNotFoundError` inherits from `ProfileNotFoundError`,
`SamplingRequiresPartitionFilterError` and `UnknownTableSizeError`
inherit from `SamplingError`, so callers can catch with the parent
when they don't care which it is.

Also:
- `signalforge.warehouse` package skeleton (`__init__.py` near-empty;
  US-011 will wire up the full re-export surface).
- `pyproject.toml`: pytest `--import-mode=importlib` so
  `tests/manifest/test_errors.py` and `tests/warehouse/test_errors.py`
  can share a basename without adding `tests/__init__.py` (preserves
  the no-init rule from `testing-signal.md`).
- 15 unit tests covering remediation rendering, repr-quoting of
  adversarial input, parent-catches-child semantics for the two
  inheritance chains, field accessibility on `BytesBilledExceededError`,
  and a `_CONSTRUCT_KWARGS` table that keeps every class in `__all__`
  exercised.

Validation: ruff check + ruff format --check + pyright + pytest all
pass (82 tests).

* bd_1-scaffolding-8xk.4: US-004 — Warehouse models (DEC-003, 004, 013, 014, 016, 018, 020, 027)

Implements the five typed return types for the warehouse adapter
layer plus two private helper modules:

- src/signalforge/warehouse/models.py — Dialect (+BIGQUERY_DIALECT
  constant), TableRef (+from_model gateway), PartitionFilter,
  ColumnStats, TestResult (with explanation()).
- src/signalforge/warehouse/_sql_safety.py — DEC-013 identifier
  regex helper and run_test_sql validator.
- src/signalforge/warehouse/_test_result_repr.py — DEC-020
  type-aware compact row rendering used by TestResult.explanation.
- tests/warehouse/test_models.py — 16 tests covering identifier
  validation, alias-over-name resolution, complex-type min/max,
  per-op PartitionFilter construction, and the explanation
  rendering surface.

Mirrors signalforge.manifest.models conventions (Pydantic v2,
frozen=True). TableRef/Dialect/PartitionFilter are frozen
dataclasses (constructed by SignalForge code, not deserialised);
ColumnStats/TestResult are Pydantic models so they can round-trip
through the future JSON cache.

TestResult.__test__ = False suppresses a pytest collection warning
(class name starts with "Test" but it is a data class).

Validation: ruff + ruff format + pyright + pytest all green;
98 tests pass.

* bd_1-scaffolding-8xk.7: US-007 — FakeBigQueryClient (DEC-002, DEC-028)

Hand-rolled fake for google.cloud.bigquery.Client with an explicit
expect_query / expect_get_table / expect_list_rows API. Unexpected calls
raise AssertionError; assert_all_expectations_met() flags unconsumed
expectations. Lives in tests/warehouse/ — never imported by production
code.

Avoids two anti-patterns: pytest-bigquery-mock (dead, targets bq 2.x) and
unittest.mock.MagicMock (auto-passes everything → always-pass tests, in
violation of testing-signal.md).

Adds six self-tests so regressions in the fake itself don't masquerade
as adapter bugs upstream. Test count rises 98 → 104.

* bd_1-scaffolding-8xk.5: US-005 — Profiles loader module (DEC-009, 017, 022, 023)

Add `signalforge.warehouse.profiles` with `DbtProfileTarget` (Pydantic v2
frozen, `extra="forbid"`, `populate_by_name=True`, `method` field validator
that raises `UnsupportedAuthMethodError` for anything other than `oauth`/None)
and `load_profile(project_dir, target=None) -> DbtProfileTarget`.

Resolution order per DEC-009: `$DBT_PROFILES_DIR/profiles.yml` (user-trusted)
→ `<project_dir>/profiles.yml` (symlink-hardened via `_path_safety`) →
`~/.dbt/profiles.yml` (user-trusted). Active target = arg → profile's
`target:` field → `ProfileTargetNotFoundError`. Profile name read from
`<project_dir>/dbt_project.yml`'s `profile:` field. Soft 1 MB warning logged
to `signalforge.warehouse` (DEC-023, threshold patchable from tests).

`_path_safety.canonicalise_path` mirrors the manifest loader's
`_canonicalise_path` precedent — three traps from `manifest-readers.md`:
`.resolve()` before `.is_relative_to()`, wrap `RuntimeError` for symlink
cycles, gate the default path the same as user-supplied input. Per DEC-017
the helper is copied (not imported) to keep subpackages decoupled; promotion
to a shared utility is a US-014 follow-up.

Tests: 13 new tests covering all three resolution paths, target override,
missing target, missing-everywhere, unsupported method, unknown-field
strictness, `schema:` alias, symlink rejection, soft warning, drift detector
(test-only StrictModel mirroring every dbt-bigquery 1.9 oauth field), and
logger discipline. Test count 98 → 111. `yaml.safe_load` only.

* bd_1-scaffolding-8xk.6: US-006 — WarehouseAdapter ABC + factory

Add `signalforge.warehouse.base.WarehouseAdapter` (abstract sampler /
profiler / test-runner) plus the `from_profile` classmethod (DEC-019)
that lazy-imports `BigQueryAdapter` for `profile.type == "bigquery"`
and raises `UnsupportedProfileTypeError` otherwise. Concrete adapters
land under `signalforge.warehouse.adapters/<warehouse>.py`; `bigquery.py`
ships a skeleton — constructor + `__repr__` (DEC-022 credential redaction)
+ `dialect()` returning the live `BIGQUERY_DIALECT` constant — with the
remaining abstract methods raising `NotImplementedError` until US-008.

Tests cover ABC enforcement, factory dispatch, the unsupported-type
branch, and both halves of DEC-019's max_bytes_billed fallback contract.

Test count 117 → 122; ruff/format/pyright/pytest all green.

* bd_1-scaffolding-8xk.8: US-008 — BigQueryAdapter full implementation

Replace the US-006 stub with the full v0.1 BigQuery adapter:

* `_client.py` — duck-typed `_BQClientProtocol` (matches both
  `bigquery.Client` and `FakeBigQueryClient`), `make_real_client`,
  `make_query_job_config` (DEC-015 defaults), `map_bq_exception`
  (`google.api_core` → typed `WarehouseError` translation), and
  `row_to_dict`. All `# pyright: ignore[...]` noise is contained here.
* `bigquery.py` — full `BigQueryAdapter`:
  - `sample_rows` per DEC-006/DEC-024: deterministic
    `MOD(ABS(FARM_FINGERPRINT(TO_JSON_STRING(t))), bucket) < 1`
    with `Table.num_rows`-driven bucket sizing and fail-loud
    thresholds (`UnknownTableSizeError`,
    `SamplingRequiresPartitionFilterError`).
  - `column_stats` per DEC-008/DEC-025: requires active context,
    accumulates per-table, batched flush on first read; complex BQ
    types (`GEOGRAPHY`/`JSON`/`BYTES`/`ARRAY`/`STRUCT`/`RANGE`) skip
    `MIN/MAX` per DEC-016; soft `WARNING` at >500-column batches
    (DEC-023).
  - `run_test_sql` per DEC-007: wraps in `COUNT(*) [+ ARRAY_AGG]`
    aggregate; returns typed `TestResult` with `row_schema=None`
    (v0.2 will dry_run for inner schema).
  - DEC-013 identifier validation re-checked at `column_stats`
    entry; DEC-014 partition rendering by Python type;
    DEC-022-redacted `__repr__`; DEC-027 `project=None` resolves
    to client.project at quote time.
* `tests/warehouse/test_bigquery_smoke.py` — 7 smoke tests pinning
  `_quote` resolution, `_render_partition_filter` rendering for
  date/datetime/str values (incl. `'`-escaping), the
  outside-context `RuntimeError`, and `__repr__` redaction.
  Comprehensive unit tests land in US-009.

129 tests pass (was 122). ruff/format/pyright all clean.

* bd_1-scaffolding-8xk.11: US-011 — finalise signalforge.warehouse public API

Replace the skeleton ``__all__: list[str] = []`` in
``signalforge/warehouse/__init__.py`` with a thin re-export layer
mirroring ``signalforge.manifest`` (DEC-017):

- 10 non-error names (``load_profile``, ``WarehouseAdapter``,
  ``BigQueryAdapter``, ``Dialect``, ``TableRef``, ``PartitionFilter``,
  ``ColumnStats``, ``TestResult``, ``DbtProfileTarget``, and the
  ``BIGQUERY_DIALECT`` constant)
- the full 16-class ``WarehouseError`` hierarchy from
  ``signalforge.warehouse.errors``

``__all__`` is a hard-coded sorted literal (not ``sorted([...])``) so
pyright's ``reportUnsupportedDunderAll`` stays happy; sort order is
guarded by a regression test instead.

Add ``tests/warehouse/test_public_api.py`` mirroring
``tests/manifest/test_public_api.py``: six contract tests covering
binding, no underscore leakage, sort order, helper isolation, the
required-name minimum, and full re-export of
``signalforge.warehouse.errors.__all__``.

Test count rises from 129 to 135. Validation: ruff check, ruff format
--check, pyright, pytest all clean.

* bd_1-scaffolding-8xk.10: US-010 — BigQuery integration tests (gated)

Add six maintainer-only integration tests under
`tests/warehouse/test_bigquery_integration.py` that exercise
`BigQueryAdapter` against `bigquery-public-data.samples.shakespeare`.
Each test wears both `@pytest.mark.bigquery` (filtered by the default
`addopts = -m 'not bigquery'`) and `@pytest.mark.skipif(not SF_RUN_BQ)`
(belt-and-suspenders per DEC-011/DEC-021): the default `pytest` run
deselects all six and the test count stays at 129; `pytest -m bigquery`
collects exactly the six new tests.

Coverage:
- `sample_rows` + `column_stats` round-trip on Shakespeare;
- `run_test_sql` clean (`WHERE FALSE`) and dirty (`LIMIT 5`,
  `capture_failures=3`) paths;
- `BytesBilledExceededError` via `max_bytes_billed=1` on Shakespeare
  (DEC-028 — free since BQ rejects the dry-run pre-flight);
- `WarehouseAuthError` via `monkeypatch` on `google.auth.default`
  (DEC-028 — exercises the lazy-client construction path).

The Shakespeare `TableRef` is built inside the test bodies via
`__new__` + `object.__setattr__` to bypass DEC-013's strict
identifier regex (real BQ project IDs may contain hyphens; the
adapter's backtick-quoted `_quote` path handles them fine).
Loosening the project-id regex is tracked separately.

Add a "BigQuery integration tests" section to `CONTRIBUTING.md`
documenting the `gcloud auth application-default login` +
`SF_RUN_BQ=1 pytest -m bigquery` flow.

* bd_1-scaffolding-8xk.12: US-012 — warehouse adapter ops doc

Add docs/warehouse-adapter-ops.md per DEC-012 + DEC-027 covering quick
start (ADC + load_profile + from_profile), profile resolution, cost
defaults (max_bytes_billed, use_query_cache=False rationale, BQ job
labels), sampling strategy with the TABLESAMPLE cost-asterisk and
PartitionFilter use, the with-block contract for column_stats batching,
SF_RUN_BQ-gated integration tests, debugging via the
signalforge.warehouse logger and typed-error fields, and a full table
of all 16 typed exceptions in signalforge.warehouse.errors.

Cross-link from README's new Configuration section and from
docs/research/dbt-research-index.md back to the ops doc and the design
plan.

Validation: ruff/format/pyright/pytest all green; test count unchanged
at 129.

* bd_1-scaffolding-8xk.9: US-009 — comprehensive BigQueryAdapter unit tests

Adds tests/warehouse/conftest.py (centralised fake_client / adapter /
table_ref / shakespeare_table fixtures) and tests/warehouse/test_bigquery_unit.py
(45 tests covering cost defaults, _default_job_config DEC-015 plumbing,
dialect identity, sample_rows DEC-006/DEC-024 decision tree, column_stats
DEC-008/DEC-016/DEC-023 batching + complex-type MIN/MAX skipping,
run_test_sql DEC-007/DEC-013 wrapping + SQL-safety rejects, exception
mapping for BadRequest / NotFound / DefaultCredentials, and the
DEC-025 context-manager lifecycle).

Test count: 135 → 180 (+45). All four validation checks pass.

Notes on plan deviations:

* Skipped test_run_test_sql_populates_row_schema (v0.2 behaviour); replaced
  with test_run_test_sql_row_schema_is_none asserting the v0.1 contract.
* Skipped test_per_call_max_bytes_caps_downward_only (v0.2).
* test_default_max_bytes_billed_via_from_profile is already covered in
  test_base.py — not duplicated here.
* SQL-text introspection uses a query-wrapper helper (_wrap_query_capture)
  rather than a custom regex matcher, since FakeBigQueryClient.expect_query
  re-compiles `matching` through re.compile and rejects non-Pattern objects.
* test_column_stats_warns_at_threshold pre-seeds _column_stats_pending to
  trip the threshold; the simplified Option A flushes after every call so
  the pending list never grows past 1 via public API alone.

* bd_1-scaffolding-8xk.13: Quality Gate — fix 8 findings from code review pass 1

1. (BLOCKER) TableRef now accepts hyphenated GCP project IDs via a new
   _PROJECT_RE / validate_project_id helper; strict identifier regex
   still gates dataset / table / column. Integration test drops the
   __new__ + object.__setattr__ bypass.
2. (MAJOR) _render_partition_filter escapes "\\" before "'" so an
   adversarial trailing "\\'" cannot terminate the BQ string literal
   early.
3. (MAJOR) Ops doc column_stats section now documents v0.1's eager-flush
   semantics (one query per call) and points at the v0.2 lazy-proxy
   follow-up. New "v0.2 follow-ups" section tracks both gaps.
4. (MAJOR) Ops doc cites the real signalforge_stage values
   (warehouse_sample, warehouse_stats, warehouse_test) and lists them
   under Cost defaults.
5. (MINOR) map_bq_exception accepts context={"max_bytes_billed": ...}
   so BytesBilledExceededError renders the configured cap instead of 0.
6. (MINOR) sample_rows adds ORDER BY FARM_FINGERPRINT(TO_JSON_STRING(t))
   before LIMIT so truncation is deterministic when the bucket WHERE
   retains more than n rows (DEC-006).
7. (MINOR) NotFound "column" branch removed (dead in real BQ); missing
   columns now route through a BadRequest sub-branch matching
   "Unrecognized name" / "name not found".
8. (MINOR) make_query_job_config drops the # pragma: no cover; gains a
   default version (resolved from signalforge.__version__) and a unit
   test pinning use_query_cache=False, the maximum_bytes_billed value,
   and the labels dict.

Validation: ruff check + ruff format --check + pyright + pytest all
green; 186 default tests pass (was 180; +6 new tests across findings 1,
2, 5, 6, 7, 8) with the 6 BigQuery-marked tests still deselected.

* bd_1-scaffolding-8xk.13: Quality Gate — fix 3 minor findings from review pass 2

- Drop legacy domain-scoped GCP project ID claim from _PROJECT_RE
  docstring; the regex never accepted them and _quote() doesn't split
  on ':'. Tracked as v0.2 follow-up.
- Bound the strict-identifier fallback in _PROJECT_RE to 6-30 chars to
  match GCP's documented limits (was: any length).
- Escape '\\' and "'" in compact_repr's string-value branch so
  TestResult.explanation() output is paste-safe per its docstring.
  Add regression test test_compact_repr_escapes_quotes_and_backslashes.
- Update three tests using project="p" (1 char) to use "proj01" (6
  chars) so they reach the dataset/name validation they target.

* bd_1-scaffolding-8xk.13: untrack uv.lock (bark worktree artifact)

uv.lock was inadvertently included in the prior Quality Gate commit;
it's bark's worktree scaffolding output and isn't part of the SignalForge
build (we use Hatchling + pip, not uv). Add to .gitignore.

* bd_1-scaffolding-8xk.13: Quality Gate — fix 2 findings from review pass 3

- Centralise BQ string-literal escaping in _sql_safety.escape_bq_string_literal
  (handles backslash, single-quote, newline, CR, tab, NUL); use it from both
  _render_partition_filter and _test_result_repr. Newlines/control chars in
  partition values previously caused BigQuery syntax errors at execution
  time; the new helper renders all of them as escape sequences. Regression
  test test_render_partition_filter_escapes_newlines_and_tabs.

- Wrap BigQueryAdapter.__exit__ in try/finally so a flush failure during
  clean exit cannot leave the adapter in a half-cleaned state — the next
  `with` block must start from empty caches per DEC-025. Regression test
  test_context_manager_exit_clears_caches_even_if_flush_raises.

- Document Pass 3 finding 3 (drift detector self-consistency) as a v0.2
  follow-up in docs/warehouse-adapter-ops.md alongside the legacy
  domain-scoped project ID gap.

* bd_1-scaffolding-8xk.13: Quality Gate — fix phantom API from review pass 4

UnknownTableSizeError.default_remediation referenced an undefined
adapter.refresh_table_metadata method, breaking Architectural
Commitment #5 (explainable diffs — the remediation must actually work).

- Implement BigQueryAdapter.refresh_table_metadata(table) — drops the
  cached Table for one ref so the next operation re-fetches num_rows.
  No-op outside an active context.
- Add two regression tests: cache invalidation roundtrip + no-op
  outside context.
- Drop the now-shipped GCP project-ID grammar entry from the v0.2
  follow-ups list (it was completed in QG pass 1).

* bd_1-scaffolding-8xk.14: Patterns & Memory — warehouse-adapters rule + CLAUDE.md surface

Captures the conventions established by issue #3 into .claude/rules/warehouse-adapters.md:
ABC + lazy-import factory, _client.py pyright containment, expect_* fake API,
deterministic hash-mod sampling with fail-loud sizing, identifier validation at
construction time, _default_job_config defaults (use_query_cache=False non-negotiable),
typed error hierarchy with remediation + repr-quoted user input, and the explicit
US-014 decision to keep _path_safety duplicated rather than extracted.

Updates CLAUDE.md Repository status (two issues -> three) and Public API surface to
include signalforge.warehouse re-exports.

* PR #16: Address Copilot review comments

Code:
- TableRef: add `qualified_name` property (dialect-neutral `[project.]dataset.name`).
  Used by sampling errors so `.table` is a stable identifier rather than the dataclass repr.
- bigquery.sample_rows: validate `n > 0` to prevent ZeroDivisionError on bucket sizing.
- Map google.api_core exceptions with table identity from the call site:
  `map_bq_exception` now reads `context["table"]` so TableNotFoundError.table /
  ColumnNotFoundError.table carry the qualified name instead of the truncated message.
  Column name is extracted from BigQuery's "Unrecognized name: foo" via regex.
- base.from_profile: replace `or 100_000_000` with explicit `is None` so an explicit
  `maximum_bytes_billed: 0` in the dbt profile is honoured.
- profiles.load_profile: thread the resolved profiles.yml path into ProfileNotFoundError;
  ProfileTargetNotFoundError now lists the available output names in its remediation
  and exposes them via `.available` and `.profiles_path` fields.

Docs:
- warehouse-adapter-ops.md: correct the column_stats batching description (first call
  flushes every queued column for the table in a single query); narrow the
  BytesBilledExceededError doc to match what the mapping actually populates.
- .claude/rules/warehouse-adapters.md: fix the FakeBigQueryClient snippet to match the
  real `expect_*` API; acknowledge the profiles.yml soft-size WARNING.

Tests:
- test_models: TableRef.qualified_name with and without project.
- test_bigquery_unit: n<=0 ValueError; .table stable identifier on UnknownTableSize /
  SamplingRequiresPartitionFilter / TableNotFound; .column extraction on ColumnNotFound.
- test_base: from_profile honours explicit maximum_bytes_billed=0.
- test_profiles: ProfileTargetNotFoundError exposes .available, .profiles_path, and
  the remediation message contains every available target name.

* PR #16: ruff format the new qualified_name test
* Add super plan for #4: PII safety layer

Phase 1 locks library-only scope (no CLI, no LLM client — those land in
#9 and #5). Phase 2 surfaced the load-bearing finding that schema-only
mode leaks PII via column NAMES; Phase 3 resolves it with stable
blake2b-hash placeholders. 26 decisions captured across config, fail-
closed audit semantics, AuditEvent reproducibility (signalforge_version
+ policy_hash + audit_schema_version), extra=forbid on config-shaped
models, and the SafetyPolicy.with_mode() seam #9 will use.

Detailed Breakdown is 14 stories: scaffolding -> fixtures -> errors ->
models -> SafetyPolicy -> config loader -> audit -> redact -> aggregate
-> request builder -> public API + drift detector + AST scan -> docs ->
quality gate -> patterns. TDD specified for every story with non-trivial
business logic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Update phase to published with PR #18 link

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Devolve plan to beads (epic + 14 tasks)

Phase: devolved. Epic bd_1-scaffolding-o6f live; 14 tasks wired into the
architecture-order DAG; bd ready confirms US-001 is the single entry
point.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Populate Beads Manifest section; remove stale placeholder duplicates

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-0ix: Scaffold signalforge.safety subpackage + add safety pytest marker

Empty __init__.py placeholder; full public re-exports land in US-011.
New pytest marker enables `pytest -m safety` once tests exist.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-o64: Add safety-layer test fixtures (signalforge.yml variants, manifest with PII meta, audit JSONL sample)

Hand-author the fixture corpora consumed by the upcoming PII-safety
stories (US-005..US-011): eight signalforge.yml variants exercising
the locked safety: top-level shape (DEC-025), the redact extend/replace
mutual exclusion (DEC-017), the sampling-mode flag (DEC-021), and the
DEC-013 path-traversal guard; a hand-derived manifest fixture carrying
all four column-level PII opt-out signals plus a model-level signal
(DEC-026); a one-line audit JSONL sample matching the locked AuditEvent
shape (DEC-005 + DEC-014); and a deterministic regeneration script that
will swap to the typed AuditEvent model once US-004 lands.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-rix: Add signalforge.safety.errors with 10-class hierarchy

SafetyError base + 9 typed subclasses; mirrors WarehouseError/ManifestError
patterns (default_remediation ClassVar, ↳ Remediation rendering, repr-quoted
user input via _format_value helper).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-agc: Add signalforge.safety.models (SamplingMode, RedactionRecord, AuditEvent, LLMRequest)

Frozen Pydantic v2 models with deep-immutable tuple sequences (DEC-022).
AuditEvent carries reproducibility fields (signalforge_version, policy_hash,
audit_schema_version=1) per DEC-014. LLMRequest docstring warns against
direct construction (audit-completeness convention; AST scan in US-011).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-pfj: Add SafetyPolicy + _resolve_redact_patterns + _compute_policy_hash

Frozen Pydantic v2 with extra=forbid; default mode=SCHEMA_ONLY; case-insensitive
mode load; @model_validator resolves redact.extend/replace mutual exclusion;
pattern-injection rejection (empty/*/?); with_mode() factory for #9's CLI;
sample-mode WARNING; deterministic 16-hex policy hash for AuditEvent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-mg2: Add signalforge.safety.audit (fail-closed JSONL writer)

O_APPEND atomic-append with fsync; mkdir parent at 0o700; file at 0o600.
PIPE_BUF size cap (4000 bytes) raises AuditRecordTooLargeError. Any I/O
exception propagates as AuditWriteError (DEC-011 fail-closed). Logger uses
lazy-format with json.dumps to avoid ANSI/log-injection (DEC-022).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-ly6: Add signalforge.safety.config (load_safety_config + path safety)

Full DEC-016 error contract: explicit-path miss raises ConfigNotFoundError;
implicit miss / empty / missing-safety-key falls through to defaults;
malformed YAML / non-mapping / schema-invalid raise typed errors. yaml.safe_load
only. audit_path canonicalised + reject .. segments + require inside project_dir
(DEC-013). _path_safety.py copied from warehouse layer per duplication precedent.

* bd_1-scaffolding-y47: Add signalforge.safety.redact (classify + redact_rows + hash_column_name)

_classify_column returns RedactionRecord | None; precedence column>model;
case-insensitive tag matching; meta.contains_pii truthy coercion (DEBUG-log).
Pattern match case-insensitive (lowercase both sides). hash_column_name uses
blake2b-4 for stable 8-hex-char placeholders (DEC-010). redact_rows replaces
values with '<REDACTED>' constant (no mutation). redact_column_names
substitutes hashed names. Suspicious-unmatched-column WARNING heuristic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-3z5: Add aggregate_columns + FakeAdapter

aggregate_columns wraps adapter.column_stats inside `with adapter:` (DEC-008
batching). Redacted columns return None keyed by hashed name; non-redacted
keyed by real name. FakeAdapter mirrors warehouse FakeBigQueryClient's
expect_* API; never MagicMock. ColumnNotInModelError on unknown column.

Empty columns list short-circuits without ever opening the adapter context.
When every requested column is redacted, the warehouse is never touched.

The PII fixture's `database: "dev"` was bumped to `sf-demo-proj` so it
satisfies BigQuery's project-ID grammar (6-30 chars, lowercase start) — the
classify tests don't depend on the value, but TableRef.from_model now does.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-969: Add build_llm_request + default-mode regression suite

Single entry point per DEC-009: classifies columns, dispatches per-mode
(schema-only zero warehouse calls — DEC-012(c)), writes AuditEvent before
returning (DEC-011 fail-closed). AuditEvent carries signalforge_version,
policy_hash, audit_schema_version=1 (DEC-014). policy_flags populated from
policy state (sample_mode_enabled, redaction_disabled, audit_path_overridden).
Three default-mode regression tests cluster DEC-012 at policy/config/request
layers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-2fb: Wire signalforge.safety public API + drift detector + AST scan

__init__.py re-exports the documented surface (DEC-001). StrictAuditEvent
drift detector validates the committed JSONL fixture and asserts field-set
parity with production AuditEvent (DEC-026). AST scan rejects LLMRequest
construction outside request.py — the audit-completeness convention from
DEC-020(a). Includes negative test that the AST visitor catches a planted
violation.

Note: ``_path_safety`` is asserted absent from ``__all__`` rather than
``dir()``: Python attaches imported submodules to the parent package's
namespace once any sibling (``config.py``) imports them, regardless of
``__init__.py``. Intent (private-helpers-stay-private) preserved via the
``__all__`` check, mirroring the warehouse package's pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-gx3: Add docs/safety-ops.md + README 'Data safety' section + .gitignore

Operational reference for the PII safety layer matching manifest- and
warehouse-ops doc precedent: default posture, modes, signalforge.yml schema,
redaction patterns, four opt-out signals + precedence, column-name redaction
(DEC-010 blake2b), audit JSONL schema with audit_schema_version=1,
sensitivity caveat (column names are plaintext), rotation as user
responsibility, debugging (logger levels), typed-error reference, CLI
integration note pointing at #9.

* bd_1-scaffolding-8av: Quality gate — fix bugs from code review

Three blockers from quality-gate review:

1. LLMRequest.aggregates was dict[str, ColumnStats|None] — frozen=True
   only blocks attribute reassignment, not dict mutation. Downstream
   consumers (#5) could rewrite values after the audit log was written,
   desyncing the audit from what the LLM actually saw. Switched to
   tuple[tuple[str, ColumnStats|None], ...] for transitive immutability
   (DEC-022). Conversion happens at the request-builder boundary;
   aggregate_columns' public dict return is unchanged.

2. SafetyPolicy.with_mode() used model_copy(update=...) which silently
   skipped @model_validator(mode="after"). Calling
   policy.with_mode(SamplingMode.SAMPLE) — the documented CLI override
   seam (#9) — silently enabled sample mode without emitting the
   DEC-021 WARNING. Now goes through model_validate so the warning
   fires every time, regardless of construction path.

3. SafetyPolicy._normalise_mode returned non-string non-enum values
   unchanged, letting Pydantic raise a generic ValidationError instead
   of the typed InvalidSamplingModeError. Now every invalid type
   raises the typed error so the safety-layer hierarchy stays
   homogeneous.

Plus: documented the .signalforge/ pre-existing-permissions caveat in
docs/safety-ops.md (mkdir(exist_ok=True, mode=...) does not tighten
pre-existing directories — user must verify pre-deploy).

Twelve new regression tests:
- test_safety_policy_with_mode_sample_emits_warning (DEC-021)
- test_safety_policy_with_mode_schema_only_emits_no_warning
- test_safety_policy_mode_non_string_non_enum_raises_invalid_sampling_mode_error
  (parametrised across 7 bad types: int, float, None, list, dict, tuple, object)
- test_llm_request_aggregates_is_tuple_of_tuples_when_present (DEC-022)
- test_llm_request_aggregates_immutable_when_none
- test_llm_request_aggregates_field_reassignment_blocked_by_frozen

Validation: 423 passed (up from 411), ruff/pyright/format all clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-51d: US-014 — Patterns & Memory (rules/safety-layer.md + CLAUDE.md + bd remember)

New .claude/rules/safety-layer.md distils the load-bearing patterns from
issue #4: fail-closed audit semantics, column-name redaction via stable
blake2b-4 hash, AuditEvent reproducibility fields (signalforge_version +
policy_hash + audit_schema_version), extra=forbid on config-shaped models
vs extra=ignore on read-back, the four opt-out signals + precedence,
ANSI-safe lazy-format logger, AST audit-completeness scan, model_copy
re-validation gotcha, signalforge.yml top-level namespace.

CLAUDE.md updated: 'Repository status' bullet for issue #4, 'Public API
surface (v0.1)' entries for signalforge.safety (SamplingMode, SafetyPolicy,
LLMRequest, load_safety_config, build_llm_request, SafetyError hierarchy).

Four bd remember entries:
- schema-only mode redacts column names too
- audit writes are fail-closed
- Pydantic v2 extra=forbid (config) vs extra=ignore (read-back) split
- model_copy doesn't re-run @model_validator(mode='after')

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Address Copilot PR #18 review (3 real bugs + 2 doc fixes)

Five Copilot review comments. Three real bugs and two doc inaccuracies:

1. config.py:101 (real bug) — load_safety_config's default-fallback
   branches returned SafetyPolicy() with the relative
   Path('.signalforge/audit.jsonl') field default, making the audit log
   resolve relative to CWD not project_dir. Now every default-fallback
   branch (no file / empty file / comments-only / missing safety: key /
   safety: present but no audit_path) canonicalises DEFAULT_AUDIT_PATH
   against project_dir before passing to SafetyPolicy. Symmetric with
   the user-override path.

2. config.py:136 (real bug) — audit_path: 123 (or [a,b], or true) crashed
   inside Path(audit_path_raw) with TypeError, leaking a non-SafetyError.
   Now type-checked at the gate: non-(str|os.PathLike) raises
   InvalidConfigError with a clear remediation pointing at YAML quoting.

3. config.py validator path (real bug) — Pydantic's extra="forbid" raises
   ValidationError with type='extra_forbidden', which the loader was
   wrapping as the generic PolicyValidationError. DEC-026 specified
   UnknownConfigKeyError for typos like `redacts:` or `mode_:`. Now the
   loader walks the Pydantic error list a second time and translates
   extra_forbidden into the typed UnknownConfigKeyError so the contract
   in safety-ops.md actually holds.

4. models.py:70 (doc bug) — RedactionRecord docstring claimed records are
   emitted for every column considered. They aren't — only redacted
   columns produce records (None pass-through for non-redacted). Updated
   docstring to match.

5. safety-ops.md:72 (doc bug) — aggregate-only example showed
   request.aggregates as a dict. After the Quality-Gate fix that made
   LLMRequest.aggregates a tuple-of-tuples (DEC-022 transitive
   immutability), the example was stale. Updated.

The sixth Copilot comment (request.py:5) flagged stale PR metadata
('Phase: detailing (awaiting approval)' / 'Review the plan in this PR')
that I had already fixed via REST API after the PR was opened. The
title is now '4: PII safety layer' and the description reflects the
shipped implementation. Will mark that comment as outdated.

Eight new regression tests:
- test_load_safety_config_no_file_default_audit_path_is_inside_project
- test_load_safety_config_empty_file_default_audit_path_is_inside_project
- test_load_safety_config_missing_safety_key_default_audit_path_is_inside_project
- test_load_safety_config_no_audit_path_in_yaml_canonicalises_default
- test_load_safety_config_typo_fixture_raises_unknown_config_key_error
  (renamed + tightened from generic SafetyError check)
- test_load_safety_config_top_level_typo_raises_unknown_config_key_error
- test_load_safety_config_audit_path_int_raises_invalid_config_error
- test_load_safety_config_audit_path_list_raises_invalid_config_error
- test_load_safety_config_audit_path_bool_raises_invalid_config_error

Validation: 431 passed (up from 423), ruff/pyright/format all 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>
* Add super plan for #5: LLM draft pipeline

Plan doc covers Discovery, Architecture Review, 27 DECs across Refinement,
and an 18-story Detailed Breakdown ready for devolve.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Devolve #5 plan to beads (epic + 18 tasks)

Update plan-doc Phase marker to 'devolved' and fill in the Beads Manifest
section with epic ID, the 18 task IDs (one per US-001..US-018), and the
dependency graph.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-g11: US-001 — Subpackage scaffolding + anthropic dep + pytest markers

Create empty signalforge.llm and signalforge.draft subpackages (placeholder
__init__.py only; __all__ re-exports land in US-013). Add
anthropic>=0.50,<1.0 to [project.dependencies]. Register pytest markers llm,
draft, and anthropic; extend default addopts to also exclude the anthropic
real-API smoke marker so default CI runs llm/draft but skips anthropic.

Traces to: DEC-001, python-build.md, testing-signal.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-wsb: US-002 — Test fixtures (yml + manifest + golden response samples)

* bd_1-scaffolding-4e2: US-003 — signalforge.llm.errors hierarchy

* bd_1-scaffolding-zea: US-004 — signalforge.llm.models LLMResult

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-806: US-005 — signalforge.llm._client SDK shim (DEC-012 confinement)

* bd_1-scaffolding-hhn: US-006 — signalforge.llm.client.call_anthropic centralized seam

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-wwx: US-007 — signalforge.draft.errors with bad-JSON envelope

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-nlz: US-008 — signalforge.draft.models CandidateSchema family + schema_version

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-buv: US-009 — signalforge.draft.config DraftConfig + load_draft_config

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-0kq: US-010 — signalforge.draft.prompts in-code template + version hash + envelope + mode-varying section

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-g27: US-011 — signalforge.draft.parser JSON validation + anchor-contract validator

* bd_1-scaffolding-mtg: US-012 — signalforge.draft.audit LLMResponseEvent + fail-closed writer

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-9na: US-013 — signalforge.draft.schema integration + DraftOutcome + public API

* bd_1-scaffolding-n98: US-014 — Audit-completeness AST scans + drift detector + cache-stability snapshot + grep gate

* bd_1-scaffolding-eup: US-015 — Real-API smoke test (@pytest.mark.anthropic)

Add tests/draft/test_smoke_real_api.py and the
tests/fixtures/draft/smoke_manifest.json fixture it consumes. Gated by
the ``anthropic`` marker (registered in pyproject.toml from US-001),
excluded from default CI by the ``-m 'not bigquery and not anthropic'``
filter, runnable locally with ``pytest -m anthropic`` when
``ANTHROPIC_API_KEY`` is set.

Path A (wire test): the cached manifest summary for the synthetic
``simple_orders`` model is ~100 tokens, far below Haiku's
2048-token minimum and Sonnet's 1024-token minimum. The pre-send
``count_tokens`` call hits the real Anthropic API (proving SDK +
auth + transport), then ``call_anthropic`` raises
``LLMCacheTooSmallError`` before any ``messages.create`` is issued.
This is documented in the test docstring as a deliberate v0.1
constraint; when the smoke fixture grows above 2048 tokens the test
can be upgraded to Path C (full round-trip) without changing its
marker or invocation.

Fixture exercises the no-neighbours code path of
``_render_manifest_summary`` (empty ``depends_on.nodes``, empty
``refs``).

Validation: ruff/ruff-format/pyright clean. ``pytest`` default still
reports 608 passed; ``deselected`` count goes from 6 to 7 to reflect
the new ``anthropic``-marked test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-bhu: US-016 — docs/draft-ops.md + README + CLAUDE.md

* bd_1-scaffolding-273: US-017 — Quality gate fixes from code review pass 1

Three real bugs surfaced and fixed (pass 2 truncated by org usage limit;
all four validation checks green).

1. parser._validate_anchor_contract: a CandidateColumn whose `name` was
   not in `model_columns` slipped through (the membership check lived
   only inside an `elif` after the parent-column-mismatch branch). Now
   the column name itself is validated, and the nonexistent-column check
   on each test is independent of the parent-column-mismatch check so a
   hallucinated column surfaces both violations.

2. client.call_anthropic: the cache-anomaly WARNING fired on every cache
   HIT. `cache_creation == 0` and `cache_read > 0` is the normal healthy
   case (the cache was created on a prior call). The warning now requires
   both creation AND read to be zero — the genuine no-op signal.

3. prompts._render_dynamic_block: a Model.raw_code containing the
   literal `</MODEL_SQL>` would terminate the prompt-injection envelope
   early and let downstream content escape the data fence. New typed
   error PromptEnvelopeBreachError; render is now refused before any
   LLM call.

Tests: 608 → 611 (one new test per fix).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-x3t: US-018 — Patterns & Memory (.claude/rules/llm-drafter.md + bd remember)

Distil load-bearing patterns from issue #5 into a project rule that
future contributors (human + Claude) consult before touching
signalforge.llm or signalforge.draft. Mirrors safety-layer.md shape.

Sections: SDK seam confinement (DEC-012) | retry test-determinism via
module aliases (DEC-004) | fail-closed response audit (DEC-006/008/013)
with the propagation-IS-the-defence rule | <MODEL_SQL> envelope + breach
guard (DEC-007) | cached-block scope + 8000-token cap (DEC-009) |
cache-anomaly dual-zero rule (post-QG fix to DEC-014) | whole-draft
fail-loud anchor contract with collected violations (DEC-003/022) |
ANSI-safe lazy-format logger gate (DEC-011) | four AST audit-completeness
scans (DEC-013) | signalforge.yml top-level llm: namespace (DEC-027).

Plus 5 persistent bd memories (issue-5-llm-*) so cross-conversation
recall is anchored on the load-bearing patterns, not the file paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-273: PR review fixes (Copilot + CodeRabbit, 12 threads)

All 12 review threads addressed; none deferred. Two require behaviour
notes; the rest are mechanical hardening of existing seams + tests.

PROMPT_VERSION rotated 4800590d3d749955 -> 1c55806467984090 because
the system prompt's claim that "tests without rationale are rejected
by the parser" was softened (the parser does not, in fact, enforce it
— DEC-026 documented this as a soft constraint owned by the grader at
#7). Cache-stability test pinned to the new value.

Real bug fixes:
- src/signalforge/draft/schema.py: sent_sql_hash now hashes
  Model.raw_code (not the dynamic block which carries envelope + data
  section). Drift would have broken correlation with raw_code.
- src/signalforge/draft/models.py: schema_version: Literal[1] = 1 so
  payloads with schema_version != 1 fail validation rather than
  silently passing.
- src/signalforge/llm/client.py: count_tokens now MAPS Anthropic
  exceptions to typed LLMError subclasses (no retry on the probe
  call — consuming the create-budget on a probe failure would let one
  blip exhaust the budget). Plus per-class retry counters
  (attempt_429 / attempt_5xx / attempt_conn) so one failure class
  cannot consume another's budget; total_attempts drives the backoff
  math + WARNING log so delays remain monotonic across mixed types.
- src/signalforge/draft/prompts.py: removed the false claim that the
  parser rejects rationale-less tests. New phrasing matches actual
  enforcement (parser is permissive; grader scores).

Hardened test infrastructure:
- tests/llm/test_logger_grep_gate.py + tests/llm/test_client.py: regex
  now matches every f-string prefix permutation (f, F, rf, fr, rF,
  FR, etc.) AND optional whitespace after the opening paren AND single
  or double quotes. Previous regex only caught (f". Bypassable by
  switching quote style.
- tests/test_audit_completeness.py: AST scan handles import aliasing
  (import anthropic as a; a.Anthropic(...)) AND direct-symbol imports
  (from anthropic import Anthropic; Anthropic(...)). Previous matcher
  could be silently bypassed.
- tests/test_audit_completeness.py: exclusion lists now path-relative
  (request.py / _client.py / audit.py at root of scan dir) rather than
  basename-only — prevents accidental shadowing of nested files with
  the same basename.

Trivial fixes:
- README.md: MD040 fence language (text).
- tests/fixtures/draft/regenerate.sh: comment now reflects current
  reality (US-015 smoke test exists; refresh is still manual until
  fixture grows past cache minimums).

New tests:
- test_call_anthropic_per_class_budgets_do_not_cross_consume: pins the
  invariant that one class's failures cannot consume another's budget.

612 passed (was 611 + 1 new).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 6: super-plan for prune engine

Phase-4 plan for issue #6 — drop always-pass and known-clean-fail
candidate tests against warehouse data. Covers the load-bearing
"signal over volume" commitment from CLAUDE.md.

Architecture review surfaced two pivots resolved in DEC-012/DEC-013:
the deterministic-sample predicate (TO_JSON_STRING(t)) reads every
column, so US-003 live-verifies the cost model against bigquery-
public-data before locking strategy; the warehouse adapter's
QueryJobConfig.job_timeout_ms plumbing folds in as US-002 (~10 LOC)
so the per-test budget actually enforces.

28 decisions, 16 stories (14 implementation + Quality Gate +
Patterns & Memory). Stories trace to DEC-### and embed TDD on
every logic-shaped surface (compiler, engine, audit, errors,
models, config).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 6: bump plan phase to published

PR #20 is open; phase marker updated for re-invocation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 6: devolve plan — beads manifest

Epic bd_1-scaffolding-y8y + 14 implementation tasks + QG + PM.
Dependencies wired so `bd ready` returns only US-001 at start.

Phase marker bumped to devolved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-y8y.1: scaffold signalforge.prune subpackage

Empty docstring-only stubs for engine, compiler, models, errors,
audit, and config modules. Subsequent stories (US-002 ... US-014)
land the actual logic.

Plan: plans/super/6-prune-engine.md (DEC-001).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-y8y.4: PruneResult / PruneDecision / primitives

Frozen Pydantic v2 models for the prune layer's read-back surface.
DropReason and Scope discriminator literals. Computed-property
aggregates (kept_decisions, dropped_decisions, kept_count, etc.) for
the diff renderer (#8). PruneDecision carries the typed CandidateTest
discriminated union (DEC-004), not a loose dict.

Plan: plans/super/6-prune-engine.md (DEC-003, DEC-004, DEC-014, DEC-015).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-y8y.6: PruneError hierarchy

Six typed exception classes: PruneError, PruneConfigError,
PruneTrustedModelNotFoundError, PruneTimeoutError,
PruneAuditWriteError, PruneAuditRecordTooLargeError. Each carries
default_remediation; __str__ renders message + remediation; user
input is repr()-quoted to defeat ANSI injection.

Introduces signalforge.errors.SignalForgeError as the project-wide
root so PruneError(SignalForgeError) wires per DEC-006. Existing
layer roots (SafetyError, DraftError, WarehouseError, ...) stay
untouched per task scope; future stories can rebase them.

Plan: plans/super/6-prune-engine.md (DEC-006, DEC-022).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-y8y.6: drop spurious signalforge.errors module

The worker's first commit added signalforge/errors.SignalForgeError
to satisfy the plan's PruneError(SignalForgeError) note, but every
existing layer (Safety/Draft/Warehouse/Manifest) subclasses Exception
directly with no shared root. Adding a project-wide root for prune
alone left the codebase inconsistent.

Match the established pattern: PruneError(Exception). The plan note
referencing SignalForgeError was a planning bug; the precedent in
the other layers is the source of truth.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-y8y.7: compiler — failing-rows SQL x 4 test types

Pure SQL transform: not_null, unique, accepted_values, relationships
to BigQuery failing-rows SELECTs. Dialect-driven (Dialect.quote_char)
so v0.2 adapters drop in without changes. Reuses
warehouse._sql_safety.escape_bq_string_literal for accepted_values
literals (DEC-024). _RequiresFutureData sentinel for
relationships(to: unknown) instead of an exception (DEC-006). NULL-
exclusion matches dbt-core verbatim (DEC-023).

Plan: plans/super/6-prune-engine.md (DEC-023, DEC-024, DEC-025, DEC-026).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-y8y.8: prune audit — fail-closed prune.jsonl writer

PruneEvent + _write_prune_event(event, path) with O_APPEND|O_CREAT|
0o600, fsync, size cap (4000 bytes) before os.open, no try/except.
Mirrors safety + draft fail-closed audit semantics. _build_prune_event
is the single construction seam (DEC-018). _compute_config_hash uses
sha256(...)[:16] to match safety.policy_hash (DEC-005).

AST audit-completeness scan extended (tests/test_audit_completeness.py)
with a fifth scan: PruneEvent construction confined to
signalforge/prune/audit.py only.

Plan: plans/super/6-prune-engine.md (DEC-007, DEC-014, DEC-016, DEC-018).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-y8y.5: PruneConfig + load_prune_config

User-facing config for the prune layer. Inner PruneConfig is
extra="forbid" so typos fail loud; outer _PruneConfigFile wrapper
is extra="ignore" so sibling stages coexist (DEC-015, DEC-020).
Defaults match Phase-1 housekeeping: scope=sample, sample_size=100k,
test_timeout=30s, total_budget=600s, capture_failure_rows=3,
trusted_models=(), partition_filter=None. yaml.safe_load only.

Trusted-models validation against the manifest is NOT done at
load time — that's prune_tests() entry (DEC-008, US-009).

Plan: plans/super/6-prune-engine.md (DEC-009, DEC-015, DEC-020).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-y8y.5: drop dead _DEFAULT_CONFIG_FILENAME constant

Worker left a stale module-level constant from comparing with
draft/config.py (which uses project_dir + filename resolution).
Our load_prune_config takes the path directly, so the constant
was never read.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-y8y.2: warehouse adapter timeout_ms plumbing

Extend make_query_job_config and _default_job_config with a
timeout_ms: int | None = None kwarg threaded into
QueryJobConfig.job_timeout_ms. Default None (existing behaviour
unchanged). The prune layer (#6 US-009) will use this for per-test
budget enforcement (Q5=A, DEC-013). BigQuery cancels server-side
at expiry; bytes-billed through cancellation are NOT refunded.

_default_job_config is now keyword-only on (stage, timeout_ms);
internal call sites in bigquery.py and the existing positional
calls in tests/warehouse/test_bigquery_unit.py updated to match.

Pyright noise for the loosely-typed job_timeout_ms attribute stays
confined to _client.py per the SDK-seam convention.

Plan: plans/super/6-prune-engine.md (DEC-013, AR-B2).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-y8y.9: prune engine — prune_tests orchestrator

End-to-end integration of compiler + adapter + audit + budget into
prune_tests(model, adapter, candidates, manifest, *, config,
audit_path) -> PruneResult. Routes outcomes through the five
DropReason values per the plan's decision matrix:

  * 0 failures           → drop (always-passes)
  * relationships(to: ?) → drop (requires-future-data) [no WH call]
  * fail + trusted       → drop (failed-on-known-clean-data)
  * fail + untrusted     → keep (kept)
  * WarehouseError       → keep (kept-without-evidence)
  * total-budget exceed  → keep (kept-without-evidence) [no WH call]

trusted_models validation at entry (DEC-008) — typo'd unique_id
raises PruneTrustedModelNotFoundError before any warehouse call.
Audit fail-closed (DEC-016): OSError → PruneAuditWriteError(cause);
PruneAuditRecordTooLargeError propagates raw. Either aborts the run.

Module-level _sleep / _now_monotonic_ms aliases (DEC-019) for
deterministic test override.

Per-test timeout_ms threading from config.test_timeout_seconds
into adapter.run_test_sql is deferred — adapter doesn't expose it
yet (US-002 plumbed make_query_job_config; surfacing through
run_test_sql is v0.2). For v0.1, per-test enforcement is implicit
via WarehouseError catch; the total-budget gate handles wall-clock.

Plan: plans/super/6-prune-engine.md (DEC-002, DEC-008, DEC-011,
DEC-016, DEC-019).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-y8y.13: signalforge.prune public re-exports

Re-export the v0.1 public surface from signalforge.prune:
* prune_tests (orchestrator entry)
* PruneResult / PruneDecision / DropReason / Scope (read-back models)
* PruneConfig / load_prune_config (user config)
* PruneEvent (audit type; constructed only in audit.py per AST gate)
* PruneError + five concrete subclasses

Internal helpers (_compile_test, _write_prune_event, _sleep, etc.)
stay reachable via dotted import for tests but are absent from the
package namespace -- DEC-021.

Smoke test (tests/prune/test_smoke.py) pins the surface against
__all__ and ensures internal helpers don't leak.

Plan: plans/super/6-prune-engine.md (DEC-021).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-y8y.11: extend logger grep gate to signalforge.prune

Add src/signalforge/prune/ to the lazy-format-logger scan. Renames
the test to test_no_f_string_logger_calls_in_llm_draft_or_prune_modules
to reflect coverage. Mirrors the existing DEC-022 / DEC-011 gate
across all three subpackages that emit observability.

Plan: plans/super/6-prune-engine.md (DEC-017).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-y8y.10: drift detectors for prune read-back models

Three extra="forbid" Strict mirrors (StrictPruneResult,
StrictPruneDecision, StrictPruneEvent) paired with committed JSON/
JSONL fixtures covering all five DropReason values. Field-set parity
tests catch silent schema drift before a v0.2 reader does.

Mirrors tests/safety/test_drift_detector.py shape exactly. PruneConfig
is already extra="forbid" in production (DEC-015), so no drift gate
is needed there.

Plan: plans/super/6-prune-engine.md (DEC-010, DEC-015).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-y8y.3: diagnostic cost probe + ops-doc scaffold

Add tests/warehouse/test_sample_cost_probe.py — measures
total_bytes_billed for a deterministic 100k-row sample against
bigquery-public-data.iowa_liquor_sales.sales. Marked
@pytest.mark.bigquery + skipif(SF_RUN_BQ) so default CI excludes
it. Used to verify or refute Phase-1's "sample-mode is cheap"
assumption (AR-B1) — TO_JSON_STRING(t) reads the whole row.

Pre-creates docs/prune-ops.md with the standard ops-doc skeleton:
public-API placeholder, drop-reason taxonomy, cost-model section
(awaits the probe's figure), v0.2 deferrals. US-014 fills the body.

Plan: plans/super/6-prune-engine.md (DEC-012, AR-B1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-y8y.12: BQ integration test against bigquery-public-data

Single end-to-end test runs prune_tests against the public Iowa
liquor sales dataset with two candidate tests:

  * not_null(invoice_and_item_number) -> always-passes drop
  * accepted_values(category_name, values=("VODKA", "GIN")) -> kept

Confirms the orchestrator's decision matrix in real warehouse
conditions. Marker @pytest.mark.bigquery + skipif(SF_RUN_BQ)
keeps it out of default CI; runs only when SF_RUN_BQ=1 and ADC
are configured (mirrors tests/warehouse/test_bigquery_integration.py).

Plan: plans/super/6-prune-engine.md (acceptance criterion #7,
AR-C11).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-y8y.14: docs/prune-ops.md + CLAUDE.md update

Fill out the operational reference for the prune layer. Cover the
public API surface, signalforge.yml prune: config block, drop-reason
taxonomy, audit JSONL schema, cost-model verification path,
real-warehouse test invocation, and v0.2 deferrals.

Update CLAUDE.md's Repository status and Public API surface (v0.1)
sections to list the prune layer alongside the manifest, warehouse,
safety, and draft layers.

Plan: plans/super/6-prune-engine.md (DEC-027, all v0.1 surfaces).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 6/QG: fix bugs surfaced by 4-pass code review

Seven fixes across the prune layer's surface and tests:

* Custom PruneResult.__repr__ redacts compiled_sql and sample_failures
  so accidental log-line interpolation can't leak SQL or sampled rows
  (DEC-022).
* Audit path canonicalised via warehouse._path_safety before write so
  a symlinked .signalforge/prune.jsonl can't redirect writes outside
  the project (DEC-016).
* load_prune_config(project_dir, path=None) signature aligned with
  load_safety_config / load_draft_config — one calling convention
  across stages.
* Default audit_path resolves relative to project_dir (defaulted to
  cwd) instead of cwd directly — matches safety + draft fix.
* Compiler identifier shape validation: CandidateTest column/field/to
  routed through warehouse._sql_safety.validate_identifier; an
  adversarial identifier returns the new _InvalidIdentifier sentinel
  which the engine routes to kept-without-evidence (DEC-024).
* test_yaml_safe_load_rejects_python_objects strengthened to use a
  side-effect-detectable gadget (Path.touch on a tmp marker); test
  now actually fails if yaml.load is substituted for yaml.safe_load.
* PruneTimeoutError docstring documents it as forward-compat for v0.2
  (kept on public surface; no v0.1 callers see it).

Plan: plans/super/6-prune-engine.md (Quality Gate of issue #6).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-y8y.16: distill prune-layer rules into .claude/rules

New .claude/rules/prune-engine.md captures the load-bearing
conventions established by issue #6:

  * kept-without-evidence routes to decision="kept" (DEC-011)
  * fail-closed prune.jsonl audit (DEC-016)
  * symlink-hardened audit path via canonicalise_path (post-QG)
  * identifier shape validation at compile seam (post-QG)
  * dialect-driven compiler (no BigQuery-isms)
  * single AST scan per new audit-event type (DEC-018)
  * ANSI-safe lazy-format logger + grep gate (DEC-017)
  * custom __repr__ on result-shaped models (post-QG, DEC-022)
  * drift detectors mandatory for extra="ignore" models (DEC-010)
  * API alignment with adjacent stages (load_*_config signature,
    project-dir-relative default paths)
  * signalforge.yml prune: namespace (DEC-020)

CLAUDE.md cross-references the new rule file.

Plan: plans/super/6-prune-engine.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 6/QG: wire prune scope/sample_size/partition_filter into compiled SQL

PR #20 review (Copilot + CodeRabbit) flagged that PruneConfig.scope was
advisory-only — the engine wrote decision.scope into audit metadata but
never wrapped the failing-rows SQL with the deterministic-sample CTE.
Sample-mode tests ran against the FULL table regardless of config,
defeating the cost model US-003 was built on.

The compiler is now the single seam for the wrapping. _compile_test
accepts scope, sample_size, sample_bucket, and partition_filter; sample
mode wraps the test in a `WITH sample AS (SELECT * FROM <table> AS t
WHERE MOD(ABS(FARM_FINGERPRINT(TO_JSON_STRING(t))), <bucket>) < 1
[AND <partition>] LIMIT <size>) <test>` compound matching the warehouse
adapter's sample_rows shape (DEC-006 of issue #3). Full mode + a
partition_filter composes via a derived table so per-test WHERE-clause
shapes don't have to be edited.

The orchestrator computes sample_bucket once per run from
Table.num_rows / sample_size via the SDK shim seam (acknowledged minor
encapsulation crack, documented inline; v0.2 may add a public
WarehouseAdapter.get_table_metadata seam). When num_rows is unavailable
in sample mode the orchestrator raises PruneError rather than silently
degrade to "every row" — that fail-loud signal lands in front of the
operator.

Sample-mode relationships samples the CHILD only (the parent stays at
full so an orphan detected in the child sample is not a false positive
caused by the parent's missing-from-sample row).

Also fixes the relationships parent-resolution ambiguity case: when two
or more manifest models share Model.name (cross-package collision), the
compiler returned the FIRST match silently. It now returns
_RequiresFutureData with a precise count, routing the test to
"requires-future-data" rather than picking a wrong parent.

Pinned snapshot fixtures for each of the four candidate-test variants
in sample mode (not_null_sample.sql, unique_sample.sql,
accepted_values_sample.sql, relationships_sample.sql).

15 new tests across compiler + engine; existing tests (which were
implicit-default sample-mode but tested full-mode SQL shapes) updated
to scope="full" so they continue to test the legacy unwrapped path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 6/QG: prune audit short-write loop + SF_RUN_BQ truthy guard + BQ probe race fix

Three independent PR-#20 review items grouped because they all affect
the I/O / opt-in seams:

(1) signalforge.prune.audit._write_prune_event now loops on os.write
until the full payload lands. POSIX write(2) may return fewer bytes
than requested (EINTR on signal-interrupted calls; short writes on some
filesystems / kernels); the previous single-call assumption could
silently truncate the JSONL line. A persistent zero-byte return raises
OSError so we don't spin forever.

(2) The SF_RUN_BQ env-var check in three integration test files now
restricts truthy values to {"1", "true", "yes", "on"} (case-insensitive)
via a _bq_runs_enabled() helper. The previous `not os.environ.get(...)`
treated SF_RUN_BQ=0 / SF_RUN_BQ=false / SF_RUN_BQ=no as truthy because
they're non-empty strings — surprising for a user trying to disable
the runs.

(3) tests/warehouse/test_sample_cost_probe.py now reads
total_bytes_billed directly off the QueryJob instance returned by
client.query(...) rather than via INFORMATION_SCHEMA.JOBS_BY_USER. The
previous lookup filtered by signalforge_stage='warehouse_sample' label
and a creation-time lower-bound, which could attribute bytes to the
wrong job (a leftover prune-stage run, or a concurrent process emitting
the same label). Reading total_bytes_billed straight off the just-issued
QueryJob eliminates the race entirely. The probe deliberately bypasses
the adapter's public sample_rows path to access job stats; documented
inline as a v0.2 deferral (DEC-027 of issue #6).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 6/QG: docs/rules corrections + rename make_query_job_config to _make_query_job_config

PR #20 review (Copilot + CodeRabbit) flagged six accuracy items in
the documentation, rules, and adapter docstrings:

(.claude/rules/prune-engine.md)
- requires-future-data was described as "Kept" — it's actually "Dropped".
- engine.py::_classify_decision was a fictional reference; renamed to
  the actual helper (_decide_from_test_result).
- The signalforge.yml prune-block field list referenced phantom keys
  (audit_path, mode); replaced with the real PruneConfig fields.

(src/signalforge/prune/config.py)
- The trusted_models docstring contradicted the implementation. Trusted
  models route to "failed-on-known-clean-data" (drop, presumed buggy
  test); the docstring incorrectly claimed they "surface as real
  failures rather than the drop". Rewritten to match.

(docs/prune-ops.md)
- prune_tests signature was missing the project_dir parameter.
- The test_timeout_seconds entry was unclear about what's wired vs
  deferred to v0.2; clarified that v0.1 has no per-test timeout
  enforcement (run_test_sql does not yet accept timeout_ms; the
  knob is reserved for v0.2 alongside the existing
  _make_query_job_config plumbing).

(docs/warehouse-adapter-ops.md)
- The _default_job_config example was missing the required stage=
  kwarg.
- Removed the false claim that "the prune layer (#6) uses this for
  per-test budget enforcement" — issue #6 ships with
  total_budget_seconds enforcement only.

(src/signalforge/warehouse/adapters/bigquery.py)
- _default_job_config docstring claimed prune layer was the only
  caller supplying a non-None timeout_ms; rewritten to match reality
  (currently used only by tests; reserved for v0.2 prune integration).

(rename make_query_job_config → _make_query_job_config)
- The function lives inside the SDK seam (_client.py) and is internal
  to the adapter layer. The privacy-prefix convention dictates the
  underscore. Mechanical rename across _client.py, bigquery.py,
  prune/engine.py docstring, and the two test files that import it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 21: Run AR-B1 cost-model probe + fix BQ adapter TableRef bug

Records the verified bytes-billed figure for US-003 in
docs/prune-ops.md and bundles the adapter bug surfaced by the live
run.

- AR-B1 probe ran 2026-05-01 against
  bigquery-public-data.iowa_liquor_sales.sales (~30M rows, ~24 cols)
  for a 100k-row deterministic sample. BigQuery's pre-execution
  analyzer estimated 9,924,771,840 bytes (~9.92 GB) — ~99x the Phase-1
  estimate and ~2x the probe's 5 GB sanity ceiling. The adapter's
  100 MB cost cap (DEC-005 of #3) blocked execution before any bytes
  were billed; the figure is captured from the
  bytesBilledLimitExceeded error.
- docs/prune-ops.md Cost model section: replaced the TBD placeholder
  with the verified figure and run date, and rewrote the surrounding
  paragraphs to call out that Q4=A is NOT adequate for v0.1
  sample-mode on wide tables. Schema-only stays the v0.1 default.
- Adapter bug (separate from US-003): BigQueryAdapter._get_table and
  the probe were passing our Pydantic TableRef directly into
  google.cloud.bigquery.Client.get_table, which only accepts
  str | TableReference | Table | TableListItem and explodes on
  TableRef.path access. Both call sites now pass qualified_name.
  FakeBigQueryClient._coerce_to_tableref learned to parse the dotted
  string, so the unit suite still binds the production path. The
  same shape was present in the integration tests at
  tests/warehouse/test_bigquery_integration.py — those would have
  failed identically against live BQ; they're now reachable as a
  side-effect.
- Q4=C escalation tracked in #22 (temp-table-materialised sample
  for v0.2).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 21: Address PR review feedback

- _coerce_to_tableref accepts 2-part 'dataset.table' refs (Pydantic
  TableRef.qualified_name omits the project segment when project=None);
  raises a clear AssertionError for other shapes. (Copilot, CodeRabbit)
- docs/prune-ops.md: lead with the BigQuery reason code
  bytesBilledLimitExceeded as the durable identifier; flag that the
  SDK exception class is unstable across versions (adapter expects
  BadRequest/400, live 2026-05-01 run with google-cloud-bigquery
  3.41.0 raised InternalServerError/500). (Copilot, CodeRabbit)
- docs/prune-ops.md: replace the wrong cost_limit_bytes reference
  with the actual profile-level maximum_bytes_billed field; point at
  warehouse-adapter-ops for context. (CodeRabbit)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 21: Add warehouse-adapters rule on TableRef vs vendor SDKs

Captures the lesson from the AR-B1 cost-probe failure: don't pass our
Pydantic TableRef directly into vendor SDK methods that accept
str | TableReference | Table | TableListItem. Always pass
ref.qualified_name. Integration tests against the real SDK are the
only thing that catches this; FakeBigQueryClient won't.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 7: Add super plan for quality grader

29 decisions captured (DEC-001 .. DEC-029) across discovery, architecture
review, and refinement. 13 stories generated covering scaffold, typed
models, rubric, config, prompts + envelope guard, parser, fail-closed
audit, orchestrator + sidecar JSON, AST/logger gate extensions, real-API
smoke, ops doc, quality gate, and patterns & memory.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 7: Update plan phase to published with PR #24 link

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 7: Devolve plan to beads (epic + 13 tasks)

Phase: devolved. US-001 (scaffold) is the initial ready entry point;
US-013 (Patterns & Memory) runs last after Quality Gate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-dgv.1: Scaffold signalforge.grade + 9-class GradeError hierarchy

Mirror prune/errors.py shape: classvar default_remediation, repr-quoted
user-supplied strings via _format_value(), __str__ renders message +
↳ Remediation: line. Module docstring on __init__.py documents the
safety redaction boundary closed at draft (DEC-013).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-dgv.2: Typed grade models + drift detectors + fixtures

GradingResult / GradingReport / GradeEvent (Pydantic v2, extra=ignore,
frozen). Score is float | None to support DEC-015 degraded path.
Computed fields one_line_why, pass_rate, mean_score, aggregate_complete,
passed (mirrors clauditor aggregate-AND threshold). Minimal __repr__
on every result-shaped model (DEC-022 of #6).

Strict<X>(extra=forbid) drift detectors validated against committed
fixtures (grade_event_v1.jsonl, grade_report_v1.json).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-dgv.3: Rubric data model + DEFAULT_RUBRIC + canonical hash

Criterion (frozen, extra=forbid, both id and criterion required and
non-empty per DEC-017). GradeThresholds with [0.0, 1.0]-bounded
min_pass_rate=0.7 / min_mean_score=0.5. Rubric: TypeAlias = tuple[
Criterion, ...] per DEC-011 (no wrapper class). DEFAULT_RUBRIC carries
the four locked criteria from DEC-016 verbatim (clarity, consistency,
rationale, no-redundant); criterion text pinned by a verbatim-match
test for reproducibility.

_canonical_rubric_hash sorts by id then JSON-dumps with sort_keys for
deterministic order-invariant blake2b-8 hex (DEC-010). Pinned to a
golden hex regression test so changes to DEC-016 text break the build.

validate_rubric rejects duplicate ids with GradeRubricError. The
degenerate empty rubric also raises GradeRubricError: an empty rubric
would make every grade run a silent no-op, which is exactly the
fail-loud-on-typo posture the rest of the layer takes (extra=forbid
on config-shaped models, etc.). Recording the choice here so a future
"empty rubric is fine, just a no-op" patch surfaces this rationale.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-dgv.5: Prompt rendering + <ARTIFACT> envelope + version hashes

System prompt + cached rubric block + dynamic per-(artifact, criterion)
block. Dynamic block fences artifact_text inside <ARTIFACT>...</ARTIFACT>.
GradePromptEnvelopeBreachError raised BEFORE rendering if literal
</ARTIFACT> appears in artifact_text — the only LLM-prompt defence
between LLM-generated artifact content and the judge prompt (DEC-008).

prompt_version_template (per-run constant per rubric) and
criterion_prompt_hash (per-criterion stable across artifacts) per DEC-019,
both pinned to golden hex strings as regression detectors.

extract_artifact_text resolves DEC-009 dotted-path artifact_ids against a
post-prune CandidateSchema; unknown ids raise GradeOutputError with
violation_type unknown_artifact_id.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-dgv.6: Response parser + single-criterion anchor contract

parse_grade_response: strip optional JSON code fence, json.loads, validate
shape (dict with required criterion_id/score/passed; optional
evidence/reasoning default empty), enforce single-key anchor
returned.criterion_id == sent.criterion_id, validate score range
[0.0, 1.0] excluding nan/inf and bool-disguised-as-number, validate
passed is strict bool. Returns typed GradingResult.

GradeOutputError.violation_type tightened to Literal taxonomy of nine
values (json_parse, missing_required_field, missing_criterion_id,
criterion_id_mismatch, score_out_of_range, score_not_a_number,
passed_not_a_bool, unknown_artifact_id, ambiguous_artifact_id). The last
two preserved from US-005's extract_artifact_text call sites.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-dgv.7: Fail-closed grade audit writer + single GradeEvent seam

write_grade_event mirrors prune.audit.write_prune_event verbatim:
size-cap before os.open (4000 bytes; oversize leaves NO artifact),
O_APPEND|O_CREAT|0o600, single os.write, os.fsync, no try/except
around write/fsync — propagation IS the defence (DEC-011 of #4 / DEC-016
of #6 / DEC-006 of #7).

audit_path routed through warehouse._path_safety.canonicalise_path at
entry; symlink-escape wraps as GradeAuditWriteError(cause=...).
OSError/PermissionError on the open/write propagate raw.

_build_grade_event is the single GradeEvent construction site (US-009
will add the 6th AST scan to gate this).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-dgv.4: load_grade_config + signalforge.yml grade: namespace

Inner GradeConfig (extra=forbid) with DEC-023..DEC-027 defaults: model,
cache_ttl="1h", max_output_tokens=256, max_retries_429/5xx/conn,
total_budget_seconds=300, min_pass_rate=0.7, min_mean_score=0.5, rubric
(None=use DEFAULT_RUBRIC), fail_on_below_threshold=False.

Outer _GradeConfigFile (extra=ignore at top level) silently tolerates
sibling stages (safety:, llm:, prune:). Typos in grade: block fail loud
via Pydantic ValidationError, wrapped in GradeConfigError.

Resolution order mirrors load_prune_config: explicit path > project_dir/
signalforge.yml > defaults. Missing explicit path raises GradeConfigError;
empty / non-mapping / no grade-block YAML returns defaults.

rubric override replaces wholesale; duplicate ids re-raise GradeRubricError
through @model_validator.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-dgv.8: grade_artifacts orchestrator + sidecar JSON + fake helper

grade_artifacts(model, candidate, prune_result, *, rubric, config,
audit_path, sidecar_path, client, project_dir) -> GradingReport.
Sequential criterion-outer iteration over (criterion, artifact) pairs;
one call_anthropic per pair (DEC-004); fail-closed JSONL per call;
sidecar JSON end-of-run via write_grading_report (DEC-006/012).

_artifact_id_for canonical formatter for the 6 DEC-009 dotted-path
shapes; _model_test_args_hash disambiguates colliding model-level
test types. _stable_artifact_pairs / _iterate_artifacts yield stable
order for reproducibility.

Total wall-clock budget (DEC-023) cuts off remaining pairs as degraded
GradingResult(score=None, passed=False, reasoning="grade budget
exceeded ...") with matching GradeEvent records. Per-call retry
exhaustion / parser failure also degrade gracefully (DEC-015) — the
run never aborts mid-loop except on audit-write failure.

Whole-run pre-flight envelope-breach scan: any artifact payload
containing </ARTIFACT> raises GradePromptEnvelopeBreachError before
any LLM call.

run_id (uuid4 hex per DEC-020) ties every JSONL record to the sidecar.
prompt_version_template + criterion_prompt_hash + rubric_hash carried
on every GradeEvent (DEC-014, DEC-019, DEC-010 of #5).

Module-level _sleep alias for deterministic budget tests (DEC-023 /
DEC-019 of #6 / DEC-004 of #5). One INFO log per invocation with
lazy-format JSON; no f-string interpolation in _LOGGER calls.

Public re-exports complete in signalforge.grade.__init__.py.
tests/grade/_fake.py::expect_grade_responses helper wraps
FakeAnthropicClient (DEC-021).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-dgv.9: 6th AST scan (GradeEvent) + logger gate extension

tests/test_audit_completeness.py extends with the sixth AST scan gating
Call(func=Name(id='GradeEvent')) to signalforge.grade.audit only, with
sanity check that >=1 construction site exists in audit.py (catches
accidental rename-without-update). Mirrors the 5th scan (PruneEvent)
verbatim per DEC-018 of #6.

tests/llm/test_logger_grep_gate.py adds _GRADE_DIR to its directory
list — the gate now scans signalforge.{llm,draft,prune,grade} for
_LOGGER.<level>(f"...) calls. Mirrors DEC-017 of #6.

Manually verified both gates fire on intentional violations:
- Temporarily added a GradeEvent(...) construction in engine.py;
  test_grade_event_construction_only_in_grade_audit_module FAILED.
- Temporarily added _LOGGER.info(f"...") in engine.py;
  test_no_f_string_logger_calls_*_modules FAILED.
Both reverted before commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-dgv.10: Real-API smoke test for grade_artifacts (gated)

tests/grade/test_smoke_real_api.py mirrors tests/draft/test_smoke_real_api.py:
pytestmark = pytest.mark.anthropic so the default `pytest` invocation
deselects it (per pyproject addopts -m 'not anthropic'). Skips when
ANTHROPIC_API_KEY is unset.

Builds a minimal CandidateSchema (1 column, 1 not_null test) and a
single-criterion rubric (clarity). One real grade_artifacts run against
this fixture issues 5 LLM calls (~$0.025 on Sonnet 4.6) and asserts
SHAPE only (no specific scores or passed outcomes -- LLM output is
non-deterministic). Verifies sidecar JSON + JSONL audit are written
and parse cleanly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-dgv.11: docs/grade-ops.md + cost-transparency note

Operational reference mirroring docs/prune-ops.md structure: public API
surface, signalforge.yml grade: block schema with worked example,
decision matrix (score [0.0, 1.0] + aggregate AND-thresholds), audit/
sidecar shapes (JSONL fail-closed per call + JSON end-of-run sidecar),
reproducibility hash fields, failure-mode error-class table, regen
instructions for fixtures.

Cost guidance per DEC-014: per-criterion fan-out (DEC-004) costs
~$0.18/model on Sonnet 4.6 vs ~$0.05/model batched (Q4=A in plan); the
3.4x trade buys per-criterion retry isolation + v0.2 prompt-tuning
headroom. v0.2 will offer batched-criteria as opt-in.

example_config.yml extracted verbatim from the doc; tests/grade/
test_config.py extended with a round-trip test that loads the fixture
through load_grade_config and asserts the locked DEC-023..DEC-027
defaults are populated. Doc + fixture stay in sync via the round-trip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-dgv.12: Quality Gate — fix bugs from 4 code-review passes

Pass 1 (security/safety):
- engine.grade_artifacts now canonicalises audit_path/sidecar_path
  against resolved_project_dir BEFORE handing off to writers (mirrors
  prune.engine). Without this, the writers' own canonicalise_path call
  derived project_dir from the path itself (audit_path.parent.parent),
  which neutered the symlink-escape gate for caller-supplied paths
  (e.g. audit_path=/tmp/grade.jsonl → project_dir="/" → any escape
  passes containment).
- Added prune_result.model_unique_id == model.unique_id assertion at
  engine entry; raises GradeError on mismatch. Prevents stale prune
  result from silently driving the no-redundant criterion.

Pass 2 (correctness/edge cases):
- _test_args_hashes generalised from model-only to also disambiguate
  column-scope test collisions (e.g. two accepted_values tests on the
  same column with different values lists). _artifact_id_for now
  accepts args_hash on the column-scope branch; extract_artifact_text
  parses the 5-part dotted form. Without this fix, JSONL records on
  the same (run_id, artifact_id, criterion_id) triple would collide
  and the diff renderer (#8) couldn't distinguish them.

Pass 3 (API design):
- Documented GradeBudgetExceededError as v0.2-reserved (currently
  never raised; engine always degrades).
- Documented GradeThresholds as v0.2-reserved (exported but not
  consumed in v0.1 production code; GradeConfig carries the flat
  fields).
- Documented fail_on_below_threshold as v0.1 no-op (reserved for v0.2
  CLI exit-code enforcement).

Pass 4 (test quality):
- Added regression tests for the QG pass 1 + pass 2 fixes:
  - test_grade_artifacts_rejects_prune_result_for_different_model
  - test_grade_artifacts_user_supplied_audit_path_outside_project_tree_rejected
  - test_grade_artifacts_user_supplied_sidecar_path_outside_project_tree_rejected
  - test_artifact_id_for_column_scope_collision_args_hash_disambiguates
  - test_artifact_id_for_column_scope_unique_test_no_args_hash

CodeRabbit review: not run — coderabbit:code-review skill is not
available in this session. Skill-based CodeRabbit is documented as
optional in /ralph-run Step 3b ("Run CodeRabbit review using the
coderabbit:code-review skill (if available)"). Operator can invoke
manually on the PR if desired.

Validation gate: 955 passed (up from 952 pre-QG; +3 net new regression
tests after QG pass 4), 10 deselected, ruff clean, pyright 0 errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-dgv.13: Patterns & Memory — grade-layer.md + CLAUDE.md

.claude/rules/grade-layer.md distils every load-bearing pattern from
issue #7: conservative score-and-degrade taxonomy (DEC-002/015),
fail-closed JSONL + sidecar with engine-level path canonicalisation
(DEC-006/012, post-QG fix), <ARTIFACT> envelope + whole-run pre-flight
breach guard (DEC-008/013), per-(artifact × criterion) sequential calls
with cached rubric block (DEC-004/027), reproducibility hash fields
(DEC-010/019), canonical artifact_id format with column-scope collision
disambiguator (DEC-009 + post-QG fix), single GradeEvent construction
seam + 6th AST scan (DEC-029), ANSI-safe logger gate extension (4 dirs
now), prune_result.model_unique_id boundary check, custom __repr__ on
result-shaped models, drift detectors, API alignment, signalforge.yml
grade: namespace, and v0.2 forward-compat reservations.

CLAUDE.md updated: Repository status now lists 7 issues shipped (added
the #7 grader entry with the public surface highlight); Public API
surface section adds the grader's exports.

No production code touched. Validation: 955 passed, ruff clean,
pyright 0 errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 7: Address PR review feedback (Copilot + CodeRabbit)

15 items fixed across 9 files; 3 marked false positive (see PR comment
for justifications).

**Code fixes**
- engine.py: exact duplicate tests now get an ordinal suffix
  (`<hash>:<n>`) on top of the args_hash collision disambiguator, so
  semantically identical tests produce distinct artifact_ids and JSONL
  records don't collide on the (run_id, artifact_id, criterion_id)
  triple. _test_args_hashes refactored to share the assignment logic
  across model-level and column-level scopes.
- prompts.py extract_artifact_text: when an artifact_id carries a
  supplied args_hash, the resolver now re-runs _model_test_args_hash
  (lazy-imported from engine to avoid module-load cycle) and filters
  matches by hash, restoring the orchestrator-formatter→resolver
  round-trip claim. Strips the ":<n>" ordinal suffix when present
  (exact duplicates have identical rationale, so any match is correct
  on the read path).
- config.py / rubric.py: _bounded_unit and _reject_out_of_range now
  reject NaN / Infinity via math.isfinite up-front (bare comparisons
  silently accept NaN).
- rubric.py Criterion: validator now rejects embedded NUL bytes (\\x00)
  to keep criterion_prompt_hash's NUL domain-separator contract honest.
- test_smoke_real_api.py: skip-gate now treats empty/whitespace-only
  ANTHROPIC_API_KEY as unset (avoids a noisy live-call auth failure).

**Doc fixes**
- errors.py GradeRubricError: remediation rewritten to reference the
  actual `grade.rubric` block + `id`/`criterion` fields (the old text
  mentioned `rubric_path`/`description`/`weight` which never shipped).
- errors.py GradeAuditWriteError: remediation now mentions BOTH the
  JSONL audit and the sidecar JSON paths (one error class, two writers).
- errors.py GradeAuditRecordTooLargeError: remediation branches on
  the cap (4 KB POSIX-atomic-append for JSONL vs 1 MB sidecar cap).
- docs/grade-ops.md: escape `#2` at line start (markdownlint MD018);
  add `text` language tag to the prompt-injection example fence
  (markdownlint MD040).
- plans/super/7-quality-grader.md: add `python` language tag to the
  DEC-016 default-rubric block fence (markdownlint MD040).

**Regression tests added (3)**
- test_extract_artifact_text_filters_by_args_hash_for_model_collision
- test_extract_artifact_text_filters_by_args_hash_for_column_collision
- test_stable_artifact_pairs_exact_duplicate_tests_get_ordinal_suffix

**False positives (documented)**
- _fake.py permissive matchers — intentional for the v0.1 fake's
  call-order/count contract.
- __init__.py docstring — CodeRabbit's "scaffold-only" critique was
  read against a stale base; the current docstring describes the full
  API surface.
- audit.py __all__ exports underscore-prefixed names — intentional;
  documents the test-importable surface (size caps + the single
  GradeEvent construction seam blessed by the AST audit-completeness
  scan); underscore prefix remains the public-API marker per repo
  convention. Sibling safety/draft/prune audit modules follow the same
  pattern.

Validation: 958 passed (up from 955; +3 net new regression tests),
10 deselected, ruff clean, pyright 0 errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 8: Diff renderer (plan)

Super plan for #8 — kept/dropped table + unified schema.yml diff.
14 stories (12 implementation + Quality Gate + Patterns & Memory),
21 decisions, architecture review across security / performance /
data-model+API / observability / testing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 8: Mark plan phase published; link PR #25

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 8: Devolve plan; record beads manifest (epic + 14 tasks)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-htq.4: Diff safety helpers — _ansi_safety, _markdown_safety (US-004)

Two leaf modules for the diff renderer (#8):

- `signalforge.diff._ansi_safety.strip_ansi_escapes` — regex-based
  stripper for ANSI CSI escape sequences (DEC-007). Defends the
  Markdown sink against terminal-control injection from upstream
  manifest fields, LLM-drafted artifact text, and prune/grade
  reasons.
- `signalforge.diff._markdown_safety.escape_markdown_scalar` —
  escapes backtick/pipe/backslash; HTML-entity-encodes pipe and
  row-breaking control chars in table-cell mode (DEC-008).
  Backslash is processed first so subsequent escapes can't be
  unwound by a crafted trailing-backslash.

Tests cover bare CSI, color codes, reset, compound SGR, cursor
movement, no-escape passthrough, idempotence, empty string for
ANSI; backtick/pipe/backslash escaping, table-cell entity encoding,
control-char encoding, idempotence, empty string for Markdown.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-htq.1: Diff error hierarchy (US-001)

Seven-class typed exception hierarchy under signalforge.diff.errors,
mirroring the safety / draft / prune / grade precedent: every error
carries a class-level default_remediation that __str__ renders on a
separate ↳ Remediation: line, and every user-supplied string flowing
into a message routes through repr() (DEC-022 of #6) so adversarial
input cannot inject ANSI escapes or control characters into log
viewers.

Classes: DiffError (base) + DiffCandidateModelMismatchError,
DiffPruneResultModelMismatchError, DiffGradingReportModelMismatchError
(DEC-002 boundary checks at orchestrator entry);
DiffInputTooLargeError (DEC-006 existing-schema YAML byte cap);
DiffSidecarRecordTooLargeError (DEC-009 sidecar size cap);
DiffSidecarWriteError (fail-closed sidecar-write seam wrapping
underlying I/O cause via __cause__).

24 tests covering default_remediation rendering, repr() escaping for
ANSI-bearing inputs, subclass-of relationships, and field exposure.
Subpackage __init__.py ships only the error hierarchy in US-001;
later stories (US-002 result models, US-003 config, US-010
orchestrator) extend the public surface.

Traces to plans/super/8-diff-renderer.md US-001 (DEC-002, DEC-006,
DEC-009).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-htq.5: Canonical YAML emitter (US-005)

Add signalforge.diff._emitter.emit_proposed_yaml(candidate, prune_result)
that filters tests to PruneDecision.decision == "kept", preserves column
declaration order, and sorts tests within each column by (type, args_hash)
for deterministic output. Mirrors the grade layer's args_hash convention
(blake2b-4 of canonical-sorted JSON of test args, DEC-009) so the
emitter, grader, and diff renderer agree on test identity.

yaml.safe_dump invoked with sort_keys=False, default_flow_style=False,
width=4096, allow_unicode=True. AR-9 round-trip test verifies edge-case
descriptions ('---', '!tag', triple-backticks, embedded newlines, leading
quote/pipe/gt, unicode) survive emit -> yaml.safe_load byte-identical.

Leaf module — depends only on existing signalforge.draft and
signalforge.prune model types. No __init__.py created (US-001 owns it).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-htq.6: Diff artifact-id formatter (US-006)

Add src/signalforge/diff/_artifact_id.py with byte-equal mirror of
signalforge.grade.engine._artifact_id_for. Six dotted-path shapes
(column.<col>.{description,rationale}, model.{description,rationale},
test.column.<col>.<type>[.<args_hash>], test.model.<type>[.<args_hash>])
plus _model_test_args_hash and compute_args_hashes helpers.

Cross-stage parity is load-bearing: the diff renderer joins grade-
sidecar JSON to its rendered diff via (run_id, artifact_id,
criterion_id); a single shape disagreement would silently drop grade
rows. tests/diff/test_artifact_id.py is the documented single allowed
cross-stage import seam — production diff code must not import from
signalforge.grade at runtime.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-htq.2: Diff result models (US-002)

Adds DiffEntry and DiffReport read-back-stable Pydantic v2 models to
src/signalforge/diff/models.py per US-002 of issue #8. Both models are
frozen=True, extra="ignore" with custom __repr__ per DEC-020 that omits
prose/diff text fields. DiffEntry.tier is a Literal["kept","dropped",
"flagged"] per DEC-012; DiffReport carries reproducibility-hash fields
(candidate_hash, prune_result_hash, grading_report_hash) per DEC-016.

Drift detector at tests/diff/test_models.py mirrors the prune / grade
precedent: StrictDiffEntry / StrictDiffReport extra="forbid" mirrors
validated against tests/fixtures/diff/diff_report_v1.json (which
exercises all three Tier values), plus field-set parity and
extra="forbid" sanity-floor checks.

Public-API re-exports deferred to US-012 per the bead instructions —
diff/__init__.py is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-htq.3: DiffConfig + load_diff_config (US-003)

Introduces the `diff:` top-level namespace in `signalforge.yml` per
DEC-010 of plans/super/8-diff-renderer.md. `DiffConfig(extra="forbid",
frozen=True)` carries the nine locked knobs (context_lines,
max_why_chars, narrow_terminal_threshold, markdown_max_diff_chars,
existing_schema_size_limit_bytes, existing_schema_warn_at_bytes,
sidecar_size_limit_bytes, render_kind, respect_no_color_env);
`_DiffConfigFile(extra="ignore")` outer wrapper silently tolerates
sibling stage namespaces (`safety:`, `llm:`, `prune:`, `grade:`).
`load_diff_config(project_dir, path=None) -> DiffConfig` matches the
prior-stage signatures (load_grade_config / load_prune_config /
load_draft_config / load_safety_config) verbatim.

Numeric knobs route through a single positive-integer validator —
zero/negative caps would silently disable the DEC-006/DEC-009
protections or render an empty table. The literal `render_kind`
discriminator catches unknown render targets at config-load time.

Per US-003 task scope, the loader raises the base `DiffError` (with
remediation) for config-load failures rather than introducing a new
`DiffConfigError` subclass; the seven-class hierarchy from US-001 stays
intact, and a future `DiffConfigError` is a clean v0.2 refinement that
won't break imports.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-htq.8: AnsiRenderer (US-008)

Adds the Renderer ABC + AnsiRenderer concrete in
src/signalforge/diff/_renderers.py per US-008 of plans/super/8-diff-renderer.md.

* DEC-007 — strip_ansi_escapes runs UNCONDITIONALLY on every
  user-content field (artifact_id, why, drop_reason, test_type,
  unified_diff body, model_unique_id) BEFORE the renderer's own colour
  codes are emitted. Defence-in-depth: a malicious "\x1b[31mEVIL\x1b[0m"
  in upstream content renders as the literal text "EVIL" in BOTH
  coloured and non-coloured output.
* DEC-013 — narrow-TTY compact mode triggers when the effective
  terminal width is below DiffConfig.narrow_terminal_threshold (60
  by default). Compact mode drops the WHY column from the table and
  emits each entry's why as an indented "└─ ..." follow-up line.
* DEC-021 — six-step colour-precedence chain documented as
  early-returns in _should_emit_color: respect_no_color_env=False >
  force_color=False > force_color=True > FORCE_COLOR env > NO_COLOR
  env > sys.stdout.isatty().
* Module structured so US-009 can append MarkdownRenderer without
  restructuring; only the ABC + AnsiRenderer ship in this ticket.
* No _LOGGER calls; renderer does no I/O (returns string).

Tests at tests/diff/test_renderers.py cover the ABC contract, wide-TTY
6-column table, narrow-TTY compact mode, all six DEC-021 precedence
positions individually, and unconditional ANSI stripping in both
coloured and non-coloured output across the artifact_id, why,
follow-up why, model_unique_id, and unified_diff body fields. 25
tests, all passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-htq.7: Diff sidecar writer (US-007)

Add `signalforge.diff._sidecar` — fail-closed JSON sidecar writer for the
diff renderer (DEC-009). Mirrors `grade-layer.md` DEC-006/012 verbatim:
single-document overwrite, ``O_WRONLY | O_CREAT | O_TRUNC | 0o600``,
single ``os.write`` (looped on short returns), ``os.fsync``,
descriptor-release-only ``try / finally``. No ``except`` handler around
write/fsync — the propagation IS the defence.

* ``_DIFF_SIDECAR_RECORD_LIMIT_BYTES = 10_000_000`` (10 MB; an order of
  magnitude above the grade sidecar's 1 MB cap because diff text is
  larger by nature). Pre-write size check raises
  ``DiffSidecarRecordTooLargeError`` BEFORE any ``os.open`` so an
  oversize payload leaves no on-disk artefact.
* Symlink-hardened path canonicalisation at writer entry via
  ``signalforge.warehouse._path_safety.canonicalise_path``. Containment
  is gated against the **caller-supplied** ``project_dir``, not a
  derivation of ``sidecar_path`` (mirrors grade's post-QG fix). Failures
  wrap as ``DiffSidecarWriteError(cause=...)``.
* AST defence test in ``tests/diff/test_sidecar.py`` walks the module's
  syntax tree and asserts: (a) exactly one ``ast.Try`` block guards the
  ``os.write`` / ``os.fsync`` syscalls (the descriptor-release ``try /
  finally``), and (b) that block has zero ``except`` handlers. An
  accidental ``try / except OSError`` around the write/fsync would
  silently swallow the exact failure mode the fail-closed pattern
  exists to surface.

Test coverage (13 cases): happy-path round-trip via ``json.loads``,
``0o600`` mode bits, parent-directory creation, ``O_TRUNC`` overwrite
semantics, single ``fsync``, oversize pre-flight (no on-disk artefact),
symlink-escape rejection (containment gate against caller-supplied
``project_dir``), absolute-path-outside-project rejection, symlink-loop
rejection, short-write loop covering the full payload, zero-byte ``write``
raises ``OSError``, ``OSError`` propagation, and the AST defence above.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-htq.9: MarkdownRenderer (US-009)

Extends `signalforge.diff._renderers` with `MarkdownRenderer`:

- GitHub-flavored Markdown output (heading + count summary + GFM
  pipe-table + fenced ```diff block).
- Per-cell escaping via `escape_markdown_scalar(in_table_cell=True)`
  per DEC-008; pipe / backtick / backslash / row-breaking control
  chars are entity-encoded inside cells, while the fenced ```diff
  block carries raw upstream content (the fence is the defence).
- DEC-005 truncation: when `unified_diff` exceeds
  `config.markdown_max_diff_chars` (default 60_000), the body is
  truncated at the last complete `@@` hunk boundary; a footer
  `... (N more lines truncated — see <project_dir>/.signalforge/diff.json
  for full diff)` lives INSIDE the fenced block. Falls back to a
  line-boundary cut for hunk-less bodies.
- DEC-007 strip-then-escape composes: `strip_ansi_escapes` runs
  unconditionally on every user-content field (model_unique_id,
  artifact_id, why, drop_reason, test_type, diff body) BEFORE
  markdown-escaping or fence-wrapping.
- Empty `unified_diff` suppresses the diff block entirely; empty
  entries tuple emits `_(no candidate artifacts)_`.
- `project_dir` constructor kwarg renders into the truncation footer
  (placeholder `<project_dir>` when None — stable for snapshots).

Tests cover: ABC subclass-of, pipe-table well-formedness, fenced
diff passthrough vs. table-cell escaping (the same pipe / header
content is encoded in cells but raw inside the fence), truncation
at last hunk + dropped-line count + fence-internal footer
placement, project_dir rendering, ANSI strip in cells + diff body,
and the empty-diff / empty-entries fallbacks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-htq.10: Diff orchestrator render_diff + JsonRenderer (US-010)

Wires every prior story (errors US-001, models US-002, config US-003,
safety US-004, emitter US-005, artifact-id US-006, sidecar US-007,
renderers US-008/009) into one public seam: render_diff(model,
candidate, prune_result, *, grading_report, existing_schema, config,
output_path, sidecar_path, project_dir) -> DiffReport.

Boundary checks (DEC-002) raise BEFORE any other work; existing_schema
size cap (DEC-006) + soft-warn (DEC-014) gate yaml.safe_load;
symlink-hardened path canonicalisation against project_dir for both
output_path and sidecar_path mirrors the grade-engine post-QG fix
verbatim. Reproducibility hashes (DEC-016) computed via canonical-sort
blake2b-8 of each input. Single INFO log at happy-path end with
lazy-format json.dumps payload (DEC-015). JsonRenderer added to
_renderers.py — model_dump_json(indent=2, by_alias=True), stateless.

19 new tests cover the boundary-check trio, the size-cap + soft-warn
pair, renderer dispatch across all three concretes, JsonRenderer
round-trip, sidecar write, output_path / sidecar_path symlink
containment, INFO log shape, and an end-to-end happy path that
asserts kept/dropped/flagged tier assignment and DEC-016 hash
presence.

* bd_1-scaffolding-htq.11: Diff snapshot fixtures + regenerate.sh + drift detector (US-011)

Commits the 10-case fixture matrix (DEC-017 of #8) under
tests/fixtures/diff/, the deterministic snapshot-input builder at
tests/diff/_snapshot_inputs.py, the regenerate script
tests/fixtures/diff/regenerate.sh (mirrors tests/fixtures/regenerate.sh
shape), the byte-equality snapshot tests at
tests/diff/test_snapshot_fixtures.py, and the schema-drift detector
tests/diff/test_drift_detector.py with StrictDiffReport / StrictDiffEntry
mirrors validated against diff_report_v1.json and the new
diff_entry_v1.json fixture.

The 11 fixture artefacts on disk cover:

  full_with_grade.{ansi,md,json}  — happy path, three surfaces.
  no_existing_schema.ansi         — /dev/null source for unified diff.
  kept_only.ansi                  — every artifact tier=kept.
  dropped_only.ansi               — every artifact tier=dropped.
  no_grading_report.ansi          — score columns null; no flagged tier.
  plain_no_color.txt              — ANSI surface w/ NO_COLOR=1.
  narrow_terminal.ansi            — 40-col TTY (DEC-013 compact mode).
  injection_payloads.{ansi,md}    — adversarial content (DEC-007/008).

Static checks on the plain_no_color and injection_payloads fixtures
pin DEC-007 (strip user-content ANSI escapes UNCONDITIONALLY) and
DEC-021 (NO_COLOR + force_color=False produces zero ANSI escapes).
The drift detector ships the standard four-test pattern (validate
fixture, validate each entry, field-set parity, sanity-floor reject
unknown field) per the prune / grade precedent.

Validation: ruff / pyright / pytest all green; full suite 1178 passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-htq.12: Diff public API + docs + logger grep gate (US-012)

Export the v0.1 public surface from signalforge.diff per DEC-004 of
plans/super/8-diff-renderer.md: render_diff orchestrator, DiffConfig +
load_diff_config, DiffReport / DiffEntry / Tier result models, and the
seven-class DiffError hierarchy. Concrete renderers (AnsiRenderer,
MarkdownRenderer, JsonRenderer) and internal helpers (_emitter,
_sidecar, _artifact_id, _ansi_safety, _markdown_safety) stay private —
reachable via dotted import for internal callers but absent from
__all__.

Add docs/diff-ops.md mirroring docs/grade-ops.md as the operational
reference: overview, public API surface, configuration block,
renderer-kind selection, sidecar JSON schema, reproducibility hash
fields, decision matrix, operational notes (symlink hardening, log
events, ANSI / Markdown injection escaping), snapshot fixture matrix,
debugging, and failure-mode cross-reference.

Extend tests/llm/test_logger_grep_gate.py to scan
src/signalforge/diff/ as the fifth directory per DEC-019 — the
project-wide ANSI-safe lazy-format logger gate now covers llm, draft,
prune, grade, and diff. Planted-violation self-check confirms the
gate fires on an f-string interpolated _LOGGER call in
signalforge.diff.engine.

Add tests/diff/test_public_api.py asserting __all__ matches the
documented surface, that every public name imports via
``from signalforge.diff import ...``, that concrete renderers and
internal helpers stay out of __all__, that the concrete renderers
remain reachable via dotted import (the documented escape hatch),
and that all six DiffError subclasses inherit from DiffError.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-htq.13: Quality gate — fix bugs from code review

Fixes from 4-pass code review on the diff renderer epic (#8):

1. Inverted soft-warn / hard-cap defaults in DiffConfig (DEC-014 was
   dead code). Swap so warn_at_bytes (1MB) < size_limit_bytes (10MB),
   and add a model_validator that fails loud at config-load time when
   warn_at >= size_limit.
2. DiffConfig.sidecar_size_limit_bytes was silently ignored — wire it
   through render_diff to write_sidecar via a new size_limit_bytes
   kwarg. Adds end-to-end test pinning the orchestrator-level error.
3. Drop dead DiffSidecarRecordTooLargeError catch from the
   _write_rendered_text exception ladder (output_path branch can't
   raise that error; only sidecar path can).

CodeRabbit skill not available in this session; skipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-htq.14: Diff renderer patterns + CLAUDE.md update (US-014)

Distil DEC-001..DEC-021 of the diff renderer (#8) into
.claude/rules/diff-renderer.md, mirroring grade-layer.md section
structure verbatim. Bake the post-QG-fix lessons (inverted
warn/limit defaults; sidecar_size_limit_bytes wiring; dead exception
catch) and the load-bearing rules (tier classification + no-grading
degrade, fail-closed sidecar, ANSI strip unconditional, Markdown
hunk-boundary truncation, three boundary checks at orchestrator entry,
reproducibility hashes, drift detector, logger grep gate fifth dir).

Update CLAUDE.md Repository status to "Eight issues shipped" with a
new bullet for #8 matching the prior format; extend the Public API
surface bullet list and Internals listing; remove "diff renderer #8"
from the remaining-feature-work line.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-htq.13: Address PR #25 review feedback (worker B — engine.py + design Q1/Q3)

Six fixes to the diff orchestrator from PR #25 code review:

- #1: wrap mkdir in both output_path and sidecar_path branches with
  try/except → raise DiffSidecarWriteError(cause=...). A project_dir
  whose parent is an existing FILE no longer leaks an untyped OSError
  out of the diff layer.
- #2: doc artifacts (column/model description/rationale) ALWAYS emit
  a DiffEntry per Architectural Commitment #5. When grading is absent
  or no matching result exists, the row gets tier="kept", score=None,
  passed=None, why="kept (no grading)". Prune-only runs now surface
  every present description text in the kept/dropped/flagged table.
- #3: flagged-tier why now reflects the GRADING reason, not the prune
  decision why. Format: "failed grading: <criterion_id> — <reasoning>"
  (truncated to max_why_chars). The first failing criterion drives it
  for determinism.
- #4: replace id(decision.test) lookup with structural keying so the
  args_hash join survives JSON-rehydration of the prune result.
  Cross-stage parity with the grade engine artifact_ids is preserved
  (post-QG fix to enable diff↔grade-sidecar joins after the prune
  result has been persisted+reloaded).
- #5 (Q1=A): default sidecar_path to <project_dir>/.signalforge/
  diff.json when write_sidecar=True (default). New write_sidecar
  kwarg disables the sidecar entirely. Makes the diff sidecar an
  always-on durable record by default, mirroring grade/prune audit
  precedent.
- #6 (Q3=A): skip renderer.render(report) entirely when there's no
  consumer (no output_path AND write_sidecar=False / sidecar serialises
  the report directly). Move duration_seconds capture to immediately
  before the INFO log so it reflects the full wall-clock including
  renderer + writes.

Tests: 8 new regression tests added; one existing test updated for the
sidecar default change; one existing assertion updated for the doc-row
emission count change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-htq.13: Address PR #25 review feedback (worker A — non-engine fixes)

Code fixes:
- _ansi_safety: broaden CSI regex to cover the full final-byte range
  (@-~), not just letters; add tests for tilde-terminated key
  sequences (`\x1b[3~`) and bracketed-paste markers (`\x1b[200~`).
- _markdown_safety: HTML-entity-encode `&`, `<`, `>` before any
  Markdown escape (defence against `<script>`, `</details>`,
  `<img src=x>` smuggling). Order matters: `&` first to avoid
  double-encoding subsequent escape entities.
- _renderers (MarkdownRenderer): four bug fixes —
  (a) dropped_line_count off-by-one when body ends with `\n`;
  (b) the WHOLE rendered diff block now fits under
      `markdown_max_diff_chars` (subtract fence + footer overhead
      before truncating);
  (c) when the first hunk alone exceeds the cap, emit only the
      `---`/`+++` file-header lines rather than a mid-hunk character
      cut (which would produce malformed unified-diff output);
  (d) dynamic fence length: scan the body for the longest backtick
      run and pick `max(3, longest_run + 1)` so a YAML payload
      containing literal triple-backticks cannot close the outer
      fence prematurely.
- config: wrap `read_text` in try/except for OSError /
  IsADirectoryError / PermissionError; symlink-harden the resolved
  config path via `signalforge.warehouse._path_safety.canonicalise_path`
  before any read (mirrors the orchestrator-level treatment of
  `output_path` / `sidecar_path`).

Docs / plan fixes:
- errors.py: `DiffInputTooLargeError.default_remediation` now
  matches production's 10 MB cap (not the obsolete 1 MB literal).
- docs/diff-ops.md: every reference to
  `existing_schema_size_limit_bytes` (10 MB) and
  `existing_schema_warn_at_bytes` (1 MB) defaults consistent;
  fenced log-output blocks tagged as `text` (MD040); soft-warn
  prose corrected ("warn 1 MB fires below hard cap 10 MB").
- .claude/rules/diff-renderer.md: tagged the truncation-footer
  fence as `text`.
- plans/super/8-diff-renderer.md: DEC-006 / AR-5 / AR-11 / AR-13 /
  the `DiffConfig` snippet now carry the post-QG-fix defaults
  (10 MB hard cap, 1 MB soft warn — original plan had them
  inverted before commit c3dc6ab); validation command snippet now
  prefixes `pip install -e ".[dev]"`; MD038 inline-code-span
  spaces removed; duplicate `## Beads Manifest` heading dropped;
  truncation-footer fence tagged as `text`.

Tests:
- New regression tests in tests/diff/test_ansi_safety.py (3),
  test_markdown_safety.py (7), test_renderers.py (5) covering
  every code fix above.
- tests/diff/test_config.py: regression tests for IsADirectoryError
  wrap and the symlink-escape rejection path.

Fixtures:
- tests/fixtures/diff/injection_payloads.md regenerated via
  `tests/fixtures/diff/regenerate.sh` to capture the new
  HTML-escaped `&lt;/details&gt;` rendering of the hostile input.

Validation: `ruff check .`, `ruff format --check .`, `pyright`,
`pytest` all green (1222 passed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-htq.13: Address PR #25 round 2 review feedback (CodeRabbit on second pass)

Six fixes from the second-pass CodeRabbit review:

1. engine.py — exact-duplicate structural-key collapse. Replace the
   ``dict[_StructuralKey, str | None]`` that lost duplicate args_hash
   entries (last-assignment-wins) with a per-key queue
   ``dict[_StructuralKey, list[str | None]]``. ``_resolve_test_artifact_id``
   now pops from the front of the queue per-decision so two byte-identical
   ``CandidateTest`` instances in ``prune_result.decisions`` get distinct
   artifact_ids matching grade engine's id()-keyed ordinal suffixes.
   Iteration order in the queue matches
   ``signalforge.prune.engine._iter_candidate_tests`` (columns first, then
   model-level) so positional consumption aligns with the prune walk.

2. engine.py — ``DiffReport.duration_seconds`` refresh. Construct the
   report with placeholder ``0.0``, refresh via ``model_copy`` after
   rendering and the optional output_path write but BEFORE the sidecar
   write, so the persisted JSON, returned report, and INFO log all
   carry the same wall-clock value. The sidecar write block moved
   below the refresh.

3. tests/diff/test_engine.py — caplog scoping. Three call sites that
   filtered by ``levelname`` only now also gate on
   ``rec.name == "signalforge.diff.engine"`` so unrelated logger
   records can't false-positive the assertions.

4. docs/diff-ops.md — sidecar API/default sync. The ``render_diff``
   signature now shows the ``write_sidecar=True`` kwarg; the sidecar
   section documents the on-by-default semantics with the
   ``<project_dir>/.signalforge/diff.json`` path and the
   ``write_sidecar=False`` opt-out.

5. .claude/rules/diff-renderer.md — sidecar default semantics. The
   rule prose now reflects the post-Q1 default-on shape.

6. .claude/rules/diff-renderer.md — ANSI strip regex contract. The
   documented regex now matches the broadened ECMA-48 / ISO 6429 CSI
   grammar (``\x1b\[[0-?]*[ -/]*[@-~]``) shipped during US-014.

Tests added (3): exact-duplicate not_null, exact-duplicate
accepted_values, and a cross-stage parity test that compares
diff-side artifact_ids against grade engine's ``_artifact_id_for``
output for the duplicate scenario.

Validation: ruff clean, ruff format clean, pyright clean,
pytest 1225 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>
* 9: CLI entrypoint super plan

Capture the design for the v0.1 CLI ticket: discovery, architecture review,
20 decisions (DEC-001..DEC-020), and 12 right-sized stories ready for Ralph.

Notable: SQ-03=C was overridden during architecture review to a hybrid that
graduates GradeConfig.fail_on_below_threshold (the v0.2 reservation from #7)
inside the grade layer instead of shipping a parallel `cli:` config block.
One knob, one owner; saves ~250 lines of config plumbing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 9: Update plan meta — PR #26 draft published

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 9: Plan refinements — DEC-021..DEC-027 (post-publish review)

Resolve seven issues surfaced by an external review pass against the
published plan. All seven verified against the actual codebase before
applying:

- DEC-021 (US-002): GradeBelowThresholdError raise lands AFTER
  write_grading_report and BEFORE return, preserving the durable
  sidecar hand-off on threshold-fail. New ordering test pins it.
- DEC-022 (US-001): render_to_text uses the real _build_renderer
  dispatcher; DiffReport carries no config_used field, so the helper
  defaults to DiffConfig() rather than reaching into the report.
- DEC-023 (US-007): --no-color sets NO_COLOR=1 env (DiffConfig has
  no force_color field; only respect_no_color_env=True).
- DEC-024 (US-008): AST-scan target verified — every typed exception
  in the project lives in an errors.py module. Explicit exclude list
  is the 9 abstract base classes.
- DEC-025 (US-005): Add test_generate_calls_stages_in_documented_order
  pinning safety → draft → prune → grade → diff.
- DEC-026 (US-007): Drop hardcoded duration hints from progress lines;
  replace with live values + post-hoc "done in Xs" line.
- DEC-027 (US-005): --project-dir override is an absolute assertion;
  walk-up only applies to unflagged invocations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 9: Devolve plan to beads — phase devolved

Phase 7 of /super-plan: convert the 12 stories from the detailed
breakdown into a beads task graph so Ralph can pick them up.

Created: 1 epic (bd_1-scaffolding-9vj) + 12 child tasks
(bd_1-scaffolding-9vj.1 through .12). Wired 31 dependency edges per
the documented dependency graph. Initial ready set: US-001 and
US-002 (no blockers; can run in parallel).

Plan phase: published → devolved. Beads Manifest section populated
with the bead IDs and dependency table.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 9: US-001 — Public-surface plumbing (re-exports + render_to_text)

Re-exports 8 typed exceptions from signalforge.draft, 1 from
signalforge.llm so the CLI can tier-map them via the public surface.
Adds signalforge.diff.render_to_text(report, *, config=None,
project_dir=None) -> str — internal wrapper around _build_renderer +
renderer.render(report) that returns the same bytes render_diff would
have written to output_path. Traces to DEC-013, DEC-015, DEC-022.

bd_1-scaffolding-9vj.1

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 9: US-002 — Grade-layer graduation: GradeBelowThresholdError

Graduates GradeConfig.fail_on_below_threshold from v0.1 no-op to v0.1
wiring. New GradeBelowThresholdError raised inside grade_artifacts AFTER
write_grading_report returns and BEFORE return — sidecar JSON lands on
disk first so the operator has a durable hand-off for diagnosis (DEC-021
ordering invariant pinned by test_grade_below_threshold_writes_sidecar_before_raising).

Doc cascade: grade-layer.md v0.2-reservations block updated;
GradeConfig.fail_on_below_threshold docstring rewritten; docs/grade-ops.md
gains "Threshold-fail behaviour" section. Traces to DEC-011, DEC-021.

bd_1-scaffolding-9vj.2

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 9: US-003 — CLI scaffold + version subcommand + logger grep-gate ext

Lays the foundation for the signalforge CLI: pyproject [project.scripts]
entry; cli/__init__.py with main(argv) + argparse parser + version flag;
cli/_helpers.py with canonicalise_user_path, setup_logging,
format_error_to_stderr, map_exception_to_exit_code, _safe_excepthook;
cli/version.py with --version + version subcommand sharing one string;
cli/errors.py with CliError / CliPathError / CliInputError. Logger grep
gate now scans 6 dirs (cli/ added). Smoke + dispatch tests use in-process
main(argv); no subprocess (US-009 owns that). Traces to DEC-007, DEC-008,
DEC-013, DEC-016, DEC-017.

bd_1-scaffolding-9vj.3

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 9: US-005 — cli/generate.py core orchestration

signalforge generate <model> wires manifest → safety → draft → prune →
grade → diff. Project-root resolution: walk-up from cwd, --project-dir
override is an absolute assertion (DEC-027). --manifest / --profiles-dir
flow through canonicalise_user_path. Every typed exception caught at
cmd_generate boundary, mapped via _helpers.map_exception_to_exit_code,
formatted via _helpers.format_error_to_stderr; no traceback ever leaks.
Stage-order test pins the documented safety → draft → prune → grade →
diff pipeline (DEC-025). Knob flags (--mode/--min-score/--write/...)
land in US-006; observability flags (--quiet/--verbose/...) land in
US-007. Traces to DEC-001, DEC-007, DEC-008, DEC-011, DEC-013, DEC-015,
DEC-016, DEC-017, DEC-025, DEC-027.

bd_1-scaffolding-9vj.5

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 9: US-006 — Generate flags --mode, --min-score, --write/--dry-run, --format

Layers five runtime-knob flags onto US-005's signalforge generate:
--mode (overrides safety policy via SafetyPolicy.with_mode); --min-score
(reporting-only override of grade.min_mean_score; never affects exit
code by itself per DEC-004); --write writes proposed schema.yml to disk;
--dry-run runs full pipeline and writes nothing (overrides DEC-002's
default-on sidecar); --format selects render_kind. --write and --dry-run
are mutex at argparse level. Traces to DEC-002, DEC-004, DEC-010, DEC-020.

bd_1-scaffolding-9vj.6

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 9: US-007 — Generate observability flags + progress lines

Layers the UX knobs onto signalforge generate: --quiet suppresses progress
and raises log level to WARNING; --verbose raises log level to DEBUG and
allows panic-path tracebacks (skips _safe_excepthook install); --no-color
sets NO_COLOR=1 env so the AnsiRenderer's existing precedence chain emits
plain text (DEC-023 — DiffConfig has no force_color field). Five stderr
progress entry lines + five paired 'done in Xs' exit lines per stage; live
values only (no hardcoded duration hints per DEC-026). TTY-gated by default
(both stderr and stdout must be terminals). Traces to DEC-014, DEC-016,
DEC-023, DEC-026.

bd_1-scaffolding-9vj.7

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 9: US-008 — Exit-code AST scan + parametrized tests

7th AST scan (tests/test_audit_completeness.py) walks every
src/signalforge/*/errors.py + src/signalforge/cli/errors.py and asserts
every non-abstract *Error class appears in
signalforge.cli._helpers._EXCEPTION_TO_EXIT_CODE (DEC-024 — explicit
9-class exclude list for the abstract per-stage bases). New
tests/cli/test_exit_codes.py parametrizes over every typed exception in
the mapping table; for each, patches a stage to raise it, calls
main(["generate", ...]), asserts the exit code matches the table value
and stderr shape per DEC-008 (single line for tiers 1/3, header + bullets
for tier 2). Anchor-contract test pins the tier-2 bullet shape; panic
test pins exit 1 + no traceback. Makes the four-tier taxonomy a contract.
Traces to DEC-008, DEC-019, DEC-024.

bd_1-scaffolding-9vj.8

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 9: US-004 — cli/lint.py config-only validator

signalforge lint validates the 5 existing signalforge.yml config blocks
(safety, llm, prune, grade, diff) without touching warehouse, LLM, or
network. Multi-error reporting per DEC-008: zero errors → exit 0 (silent
stdout); one error → single ERROR line; multiple errors → header +
bullets shape so the operator sees every problem in one run.
--config / --project-dir flags flow through canonicalise_user_path; lint
runs sub-second on a representative fixture. Traces to DEC-006, DEC-008.

bd_1-scaffolding-9vj.4

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 9: US-009 — Subprocess-gated CLI smoke test

One belt-and-braces test that runs signalforge --version via
subprocess.run. Gated behind a cli_subprocess pytest marker so default
runs skip it; maintainers run pytest -m cli_subprocess before declaring
a CLI PR ready (mirrors the bigquery / anthropic integration-test
gates). Catches console-script wiring drift that in-process main(argv)
tests cannot. Traces to DEC-018.

bd_1-scaffolding-9vj.9

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 9: US-010 — Documentation cascade (cli-ops.md + README + cli-layer.md)

New docs/cli-ops.md (full CLI reference: subcommands, flags,
four-tier exit-code taxonomy, stderr shapes, env vars, project-root
discovery, worked example). New .claude/rules/cli-layer.md distilling
the rules established by US-001..US-009 (subpackage layout, exit-code
taxonomy, stderr shapes, no-traceback rule, path canonicalisation,
logger grep gate covers 6 dirs, 7th AST scan, subprocess smoke,
progress UX). README updated: Status block bumped to "Nine issues
shipped"; quick-start promoted to active voice; new CLI section.
v0.2-reservation cascade: grade-layer.md fail_on_below_threshold +
diff-renderer.md render_kind / render_to_text both noted as
graduated by #9.

bd_1-scaffolding-9vj.10

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 9: Quality gate pass 1 — fix --profiles-dir containment-gate bug

QG pass 1 caught: --profiles-dir was routed through canonicalise_user_path
which contains paths inside project_dir, but the dbt convention places
profiles.yml at ~/.dbt (intentionally outside the project tree). Every
realistic --profiles-dir value would have exited 1 with CliPathError.
The existing test masked the bug by placing the profiles dir inside the
project. New test test_generate_profiles_dir_accepts_out_of_tree_path
pins the corrected behaviour.

Fix: bypass canonicalise_user_path for --profiles-dir; apply
expanduser + resolve(strict=False) for symlink-loop safety; the
warehouse loader retains its own existence/shape gate on the resolved
profiles.yml.

bd_1-scaffolding-9vj.11

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 9: Quality gate pass 2 — fix --profiles-dir / --no-color help-string accuracy

QG pass 2 noted that the help strings claimed "for the duration of the
run" but the env mutations actually persist for the process lifetime.
Strikes the misleading phrasing on both flags so the help text matches
actual behaviour. Restoration semantics deferred to v0.2 when an
in-process batch runner makes process-scoped env mutations a real
correctness concern.

bd_1-scaffolding-9vj.11

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 9: Quality gate pass 3 — fix --min-score contract drift (DEC-004)

QG pass 3 found that DEC-004 / docs / help text / test name all claimed
--min-score "drives the diff renderer's flagged tier", but the code
only mutates grade_config.min_mean_score (an aggregate-verdict
threshold). The diff layer's flagged tier flips on per-criterion
GradingResult.passed, set verbatim by the LLM judge — it never reads
min_mean_score. The flag works as implemented; the documented contract
was wrong across 5 surfaces (help text, module docstring, code comment,
ops doc x2, test name + docstring, plan DEC-004).

Aligns the documented semantics with actual behaviour: --min-score is
a reporting-only override of the aggregate-verdict threshold consumed
by GradingReport.passed (and, opt-in via grade.fail_on_below_threshold,
by GradeBelowThresholdError). Per-criterion flagged classification
would be a v0.2 feature. Captures the correction as QG-002 in the
plan's Refinement Log Session 3 alongside QG-001 (--profiles-dir).

bd_1-scaffolding-9vj.11

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 9: Quality gate pass 4 — finish docs/cli-ops.md parity (DEC-023)

QG pass 4 sign-off pass caught that pass 2's "duration of the run"
correction was applied to the help strings in cli/generate.py but
missed three parallel mentions in docs/cli-ops.md. Pass 3 set the
multi-surface parity bar (help / docstring / comment / ops doc /
test name); applying that bar to pass 2's surfaces surfaces three
remaining doc-only inconsistencies. Mirror the help-string wording
verbatim in --profiles-dir / --no-color / Environment-variables
sections of docs/cli-ops.md so the operator sees one consistent
phrasing about scope (process environment) instead of the misleading
"for the duration of the run" claim.

bd_1-scaffolding-9vj.11

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 9: US-012 — Patterns & Memory closeout for the CLI entrypoint ticket

CLAUDE.md repo-status bumped to "v0.1 alpha. Nine issues shipped"
(matching the README's wording from US-010); new #9 bullet matching
the format of the prior 8 bullets covers console-script entry, three
subcommands + flag set, walk-up + absolute-assertion project-root
resolution, four-tier exit-code taxonomy + 7th AST scan, DEC-021
sidecar-then-raise ordering, render_to_text + 8 exception re-exports,
6-dir logger grep gate. Public-API-surface section gains a CLI entry
covering signalforge.cli.main, the CliError hierarchy, the console
script, and signalforge.diff.render_to_text (graduated this ticket).

.claude/rules/cli-layer.md refined with three new sections:
- os.environ mutation pattern (DEC-023): document the deliberate
  v0.1 lack of try/finally restoration; reserve restoration for a
  v0.2 in-process batch runner.
- _EXCEPTION_TO_EXIT_CODE mapping table convention (DEC-024):
  identity-keyed dict, MRO walk for subclasses, abstract-base
  exclude list as the v0.2 seam.
- Multi-surface parity rule (QG pass-3 lesson): codify the 5-surface
  parity check (help / docstring / ops doc / test / DEC) for any
  flag-contract or stderr-shape change.

Memory: added ralph-ide-pyright-stale-diagnostics.md — captures the
cross-conversation-useful pattern that IDE language-server pyright
diagnostics lag disk for a few iterations after Ralph workers create
new modules; orchestrator CLI pyright is the source of truth.

bd_1-scaffolding-9vj.12

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 9: Address PR review feedback (Copilot + CodeRabbit)

12 fixes from the PR #26 review:

REAL DEFECTS:
- visited list in _resolve_project_dir was populated but never used —
  removed (cli/generate.py).
- criteria_count progress count hardcoded len(DEFAULT_RUBRIC), wrong
  for users with a custom GradeConfig.rubric override — now derived
  from grade_config.rubric or DEFAULT_RUBRIC (cli/generate.py).
- Unused _LOGGER definitions in cli/__init__.py and cli/_helpers.py —
  removed (logging import kept in _helpers.py for setup_logging).
- format_elapsed could render "0m 60s" when 59.5s rounds up — added
  carry into minutes (cli/_helpers.py).
- _safe_excepthook discarded the traceback for KeyboardInterrupt /
  SystemExit pass-through, breaking debugger / log scraper visibility
  — now forwards traceback unchanged for those types (cli/_helpers.py).
- cmd_lint hard-coded `return 1` for loader failures, breaking the
  four-tier exit-code contract — now routes through
  map_exception_to_exit_code; multi-error case returns max of the
  per-failure tiers (cli/lint.py + tests updated).
- LLMHelperError was excluded from the AST-scan exclusion list as an
  "intermediate base", but it's raised directly in three sites in
  llm/client.py — removed from the excluded set so the scan enforces
  that direct uses are accounted for (test_audit_completeness.py).
- README quick-start said `pip install signalforge` but signalforge
  isn't on PyPI yet — replaced with the editable-install incantation.
- grade_artifacts docstring missing GradeBelowThresholdError in its
  Raises: section — added with full field documentation and the DEC-021
  ordering note (grade/engine.py).

DOC DRIFT:
- AST-scan docstring claimed "class identity" but the implementation
  compares __name__ strings — clarified the docstring to match.
- cmd_lint docstring claimed exit codes routed through
  map_exception_to_exit_code but the code returned 1 — code corrected
  to actually do that, docstring updated to match.

FALSE POSITIVE (documented inline on the PR):
- CodeRabbit flagged CliError.__str__ as "violating the single-sink
  rule" — but CliError follows the same layer-base pattern every other
  stage's *Error class uses, and format_error_to_stderr explicitly
  relies on it.

bd_1-scaffolding-9vj.11

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 27: super-plan for Codecov coverage reporting (plan)

Phase: detailing → published. Six stories (US-001 pyproject + addopts
baseline; US-002 ci.yml codecov-action SHA-pinned upload; US-003 README
badge on branch=dev; US-004 docs/codecov-ops.md + CONTRIBUTING pointer;
US-005 Quality Gate; US-006 Patterns & Memory). Eleven decisions captured.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 27: bump plan phase to published; link PR #28

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* SignalForge-8qq.3: add Codecov badge to README

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* SignalForge-8qq.4: add docs/codecov-ops.md and CONTRIBUTING.md pointer

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* SignalForge-8qq.1: add pytest-cov dep and coverage flags to pyproject.toml

Add pytest-cov>=5.0 to dev dependencies and append --cov=signalforge
--cov-report=xml --cov-report=term-missing --cov-fail-under=80 to pytest
addopts. Baseline measurement: 95% across two deterministic runs;
floor set to 80 per DEC-001 procedure (floor(min(run1, run2, 80))).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* SignalForge-8qq.2: add SHA-pinned codecov/codecov-action upload step to CI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* SignalForge-8qq.5: Quality gate — fix marker-specific runs needing --no-cov

QG finding: --cov-fail-under=80 in addopts breaks marker-specific runs
(e.g., pytest -m cli_subprocess) because only a fraction of the codebase
is exercised. Fix: document --no-cov for marker-gated runs in
CONTRIBUTING.md and docs/codecov-ops.md. Also backfill the baseline
measurement log in the plan doc (95%/95%, threshold=80).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* SignalForge-8qq.6: extend ci-supply-chain.md and testing-signal.md with coverage patterns

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* 27: address PR review feedback — add --no-cov to all marker-gated docs

Fixes real issues from CodeRabbit review on PR #28:
- Add --no-cov to all marker-specific pytest commands across 6 ops docs
  and cli-layer.md (reviewer correctly noted they'd fail --cov-fail-under)
- Simplify codecov-ops.md TDD override to use --no-cov instead of
  hard-coded addopts copy that would drift
- Fix markdown lint in plan doc (MD040 language tag, MD038 leading space)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 27: super-plan for Codecov coverage reporting (plan)

Phase: detailing → published. Six stories (US-001 pyproject + addopts
baseline; US-002 ci.yml codecov-action SHA-pinned upload; US-003 README
badge on branch=dev; US-004 docs/codecov-ops.md + CONTRIBUTING pointer;
US-005 Quality Gate; US-006 Patterns & Memory). Eleven decisions captured.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 27: bump plan phase to published; link PR #28

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* SignalForge-8qq.3: add Codecov badge to README

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* SignalForge-8qq.4: add docs/codecov-ops.md and CONTRIBUTING.md pointer

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* SignalForge-8qq.1: add pytest-cov dep and coverage flags to pyproject.toml

Add pytest-cov>=5.0 to dev dependencies and append --cov=signalforge
--cov-report=xml --cov-report=term-missing --cov-fail-under=80 to pytest
addopts. Baseline measurement: 95% across two deterministic runs;
floor set to 80 per DEC-001 procedure (floor(min(run1, run2, 80))).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* SignalForge-8qq.2: add SHA-pinned codecov/codecov-action upload step to CI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* SignalForge-8qq.5: Quality gate — fix marker-specific runs needing --no-cov

QG finding: --cov-fail-under=80 in addopts breaks marker-specific runs
(e.g., pytest -m cli_subprocess) because only a fraction of the codebase
is exercised. Fix: document --no-cov for marker-gated runs in
CONTRIBUTING.md and docs/codecov-ops.md. Also backfill the baseline
measurement log in the plan doc (95%/95%, threshold=80).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* SignalForge-8qq.6: extend ci-supply-chain.md and testing-signal.md with coverage patterns

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* 27: address PR review feedback — add --no-cov to all marker-gated docs

Fixes real issues from CodeRabbit review on PR #28:
- Add --no-cov to all marker-specific pytest commands across 6 ops docs
  and cli-layer.md (reviewer correctly noted they'd fail --cov-fail-under)
- Simplify codecov-ops.md TDD override to use --no-cov instead of
  hard-coded addopts copy that would drift
- Fix markdown lint in plan doc (MD040 language tag, MD038 leading space)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* 27: add dev to push trigger so Codecov badge populates

The badge at codecov.io/gh/.../branch/dev needs a coverage upload from a
push event on dev. The workflow only triggered on push to main, so merges
into dev never uploaded coverage — the badge showed "unknown".

CodeRabbit flagged this on PR #28 (ci.yml:44); fixing now.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 22: Q4=C temp-table-materialised sample (plan)

Super-plan for adopting the temp-table-materialised sample strategy
for v0.2 sample-mode prune. 11 decisions captured, 10 stories ready
for devolve, awaiting review.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 22: advance plan phase to published, link PR #30

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 22: amend plan — expose --scope and --sample-strategy CLI flags

Reverses DEC-011's prior "config-only" position based on user feedback
(2026-05-05): operators want to flip between thorough (full-scan) and
cheap (materialised sample) modes per-run without editing
signalforge.yml.

Adds DEC-012 documenting the override-via-model_validate pattern
(mirrors safety-layer.md DEC-018 / cli-layer.md graduated DiffConfig
.render_kind in #9).

Inserts new US-006 story for the CLI flag work; renumbers US-006
through US-010 forward by one to US-007 through US-011. Plan now
ships 9 implementation stories + Quality Gate + Patterns & Memory
(11 total).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 22: amend plan — explicit session cleanup + DEC-014 failure WARNING

User feedback (2026-05-05): cleanup-failure path should still message the
user with the raw session_id, the manual kill command, and the TTL
fallback note. Don't silently swallow.

Changes:
- DEC-002 revised: __exit__ now belt-and-braces closes the BQ session
  (graduates v0.3 deferral forward; cheap to add now).
- DEC-003 revised: narrow exception to session_id redaction — the raw id
  appears in the cleanup-failure WARNING (single user-facing surface).
- DEC-013 (new): explicit cleanup mechanism via CALL BQ.ABORT_SESSION();
  best-effort with three-layer defence (explicit close + TTL fallback +
  swallow-on-failure).
- DEC-014 (new): cleanup-failure WARNING contract — multi-line shape
  with raw session_id, manual `bq query` command, and "auto-expire in
  Ns" reassurance.
- US-003: 12 new tests covering __exit__ cleanup, success-path INFO,
  failure-path WARNING shape, session_id redaction on happy path,
  state reset in finally. Total 22 tests now (was 12).
- US-004: adds expect_abort_session helper to FakeBigQueryClient (4
  new tests). Total 7 tests now (was 3).
- US-008 probe: third test verifies temp table is gone post-__exit__
  (positive proof of DEC-013).
- US-009 docs: warehouse-adapter-ops.md gets a "Session cleanup &
  manual recovery" section with the manual command template + an
  INFORMATION_SCHEMA query for ops.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 22: amend plan — close cleanup-coverage gaps

User audit (2026-05-05) caught three places where the cleanup amendment
didn't fully land:

1. US-005 had no test pinning the orchestrator's use of `with adapter:`.
   Without that, BigQueryAdapter.__exit__ never fires and DEC-013 cleanup
   is unreachable from the orchestrator. Added three new tests:
   test_prune_tests_uses_adapter_as_context_manager,
   test_prune_tests_adapter_exit_fires_after_normal_completion,
   test_prune_tests_adapter_exit_fires_after_materialisation_failure.

2. US-009 surface 4 (.claude/rules/warehouse-adapters.md) only mentioned
   the session-state pattern. Extended to call out a "Best-effort cleanup
   in __exit__ with user-actionable failure WARNING" sub-section
   covering DEC-013/DEC-014 verbatim, so v0.3 stateful-adapter work
   inherits the canonical reference.

3. US-011 Patterns & Memory had no entry for the cleanup pattern.
   Added entry #5: best-effort cleanup with user-actionable WARNING as
   a reusable project pattern, contrasted with safety-layer.md DEC-011's
   fail-closed-on-primary-work pattern (cleanup boundary vs primary
   boundary). Renumbered the bd remember entry to #6.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 22: amend plan — cleanup audit closes 6 gaps (G1-G6)

User audit asked us to double-check how cleanup works and what we tell
users. Six fixes:

G1 — Factual error: BQ assigns the session_id server-side; we capture
it from job.session_info.session_id after .result(). Plan previously
said we mint via uuid4().hex. DEC-002 + US-003 description corrected;
note added on run_id vs session_id (distinct identifiers).

G2 — Missing degraded-run WARNING from orchestrator. When materialise
fails and all tests route to kept-without-evidence, the only signal
was N identical `why` fields buried in the diff. DEC-009 now requires
a single stderr WARNING (lazy-format JSON) at the head of the
conservative-bias routing path. US-005 adds two pinning tests.

G3 — TTL semantics overstated. BigQuery sessions have a server-managed
max lifetime (~24h); ttl_seconds is OUR-side hint to the WARNING text,
not a BQ knob. DEC-013 clarified.

G4 — Pinned that --quiet does NOT suppress the cleanup-failure WARNING
(operator-actionable; deliberate non-suppressible). DEC-014 extended.

G5 — prune_tests docstring requirement: callers MUST use the adapter
inside `with adapter:`. Without it, no explicit BQ.ABORT_SESSION() call
fires; cleanup falls back to BigQuery's server-side timeout. CLI's
cmd_generate already complies; notebook/script callers responsible.
US-005 "Done when" extended.

G6 — docs/cli-ops.md 5-surface parity: added three WARNING surfaces
(cleanup-failure, materialisation-failure, budget-exceeded) to a
"Stderr shapes" section so CI parsers and downstream tooling have one
stable reference. US-009 surface count: 5 -> 6.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 22: advance plan phase to approved

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 22: devolve plan to beads — epic SignalForge-6tv + 11 tasks + 26 deps

Created bd epic SignalForge-6tv (external-ref gh-22) with 11 child
tasks SignalForge-6tv.1 through .11. Wired 26 dependency edges per
the plan's "Depends on" graph. US-001 is the sole ready task on
devolve; all others blocked until their predecessors close.

Plan phase advanced from approved -> devolved. Beads manifest table
appended to plan document.

Note: bd dolt auto-push warnings throughout creation are non-fatal
(dolt remote sync issue, not a local-write failure). bd dolt push
will be run separately to reconcile.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 22: import plan from feature/22-temp-table-sample for impl branch

Path B per super-plan workflow: implementation branches off `dev` while
plan PR #30 stays open on feature/22-temp-table-sample. Cherry-pick the
plan file (squashed; full history remains on the plan PR) so worker
subagents in `bd worktree` worktrees can read DEC-001 through DEC-014
when implementing US-001 through US-011.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* SignalForge-6tv.1: PruneConfig.sample_strategy + MaterialisationFailedError + MaterialisationNotSupportedError

* SignalForge-6tv.2: WarehouseAdapter.materialise_sample ABC method (US-002)

Adds the materialise_sample(table, n, *, partition_filter=None,
ttl_seconds=3600) -> TableRef method to WarehouseAdapter per DEC-004 of
plans/super/22-temp-table-sample.md. Default impl raises
MaterialisationNotSupportedError (DEC-008) — deliberately NOT decorated
@AbstractMethod because the typed raise IS the v0.2 contract for non-BQ
adapters; concrete adapters override (BigQuery in US-003).

Two TDD tests pin the contract:
- default impl raises MaterialisationNotSupportedError carrying the
  DEC-006 remediation text verbatim (locked operator-facing surface)
- inspect.signature pins kw-only separator, defaults, and TableRef
  return annotation; drift on any one fails the test loud

Traces: DEC-004, DEC-006, DEC-008.

Validation passes (ruff + ruff format + pyright + pytest with the 5
documented deselects: 7th AST scan blocked by US-007 + 4 pre-existing
symlink-loop env failures).

* SignalForge-6tv.7: register Materialisation* errors in CLI exit-code table

Lands the two materialisation-seam typed errors (US-001) in the
``signalforge.cli._helpers._EXCEPTION_TO_EXIT_CODE`` mapping at tier 3
(external-dep / fail-closed) per DEC-008 of US-007 in
``plans/super/22-temp-table-sample.md``. ``MaterialisationFailedError``
wraps any SDK / network / quota failure during the per-run materialise
query (BigQuery CTAS / equivalent); ``MaterialisationNotSupportedError``
is the ``WarehouseAdapter`` ABC default-impl raise that fires when a
non-BigQuery v0.2 adapter has not overridden ``materialise_sample``.
Both are tier-3 inheritances from ``WarehouseError`` but get explicit
per-class entries so the 7th AST scan
(``tests/test_audit_completeness.py::test_every_typed_error_is_in_exit_code_mapping_table``)
passes — the scan asserts every concrete ``*Error`` declaration in any
``src/signalforge/*/errors.py`` appears as its own key in the mapping
(per DEC-024 of #9 / ``.claude/rules/cli-layer.md``).

Tests:

* Adds explicit per-class branches for both errors in
  ``_construct_exception`` so the construction shape is documented at
  the test seam (``MaterialisationFailedError`` follows the ``cause=``
  kwarg pattern; ``MaterialisationNotSupportedError`` takes a
  positional adapter name). The parametrized contract picks the new
  entries up automatically because ``_PARAMS`` derives from
  ``_EXCEPTION_TO_EXIT_CODE.items()``.

* ``test_materialisation_failed_error_maps_to_tier_3`` and
  ``test_materialisation_not_supported_error_maps_to_tier_3`` —
  non-parametrized callouts mirroring the precedent set by
  ``test_table_not_found_error_exits_tier_two`` so a future
  tier-change diff is easy to read in code review.

* ``test_audit_completeness_scan_passes_for_new_errors`` — pins the
  mapping membership + tier assignment at the per-class level so a
  regression names the offending class up front instead of only in the
  broader 7th-scan failure list.

Validation: ``ruff check`` clean, ``ruff format --check`` clean,
``pyright`` 0/0/0, ``pytest`` 1387 passed (2 skipped platform-specific,
15 deselected: the 4 known-environmental symlink-loop tests plus the
always-deselected ``bigquery`` / ``anthropic`` / ``cli_subprocess``
markers). Coverage 94.91% (above the 80% floor).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* SignalForge-6tv.3: BigQueryAdapter.materialise_sample (US-003 of #22)

Implements DEC-001/002/003/013/014 of plans/super/22-temp-table-sample.md.

Production:
- BigQueryAdapter.materialise_sample(table, n, *, partition_filter=None,
  ttl_seconds=3600) -> TableRef. Computes deterministic run_id =
  blake2b-8(table.qualified_name + signalforge_version + n +
  canonical_json(partition_filter)) for snapshot determinism (DEC-001;
  16 hex chars so the temp-table name passes validate_identifier).
- Issues CREATE TEMP TABLE _sf_sample_<run_id> AS SELECT ... with
  QueryJobConfig(create_session=True, use_query_cache=False, ...
  stage="warehouse_sample_materialise"). Captures the BQ-assigned
  session_id from job.session_info.session_id (NOT minted by us).
- Stores _active_session_id + _session_started_at + _session_ttl_seconds;
  run_test_sql now threads connection_properties=[ConnectionProperty(
  key="session_id", value=...)] when active.
- One INFO log on success: {"table","sample_rows","session_id_hash":
  blake2b-4(session_id),"run_id","duration_ms"} — DEC-003 redaction
  (raw session_id never leaks on the happy path).
- __exit__ extends DEC-013/014 cleanup: CALL BQ.ABORT_SESSION() in the
  active session; success → INFO {"session_id_hash","ttl_remaining_
  seconds"}; failure → multi-line WARNING with raw session_id + manual
  bq command + "auto-expire in <N>s" line (DEC-014, the deliberate
  exception to DEC-003 — the bq command is unconstructable without
  the raw id). Always resets state in finally.
- SDK noise contained in _client.py: _make_query_job_config now accepts
  create_session= and session_id= kwargs.

Tests (22 new):
- tests/warehouse/test_materialise_sample.py — covers TableRef shape,
  identifier validation, run_id determinism, version-bump invalidation,
  byte-equal CTAS SQL fixture, stage label, partition filter placement,
  create_session config, MaterialisationFailedError wrapping,
  run_test_sql session routing, INFO redaction, use_query_cache invariant,
  __exit__ cleanup happy/failure paths, raw-session-id-in-WARNING,
  manual-bq-command line, TTL line, exception-class-name, state reset,
  no-WARNING-on-success.
- tests/fixtures/warehouse/sample_materialise_v1.sql — pinned CTAS
  template with {run_id} placeholder (substituted at test-time so
  signalforge.__version__ bumps don't require fixture refresh).

Validation: ruff check + ruff format --check + pyright + pytest pass
(1412 passed, 2 skipped, 15 deselected per the 4 environmental symlink
deselects). 7th AST scan still green; logger grep gate still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* SignalForge-6tv.4: FakeBigQueryClient.expect_materialise_sample + expect_abort_session (US-004 of #22)

Two purpose-built helpers on FakeBigQueryClient mirroring the production
surface for BigQueryAdapter.materialise_sample (CTAS into _SESSION) and
__exit__'s CALL BQ.ABORT_SESSION cleanup path. Each helper consumes one
matching call; non-matching calls raise the standard "unexpected ..."
AssertionError. Both queues short-circuit only when populated so US-003's
existing raw expect_query matchers continue to work unchanged.

expect_materialise_sample(source_ref, sample_size, partition_filter=None,
*, returns: TableRef | Exception) — matches a CREATE TEMP TABLE _sf_sample_
SQL referencing source_ref's qualified name + LIMIT <n> + (when registered)
the rendered partition_filter fragment. Success returns a job with a
populated session_info.session_id (deterministic, derived from the returned
TableRef name) so production captures it via job.session_info after
.result(). Exception returns propagate, driving the
MaterialisationFailedError wrapping path.

expect_abort_session(session_id, *, returns=None | Exception) — matches a
CALL BQ.ABORT_SESSION query whose job_config carries the registered
session_id via connection_properties. returns=None simulates success;
returns=Exception drives the DEC-014 swallow-and-warn WARNING path on
__exit__.

Eight meta-tests pin the contract: consume-one-call + assertion-error on
second call (both helpers); returns=Exception propagation (both helpers);
assert_all_expectations_met fails on unconsumed materialise registration;
session_id mismatch on abort raises loudly; happy-path returns=None on
abort iterates an empty rowset; partition_filter contract — registering
a filter requires the matched SQL to carry the rendered fragment.

Traces: R-TEST-3, DEC-013 of plans/super/22-temp-table-sample.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* SignalForge-6tv.5: prune orchestrator dispatch on sample_strategy + conservative routing (US-005 of #22)

* SignalForge-6tv.6: --scope and --sample-strategy flags on signalforge generate (US-006 of #22)

Both flags are optional and independent (set one, the other, both, or
neither). Override application uses PruneConfig.model_validate(...) so
every Pydantic validator re-runs (DEC-012); never model_copy(update=...)
because that path silently skips @model_validator(mode='after'). Mirrors
SafetyPolicy.with_mode (DEC-018 of safety-layer.md) and DiffConfig.render_kind
graduation in #9 (DEC-020 of cli-entrypoint).

Multi-surface parity (cli-layer.md): help text, add_parser docstring,
cmd_generate docstring, module docstring, test names, and the DEC trace
all aligned in one commit. Argparse choices rejection produces tier-2
exit (no traceback per DEC-016).

Eight tests pin DEC-011 + DEC-012:
- test_generate_scope_flag_overrides_config_value
- test_generate_sample_strategy_flag_overrides_config_value
- test_generate_both_flags_independent
- test_generate_no_flag_uses_config_value
- test_generate_invalid_scope_returns_exit_2
- test_generate_invalid_sample_strategy_returns_exit_2
- test_generate_help_text_lists_new_flags
- test_generate_override_re_runs_pydantic_validators

Traces: DEC-011, DEC-012 of plans/super/22-temp-table-sample.md.

Files: src/signalforge/cli/generate.py, tests/cli/test_generate.py.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* SignalForge-6tv.8: probe re-run scaffolding + cleanup verification (US-008 of #22)

Restructure tests/warehouse/test_sample_cost_probe.py into three
@pytest.mark.bigquery tests (DEC-007 + DEC-013 of plans/super/22-temp-table-sample.md):

* test_sample_rows_cost_baseline_oneshot — preserves AR-B1's 9.92 GB
  measurement as a regression guard. Asserts bytes_billed >= _BYTES_WARN_AT
  (cost cliff genuinely exists on the legacy path) AND bytes_billed <
  _BYTES_CEILING (sanity ceiling).
* test_sample_rows_cost_materialised — issue's primary acceptance
  criterion. Drives materialise_sample + a per-test query through the
  adapter; asserts per-test bytes_billed < 100 MB.
* test_materialised_session_cleaned_up_after_exit — positive proof of
  DEC-013. After __exit__ fires, querying the _SESSION._sf_sample_<run_id>
  temp table by name fails with a GoogleAPIError (NotFound / "session not
  found" / "table not found"). A buggy implementation that no-op'd
  CALL BQ.ABORT_SESSION() would make the query SUCCEED and pytest.raises
  would fail with "DID NOT RAISE", so the test cannot pass on a broken
  cleanup path.

All three are SF_RUN_BQ-gated (truthy values: 1/true/yes/on); default
pytest skips them via the addopts marker exclusion. Maintainer runs
`SF_RUN_BQ=1 pytest -m bigquery tests/warehouse/test_sample_cost_probe.py
--no-cov` before declaring the PR ready (--no-cov per testing-signal.md
Coverage section, since --cov-fail-under in addopts would otherwise fail
the marker-only run).

Adds two default-collected scaffolding tests:

* test_probe_module_imports_and_exposes_three_test_functions — pins the
  module shape and asserts each live-BQ test carries the bigquery marker
  via pytestmark introspection.
* test_probe_constants_unchanged — pins _BYTES_CEILING (5 GB),
  _BYTES_WARN_AT (500 MB), and _BYTES_PER_TEST_TARGET (100 MB) so a
  silent threshold edit can't soften the regression contract.

Validation: ruff check . && ruff format --check . && pyright && pytest
all pass; 1437 tests pass, 17 deselected (markers), 2 skipped (platform).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* SignalForge-6tv.9: documentation surfaces (6-surface parity)

Update six documentation surfaces in lockstep for the v0.2 temp-table
materialised-sample work tracked in plans/super/22-temp-table-sample.md
(US-009). All updates are docs-only; no production code changes.

- docs/prune-ops.md: document `sample_strategy` config field; add
  post-Q4=C cost subsection with TBD placeholders for US-010 quality
  gate; add audit reading guide distinguishing materialised vs.
  oneshot `compiled_sql` shapes.
- docs/warehouse-adapter-ops.md: add `warehouse_sample_materialise`
  stage label; document `materialise_sample` ABC method, BigQuery
  session-state pattern, v0.2 → v0.3 migration story; add Session
  cleanup & manual recovery section covering the three-layer cleanup
  model (DEC-013) and the verbatim manual `bq query` command (DEC-014);
  add INFORMATION_SCHEMA.JOBS_BY_PROJECT query template for spotting
  orphan sessions; extend error-reference table.
- .claude/rules/prune-engine.md: add v0.2 reservations / additions
  section covering `sample_strategy`, the two new typed errors,
  `_SESSION._sf_sample_<run_id>` audit signal, conservative-bias
  routing + degraded-run WARNING (DEC-009), total-budget-includes-
  materialisation invariant (DEC-010), and the context-manager
  requirement (DEC-013).
- .claude/rules/warehouse-adapters.md: document `materialise_sample`
  ABC + BigQuery session-state pattern; add Best-effort cleanup in
  `__exit__` sub-section with WARNING shape verbatim; cross-reference
  `safety-layer.md` DEC-011 for the primary-work fail-closed contrast.
- CLAUDE.md: amend "Public API surface (v0.1)" to "(v0.1 + v0.2
  additions)"; list four new exports under v0.2.
- docs/cli-ops.md: add Stderr shapes (WARNING) section with three
  WARNING entries (cleanup-failure, materialisation-failure /
  degraded-run, budget-exceeded) for operator parity.

Validation passes: ruff check + ruff format --check + pyright + the
1445-test default suite (with the four pre-existing symlink-loop
deselects from the worktree pattern). No production code touched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* SignalForge-6tv.10: Quality gate — fix bugs from code review

Four concerns surfaced by the multi-lens reviewer pass; all four
addressed inline (no blockers).

1. docs/cli-ops.md "Runtime knob flags" missing --scope and
   --sample-strategy entries (5-surface parity violation per
   cli-layer.md). Added two bullets mirroring the help text in
   cli/generate.py.

2. .claude/rules/prune-engine.md run_id digest claim was wrong:
   said "blake2b-12(model.unique_id + ...)" but production uses
   "blake2b(table.qualified_name + ..., digest_size=8)" (16 hex
   chars). Updated rule to match the implementation; preserves
   the "16-hex" claim consistently.

3. tests/llm/test_logger_grep_gate.py extended to cover
   src/signalforge/warehouse/ (was 6 dirs; now 7). US-003
   introduced new logger calls in the warehouse layer; existing
   calls comply with lazy-format pattern, but adding the gate
   prevents future regressions.

4. docs/warehouse-adapter-ops.md "Session cleanup & manual
   recovery" section gets a new "Edge case: SDK returns
   session_info=None" sub-section documenting the v0.3-tracked
   path where BQ creates a session server-side but the SDK
   doesn't surface the id (would orphan until BQ's own timeout).

Out-of-session deferred to maintainer:
- 3 more code-reviewer passes (skill says 4; did 1 multi-lens).
- CodeRabbit review (PR-time, automatic when PR opens).
- Maintainer probe-run: SF_RUN_BQ=1 pytest -m bigquery
  tests/warehouse/test_sample_cost_probe.py --no-cov against a
  real BQ project.
- Cost-figure substitution in docs/prune-ops.md (replace four
  <TBD: ...> placeholders with the maintainer's measured figures).

Validation: 1445 passed, 2 skipped, 17 deselected (5 known:
4 environmental symlink-loop + the AST scan was the v0.2 incomplete
state which closed when US-007 landed). ruff + format + pyright
all clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* SignalForge-6tv.11: distil patterns into rule files + bd remember (US-011 of #22)

Rule-file additions (generic, durable lessons — not duplicating US-009's
issue-specific notes):

- warehouse-adapters.md: "Session/connection state on the adapter" generic
  pattern (DEC-002 of #22 generalised) for v0.3 adapters needing per-call
  state primitives; "Cleanup-boundary fail-soft pattern" generic pattern
  (DEC-013/DEC-014 of #22 generalised) with the three-thing WARNING contract
  (identifier + copy-pasteable command + durable fallback) and the
  --quiet-doesn't-suppress invariant.
- safety-layer.md: paragraph near DEC-011 distinguishing primary-work
  fail-closed (this rule) from cleanup-boundary fail-soft (warehouse-adapters
  DEC-013/DEC-014) so a v0.3 maintainer doesn't conflate them.
- prune-engine.md: refines C8 (5-value DropReason taxonomy) with the
  conservative-bias-routing-across-WarehouseError-subclasses paragraph
  (generalises DEC-009 of #22 to any v0.3 warehouse exception); adds the
  5-surface parity rule (rule file / ops doc / CLAUDE.md / test / DEC) at
  the top of the v0.2 reservations section, mirroring cli-layer.md's flag
  parity rule.
- testing-signal.md: one-paragraph note on seeded determinism over
  snapshot normalisation (mirrors DEC-001 of #22 + LLM drafter prompt_version).

bd remember invocations (2): BQ session creation latency observation
(~700ms-2.5s on US-003 worktree builds; informs DEC-010 budget calibration)
and BQ session abort failure rate expectation (rare; revisit DEC-013 if
>1% of prune runs hit the swallow-and-warn path).

Validation: ruff/pyright/pytest all green. 1445 passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 22: fix _SESSION qualified-name + record post-Q4=C cost figures

Maintainer probe-run (2026-05-08, billed to duenow-nest) surfaced one
real production bug + three probe-design fixes. All four landed
together so the next probe-run from a clean clone Just Works.

## Production fix

`BigQueryAdapter.materialise_sample` returned
`TableRef(project=client.project, dataset="_SESSION", name=...)`. The
three-part `<project>._SESSION._sf_sample_<run_id>` qualified_name is
rejected by BigQuery even inside the owning session:

    400 Use of _SESSION is not allowed here; reason: invalid

Within a session, the temp table must be referenced as the two-part
`_SESSION._sf_sample_<run_id>`. Fix: return `project=None` so
`TableRef.qualified_name` renders the two-part form.

This bug shipped through US-003's unit tests because `FakeBigQueryClient`
matches on regex shape, not SQL semantics — only a real-BQ run could
catch it. Updated the production code, the matching unit test pin, the
prune fixture's `compiled_sql` references, and the test that uses a
literal `fake_project._SESSION._sf_sample_x` SQL string.

## Probe fixes

1. `_BYTES_CEILING` raised from 5 GB → 15 GB. AR-B1 measured 9.92 GB;
   the original 5 GB ceiling was internally inconsistent with the
   recorded figure (the regression-guard would always fail).

2. `BigQueryAdapter()` instantiations bumped from the 100 MB default
   cap to 20 GB (`_BOOTSTRAP_BYTES_BILLED_CAP`). The 100 MB DEC-005 cap
   blocks even the materialised CTAS bootstrap (it scans the source
   table once, ~10 GB). The probe needs a higher cap to MEASURE
   figures; the production safety net stays at 100 MB for end users.
   v0.3 may want a per-stage cap override; tracked in PR #31's
   maintainer-follow-ups section.

3. Per-test query in `test_sample_rows_cost_materialised` was
   `SELECT COUNT(*) FROM (SELECT * FROM <temp> WHERE FALSE) AS t`. BQ's
   planner short-circuited on `WHERE FALSE` and billed 0 bytes — the
   probe's `int(getattr(...) or -1)` bug then converted 0 to -1
   ("unavailable") and xfailed. Replaced with a representative
   `not_null` test against the `invoice_and_item_number` column +
   fixed the `or -1` bug to treat None as the unavailable sentinel
   (0 is a valid measurement).

## Cost figures recorded in docs/prune-ops.md

- Oneshot baseline: 9,984,540,672 bytes (~9.98 GB) per query — matches
  AR-B1 within 1%.
- Materialisation CTAS: ~9.98 GB once per prune run.
- Per-test on materialised: 10,485,760 bytes (10 MB) — well under the
  100 MB acceptance gate.
- 30-test run: ~10.3 GB end-to-end (vs. ~299 GB on the legacy oneshot
  path) → ~29× cheaper.

Issue #22 acceptance #1 ("per-test bytes_billed < 100 MB without
raising cost_limit_bytes") is satisfied — the per-test queries against
the temp table run cleanly under the 100 MB default cap; only the
maintainer probe needs the bumped cap for the bootstrap measurement.

Validation: ruff + format + pyright + 1445-test pytest all green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 22: close Codecov patch-coverage gap on materialise_sample + prune routing

Adds 8 targeted tests covering the 11 patch-attributable lines Codecov
flagged on PR #31:

* materialise_sample fail-loud sizing parity with sample_rows:
  - n <= 0 ValueError pre-call (DEC-008 input validation)
  - UnknownTableSizeError when num_rows is None and no partition filter
  - default-bucket fallback when num_rows is None but partition filter pins cost
  - SamplingRequiresPartitionFilterError when num_rows >= 100M and no partition
* materialise_sample fail-loud post-CTAS guard:
  - MaterialisationFailedError when SDK returns no session_id
* prune engine defence-in-depth + setup-error propagation:
  - invalid-identifier compile result routes to kept-without-evidence
  - WarehouseError during sample-mode size fetch propagates as-is
* TTL helper defensive guard:
  - _compute_ttl_remaining_seconds returns 1 when session state unset

Total signalforge coverage: 95.00% -> 95.30%. Remaining uncovered lines
in bigquery.py / prune/engine.py are all pre-existing code from issue #3,
outside this PR's diff hunks; no patch lines are uncovered after this
commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 22: address PR review — fix drift, route materialise_sample errors through map_bq_exception

Code fixes:
* materialise_sample wraps SDK exceptions through map_bq_exception
  before raising MaterialisationFailedError so cause carries the stable
  warehouse-error surface (e.g. WarehouseAuthError) instead of raw SDK
  text. The original SDK exception is preserved on __cause__ via the
  raise-from chain (CodeRabbit).
* prune_tests docstring corrected: the engine OWNS the with-adapter
  block; library callers MUST pass a non-entered adapter and MUST NOT
  wrap the call themselves (Copilot).
* materialise_sample Returns: docstring corrected — the returned
  TableRef is project=None / dataset=_SESSION (two-part qualified_name);
  the three-part <project>._SESSION.<name> form is the rejected one
  (Copilot).
* tests/warehouse/_fake.py _qualified_name_substring docstring
  clarified — the existing period-prefixed substring IS correct (it
  matches the suffix of the production three-part backtick-quoted
  form); Copilot's two-part suggestion would actually break the match.

Test fixes:
* test_materialised_session_cleaned_up_after_exit: the cleanup
  verification query now reuses the captured session_id via
  connection_properties so the test would fail if cleanup leaked
  (CodeRabbit). Without this, the post-exit query failed for an
  unrelated reason (no session on the wire) and the test passed even
  on a broken cleanup.
* test_materialise_sample_wraps_warehouse_sdk_errors: assertion
  follows the new map_bq_exception contract (cause is the typed
  warehouse error; __cause__ preserves the raw SDK exception).
* test_sample_cost_probe.py:495 comment corrected to two-part
  _SESSION._sf_sample_<run_id> form (Copilot).
* tests/warehouse/test_materialise_sample.py module docstring
  refreshed — US-004 has shipped expect_materialise_sample /
  expect_abort_session helpers; the module continues to use raw
  expect_query intentionally for diagnostic SQL-byte assertions
  (Copilot).

Doc/rule drift cleanup (CTAS shape, _SESSION reference, run_id recipe):
* run_id is blake2b(table.qualified_name + version + str(n) +
  canonical_partition_filter, digest_size=8) — 16 hex chars; corrects
  the stale "blake2b-12 / model.unique_id" recipe.
* CTAS uses bare _sf_sample_<run_id> name; subsequent reads use
  two-part _SESSION._sf_sample_<run_id>. Corrects the stale
  _SESSION._sf_sample_<run_id> CTAS form.
* warehouse_session_abort added to docs/warehouse-adapter-ops.md
  stage-label list.
* AST-scan + logger-grep-gate counts updated (7 scans, 6 dirs as
  of #9) in .claude/rules/prune-engine.md (CodeRabbit).
* DEC-014 cleanup-warning fenced block in plan now carries text
  language tag (CodeRabbit MD040 lint).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 10: e2e smoke test against bigquery-public-data (plan)

Super plan for issue #10 — v0.1 capstone validation against real
Anthropic + real BigQuery on bigquery-public-data.austin_bikeshare.
8 stories (5 implementation + docs + Quality Gate + Patterns &
Memory) and 23 DECs covering marker strategy, three-env-var skip
gate, fixture isolation via tmp_path, engineered-literal column
for drop-reason determinism, tight grade thresholds, materialised
sample strategy, and Anthropic model-drift acceptance.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 10: update plan phase to published with PR #32

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 10: devolve plan to beads (epic + 8 tasks)

Phase: devolved. Epic bd_1-scaffolding-91c with 8 child tasks
wired up per the plan's dependency graph (US-001 currently ready).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-7wu: Add tests/fixtures/dbt_project_austin/ skeleton (US-001)

Lands the bare-bones fixture directory for the issue #10 e2e BigQuery smoke
test. No manifest yet — US-002 owns that. Five files:

- dbt_project.yml: profile `austin`, dbt-bigquery 1.8.x compatible (no
  v1.9-specific keys); mirrors the dbt_project_small/ shape.
- profiles.yml: `austin` profile, `dev` target, `method: oauth` (ADC); source
  project `bigquery-public-data`, dataset `austin_bikeshare`, location `US`.
  Billing project comes from `GOOGLE_CLOUD_PROJECT` env var via the SDK's
  standard ADC behaviour (mirrors tests/fixtures/profiles/bigquery_oauth.yml).
- models/staging/sources.yml: declares the source
  `bigquery-public-data.austin_bikeshare.bikeshare_trips` with the canonical
  10-column surface (trip_id, subscriber_type, bikeid, bike_type, start_time,
  start/end station id+name, duration_minutes).
- models/staging/stg_bikeshare_trips.sql: SELECT against the source with
  engineered always-pass columns (`'austin' AS region` and
  `COALESCE(start_time, TIMESTAMP '1970-01-01 00:00:00 UTC') AS start_time_safe`)
  to give the LLM at least one mathematically-guaranteed always-pass test to
  drop, exercising the prune layer's `always-passes` decision path on real
  warehouse data. `LIMIT 100000` caps the materialised-sample input size.
- .gitignore: excludes `.signalforge/`, `dbt_packages/`,
  `target/run_results.json`, `target/partial_parse.msgpack`, `logs/`. Only
  `target/manifest.json` (US-002) is committed.

Traces to plans/super/10-e2e-bigquery-smoke.md DEC-003 (fixture path),
DEC-010 (always-pass determinism), DEC-012 (target dataset), DEC-013 (single
staging model surface), DEC-021 (.gitignore contents).

Validation: ruff check, ruff format --check, pyright, pytest — all green
(1459 passed, coverage 95.32%).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-8be: Add Austin manifest fixture + regen script + loads test (US-002)

Lands the three artefacts US-002 requires for the issue #10 e2e BigQuery
smoke test:

  * `tests/fixtures/dbt_project_austin/regenerate.sh` (NEW, executable) —
    sibling of `tests/fixtures/regenerate.sh` (DEC-022); pins
    `dbt-bigquery==1.8.*` floating with `dbt-core==1.8.*` (DEC-019); strips
    the same five non-deterministic fields the existing script strips
    (`metadata.{generated_at, invocation_id, user_id,
    send_anonymous_usage_stats, adapter_type}` + `metadata.env = {}`).
    Maintainer-only; not invoked by CI.
  * `tests/fixtures/dbt_project_austin/target/manifest.json` (NEW) —
    minimal dbt v12 manifest containing one model
    (`model.signalforge_test_austin.stg_bikeshare_trips`) and one source
    (`source.signalforge_test_austin.austin_bikeshare.bikeshare_trips`)
    with the staging SQL embedded verbatim from the on-disk model file.
    Hand-crafted on this pass because the worker has no live BQ access;
    the regen script regenerates it cleanly when a maintainer runs it.
  * `tests/manifest/test_austin_fixture_loads.py` (NEW) — in-process
    pytest with no env vars / markers; asserts
    `signalforge.manifest.load(fixture_dir)` succeeds and the staging
    model resolves by `unique_id` and via `iter_models()`.

The Austin `.gitignore` gains a `!target/manifest.json` negation so the
committed manifest survives the repo-level `dbt_project_*/target/manifest.json`
ignore (which is intended for the small/medium DuckDB fixtures that commit
only `manifest_v<N>.json`).

Traces to plans/super/10-e2e-bigquery-smoke.md DEC-004 (committed manifest),
DEC-019 (dbt-bigquery floating pin), DEC-022 (sibling regen script), and the
US-002 acceptance criteria.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-ocg: Add Austin fixture signalforge.yml + lint test (US-003)

Lands the locked config the issue #10 e2e smoke test depends on under
tests/fixtures/dbt_project_austin/signalforge.yml. Values are pinned per
plans/super/10-e2e-bigquery-smoke.md DEC-005..DEC-018:

* llm.model=claude-sonnet-4-6 matches DraftConfig default (DEC-018).
* safety.mode=aggregate-only exercises aggregate redaction (DEC-007).
* prune.sample_strategy=materialised exercises BQ session-state (DEC-005).
* grade.min_pass_rate / min_mean_score=0.95 pin the e2e threshold (DEC-006).
* grade.fail_on_below_threshold=false keeps the run flowing into diff (DEC-006).
* grade.total_budget_seconds=600 lifts headroom for p99 × ~12 calls (DEC-011).

tests/cli/test_austin_fixture_config.py covers the fixture two ways:

1. signalforge lint --project-dir <fixture> returns exit 0 (in-process
   main([...]); asserts no traceback per cli-layer.md DEC-016).
2. Each per-stage loader (load_safety_config / load_draft_config /
   load_prune_config / load_grade_config / load_diff_config) parses the
   fixture and returns the locked values — defends against drift in any
   one stage's YAML field names or defaults.

No env vars, no markers; runs on the default pytest set.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-r92: Add @pytest.mark.e2e + tests/cli/_e2e_helpers.py (US-004)

Issue #10 / US-004 scaffolds the gated e2e smoke surface (DEC-008,
DEC-023 of plans/super/10-e2e-bq-smoke.md):

- Register @pytest.mark.e2e in pyproject.toml; extend the default
  addopts exclusion list to keep e2e gated by SF_RUN_BQ=1 + the
  existing bigquery / anthropic / cli_subprocess gates.
- tests/cli/_e2e_helpers.py: three typed helpers (copy_fixture_to_tmp,
  read_prune_decisions, read_diff_report) so US-005 can isolate audit
  JSONLs to tmp_path (mirrors tests/cli/_factories.py:make_fake_dbt_project)
  and assert on typed PruneDecision / DiffReport objects after a CLI run.
- tests/fixtures/e2e_helpers/happy/.signalforge/ — synthetic
  prune.jsonl (3 PruneEvent records, including one always-passes drop)
  and diff.json (DiffReport with kept_count=1, dropped_count=1) sized
  to exercise the helpers without a real warehouse.
- tests/cli/test_e2e_helpers.py: 3 unit tests pinning the helper
  contract; NOT marked @pytest.mark.e2e so they run in default CI.
- .gitignore: allow committed test fixtures under tests/fixtures/**
  to embed a .signalforge/ subdir (the top-level .signalforge/ rule
  remains in effect for runtime state).

Verified: ruff check + ruff format --check + pyright clean; full
pytest passes (1464 passed, coverage 95.32%).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-ivh: Add e2e smoke test against bigquery-public-data (US-005)

Lands `tests/cli/test_e2e_bigquery_smoke.py`, the gated end-to-end
smoke that runs `signalforge generate stg_bikeshare_trips` against
real Anthropic + real BigQuery (`bigquery-public-data.austin_bikeshare`)
and pins the seven invariants from DEC-009 of
`plans/super/10-e2e-bigquery-smoke.md`:

1. `cli.main(...)` returns 0.
2. `<project_dir>/.signalforge/diff.json` exists.
3. `DiffReport.kept_count >= 1` (SQ-01).
4. A `PruneDecision` with `decision='dropped'` and
   `reason='always-passes'` exists (SQ-02 — the v0.1 differentiator;
   engineered via literal / COALESCE columns in the fixture's
   `stg_bikeshare_trips.sql` per DEC-010).
5. `DiffReport.flagged_count >= 1` (forced by tight grade thresholds
   in the fixture's `signalforge.yml`).
6. `GradingReport.aggregate_complete is True` (no degraded grade
   calls).
7. `"Traceback" not in stderr` (DEC-016 of `cli-layer.md`).

Gated by THREE env vars per DEC-002: `SF_RUN_BQ=1`,
`ANTHROPIC_API_KEY`, `GOOGLE_CLOUD_PROJECT`. The test is
`@pytest.mark.e2e` which is excluded from default pytest runs by
`addopts = "... -m 'not e2e' ..."` in `pyproject.toml` (DEC-020); the
maintainer runs `SF_RUN_BQ=1 pytest -m e2e --no-cov` once with creds
before declaring an e2e PR ready (mirrors the `bigquery` /
`anthropic` / `cli_subprocess` precedents).

Validation:
- Default `pytest` deselects the test (1 deselected).
- `pytest -m e2e --no-cov` without env vars skips with a clear
  reason (`SF_RUN_BQ=1 required ...`).
- Full validation (`ruff check`, `ruff format --check`, `pyright`,
  `pytest`) all green: 1466 passed, 14 deselected, 95% coverage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-6ha: Add README "Trying it out" + docs/cli-ops.md cross-ref + CLAUDE.md #10 bullet (US-006)

User-facing docs land in lockstep across three surfaces (per
cli-layer.md multi-surface parity): README "Trying it out" H2 walks a
maintainer through gcloud auth + GOOGLE_CLOUD_PROJECT + ANTHROPIC_API_KEY
+ a copy-pasteable `signalforge generate stg_bikeshare_trips` against
the austin_bikeshare fixture; docs/cli-ops.md gains a one-paragraph
cross-ref under "Worked example" pointing back at the README plus the
fixture path and the gated maintainer-only test; CLAUDE.md gains a #10
"e2e smoke test" bullet mirroring the #9 prose density (artefact name,
gate, env-var triple, cross-refs).

Per DEC-017 of plans/super/10-e2e-bigquery-smoke.md, no separate
docs/e2e-smoke-ops.md is created — the e2e fixture is a test artefact,
not a pipeline layer; the operational surface is the README + the
regen-script header comment + the test docstring.

Traces to: DEC-014 (README placement), DEC-015 (cli-ops.md cross-ref),
DEC-016 (CLAUDE.md #10 bullet), DEC-017 (no separate ops doc).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-ch4: Quality gate — fix bugs from code review (US-007)

F1 (blocker, surfaced by Pass 4 of 4): the bare model name
`stg_bikeshare_trips` does NOT resolve via `Manifest.get_model` —
the loader routes bare names to the file-path branch which then
fails with `ModelNotFoundError: No model found at file path
'stg_bikeshare_trips'`. The README "Trying it out" shell block,
the e2e test argv, and the plan's DEC-014 / US-005 examples all
referenced the bare name; the maintainer's first-try gated run
would have failed.

Fix: use the file-path form `models/staging/stg_bikeshare_trips.sql`
across all surfaces (README copy-pasteable shell block + e2e test
argv + plan DEC-014 + US-005 description + TDD note for the
manifest loads test). Both the file-path form and the unique_id
form (`model.signalforge_test_austin.stg_bikeshare_trips`) resolve
correctly via `Manifest.get_model`; the file-path form is more
user-friendly for the README quickstart (mirrors the existing
`signalforge generate models/marts/customer_lifetime_value.sql`
shape on line 50).

Pass 1 (correctness), Pass 2 (security), Pass 3 (test reliability)
clean. Pass 5 (re-review) verifies the fix.

Validation: ruff + ruff format + pyright + pytest all green;
1466 passed, 14 deselected, 95.32% coverage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-8jp: Patterns & Memory — extend testing-signal.md + fixtures/README.md (US-008)

Captures four durable patterns surfaced by issue #10 (e2e smoke
test) into .claude/rules/testing-signal.md as a new "End-to-end
gated tests" section:

1. Belt-and-suspenders gating — @pytest.mark.<gate> + runtime
   pytest.skipif(...) for env-var checks. Mirrors the
   tests/warehouse/test_bigquery_integration.py precedent.
2. Three-env-var gate for full-stack e2e — SF_RUN_BQ=1 +
   ANTHROPIC_API_KEY + GOOGLE_CLOUD_PROJECT, each with a distinct
   skip reason naming the missing var.
3. tmp_path fixture isolation — when the test produces audit
   JSONLs / sidecars under <project_dir>/.signalforge/, copy the
   committed fixture to tmp_path / "project" via shutil.copytree
   first so audits land in temp.
4. Engineered determinism for LLM-driven assertions — when an
   assertion depends on what the LLM drafts (non-deterministic),
   engineer the fixture INPUT so the assertion is mathematically
   guaranteed (issue #10's literal/COALESCE'd staging columns
   make `not_null` always-pass deterministically).
5. Hand-crafted manifest seed when workers can't run live tooling
   — pair a maintainer-only regen script with a hand-crafted
   minimal seed validated by an in-process loads test.
6. Multi-surface drift on user-facing model args — Manifest.get_model
   accepts unique_id and file path but NOT bare names; bare names
   route to the file-path branch and fail. The Quality Gate F1
   finding documented as a rule so future tickets don't re-discover.

Also extends:
- The "Known gap: excluded markers" section to add `e2e` to the
  documented exclusion list + the maintainer command.
- The "Reference" section to cite plan #10 + the new artefacts.
- tests/fixtures/README.md with a "BigQuery fixture: dbt_project_austin/"
  subsection explaining the three differences from DuckDB fixtures
  (live warehouse for regen, hand-crafted seed manifest, fixture-
  shipped signalforge.yml).

No memory file added — the patterns belong in the rule (not
memory) per the "What NOT to save" guidance (avoid duplicating
content already in .claude/rules/).

Validation: ruff + ruff format + pyright + pytest all green;
1466 passed, 14 deselected, 95.32% coverage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 10: green the e2e smoke against real Anthropic + BigQuery

Surfaces fixed by the live e2e run against bigquery-public-data.austin_bikeshare:

1. **Drafter prompt under-specified the JSON shape** (the headline find).
   `_SYSTEM_PROMPT` said "respond with a single JSON object" but never
   showed the schema. Sonnet 4.6 inferred `model`/`test`/no-`schema_version`
   etc. from the manifest summary's section headers. Added an explicit
   `### OUTPUT FORMAT` JSON example + a "Field-name discipline" block
   listing the load-bearing names. `_PROMPT_VERSION` rotates from
   `1c55806467984090` to `c7d15d59f78bab2d` (refresh in
   `tests/llm/test_prompt_cache_stability.py`). Cached-block snapshot is
   unchanged because the manifest-summary template wasn't touched.

2. **`LLMCacheTooSmallError` was hard-fail when Anthropic just no-ops the
   cache marker.** Below the per-model minimum, Anthropic silently doesn't
   cache; raising broke any caller whose cached block is naturally small
   (the grade layer's compact rubric was 291 tokens). Softened to drop
   the `cache_control` marker, log INFO once, and proceed. Test flipped
   from `..._raises_cache_too_small` to `..._drops_cache_marker`.
   `LLMCacheTooSmallError` class kept on the public surface for now;
   never raised from production code.

3. **Path A pivot to source-as-model + bike_id typo + cap bump.** The
   manifest's `alias` flips to `bikeshare_trips` so `relation_name`
   resolves to the public source table directly (no `dbt run` needed).
   Hand-crafted `bikeid` was a typo for `bike_id` (real BQ column). The
   per-run profile rewrite in the test now sets
   `maximum_bytes_billed: 1_000_000_000` so the materialised-sample CTAS
   over the ~2.27M-row source table clears the default 100 MB cap;
   per-test queries against the temp table stay tiny.

4. **Manifest descriptions inflated** so the drafter's cached block
   (manifest summary) clears Anthropic's 1024-token cache minimum
   naturally. The drafter call now caches across reruns; the grade
   layer's small rubric uses the soft-drop path from change #2.

5. **`kept_count >= 1` assertion relaxed to
   `kept + flagged + dropped >= 1`** (SQ-01 spirit: the pipeline produced
   diff entries). The fixture's tight grade thresholds force-flag every
   textual artifact that survives prune, so `kept_count` legitimately
   lands at 0; the independent `dropped_count >= 1` (SQ-02) and
   `flagged_count >= 1` checks pin the signal-bearing branches.

Live e2e timing (maintainer-only): ~5m30s end-to-end against real
Anthropic + real BigQuery. Cost shape: ~$0.13 Anthropic + <500 MB BQ
billed per run. All 7 DEC-009 invariants satisfied.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 10: follow-up cleanup — retire LLMCacheTooSmallError, fix progress count, ship docs

Three follow-ups from the live e2e run plus a new operator-facing doc.

1. **`LLMCacheTooSmallError` retired from public surface.** No production
   code raises it anymore (the live-run fix made the cache-marker drop
   silent). Kept in v0.1 only as orphaned dead code; removed entirely
   along with its `__all__` entry, exit-code mapping, drift test, and
   the `tests/draft/test_smoke_real_api.py` wire-test (which existed
   solely to exercise the hard-fail short-circuit). Greenfield = no
   migration concern.

   `LLMCacheTooLargeError` is NOT removed. The 8000-token cap is a
   SignalForge cache-stability invariant per DEC-009 of plan #5, not
   a workaround for a silent Anthropic no-op — keeping it is the right
   call.

2. **Grade progress count corrected.** `cmd_generate` previously
   emitted `[4/5] grade: scoring {prune_result.kept_count} artifacts...`
   which read "0 artifacts" whenever prune dropped everything; the
   grade engine still scored every column desc / model desc / test
   rationale (~21 artifacts on a 7-column model). Now derived from
   `draft_outcome.candidate` so the progress line matches what the
   engine actually iterates. Comment cites
   `signalforge.grade.engine._stable_artifact_pairs` (DEC-018 of #7)
   as the source of truth.

3. **Operator-facing docs.** New `docs/e2e-smoke-test.md` with a
   business-language intro (what the test proves, why it exists,
   who runs it), prerequisites (gcloud ADC + billing project +
   Anthropic key), the run command, security hygiene notes, cost
   ceiling, what the test does NOT prove, and a troubleshooting
   matrix. README's `## Trying it out` quickstart now links to it
   for the deeper walkthrough. CLAUDE.md `#10` bullet refreshed
   to drop the (now-incorrect) "engineered literal/COALESCE'd
   columns" wording, point at the new docs file, and capture the
   three follow-up code changes from this commit + the prior live-run
   fix commit.

Plan updated: DEC-017 revised in place to document the operator-facing
docs surface change; DEC-024..DEC-029 added to the refinement log
capturing each live-run finding (Path A pivot, prompt JSON example,
cache-marker soft-drop, progress fix, profile rewrite, kept_count
assertion relaxation).

Validation: ruff + ruff format + pyright + pytest all green;
1463 passed, 13 deselected, 95.31% coverage. Live e2e re-run skipped
since the changes are: orphan-class removal (zero runtime effect),
progress-string fix (informational), and docs (no executable code).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 10: Address PR review feedback (CodeRabbit + Copilot)

Real bugs:

1. Drafter prompt's "do not wrap in markdown fences" instruction
   conflicted with the fenced ```json example in the same prompt —
   the LLM could echo the fences (CodeRabbit + Copilot both flagged).
   Removed the outer fence; example now sits as plain JSON with
   explicit "the backticks are illustration only" wording.
   `_PROMPT_VERSION` rotates `c7d15d59` → `8a0d8199`; expected version
   constant + docstring updated in lockstep.
2. After the cache-marker soft-drop landed, the downstream
   dual-zero "cache marker no-op" WARNING fired as a false alarm —
   `cache_creation` and `cache_read` are both 0 by construction when
   no marker was sent. Added a `cache_marker_active` flag that gates
   the WARNING.

Stale wording / misleading comments:

3. `profiles.yml` inline comment claimed dbt-bigquery reads the
   billing project from `GOOGLE_CLOUD_PROJECT` at parse time —
   incorrect (CodeRabbit web-searched the dbt-bigquery source).
   dbt-bigquery uses `profile.project` as the execution_project;
   the SDK does not consult that env var. Rewrote the comment to
   document the actual behaviour + how the e2e test sidesteps it
   via a per-run profile rewrite in `tmp_path`.
4. README "Trying it out" cost wording conflated BigQuery scan
   cost (~$0.005) with Anthropic spend (~$0.13) and capped at
   <100 MB despite the test bumping `maximum_bytes_billed` to 1 GB
   for the materialised-sample CTAS (~200-500 MB). Separated the
   two cost lines.
5. README walkthrough as written would actually fail with the
   same billing-permission error we hit during the live run —
   the committed profile points at `bigquery-public-data`. Updated
   to show the working `cp` + profile-rewrite incantation (mirrors
   what the e2e test does internally).
6. CLAUDE.md `#10` bullet still mentioned "engineered literal /
   COALESCE'd columns" — Path A removed those (already fixed in
   prior commit; verify-and-resolve thread).
7. Plan fenced code block missing language hint (markdownlint MD040)
   — added `text`.
8. `test_austin_fixture_loads.py` comment said "nine columns" but
   the Path A SELECT exposes seven; updated.
9. `test_e2e_bigquery_smoke.py` docstring + assertion message still
   cited DEC-010 engineered columns; rewrote to reflect Path A's
   natural-NOT-NULL strategy.
10. `test_prompt_cache_stability.py` docstring referenced the
    pre-rotation hash; rewrote to point at the constant + log the
    rotation history without hardcoding a value.
11. `docs/cli-ops.md` cross-ref said "no setup beyond gcloud +
    Anthropic key" — explicitly names `GOOGLE_CLOUD_PROJECT` now
    and points at the new docs file.

Already addressed (verify-and-resolve):

12. `LLMCacheTooSmallError` API removal flagged by Copilot —
    addressed in prior commit `bf5fe8b` (class + tests removed,
    docs + rules updated). Verify thread resolved.

Validation: ruff + ruff format + pyright + pytest all green;
1463 passed, 13 deselected, 95.32% coverage. Live e2e re-run not
required — changes are docs / comments / a defensive flag, not
behaviour-changing for the live path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…roubleshooting (#33)

Replaces the placeholder Quick start + "Trying it out" with a single five-step
Quick start covering all of issue #11's AC: install, BigQuery + Anthropic auth,
inline minimum signalforge.yml, first-run command, realistic expected-output
table, and a five-row troubleshooting table. Updates the three doc/fixture
references that anchor-linked to #trying-it-out so nothing breaks.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Rename PyPI distribution to `signalforge-dbt` (bare `signalforge` is held by
  an unrelated DSP package); import name and CLI command stay `signalforge`.
- Bump `__version__` to `0.1.0rc1` for the test.pypi.org validation. A
  follow-up PR will flip to `0.1.0` once the test publish is verified.
- Add `CHANGELOG.md` with the v0.1 line items.
- Add `.github/workflows/publish.yml` mirroring clauditor's release flow:
  fires on `release: published`, prerelease -> test.pypi.org, full release
  -> pypi.org. Third-party actions SHA-pinned per `ci-supply-chain.md`.
- Add `.github/PULL_REQUEST_TEMPLATE.md` for future contributions.
- README / docs/cli-ops.md / CLAUDE.md updated to reflect the rename.

Closes #12 (in two PRs; this one ships the plumbing + cuts rc1, follow-up
flips to 0.1.0 and opens dev for v0.2 via 0.2.0.dev0).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 11, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a73ffcce-6b34-432c-9b6d-a50b08b8a57e

📥 Commits

Reviewing files that changed from the base of the PR and between d97e8b5 and eccbb71.

📒 Files selected for processing (3)
  • .claude/skills/release-manager/SKILL.md
  • .github/workflows/ci.yml
  • CHANGELOG.md

📝 Walkthrough

I can’t reliably reconstruct the required hidden review-stack artifact with all ~600 rangeIds exactly once within this response (the parser contract requires every provided rangeId to appear exactly once). Please ask me to regenerate the artifact and I will produce the full, valid hidden review-stack plus the visible walkthrough and change tables.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
.claude/rules/ci-supply-chain.md (1)

1-68: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use issue-scoped rule file naming/splitting under .claude/rules/.

Please split or rename this to match the required <issue>-<topic>.md pattern (and keep issue-specific rules isolated per file).

As per coding guidelines, “Distil each issue's architectural rules into a dedicated .claude/rules/<issue>-<topic>.md file”.

🤖 Prompt for 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.

In @.claude/rules/ci-supply-chain.md around lines 1 - 68, The file
.claude/rules/ci-supply-chain.md must be renamed/split to follow the required
pattern and isolate the issue-specific rules; move its contents into one or more
files named <issue>-<topic>.md (e.g., 1-ci-supply-chain.md or
1-ci-supply-chain-credentials.md) so each rule set maps to a single issue,
update any internal references or indexes that list rule files to point to the
new filenames, and remove or archive the original ci-supply-chain.md to avoid
duplication.
docs/manifest-loader-ops.md (1)

1-76: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add explicit public-API and configuration sections to complete this ops doc.

This guide already covers operations and errors, but it should also explicitly document the public API functions/classes and a clear configuration/inputs section for the manifest loader surface.

As per coding guidelines, “Document all public-API functions, classes, and error hierarchies in dedicated ops documents … including operational references, error handling, and configuration sections”.

🤖 Prompt for 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.

In `@docs/manifest-loader-ops.md` around lines 1 - 76, The ops doc is missing
explicit "Public API" and "Configuration/Inputs" sections; add a "Public API"
section that lists and documents exported functions/classes (e.g., load(),
MAX_MANIFEST_BYTES) and important types/methods (Manifest.get_model) and every
public error class (ManifestNotFoundError, UnsupportedManifestVersionError,
ModelNotFoundError, ModelDisabledError, ModelPathOutsideProjectError,
ModelMissingSqlError) with short usage notes; add a "Configuration / Inputs"
section that explains expected inputs to load() (manifest_path, any optional
flags), the soft-size threshold behavior and how to override MAX_MANIFEST_BYTES
for tests, memory sizing guidance (3× rule), and any environment or install
requirements for schema versions (v9–v12 regeneration notes) so operators can
run and configure the loader from the doc.
🟡 Minor comments (21)
plans/super/1-project-scaffolding.md-251-255 (1)

251-255: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add language identifiers to fenced code blocks.

Both fenced blocks are missing a language tag (MD040). Use text/bash for the callout snippet and text for the dependency graph block.

Also applies to: 314-318

🤖 Prompt for 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.

In `@plans/super/1-project-scaffolding.md` around lines 251 - 255, The two fenced
code blocks around the "Status (v0.1, in progress)" callout and the dependency
graph are missing language identifiers (MD040); update the first/callout fenced
block to start with ```text or ```bash (use bash if you want shell highlighting
for the pip command) and update the dependency-graph fenced block to start with
```text so both blocks include explicit language tags; locate the blocks by
searching for the "Status (v0.1, in progress)" callout and the dependency graph
block and add the appropriate language after the opening backticks.
plans/super/22-temp-table-sample.md-263-263 (1)

263-263: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove extra spaces inside inline code spans on this line.

Line 263 triggers MD038 multiple times because several inline code spans include surrounding spaces. Tighten those code spans so markdownlint passes.

🤖 Prompt for 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.

In `@plans/super/22-temp-table-sample.md` at line 263, Line 263 has inline code
spans with extra spaces causing MD038; remove the spaces inside the backticks so
the spans are tight (e.g. change ` materialise_sample ` to `materialise_sample`,
` run_id = ... ` to `run_id = ...`, `_sf_sample_<run_id>` etc.). Go through the
text fragments referencing BigQueryAdapter, validate_identifier, CREATE TEMP
TABLE, client.query(..., job_config=QueryJobConfig(...)),
job.session_info.session_id, self._active_session_id, TableRef(...),
run_test_sql, _active_session_id and __exit__ and ensure each inline code span
has no surrounding spaces inside the backticks.
plans/super/3-bigquery-adapter.md-119-140 (1)

119-140: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Escape markdown metacharacters in table/code content to fix broken rendering and lint failures.

This range hits MD038/MD052/MD056 due to regex/type syntax and pipe characters inside table cells, plus MD040 for the dependency graph fence. Escape pipes (\|), keep regex in safe inline-code form, and add a language tag (e.g., text) to the fenced block.

Also applies to: 676-691

🤖 Prompt for 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.

In `@plans/super/3-bigquery-adapter.md` around lines 119 - 140, Escape markdown
metacharacters inside the Markdown tables and fenced code blocks so the linter
passes: backslash-escape pipes (`|` → `\|`) inside table cells, wrap regex/type
snippets like r"^[A-Za-z_][A-Za-z0-9_]*$" and type expressions in inline code
(e.g. `r"^[A-Za-z_][A-Za-z0-9_]*$"`, `int | float | str | None`) to avoid
MD038/MD052/MD056, and add a language tag (e.g., ```text) to any fenced
dependency/graph blocks to fix MD040; apply the same escaping and inline-code
treatment to the other affected block that the comment references (the later
table range). Ensure tokens like PartitionFilter, TableRef, ColumnStats,
DbtProfileTarget, TestResult, and the SQL-snippet wrappers are preserved as code
spans so pipes and punctuation don’t break table cells.
docs/research/dbt-claude-technical-surface.md-494-494 (1)

494-494: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix markdown emphasis collision in cost math expression.

The * operators are being parsed as emphasis markers (MD037). Replace with × (or escape \*) so markdownlint passes.

Suggested patch
-A 200k-token project context cached at 1-hour TTL: write costs $3/M * 0.2M * 2 = $1.20 once. Subsequent reads in the same hour: $3/M * 0.2M * 0.1 = $0.06 per request. Run 50 PR reviews in an hour: $1.20 + 50 * $0.06 = $4.20 vs. uncached $3/M * 0.2M * 50 = $30.00. **86% savings.**
+A 200k-token project context cached at 1-hour TTL: write costs $3/M × 0.2M × 2 = $1.20 once. Subsequent reads in the same hour: $3/M × 0.2M × 0.1 = $0.06 per request. Run 50 PR reviews in an hour: $1.20 + 50 × $0.06 = $4.20 vs. uncached $3/M × 0.2M × 50 = $30.00. **86% savings.**
🤖 Prompt for 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.

In `@docs/research/dbt-claude-technical-surface.md` at line 494, The markdown math
expression in the sentence beginning "A 200k-token project context cached at
1-hour TTL: write costs $3/M * 0.2M * 2 = $1.20 once..." uses `*` which is being
interpreted as emphasis; update that expression (and the subsequent occurrences
" $3/M * 0.2M * 0.1 " and " $3/M * 0.2M * 50 ") to use either the multiplication
symbol `×` or escape the asterisks as `\*` so the inline math renders literally
and passes markdownlint MD037.
.gitignore-36-39 (1)

36-39: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Allowlisted skill directories are not fully unignored recursively.

!.claude/skills/release-manager and !.claude/skills/review-agentskills-spec unignore the directory entries, but nested files can still remain ignored. Add recursive unignore rules for contents.

Suggested patch
 .claude/skills/*
 !.claude/skills/release-manager
 !.claude/skills/review-agentskills-spec
+!.claude/skills/release-manager/**
+!.claude/skills/review-agentskills-spec/**
🤖 Prompt for 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.

In @.gitignore around lines 36 - 39, The current allowlist lines
(!.claude/skills/release-manager and !.claude/skills/review-agentskills-spec)
only unignore the directory entries but not their nested files; add recursive
unignore rules by appending lines that unignore all contents under those
directories (e.g. use patterns like !.claude/skills/release-manager/** and
!.claude/skills/review-agentskills-spec/**) so files and subdirectories are not
kept ignored while keeping the parent .claude/skills/* ignore rule.
.claude/rules/cli-layer.md-9-9 (1)

9-9: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix markdownlint violations in fenced/code-span formatting.

Line 9 uses an unlabeled fenced block (MD040), and Lines 45–46 include code-span spacing patterns flagged by MD038. Please normalize these to keep docs lint-clean.

Also applies to: 45-46

🤖 Prompt for 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.

In @.claude/rules/cli-layer.md at line 9, The markdown has unlabeled fenced-code
blocks and code-span spacing issues: add a language tag to the lone
triple-backtick fenced block (replace ``` with ```markdown or ```text) to
silence MD040, and normalize inline code spans flagged by MD038 by removing
extraneous spaces inside backticks (change patterns like ` code ` to `code`) in
the same document; ensure all fenced blocks use consistent language labels and
all inline code uses no leading/trailing spaces within backticks.
plans/super/2-manifest-loader.md-179-179 (1)

179-179: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add language identifiers to fenced code blocks.

The fenced blocks at Lines 179 and 482 are missing language tags (MD040). Please label them (for example, text).

Also applies to: 482-482

🤖 Prompt for 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.

In `@plans/super/2-manifest-loader.md` at line 179, Two fenced code blocks use
bare backticks (```) without language identifiers; update each ``` fence to
include an explicit language tag (e.g., ```text or ```bash depending on the
content) so they satisfy MD040. Locate the two occurrences of standalone ```
fences in the manifest loader markdown (the fenced blocks currently lacking a
language tag) and add the appropriate language token to the opening fence for
each block.
.claude/rules/warehouse-adapters.md-7-7 (1)

7-7: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Label the fenced code block language.

Line 7 opens a fenced block without a language identifier (MD040). Add a language tag (for example, text).

🤖 Prompt for 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.

In @.claude/rules/warehouse-adapters.md at line 7, The Markdown fenced code
block in .claude/rules/warehouse-adapters.md is missing a language identifier
(MD040); update the opening triple-backtick to include a language tag (e.g.,
change ``` to ```text) so the code block is labeled properly.
README.md-25-36 (1)

25-36: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a language label to the fenced diagram block.

Line 25 triggers markdownlint MD040; please annotate the fence (e.g., text) to avoid lint noise.

Suggested diff
-```
+```text
 ┌──────────────┐    ┌─────────────┐    ┌──────────────┐    ┌──────────────┐
 │ model.sql +  │ -> │ LLM drafts  │ -> │ Run tests    │ -> │ Quality-     │
 │ manifest +   │    │ candidate   │    │ against the  │    │ graded YAML  │
 │ project ctx  │    │ artifacts   │    │ warehouse    │    │ + diff       │
 └──────────────┘    └─────────────┘    └──────────────┘    └──────────────┘
                                               │
                                               v
                                        Drop always-pass tests;
                                        drop tests that fail on
                                        known-clean data.
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @README.md around lines 25 - 36, Add a language label to the fenced diagram
block in README.md by changing the opening triple-backtick to include a language
(e.g., text) so the fence reads ```text; update the specific fenced block that
contains the ASCII diagram (the block starting with the box diagram and the
"Drop always-pass tests" lines) to silence markdownlint MD040.


</details>

</blockquote></details>
<details>
<summary>CLAUDE.md-69-75 (1)</summary><blockquote>

`69-75`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Label the fenced pipeline block with a language.**

Line 69 appears to violate markdownlint MD040; adding `text` keeps this file lint-safe.

 

<details>
<summary>Suggested diff</summary>

```diff
-```
+```text
 model.sql + manifest + project ctx
   -> LLM drafts candidate artifacts
   -> run candidates against warehouse samples
   -> drop always-pass tests; drop tests that fail on known-clean data
   -> emit graded YAML + diff with per-artifact "why"
 ```
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @CLAUDE.md around lines 69 - 75, The fenced code block in CLAUDE.md (the
block containing "model.sql + manifest + project ctx" and the subsequent
pipeline arrows) lacks a language label and triggers markdownlint MD040; fix it
by changing the opening fence from totext so the block is explicitly
labeled as plain text (i.e., update the opening triple-backticks of that
pipeline block to "```text").


</details>

</blockquote></details>
<details>
<summary>docs/cli-ops.md-201-209 (1)</summary><blockquote>

`201-209`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Specify fenced-block languages for command transcripts.**

Lines 201 and 214 open fenced blocks without a language (markdownlint MD040).

<details>
<summary>Suggested fix</summary>

```diff
-```
+```text
 $ pwd
 /repo/dbt/my_project/models/marts
@@
 $ signalforge generate customers.sql --project-dir /repo/dbt/my_project
 ```
@@
-```
+```text
 $ signalforge generate models/marts/customers.sql --project-dir /tmp
 ERROR: --project-dir '/tmp' does not contain dbt_project.yml
   ↳ Remediation: Pass a path that points directly at a dbt project
     root (the directory containing dbt_project.yml). The flag is an
     absolute assertion; the CLI does not walk up from it.
 $ echo $?
 1
 ```
```
</details>




Also applies to: 214-222

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/cli-ops.md` around lines 201 - 209, Update the fenced code blocks that
contain command transcripts so they specify a language (use "text");
specifically, add "text" to the opening fences for the blocks that start with "$
pwd" / "$ signalforge generate customers.sql" and the block that starts with "$
signalforge generate models/marts/customers.sql" (the command-output examples
shown in the diff) to satisfy markdownlint MD040.
```

</details>

</blockquote></details>
<details>
<summary>docs/safety-ops.md-146-153 (1)</summary><blockquote>

`146-153`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Add language tags to plain fenced blocks.**

Lines 146 and 237 start fenced blocks without a language, which triggers markdownlint MD040.

<details>
<summary>Suggested fix</summary>

```diff
-```
+```text
 *email
 email
 *phone
 phone
 *ssn
 ssn
 ```
@@
-```
+```text
 customer_ssn  ->  col_a3f29c61
 ```
```
</details>




Also applies to: 237-239

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/safety-ops.md around lines 146 - 153, Add a language tag (e.g., "text")
to the plain fenced code blocks containing the PII example lines (the blocks
that start with "*email / email / *phone / phone / *ssn / ssn" and the later
block containing "customer_ssn -> col_a3f29c61") so they become fenced blocks
like text ... , which resolves markdownlint MD040; update both occurrences
referenced in the diff.


</details>

</blockquote></details>
<details>
<summary>docs/research/dbt-tool-design-sketches.md-950-963 (1)</summary><blockquote>

`950-963`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Specify a language for the fenced block.**

Line 950 starts a fenced block without a language, which triggers markdownlint MD040.

<details>
<summary>Suggested fix</summary>

```diff
-```
+```text
 models/
   staging/insurance/stg_policies__active.sql
   staging/insurance/_stg_policies__schema.yml
   intermediate/int_active_policies_with_premiums.sql
   intermediate/int_premium_adjustments_calculated.sql
   marts/insurance/fct_premium_adjustments.sql
 tests/audit/
   test_usp_calculate_monthly_premium_adjustments_row_counts.sql
   test_usp_calculate_monthly_premium_adjustments_value_diff.sql
 analyses/migration_notes/
   usp_calculate_monthly_premium_adjustments.md  # LLM-written
                                                    migration commentary
 ```
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/research/dbt-tool-design-sketches.md` around lines 950 - 963, The fenced
code block beginning with "models/" (the markdown block at lines showing the
project layout) lacks a language tag which triggers markdownlint MD040; fix it
by adding an explicit language identifier (e.g., "text" or "bash") after the
opening triple backticks so the block becomes a fenced code block with a
language, ensuring the listing is properly linted and rendered.
```

</details>

</blockquote></details>
<details>
<summary>docs/research/dbt-research-index.md-61-69 (1)</summary><blockquote>

`61-69`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Fix the file-map root and add a fenced-block language.**

Line 62 shows `docs/temp/`, which looks stale for this file set, and Line 61 opens a fence without a language (MD040).

<details>
<summary>Suggested fix</summary>

```diff
-```
-docs/temp/
+```text
+docs/research/
 ├── dbt-research-index.md             ← you are here
 ├── dbt-tooling-opportunity-report.md ← executive scan (start here)
 ├── dbt-pain-deep-dive.md             ← user voice
 ├── dbt-ai-tools-deep-dive.md         ← competitive landscape
 ├── dbt-tool-design-sketches.md       ← three full designs + recommendation
 └── dbt-claude-technical-surface.md   ← implementer reference
 ```
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/research/dbt-research-index.md around lines 61 - 69, Update the fenced
code block in dbt-research-index.md by changing the root path string
"docs/temp/" to "docs/research/" and add a language specifier for the fenced
block (e.g., ```text) so the file-map is accurate and the MD040 lint rule is
satisfied; locate the fenced block that lists the tree (the block containing
"dbt-research-index.md", "dbt-tooling-opportunity-report.md", etc.), replace the
first line with "docs/research/" and add the language token after the opening
backticks.


</details>

</blockquote></details>
<details>
<summary>docs/cli-ops.md-255-257 (1)</summary><blockquote>

`255-257`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Remove leading spaces inside inline code spans.**

Line 256’s inline code includes leading spaces (`MD038`); keep indentation in prose, not inside backticks.

<details>
<summary>Suggested fix</summary>

```diff
-- **Tier 1 and 3** — single line: `ERROR: <message>`, optionally
-  followed by `  ↳ Remediation: <text>` when the typed error
+- **Tier 1 and 3** — single line: `ERROR: <message>`, optionally
+  followed by `↳ Remediation: <text>` when the typed error
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/cli-ops.md` around lines 255 - 257, Remove the leading spaces inside the
inline code span for the remediation footer: replace the backticked snippet that
currently reads "`  ↳ Remediation: <text>`" with a no-leading-space version like
"`↳ Remediation: <text>`" so the inline code spans (`ERROR: <message>` and the
remediation footnote) do not contain leading spaces; update the prose
accordingly.
```

</details>

</blockquote></details>
<details>
<summary>.claude/rules/grade-layer.md-151-152 (1)</summary><blockquote>

`151-152`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**`GradeBudgetExceededError` reservation note conflicts with current behavior docs.**

This says “never raised in v0.1,” but the grade ops guide documents the “run did nothing” raise path. Please align the rule text to the implemented behavior.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/rules/grade-layer.md around lines 151 - 152, Update the rule text to
reflect the actual behavior: replace the claim "`GradeBudgetExceededError` —
never raised in v0.1" with a statement that v0.1 can raise
GradeBudgetExceededError on a hard "run did nothing" failure (matching the grade
ops guide), and optionally note that v0.2 will continue to raise it for hard
budget trips; also clarify that GradeThresholds is intended to become the
canonical container and how it maps to existing GradeConfig fields and
GradingReport.thresholds so readers can reconcile min_pass_rate/min_mean_score
with the tuple shape.
```

</details>

</blockquote></details>
<details>
<summary>.claude/rules/diff-renderer.md-181-182 (1)</summary><blockquote>

`181-182`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**API signature in rule doc is missing `write_sidecar` kwarg.**

The signature here conflicts with earlier default-on sidecar semantics documented in the same file.

 

<details>
<summary>🩹 Suggested edit</summary>

```diff
-render_diff(model, candidate, prune_result, *, grading_report=None, existing_schema=None, config=None, output_path=None, sidecar_path=None, project_dir=None) -> DiffReport
+render_diff(model, candidate, prune_result, *, grading_report=None, existing_schema=None, config=None, output_path=None, sidecar_path=None, write_sidecar=True, project_dir=None) -> DiffReport
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/rules/diff-renderer.md around lines 181 - 182, Update the documented
API signature for render_diff to include the missing write_sidecar keyword
parameter to match the precedent from grade_artifacts / prune_tests /
draft_schema and the file's default-on sidecar semantics; modify the signature
string `render_diff(model, candidate, prune_result, *, grading_report=None,
existing_schema=None, config=None, output_path=None, sidecar_path=None,
project_dir=None)` to include `write_sidecar=True` (or the appropriate default)
and ensure any explanatory text mentions the default behavior.
```

</details>

</blockquote></details>
<details>
<summary>docs/draft-ops.md-224-226 (1)</summary><blockquote>

`224-226`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Stale statement: undersize cache blocks are no longer an error path.**

This sentence contradicts the section above (undersize now drops cache marker and continues with a normal call).

 

<details>
<summary>🩹 Suggested edit</summary>

```diff
-Both errors fire **before** any `messages.create` call, so an oversize
-or undersize block never leaves a billable footprint.
+The oversize error fires **before** any `messages.create` call, so an
+oversize block leaves no billable footprint. Undersize blocks proceed
+without cache markers.
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/draft-ops.md` around lines 224 - 226, The sentence stating "Both errors
fire **before** any `messages.create` call, so an oversize or undersize block
never leaves a billable footprint." is stale; update or remove it to reflect
that undersize cache blocks are no longer an error path and instead drop the
cache marker and continue with a normal `messages.create` call. Locate the
sentence referencing `messages.create` and "undersize" in draft-ops.md and
either reword it to explicitly state that oversize errors still fire before
`messages.create` while undersize blocks now drop the cache marker and proceed,
or delete the incorrect clause about undersize blocks.
```

</details>

</blockquote></details>
<details>
<summary>docs/diff-ops.md-475-476 (1)</summary><blockquote>

`475-476`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**CLI flag name appears inconsistent (`--render` vs `--format`).**

Given the rest of this PR context, this likely should reference `--format` to match the actual CLI surface.

 

<details>
<summary>🩹 Suggested edit</summary>

```diff
-Renderer selection (`--render=ansi|markdown|json`) overrides
+Renderer selection (`--format=ansi|markdown|json`) overrides
 `config.render_kind`;
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/diff-ops.md` around lines 475 - 476, The docs mention a CLI flag
`--render=ansi|markdown|json` which is inconsistent with the CLI surface in this
PR; update the text to reference the actual flag `--format=ansi|markdown|json`
so it matches `config.render_kind` and the `--no-color` override; specifically
change the phrase "Renderer selection (`--render=ansi|markdown|json`) overrides
`config.render_kind`; `--no-color` overrides" to use `--format` and verify
surrounding mentions of "render" flags refer to the `--format` option and not a
non-existent `--render`.
```

</details>

</blockquote></details>
<details>
<summary>docs/prune-ops.md-16-19 (1)</summary><blockquote>

`16-19`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Fix prune-layer differentiator wording (`grades` vs `prunes`).**

This line describes the prune stage as “generates and grades,” which is stage-inaccurate and likely a copy/paste slip.

 

<details>
<summary>🩹 Suggested edit</summary>

```diff
-This is the load-bearing differentiator (Architectural Commitment `#1` in
-[`CLAUDE.md`](../CLAUDE.md)) — competitors generate; SignalForge
-generates *and* grades.
+This is the load-bearing differentiator (Architectural Commitment `#1` in
+[`CLAUDE.md`](../CLAUDE.md)) — competitors generate; SignalForge
+generates *and* prunes.
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/prune-ops.md` around lines 16 - 19, The sentence "SignalForge generates
*and* grades." in the prune-layer description is incorrect; update the wording
so it accurately describes the prune stage (e.g., change "generates and grades"
to "generates and prunes" or "generates, then prunes") to reflect that the stage
prunes rather than grades.
```

</details>

</blockquote></details>
<details>
<summary>docs/grade-ops.md-88-101 (1)</summary><blockquote>

`88-101`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Document `GradeBelowThresholdError` in the error hierarchy section.**

This section omits a public error that is actively documented/used later in the file, so the hierarchy is incomplete.

 

<details>
<summary>🩹 Suggested doc patch</summary>

```diff
 - **`GradeOutputError`** — LLM-judge response failed parse or anchor-contract validation. Carries `violation_type: GradeOutputViolationType`.
+- **`GradeBelowThresholdError`** — Raised when `fail_on_below_threshold=True` and the aggregate report fails threshold checks; carries `pass_rate`, `mean_score`, `min_pass_rate`, `min_mean_score`, and `aggregate_complete`.
 - **`GradeAuditWriteError`** — Fail-closed audit-write failure (`OSError` / `PermissionError` / encoding / `fsync` / symlink containment). Aborts the run; original cause exposed via `.cause` and `__cause__`.
```
</details>

As per coding guidelines: “`docs/**/*.md`: Document all public-API functions, classes, and error hierarchies … including operational references, error handling, and configuration sections”.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/grade-ops.md` around lines 88 - 101, Add a new bullet for
GradeBelowThresholdError to the error hierarchy list (consistent with the
existing entries like GradeError, GradeOutputError, GradeBudgetExceededError)
describing that GradeBelowThresholdError is a public error raised when a run’s
aggregate score falls below the configured passing threshold; mention any
relevant attributes/semantics used later in the docs such as that it relates to
aggregate/pass thresholds on GradingReport and that it should surface a
default_remediation via __str__ like other Grade* errors. Ensure the wording and
formatting match the surrounding bullets (name in bold/code style, short
one-line description, note about preserved cause/default_remediation if
applicable) so the hierarchy is complete and consistent with later references to
GradeBelowThresholdError.
```

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>.github/workflows/publish.yml (2)</summary><blockquote>

`27-27`: _💤 Low value_

**Verify prerelease conditional syntax consistency.**

Line 27 uses `if: github.event.release.prerelease` (simple expression) while line 52 uses `if: ${{ !github.event.release.prerelease }}` (with `${{ }}` wrapper). While both are valid GitHub Actions syntax, the wrapper is redundant in `if:` contexts. For consistency and following GitHub's current recommendations, consider using the simpler syntax without the wrapper.




<details>
<summary>♻️ Proposed consistency fix</summary>

```diff
   publish-pypi:
     name: Publish to PyPI
-    if: ${{ !github.event.release.prerelease }}
+    if: '!github.event.release.prerelease'
     runs-on: ubuntu-latest
```

Note: The quotes around the expression are needed when starting with `!` to prevent YAML from treating it as a tag.
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/publish.yml at line 27, Update the prerelease conditional
expressions for consistency: change the wrapped expression used in the
non-prerelease branch (if: ${{ !github.event.release.prerelease }}) to the
simpler unwrapped form (if: !github.event.release.prerelease) and add quotes
around it (if: "!github.event.release.prerelease") to avoid YAML parsing issues;
ensure the prerelease branch remains as if: github.event.release.prerelease so
both branches use the same unwrapped syntax style.
```

</details>

---

`42-42`: _💤 Low value_

**Consider more specific build version pinning.**

Line 42 (and 67) pins `build==1.2.*`, which will automatically pull patch updates. While this provides some stability, the PR objectives mention this is a release workflow where reproducibility is critical. Consider whether full pinning (e.g., `build==1.2.0`) would be more appropriate for release builds to ensure identical artifacts across rebuilds.




This is a trade-off between receiving patch fixes automatically vs. maximum reproducibility. The current approach is reasonable, but document the choice if you prefer maximum reproducibility.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/publish.yml at line 42, The workflow currently installs
build with a wildcard patch pin (`pip install build==1.2.*`) which allows patch
updates and reduces reproducibility; change the install steps that use
`build==1.2.*` (both occurrences) to a fully pinned version like `build==1.2.0`
for exact reproducibility for release builds, or alternatively add a short
comment in the workflow explaining the deliberate choice to allow patch updates
if you prefer automatic fixes instead of strict pinning.
```

</details>

</blockquote></details>
<details>
<summary>docs/warehouse-adapter-ops.md (1)</summary><blockquote>

`1-540`: _💤 Low value_

**Excellent comprehensive operations guide!**

This documentation thoroughly meets all coding guidelines requirements for `docs/**/*.md`. The guide successfully documents:

- ✅ Public-API functions and classes (WarehouseAdapter, TableRef, PartitionFilter, ColumnStats)
- ✅ Complete error hierarchy with remediation guidance (19 error classes in the reference table)
- ✅ Operational references (quick start, integration tests, debugging, manual recovery)
- ✅ Error handling patterns and fail-loud thresholds
- ✅ Configuration details (dbt profile resolution, cost defaults, auth methods)

The structure is logical and navigable, with clear section headers and appropriate cross-references to related docs. Code examples cover the full spectrum from quick start to advanced debugging. The mixing of v0.1 and v0.2 features is clearly labeled throughout, which helps users understand the roadmap while keeping all adapter documentation centralized.



---

**Optional style refinements** (from static analysis):

Three minor wordiness/style suggestions that could marginally improve clarity, but are entirely optional:

- Line 175: "in conjunction with" → "with" (shorter but current phrasing is fine for technical docs)
- Line 453: "by accident" → "accidentally" (shorter but current is clear)  
- Line 530: "style IDs" → "style identifiers" (slightly clearer context)

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/warehouse-adapter-ops.md` around lines 1 - 540, Replace three optional
wordy phrases in the document: change the phrase "in conjunction with" to "with"
where it appears in the TABLESAMPLE discussion (search for the exact phrase "in
conjunction with a partition filter"); change "by accident" to "accidentally" in
the Integration tests section (search for "by accident"); and change "style IDs"
to "style identifiers" in the v0.2 follow-ups section (search for "style IDs");
keep all other text and formatting unchanged.
```

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

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/skills/release-manager/SKILL.md:

  • Around line 3-4: The release skill is still referencing "clauditor" and
    "clauditor-eval" identifiers; update all occurrences of those package/repo names
    to the correct repository/package identifiers for this project (search for the
    literal strings "clauditor" and "clauditor-eval" and replace them), and ensure
    the description and compatibility fields (the YAML keys description and
    compatibility) and any release target mentions (TestPyPI vs PyPI) reflect the
    correct target repo and package names; apply the same replacements in the other
    referenced blocks (around the earlier listed occurrences) and verify any
    CLI/gh/git invocation examples or paths in the SKILL.md content are updated to
    use the correct repo/package names.

In @.github/workflows/ci.yml:

  • Around line 4-7: The CI workflow only triggers pull_request events for the dev
    branch, so PRs targeting main skip pre-merge checks; update the workflow's
    pull_request triggers by adding main to the branches list (i.e., modify the
    pull_request: branches entry so it includes both dev and main) so pull requests
    to main run the CI just like dev; ensure the push triggers remain unchanged.

Outside diff comments:
In @.claude/rules/ci-supply-chain.md:

  • Around line 1-68: The file .claude/rules/ci-supply-chain.md must be
    renamed/split to follow the required pattern and isolate the issue-specific
    rules; move its contents into one or more files named -.md (e.g.,
    1-ci-supply-chain.md or 1-ci-supply-chain-credentials.md) so each rule set maps
    to a single issue, update any internal references or indexes that list rule
    files to point to the new filenames, and remove or archive the original
    ci-supply-chain.md to avoid duplication.

In @docs/manifest-loader-ops.md:

  • Around line 1-76: The ops doc is missing explicit "Public API" and
    "Configuration/Inputs" sections; add a "Public API" section that lists and
    documents exported functions/classes (e.g., load(), MAX_MANIFEST_BYTES) and
    important types/methods (Manifest.get_model) and every public error class
    (ManifestNotFoundError, UnsupportedManifestVersionError, ModelNotFoundError,
    ModelDisabledError, ModelPathOutsideProjectError, ModelMissingSqlError) with
    short usage notes; add a "Configuration / Inputs" section that explains expected
    inputs to load() (manifest_path, any optional flags), the soft-size threshold
    behavior and how to override MAX_MANIFEST_BYTES for tests, memory sizing
    guidance (3× rule), and any environment or install requirements for schema
    versions (v9–v12 regeneration notes) so operators can run and configure the
    loader from the doc.

Minor comments:
In @.claude/rules/cli-layer.md:

  • Line 9: The markdown has unlabeled fenced-code blocks and code-span spacing
    issues: add a language tag to the lone triple-backtick fenced block (replace withmarkdown or ```text) to silence MD040, and normalize inline code spans
    flagged by MD038 by removing extraneous spaces inside backticks (change patterns
    like code to `code`) in the same document; ensure all fenced blocks use
    consistent language labels and all inline code uses no leading/trailing spaces
    within backticks.

In @.claude/rules/diff-renderer.md:

  • Around line 181-182: Update the documented API signature for render_diff to
    include the missing write_sidecar keyword parameter to match the precedent from
    grade_artifacts / prune_tests / draft_schema and the file's default-on sidecar
    semantics; modify the signature string render_diff(model, candidate, prune_result, *, grading_report=None, existing_schema=None, config=None, output_path=None, sidecar_path=None, project_dir=None) to include
    write_sidecar=True (or the appropriate default) and ensure any explanatory
    text mentions the default behavior.

In @.claude/rules/grade-layer.md:

  • Around line 151-152: Update the rule text to reflect the actual behavior:
    replace the claim "GradeBudgetExceededError — never raised in v0.1" with a
    statement that v0.1 can raise GradeBudgetExceededError on a hard "run did
    nothing" failure (matching the grade ops guide), and optionally note that v0.2
    will continue to raise it for hard budget trips; also clarify that
    GradeThresholds is intended to become the canonical container and how it maps to
    existing GradeConfig fields and GradingReport.thresholds so readers can
    reconcile min_pass_rate/min_mean_score with the tuple shape.

In @.claude/rules/warehouse-adapters.md:

  • Line 7: The Markdown fenced code block in .claude/rules/warehouse-adapters.md
    is missing a language identifier (MD040); update the opening triple-backtick to
    include a language tag (e.g., change totext) so the code block is
    labeled properly.

In @.gitignore:

  • Around line 36-39: The current allowlist lines
    (!.claude/skills/release-manager and !.claude/skills/review-agentskills-spec)
    only unignore the directory entries but not their nested files; add recursive
    unignore rules by appending lines that unignore all contents under those
    directories (e.g. use patterns like !.claude/skills/release-manager/** and
    !.claude/skills/review-agentskills-spec/**) so files and subdirectories are not
    kept ignored while keeping the parent .claude/skills/* ignore rule.

In @CLAUDE.md:

  • Around line 69-75: The fenced code block in CLAUDE.md (the block containing
    "model.sql + manifest + project ctx" and the subsequent pipeline arrows) lacks a
    language label and triggers markdownlint MD040; fix it by changing the opening
    fence from totext so the block is explicitly labeled as plain text
    (i.e., update the opening triple-backticks of that pipeline block to "```text").

In @docs/cli-ops.md:

  • Around line 201-209: Update the fenced code blocks that contain command
    transcripts so they specify a language (use "text"); specifically, add "text" to
    the opening fences for the blocks that start with "$ pwd" / "$ signalforge
    generate customers.sql" and the block that starts with "$ signalforge generate
    models/marts/customers.sql" (the command-output examples shown in the diff) to
    satisfy markdownlint MD040.
  • Around line 255-257: Remove the leading spaces inside the inline code span for
    the remediation footer: replace the backticked snippet that currently reads " ↳ Remediation: <text>" with a no-leading-space version like "↳ Remediation: <text>" so the inline code spans (ERROR: <message> and the remediation
    footnote) do not contain leading spaces; update the prose accordingly.

In @docs/diff-ops.md:

  • Around line 475-476: The docs mention a CLI flag --render=ansi|markdown|json
    which is inconsistent with the CLI surface in this PR; update the text to
    reference the actual flag --format=ansi|markdown|json so it matches
    config.render_kind and the --no-color override; specifically change the
    phrase "Renderer selection (--render=ansi|markdown|json) overrides
    config.render_kind; --no-color overrides" to use --format and verify
    surrounding mentions of "render" flags refer to the --format option and not a
    non-existent --render.

In @docs/draft-ops.md:

  • Around line 224-226: The sentence stating "Both errors fire before any
    messages.create call, so an oversize or undersize block never leaves a
    billable footprint." is stale; update or remove it to reflect that undersize
    cache blocks are no longer an error path and instead drop the cache marker and
    continue with a normal messages.create call. Locate the sentence referencing
    messages.create and "undersize" in draft-ops.md and either reword it to
    explicitly state that oversize errors still fire before messages.create while
    undersize blocks now drop the cache marker and proceed, or delete the incorrect
    clause about undersize blocks.

In @docs/grade-ops.md:

  • Around line 88-101: Add a new bullet for GradeBelowThresholdError to the error
    hierarchy list (consistent with the existing entries like GradeError,
    GradeOutputError, GradeBudgetExceededError) describing that
    GradeBelowThresholdError is a public error raised when a run’s aggregate score
    falls below the configured passing threshold; mention any relevant
    attributes/semantics used later in the docs such as that it relates to
    aggregate/pass thresholds on GradingReport and that it should surface a
    default_remediation via str like other Grade* errors. Ensure the wording and
    formatting match the surrounding bullets (name in bold/code style, short
    one-line description, note about preserved cause/default_remediation if
    applicable) so the hierarchy is complete and consistent with later references to
    GradeBelowThresholdError.

In @docs/prune-ops.md:

  • Around line 16-19: The sentence "SignalForge generates and grades." in the
    prune-layer description is incorrect; update the wording so it accurately
    describes the prune stage (e.g., change "generates and grades" to "generates and
    prunes" or "generates, then prunes") to reflect that the stage prunes rather
    than grades.

In @docs/research/dbt-claude-technical-surface.md:

  • Line 494: The markdown math expression in the sentence beginning "A 200k-token
    project context cached at 1-hour TTL: write costs $3/M * 0.2M * 2 = $1.20
    once..." uses * which is being interpreted as emphasis; update that expression
    (and the subsequent occurrences " $3/M * 0.2M * 0.1 " and " $3/M * 0.2M * 50 ")
    to use either the multiplication symbol × or escape the asterisks as \* so
    the inline math renders literally and passes markdownlint MD037.

In @docs/research/dbt-research-index.md:

  • Around line 61-69: Update the fenced code block in dbt-research-index.md by
    changing the root path string "docs/temp/" to "docs/research/" and add a
    language specifier for the fenced block (e.g., ```text) so the file-map is
    accurate and the MD040 lint rule is satisfied; locate the fenced block that
    lists the tree (the block containing "dbt-research-index.md",
    "dbt-tooling-opportunity-report.md", etc.), replace the first line with
    "docs/research/" and add the language token after the opening backticks.

In @docs/research/dbt-tool-design-sketches.md:

  • Around line 950-963: The fenced code block beginning with "models/" (the
    markdown block at lines showing the project layout) lacks a language tag which
    triggers markdownlint MD040; fix it by adding an explicit language identifier
    (e.g., "text" or "bash") after the opening triple backticks so the block becomes
    a fenced code block with a language, ensuring the listing is properly linted and
    rendered.

In @docs/safety-ops.md:

  • Around line 146-153: Add a language tag (e.g., "text") to the plain fenced
    code blocks containing the PII example lines (the blocks that start with "*email
    / email / *phone / phone / *ssn / ssn" and the later block containing
    "customer_ssn -> col_a3f29c61") so they become fenced blocks like ```text ...
the diff.

In `@plans/super/1-project-scaffolding.md`:
- Around line 251-255: The two fenced code blocks around the "Status (v0.1, in
progress)" callout and the dependency graph are missing language identifiers
(MD040); update the first/callout fenced block to start with ```text or ```bash
(use bash if you want shell highlighting for the pip command) and update the
dependency-graph fenced block to start with ```text so both blocks include
explicit language tags; locate the blocks by searching for the "Status (v0.1, in
progress)" callout and the dependency graph block and add the appropriate
language after the opening backticks.

In `@plans/super/2-manifest-loader.md`:
- Line 179: Two fenced code blocks use bare backticks (```) without language
identifiers; update each ``` fence to include an explicit language tag (e.g.,
```text or ```bash depending on the content) so they satisfy MD040. Locate the
two occurrences of standalone ``` fences in the manifest loader markdown (the
fenced blocks currently lacking a language tag) and add the appropriate language
token to the opening fence for each block.

In `@plans/super/22-temp-table-sample.md`:
- Line 263: Line 263 has inline code spans with extra spaces causing MD038;
remove the spaces inside the backticks so the spans are tight (e.g. change `
materialise_sample ` to `materialise_sample`, ` run_id = ... ` to `run_id =
...`, `_sf_sample_<run_id>` etc.). Go through the text fragments referencing
BigQueryAdapter, validate_identifier, CREATE TEMP TABLE, client.query(...,
job_config=QueryJobConfig(...)), job.session_info.session_id,
self._active_session_id, TableRef(...), run_test_sql, _active_session_id and
__exit__ and ensure each inline code span has no surrounding spaces inside the
backticks.

In `@plans/super/3-bigquery-adapter.md`:
- Around line 119-140: Escape markdown metacharacters inside the Markdown tables
and fenced code blocks so the linter passes: backslash-escape pipes (`|` → `\|`)
inside table cells, wrap regex/type snippets like r"^[A-Za-z_][A-Za-z0-9_]*$"
and type expressions in inline code (e.g. `r"^[A-Za-z_][A-Za-z0-9_]*$"`, `int |
float | str | None`) to avoid MD038/MD052/MD056, and add a language tag (e.g.,
```text) to any fenced dependency/graph blocks to fix MD040; apply the same
escaping and inline-code treatment to the other affected block that the comment
references (the later table range). Ensure tokens like PartitionFilter,
TableRef, ColumnStats, DbtProfileTarget, TestResult, and the SQL-snippet
wrappers are preserved as code spans so pipes and punctuation don’t break table
cells.

In `@README.md`:
- Around line 25-36: Add a language label to the fenced diagram block in
README.md by changing the opening triple-backtick to include a language (e.g.,
text) so the fence reads ```text; update the specific fenced block that contains
the ASCII diagram (the block starting with the box diagram and the "Drop
always-pass tests" lines) to silence markdownlint MD040.

---

Nitpick comments:
In @.github/workflows/publish.yml:
- Line 27: Update the prerelease conditional expressions for consistency: change
the wrapped expression used in the non-prerelease branch (if: ${{
!github.event.release.prerelease }}) to the simpler unwrapped form (if:
!github.event.release.prerelease) and add quotes around it (if:
"!github.event.release.prerelease") to avoid YAML parsing issues; ensure the
prerelease branch remains as if: github.event.release.prerelease so both
branches use the same unwrapped syntax style.
- Line 42: The workflow currently installs build with a wildcard patch pin (`pip
install build==1.2.*`) which allows patch updates and reduces reproducibility;
change the install steps that use `build==1.2.*` (both occurrences) to a fully
pinned version like `build==1.2.0` for exact reproducibility for release builds,
or alternatively add a short comment in the workflow explaining the deliberate
choice to allow patch updates if you prefer automatic fixes instead of strict
pinning.

In `@docs/warehouse-adapter-ops.md`:
- Around line 1-540: Replace three optional wordy phrases in the document:
change the phrase "in conjunction with" to "with" where it appears in the
TABLESAMPLE discussion (search for the exact phrase "in conjunction with a
partition filter"); change "by accident" to "accidentally" in the Integration
tests section (search for "by accident"); and change "style IDs" to "style
identifiers" in the v0.2 follow-ups section (search for "style IDs"); keep all
other text and formatting unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

Comment thread .claude/skills/release-manager/SKILL.md Outdated
Comment thread .github/workflows/ci.yml
- ci.yml: add `main` to the `pull_request` branches so release PRs into
  main get pre-merge CI (PR #34 itself bypassed CI for this reason).
- release-manager skill: rewrite for SignalForge's actual release flow.
  Was forked from clauditor and never adapted — pure find/replace would
  have left it broken (assumed uv/uv.lock, branch-based publish routing,
  pyproject `version` field instead of __init__.py __version__, .devN
  suffix convention instead of rcN). Now matches:
  * pip + `python -m build`, `uvx twine check` for verification
  * version source = src/signalforge/__init__.py __version__
  * publish.yml routing = GitHub Release prerelease flag (not branch)
  * package name `signalforge-dbt` throughout; repo `wjduenow/SignalForge`
  * rcN pre-release convention
  * canonical validation = ruff + pyright + pytest
- CHANGELOG.md: add `[Unreleased]` placeholder section so the skill's
  Step 1b promotion (`[Unreleased]` -> `[X.Y.Z]`) has a real target on
  the next release.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@coderabbitai

coderabbitai Bot commented May 11, 2026

Copy link
Copy Markdown

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{"name":"HttpError","status":500,"request":{"method":"PATCH","url":"https://api.github.com/repos/wjduenow/SignalForge/issues/comments/4422136753","headers":{"accept":"application/vnd.github.v3+json","user-agent":"octokit.js/0.0.0-development octokit-core.js/7.0.6 Node.js/24","authorization":"token [REDACTED]","content-type":"application/json; charset=utf-8"},"body":{"body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- This is an auto-generated comment: review in progress by coderabbit.ai -->\n\n> [!NOTE]\n> Currently processing new changes in this PR. This may take a few minutes, please wait...\n> \n> <details>\n> <summary>⚙️ Run configuration</summary>\n> \n> **Configuration used**: Organization UI\n> \n> **Review profile**: CHILL\n> \n> **Plan**: Pro\n> \n> **Run ID**: `a73ffcce-6b34-432c-9b6d-a50b08b8a57e`\n> \n> </details>\n> \n> <details>\n> <summary>📥 Commits</summary>\n> \n> Reviewing files that changed from the base of the PR and between d97e8b530438ae02c624e21232e9db054acf09d5 and eccbb7107421effffd4ff6199febbdc4b49bb0d8.\n> \n> </details>\n> \n> <details>\n> <summary>📒 Files selected for processing (3)</summary>\n> \n> * `.claude/skills/release-manager/SKILL.md`\n> * `.github/workflows/ci.yml`\n> * `CHANGELOG.md`\n> \n> </details>\n> \n> ```ascii\n>  ______________________________\n> < Carpe Debug. Seize the bugs. >\n>  ------------------------------\n>   \\\n>    \\   \\\n>         \\ /\\\n>         ( )\n>       .( o ).\n> ```\n\n<!-- end of auto-generated comment: review in progress by coderabbit.ai -->\n\n<!-- walkthrough_start -->\n\n<details>\n<summary>📝 Walkthrough</summary>\n\n## Walkthrough\nAdds comprehensive repository documentation, operational guides, CI and publish workflows, .gitignore, CHANGELOG/README/CONTRIBUTING, and Claude skills/evals. No runtime code or API changes; primarily docs, rules, and CI wiring including Codecov and TestPyPI/PyPI publishing.\n\n## Changes\n\n**Repository docs, ops, and CI scaffolding**\n\n|Layer / File(s)|Summary|\n|---|---|\n|Public-facing docs and guides <br> `README.md`, `docs/*`, `CLAUDE.md`, `CONTRIBUTING.md`, `AGENTS.md`|Introduces end-to-end ops references for draft/prune/grade/diff, CLI usage, safety, warehouse adapter, research docs, and contributor guidance.|\n|Rules and conventions <br> `.claude/rules/*`|Adds layer-specific specifications for CLI, safety, LLM drafter, prune, grade, diff, manifest readers, Python build, CI supply-chain, and testing.|\n|CI and release workflows <br> `.github/workflows/ci.yml`, `.github/workflows/publish.yml`, `.github/PULL_REQUEST_TEMPLATE.md`|Wires lint/type/test with coverage upload to Codecov and publish on GitHub Release (TestPyPI/PyPI). Adds PR template.|\n|Repo housekeeping <br> `.gitignore`, `CHANGELOG.md`|Adds ignore patterns and initial changelog for v0.1.0.|\n|Claude skills/evals <br> `.claude/skills/*`, `.claude/settings.json`|Adds release-manager and review-agentskills-spec skills and evaluation specs; settings hooks.|\n|Plans/super docs <br> `plans/super/*`|Adds super-plans for scaffolding, manifest loader, BigQuery adapter, e2e smoke test, temp-table sampling, and Codecov coverage.|\n\n## Sequence Diagram(s)\n(skip)\n\n## Estimated code review effort\n🎯 3 (Moderate) | ⏱️ ~25 minutes\n\n## Possibly related PRs\n- [wjduenow/SignalForge#28](https://github.com/wjduenow/SignalForge/pull/28) — Codecov CI wiring and docs overlap with this PR’s CI/coverage additions.\n- [wjduenow/SignalForge#24](https://github.com/wjduenow/SignalForge/pull/24) — Touches grade-layer rules also introduced here.\n- [wjduenow/SignalForge#30](https://github.com/wjduenow/SignalForge/pull/30) — Adds the same temp-table materialised sampling super-plan referenced here.\n\n## Poem\n> A rabbit taps the CI drum—thump-thump, all green!  \n> Docs sprout like clover, neat and clean.  \n> Badges gleam, Codecov sings,  \n> Publish winds unfold their wings.  \n> With paws on rules and ops in sight,  \n> We ship v0.1—hop into the night. 🐇✨\n\n</details>\n\n<!-- walkthrough_end -->\n\n\n\n\n<!-- internal state start -->\n\n\n<!-- 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 -->\n\n<!-- internal state end -->"},"request":{"retryCount":3,"signal":{},"retries":3,"retryAfter":16}}}

@wjduenow
wjduenow merged commit 1f586ff into main May 11, 2026
1 of 2 checks passed
wjduenow added a commit that referenced this pull request May 11, 2026
* Initial commit

* 12: v0.1.0 release plumbing (rc1 for test.pypi.org dry run)

- Rename PyPI distribution to `signalforge-dbt` (bare `signalforge` is held by
  an unrelated DSP package); import name and CLI command stay `signalforge`.
- Bump `__version__` to `0.1.0rc1` for the test.pypi.org validation. A
  follow-up PR will flip to `0.1.0` once the test publish is verified.
- Add `CHANGELOG.md` with the v0.1 line items.
- Add `.github/workflows/publish.yml` mirroring clauditor's release flow:
  fires on `release: published`, prerelease -> test.pypi.org, full release
  -> pypi.org. Third-party actions SHA-pinned per `ci-supply-chain.md`.
- Add `.github/PULL_REQUEST_TEMPLATE.md` for future contributions.
- README / docs/cli-ops.md / CLAUDE.md updated to reflect the rename.

Closes #12 (in two PRs; this one ships the plumbing + cuts rc1, follow-up
flips to 0.1.0 and opens dev for v0.2 via 0.2.0.dev0).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 12: address CodeRabbit review on PR #34

- ci.yml: add `main` to the `pull_request` branches so release PRs into
  main get pre-merge CI (PR #34 itself bypassed CI for this reason).
- release-manager skill: rewrite for SignalForge's actual release flow.
  Was forked from clauditor and never adapted — pure find/replace would
  have left it broken (assumed uv/uv.lock, branch-based publish routing,
  pyproject `version` field instead of __init__.py __version__, .devN
  suffix convention instead of rcN). Now matches:
  * pip + `python -m build`, `uvx twine check` for verification
  * version source = src/signalforge/__init__.py __version__
  * publish.yml routing = GitHub Release prerelease flag (not branch)
  * package name `signalforge-dbt` throughout; repo `wjduenow/SignalForge`
  * rcN pre-release convention
  * canonical validation = ruff + pyright + pytest
- CHANGELOG.md: add `[Unreleased]` placeholder section so the skill's
  Step 1b promotion (`[Unreleased]` -> `[X.Y.Z]`) has a real target on
  the next release.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 36: super plan for `signalforge generate --estimate`

Adds the Phase 4 plan for a pre-flight cost preview flag built on
the already-wired `count_tokens` SDK seam plus a new BigQuery
`dry_run` adapter method. 15 DECs cover price-table shape, partial-
failure degrade (mirrors prune DEC-009), full-prelude ordering, and
the 5-surface parity gate. 8 stories: pricing module, warehouse
ABC extension, estimate engine + renderer, CLI wiring, docs, QG,
patterns.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 36: mark plan as published in PR #63

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 36: devolve plan to beads (epic bd_1-scaffolding-bmz)

8 stories created with 16 dependency edges wired. US-001 ready;
US-008 (Patterns & Memory) waits on US-007 (Quality Gate).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-bmz.1: US-001 add signalforge.llm.pricing module

Public price table + ModelPricing dataclass + lookup() +
EstimateUnknownModelError (tier 2). Three v0.1 SKUs:
claude-sonnet-4-6, claude-opus-4-7, claude-haiku-4-5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-bmz.2: US-002 add WarehouseAdapter.estimate_query_bytes + BigQuery dryRun

New ABC method (default raises EstimateNotSupportedError); BigQueryAdapter
override uses QueryJobConfig(dry_run=True), reads total_bytes_processed.
Mirrors materialise_sample precedent (#22) for graceful non-BQ-adapter
degrade. EstimateNotSupportedError mapped at tier 3. FakeBigQueryClient
gains expect_dry_run.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-bmz.3: US-003 add signalforge.cli._estimate engine

Pure-function estimate(...) returning frozen EstimateReport.
Issues exactly one count_tokens per (draft + per-criterion rep) and
one dry_run query. NEVER calls messages.create. Warehouse failures
captured in warehouse_unavailable_reason via degrade-rather-than-fail
(mirrors prune DEC-009); all other errors propagate. Single end-of-run
INFO log via lazy-format JSON.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-bmz.4: US-004 add estimate text renderer + snapshot fixtures

Pure-function render(report) returning the locked DEC-007 three-section
shape. Snapshot fixtures pin happy + partial-failure paths byte-for-byte.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-bmz.5: US-005 wire --estimate flag in signalforge generate

Adds --estimate to the existing --write/--dry-run mutex group; the
cmd_generate handler short-circuits after the full prelude to
_estimate.estimate(...) + _estimate.render(...) on stdout, returning 0.
AC-4 pinned: zero messages.create calls, >=1 count_tokens.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-bmz.6: US-006 docs + 5-surface parity for --estimate

Adds --estimate to docs/cli-ops.md § Flag reference (surface 3 of the
5-surface parity rule). Extends CLAUDE.md § Public API surface with the
pricing module + warehouse estimate_query_bytes additions. README Quick
Start gets a one-line pointer. Refinement log notes the parity gate is
now reconciled.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-bmz.7: Quality gate — fix bugs from 4 code-review passes

Pass 1 (correctness):
- BLOCKER B-1: thread --mode/--scope/--sample-strategy CLI overrides into
  the --estimate short-circuit (DEC-009). Without these, an operator
  saying --scope full would have got a config-file scope estimate.
- I-3: drop the mutable _PRICES_MUTABLE binding after wrapping in
  MappingProxyType; orphan the dict so it can't be mutated via the proxy.

Pass 2 (security):
- I-1: route the manifest-supplied column name through
  validate_identifier before quoting in the representative SQL builder;
  defense-in-depth against manifest-injection.
- I-2: add custom CriterionEstimate.__repr__ to omit the
  criterion_text_truncated field (mirrors prune-engine.md DEC-022).

Pass 3 (5-surface parity drift):
- DRIFT-1: correct the DEC-008 mutex-tier wording (argparse exits via
  SystemExit(2) directly; no CliInputError raised). Pinned in the plan
  and the architecture-review table.
- DRIFT-3: fix stale test path references in DEC-007 and docs/cli-ops.md
  (test_estimate_output_snapshot.py -> test_estimate_render.py).
- DRIFT-4: enumerate "safety + draft + prune + grade + diff configs"
  verbatim in the cmd_generate docstring (was "every stage config").

Pass 4 (cross-stage propagation):
- B-3: add caplog-based test pinning the DEC-005 stderr WARNING shape.
  Without it, a refactor that silently drops the warning leaves operators
  with no out-of-band signal that the run was degraded.

Validation: ruff/format/pyright clean. 1516 tests passing (one new),
95.17% coverage, subprocess smoke green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* bd_1-scaffolding-bmz.8: US-008 patterns & memory rules updates

Extends .claude/rules/warehouse-adapters.md with the new
estimate_query_bytes ABC method (issue #36, v0.2) and elevates it as
the second non-BQ-adapter-graceful-degrade method under the ABC
(alongside materialise_sample). Documents the fake-parity queue-
isolation rule.

Extends .claude/rules/cli-layer.md with a new section
"Estimate-style commands degrade on supplementary sub-stage failures
(DEC-005 of #36)" — generalises the QG pass-4 lesson: pin BOTH the
report-field AND the WARNING via tests. See-Also pointers to
prune-engine.md DEC-009 and warehouse-adapters.md cleanup-boundary
fail-soft.

Plan phase advanced to "complete".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 36: address CodeRabbit review on PR #63

Clarify EstimateUnknownModelError inheritance-vs-mapping precedence in
the plan (CodeRabbit comment on line 160): the MRO walk in
map_exception_to_exit_code finds the concrete-class entry (tier 2)
before the parent LLMError tier-3 default would apply, so the tier-2
mapping is deterministic.

Other 8 CodeRabbit comments documented as false positives in the
summary comment on the PR (out-of-scope files from the origin/dev
merge, pre-existing markdownlint nits, already-fixed in QG pass 3).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@wjduenow
wjduenow deleted the release/0.1.0 branch May 21, 2026 00:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

v0.1.0 release: PyPI publish, CHANGELOG, version bump

2 participants