NoWreck v0.12.0 — Provider Consolidation + Scan Caching
Deterministic AI Verifier — v0.12.0
NoWreck is a deterministic structural verifier for AI-generated code-change claims. When an AI describes a code change, NoWreck compares the claims against structural evidence derived by its own scanners — the verifier never asks another AI for an opinion. Where the evidence comes from depends on the mode: in Pre/Post and Claims modes, from actual before/after repository snapshots; in Prompt Mode, from the model's own proposed diff.
$ nowreck fix "Add email validation to auth.py"
Summary
────────────────────
● 3 claims total
● 2 confirmed
● 1 contradicted
CONFIRMED
─────────
✓ ADD_FUNCTION validate_email → auth.py (conf: 100%)
Evidence: Function 'validate_email' was added in auth.py
CONTRADICTED
────────────
✗ CALLS_FUNCTION validate_email → auth.py (conf: 100%)
Evidence: Function 'validate_email' was added in auth.py
Release date: August 2026
Previous release: v0.11.1 (Bugfix Release)
Focus: Two infrastructure improvements — consolidate provider resolution
into a single function and add file-level scan caching for large repositories.
What's new in v0.12.0
Provider consolidation ✅
Provider detection was split between two independent functions —
_auth_header() in provider.py and detect_adapter() in adapters.py —
both doing URL-matching independently. A new provider required updating both,
and they had to agree.
Now a single resolve_provider(base_url, override) function returns both
the adapter and the auth header type in one call:
ModelProvider._default_http_call()
│
└── resolve_provider(base_url, provider) → ProviderInfo
Single URL matching in adapters.py:
"api.anthropic.com" → (AnthropicAdapter, x-api-key)
"generativelanguage.googleapis.com" → (GeminiAdapter, x-goog-api-key)
else → (OpenAIAdapter, Authorization: Bearer)
One call. One URL match. Adapter and auth header always agree.
New: ProviderInfo dataclass, resolve_provider() function.
Removed: _auth_header() standalone function (replaced by _auth_header_from_type()).
Deprecated: detect_adapter() (still works, delegates to resolve_provider()).
Scan caching ✅
Every nowreck fix run re-scans every file in the repository from scratch.
For a repository with 500+ source files, this means 500+ parse operations
per run.
Now RepositoryScanner.scan() caches per-file results in .nowreck/cache/.
On subsequent runs, only files that actually changed are re-parsed:
RepositoryScanner.scan()
→ ScanCache.get(file_path, mtime, size, content_hash)
→ If hit: use cached result (no parse needed)
→ If miss: parse, then cache result
→ ScanCache.save() (atomic write to .nowreck/cache/scan_cache.json)
Cache key: (path, mtime, size, content_hash) — MD5 of file contents
catches same-size rewrites within the same filesystem timestamp quantum.
Cache format:
Python files: source text stored; ast.Module reconstructed via ast.parse() on load
JS/TS/Rust/Go files: symbol lists stored directly
Invalidation: mtime, size, content hash, or version mismatch → re-parse.
Version bump forces full re-parse. Cache is gitignored by .nowreck/.
New: ScanCache, CacheEntry, file_content_hash() in scanner/scan_cache.py.
Modified: RepositoryScanner.scan() integrates cache transparently.
New: Symbol.to_dict() / Symbol.from_dict() for JSON round-tripping.