diff --git a/QUICKSTART.md b/QUICKSTART.md index 9889ef5..8d08cbc 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -27,7 +27,7 @@ python -m venv .venv source .venv/bin/activate # Windows (PowerShell): .venv\Scripts\Activate.ps1 # Windows (Git Bash): source .venv/Scripts/activate pip install -e ".[dev]" -pytest # optional: all tests pass (126 at v0.3.0) +pytest # optional: all tests pass (129 at v0.4.0) ``` ## 3. Create the demo repo @@ -132,7 +132,7 @@ A fresh-clone check was run on the current candidate: - **OS:** Windows 11 (Git Bash) - **Python:** 3.11.9 - **Install:** `pip install -e ".[dev]"` -- **Tests:** all passing (126 at v0.3.0) +- **Tests:** all passing (129 at v0.4.0) - **Demo:** `dtc init` / `dtc scan` / `dtc concepts` / `dtc explain "Billing Webhooks"` all produced the expected output from a clean `git clone`. diff --git a/RELEASE_NOTES_v0.4.0.md b/RELEASE_NOTES_v0.4.0.md new file mode 100644 index 0000000..61aa60c --- /dev/null +++ b/RELEASE_NOTES_v0.4.0.md @@ -0,0 +1,36 @@ +# DevTime v0.4.0 - diff-aware claim impact + +A pull request should tell you which verified claims it destabilizes. Now it +does. No cloud, no telemetry, no AI, no code execution - unchanged. + +## dtc risk --diff now reports claim impact + +``` +Claim impact: + - billing-webhook-signature (previous status: SUPPORTED) + changed evidence: src/billing/stripe-webhook.ts + re-verify: dtc verify billing-webhook-signature +``` + +The rules: + +- Only a claim's recorded evidence files count. A diff touching unrelated + files never flags a claim. +- Nothing is printed when no verified claim is affected. Advisory, never noisy. +- The output names the exact changed evidence and the exact command to + re-verify. + +This connects the verification layer (v0.2, v0.3) to the risk layer: risk +review says "this change class needs review"; claim impact says "this specific +verified claim may no longer hold, check it." + +## Notes + +- 129 passing tests (3 new: affected claim reported with previous status and + changed evidence; unrelated diffs affect nothing; no verifications means no + impact output). +- No breaking changes. Risk output gains a section only when relevant. + +## Names + +- PyPI distribution: `devtime-ei`. Python import: `devtime`. CLI: `dtc`. diff --git a/VERIFICATION.md b/VERIFICATION.md index f13639f..3cc8e18 100644 --- a/VERIFICATION.md +++ b/VERIFICATION.md @@ -71,6 +71,20 @@ available in community edition"). The file name claims webhook handling; the implementation is a disabled stub. `dtc verify` reports CONTRADICTED with both sides and exact paths. +## Diff impact (v0.4) + +`dtc risk --diff` now reports which verified claims a diff destabilizes: + +``` +Claim impact: + - billing-webhook-signature (previous status: SUPPORTED) + changed evidence: src/billing/stripe-webhook.ts + re-verify: dtc verify billing-webhook-signature +``` + +Only a claim's recorded evidence files count. A diff touching unrelated files +never flags a claim, and nothing is printed when no verified claim is affected. + ## Trust model - Deterministic and rule-driven. No AI, no network, no code execution. @@ -94,6 +108,6 @@ sides and exact paths. ## Where this is going Next candidates, in order: more built-in claims over well-covered domains -(webhook idempotency), more contradiction detectors, and diff-aware claim -staleness in `dtc risk`. User-defined +(webhook idempotency), more contradiction detectors, and machine-readable +claim impact in risk output. User-defined claims come after built-in claims prove trustworthy on real repositories. diff --git a/pyproject.toml b/pyproject.toml index 3b097de..3ee41ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "devtime-ei" -version = "0.3.0" +version = "0.4.0" description = "Local-first Engineering Intelligence for software repositories" readme = "README.md" requires-python = ">=3.11" @@ -50,7 +50,7 @@ dev = [ Homepage = "https://github.com/Shakargy/devtime" Repository = "https://github.com/Shakargy/devtime" Issues = "https://github.com/Shakargy/devtime/issues" -"Release Notes" = "https://github.com/Shakargy/devtime/releases/tag/v0.3.0" +"Release Notes" = "https://github.com/Shakargy/devtime/releases/tag/v0.4.0" Demo = "https://youtu.be/1Hiu3Y9J_SI" [project.scripts] diff --git a/server.json b/server.json index 2343df3..2fdce24 100644 --- a/server.json +++ b/server.json @@ -8,12 +8,12 @@ "source": "github" }, "websiteUrl": "https://github.com/Shakargy/devtime", - "version": "0.3.0", + "version": "0.4.0", "packages": [ { "registryType": "pypi", "identifier": "devtime-ei", - "version": "0.3.0", + "version": "0.4.0", "transport": { "type": "stdio" } diff --git a/src/devtime/__init__.py b/src/devtime/__init__.py index eee5005..bc80f49 100644 --- a/src/devtime/__init__.py +++ b/src/devtime/__init__.py @@ -1,6 +1,6 @@ """DevTime - local-first Engineering Intelligence for repository memory.""" -__version__ = "0.3.0" +__version__ = "0.4.0" # Version metadata (Builder Edition, Chapter 20). EVIDENCE_MODEL = "2026.06.1" diff --git a/src/devtime/cli.py b/src/devtime/cli.py index a4bd8bf..eaff4c9 100644 --- a/src/devtime/cli.py +++ b/src/devtime/cli.py @@ -347,11 +347,29 @@ def risk( conn = connection.connect() try: intelligence = repository.load_all_concepts(conn) + from devtime.intelligence.verification import claims_affected_by_paths + + claim_impact = claims_affected_by_paths(conn, info.changed_files) finally: conn.close() review = review_diff(info, intelligence) console.print(render_risk_review(review), markup=False) + + # v0.4.0: claim impact. A diff is not just risky in general - it can + # destabilize a previously verified claim. Only evidence files count; + # nothing is printed when no verified claim is affected. + if claim_impact: + console.print("\nClaim impact:", markup=False) + for item in claim_impact: + console.print( + f" - {item['claim_id']} (previous status: {item['previous_status']})", + markup=False, + ) + for p in item["changed_evidence"]: + console.print(f" changed evidence: {p}", markup=False) + console.print(f" re-verify: {item['suggested_action']}", markup=False) + if review.state == STATE_REVIEW_FAILED: raise typer.Exit(code=1) diff --git a/src/devtime/intelligence/verification.py b/src/devtime/intelligence/verification.py index fae5473..ffca201 100644 --- a/src/devtime/intelligence/verification.py +++ b/src/devtime/intelligence/verification.py @@ -580,6 +580,38 @@ def load_latest_verification( ) +def claims_affected_by_paths( + conn: sqlite3.Connection, changed_paths: list[str] +) -> list[dict]: + """Claim impact for a set of changed paths (v0.4.0, diff integration). + + For every claim with a stored verification, report it when a changed path + is one of its recorded evidence files. Only evidence files count: a diff + touching unrelated files never flags a claim. Advisory output - the caller + decides what to do with it. + """ + changed = set(changed_paths) + out: list[dict] = [] + for slug in BUILTIN_CLAIMS: + latest = load_latest_verification(conn, slug) + if latest is None: + continue + result, fingerprints, created_at = latest + evidence_paths = {fp["path"] for fp in fingerprints} + hits = sorted(evidence_paths & changed) + if hits: + out.append( + { + "claim_id": slug, + "previous_status": result["status"], + "verified_at": created_at, + "changed_evidence": hits, + "suggested_action": f"dtc verify {slug}", + } + ) + return out + + def freshness_for(conn: sqlite3.Connection, slug: str) -> tuple[str, list[str]]: """Compare stored evidence fingerprints against current file hashes. diff --git a/tests/integration/test_evidence_precision.py b/tests/integration/test_evidence_precision.py index 99b811a..11f4cb0 100644 --- a/tests/integration/test_evidence_precision.py +++ b/tests/integration/test_evidence_precision.py @@ -14,14 +14,14 @@ # --- version ------------------------------------------------------------------ -def test_version_is_release_0_3_0(): - # v0.3.0 release: package metadata and __version__ agree on the release version. +def test_version_is_release_0_4_0(): + # v0.4.0 release: package metadata and __version__ agree on the release version. import importlib.metadata as m - assert devtime.__version__ == "0.3.0" + assert devtime.__version__ == "0.4.0" # Distribution is published as "devtime-ei" (the name "devtime" is reserved on # PyPI); the import package and the dtc command stay "devtime"/"dtc". - assert m.version("devtime-ei") == "0.3.0" + assert m.version("devtime-ei") == "0.4.0" # --- P0 Authentication headline precision ------------------------------------ diff --git a/tests/integration/test_verification.py b/tests/integration/test_verification.py index b4b13c8..797d5ef 100644 --- a/tests/integration/test_verification.py +++ b/tests/integration/test_verification.py @@ -329,3 +329,45 @@ def test_verify_all_returns_both_claims(tmp_path, monkeypatch): payload = json.loads(result.stdout) ids = {r["claim_id"] for r in payload["results"]} assert ids == {"billing-webhook-signature", "jwt-authentication"} + + +# --- diff-aware claim impact (v0.4.0) --------------------------------------------- + +def test_claims_affected_by_changed_evidence(tmp_path, monkeypatch): + _repo(tmp_path, {"src/billing/stripe-webhook.ts": STRIPE_HANDLER}) + _init_scan(tmp_path, monkeypatch) + conn = connection.connect() + try: + ver.save_verification(conn, ver.verify_claim(conn, "billing-webhook-signature")) + impact = ver.claims_affected_by_paths(conn, ["src/billing/stripe-webhook.ts"]) + assert len(impact) == 1 + item = impact[0] + assert item["claim_id"] == "billing-webhook-signature" + assert item["previous_status"] == ver.SUPPORTED + assert item["changed_evidence"] == ["src/billing/stripe-webhook.ts"] + assert "dtc verify" in item["suggested_action"] + finally: + conn.close() + + +def test_unrelated_diff_affects_no_claims(tmp_path, monkeypatch): + _repo(tmp_path, {"src/billing/stripe-webhook.ts": STRIPE_HANDLER}) + _init_scan(tmp_path, monkeypatch) + conn = connection.connect() + try: + ver.save_verification(conn, ver.verify_claim(conn, "billing-webhook-signature")) + impact = ver.claims_affected_by_paths(conn, ["README.md", "src/util/other.ts"]) + assert impact == [] + finally: + conn.close() + + +def test_no_verifications_means_no_impact(tmp_path, monkeypatch): + _repo(tmp_path, {"src/billing/stripe-webhook.ts": STRIPE_HANDLER}) + _init_scan(tmp_path, monkeypatch) + conn = connection.connect() + try: + impact = ver.claims_affected_by_paths(conn, ["src/billing/stripe-webhook.ts"]) + assert impact == [] + finally: + conn.close()