Skip to content

feat(diff): register kb.diff at all four kb.* surface sites#344

Open
jsdevninja wants to merge 1 commit into
vouchdev:mainfrom
jsdevninja:feat/327-kb-diff-parity
Open

feat(diff): register kb.diff at all four kb.* surface sites#344
jsdevninja wants to merge 1 commit into
vouchdev:mainfrom
jsdevninja:feat/327-kb-diff-parity

Conversation

@jsdevninja

@jsdevninja jsdevninja commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

summary

closes #327. vouch diff <id-old> <id-new> shipped CLI-only in 0.1.0
(#86) — an explicit non-goal at the time ("MCP/JSONL parity ... kb.*
surface unchanged"). that left kb.diff as the only read method
skipping the four-site registration convention (CLAUDE.md §"When you
add a new kb.* method"), so agents talking MCP or JSONL had no way to
fetch a revision diff.

  • registers kb.diff at the remaining three sites: kb_diff MCP tool
    in server.py, _h_diff/kb.diff in jsonl_server.py, and
    capabilities.METHODS — placed next to kb_read_claim/kb_read_page
    with the same unrestricted-read posture (no ViewerContext scope
    filtering, matching the other by-id reads).
  • new_id is now optional for a claim: diff_artifacts and the CLI
    both resolve it from superseded_by when omitted, erroring clearly
    when there's no successor. pages still require an explicit new_id
    (no successor pointer on the Page model).
  • updates the original design doc
    (docs/superpowers/specs/2026-05-25-vouch-diff-design.md) to record
    the superseded "CLI-only" decision and the new surface.
  • diff logic still lives entirely in src/vouch/diff.py; storage.py
    is untouched.

read-only throughout — no proposal emitted, no artifact or audit event
written (asserted directly in tests/test_diff.py).

test plan

  • pytest tests/test_diff.py tests/test_capabilities.py tests/test_provenance.py -q — 66 passed (20 in test_diff.py,
    including the new kb.diff RPC-surface, omitted-new_id, and
    read-only tests)
  • mypy src — clean (two pre-existing, unrelated sandbox.py
    errors on this Windows checkout, confirmed present on main too)
  • ruff check src tests — clean
  • confirmed kb.diff registered at all four sites; test_capabilities
    (capabilities.METHODS == HANDLERS.keys()) stays green

Summary by CodeRabbit

  • New Features

    • Added a new diff capability in the app’s CLI and API surfaces.
    • Diff requests can now omit the newer revision for superseded claims, with the system resolving it automatically when possible.
  • Bug Fixes

    • Improved error handling for diffs when no successor revision exists or when a page is used without a newer revision.
    • Diff responses remain read-only and do not create audit or proposal activity.
  • Documentation

    • Updated the release notes and design documentation to reflect the new diff behavior and available interfaces.

vouch diff shipped CLI-only in 0.1.0, an explicit non-goal at the time
("MCP/JSONL parity ... kb.* surface unchanged"). that leaves it the only
read method skipping the four-site registration convention documented in
CLAUDE.md, so agents talking MCP/JSONL have no way to fetch a revision
diff. adds the kb_diff MCP tool, the kb.diff JSONL handler, and the
capabilities.METHODS entry, next to the other by-id read tools
(kb_read_claim/kb_read_page) with the same unrestricted-read posture.

also makes new_id optional for a superseded claim: diff_artifacts and
the CLI both resolve it from superseded_by when omitted, erroring
clearly when there's no successor (pages still require an explicit
new_id — they have no successor pointer). closes vouchdev#327.
@github-actions github-actions Bot added docs documentation, specs, examples, and repo guidance cli command line interface mcp mcp, jsonl, and http surfaces sync sync, vault mirror, and diff flows tests tests and fixtures size: S 50-199 changed non-doc lines labels Jul 3, 2026
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The diff_artifacts function now accepts an optional new_id, resolving it via superseded_by when omitted and raising DiffError if no successor exists. kb.diff is registered as a full MCP tool, JSONL handler, and capability method. CLI, tests, changelog, and design docs are updated accordingly.

Changes

kb.diff optional new_id and MCP/JSONL parity

Layer / File(s) Summary
Optional new_id resolution in diff_artifacts
src/vouch/diff.py
diff_artifacts now accepts new_id: str | None = None, resolving the successor via old.superseded_by when omitted and raising DiffError for pages or unsuperseded claims.
CLI diff command update
src/vouch/cli.py
The diff command's new_id argument becomes optional (str | None), with docstring updates describing successor resolution.
kb.diff MCP tool, JSONL handler, capability registration
src/vouch/server.py, src/vouch/jsonl_server.py, src/vouch/capabilities.py
Adds kb_diff MCP tool, _h_diff JSONL handler wired into HANDLERS, and registers "kb.diff" in capabilities.METHODS.
Test coverage
tests/test_diff.py
Adds tests for successor resolution, error paths, read-only guarantees (no audit events/proposals), CLI omitted-new_id behavior, and kb.diff JSONL RPC responses.
Changelog and design spec
CHANGELOG.md, docs/superpowers/specs/2026-05-25-vouch-diff-design.md
Documents full kb.* parity, optional new_id semantics via superseded_by, updated CLI usage, and revised non-goals/decision rationale referencing issue #327.

Estimated code review effort: 2 (Simple) | ~15 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant MCPServer as server.py (kb_diff)
  participant JSONLServer as jsonl_server.py (_h_diff)
  participant DiffModule as diff.py (diff_artifacts)
  participant Store

  Client->>MCPServer: kb_diff(old_id, new_id=None)
  MCPServer->>DiffModule: diff_artifacts(store, old_id, new_id)
  DiffModule->>Store: fetch old artifact
  DiffModule->>Store: resolve superseded_by if new_id omitted
  DiffModule-->>MCPServer: ArtifactDiff
  MCPServer-->>Client: asdict(ArtifactDiff)

  Client->>JSONLServer: kb.diff request {old_id, new_id?}
  JSONLServer->>DiffModule: diff_artifacts(store, old_id, new_id)
  DiffModule-->>JSONLServer: ArtifactDiff
  JSONLServer-->>Client: dict response
Loading

Possibly related PRs

  • vouchdev/vouch#84: Both PRs modify vouch diff CLI and diff_artifacts signatures around new_id handling.
  • vouchdev/vouch#86: Both PRs introduce/modify src/vouch/diff.py's diff_artifacts and its wiring into the vouch diff CLI.
  • vouchdev/vouch#89: Both PRs touch the diff/diff_artifacts feature, with this PR extending it to support optional new_id resolution.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR appears to drop the required ViewerContext scope filtering; the design notes explicitly say kb.diff is unrestricted by-id. Restore scope-aware resolution for ViewerContext-backed reads, or update the issue/spec if unrestricted access is intended.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately highlights the main change: registering kb.diff across kb.* surfaces.
Out of Scope Changes check ✅ Passed Changes stay focused on kb.diff registration, optional successor resolution, and related tests/docs; no unrelated code paths stand out.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@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.

🧹 Nitpick comments (1)
src/vouch/diff.py (1)

86-96: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Reuse the already-fetched claim to avoid a redundant read.

When new_id is omitted, old_id's claim is fetched here to read superseded_by, then fetched again at line 106 to build old. Since kb.diff is now also exposed via MCP/JSONL (potentially higher call volume than the CLI), reusing the object avoids an unnecessary disk read + YAML parse per call.

♻️ Proposed fix
     if new_id is None:
         if old_kind != "claim":
             raise DiffError(
                 f"{old_id} is a {old_kind}; pages have no successor pointer, "
                 "pass new_id explicitly"
             )
         old_claim = store.get_claim(old_id)
         if not old_claim.superseded_by:
             raise DiffError(f"{old_id} has not been superseded; pass new_id explicitly")
         new_id = old_claim.superseded_by
+    else:
+        old_claim = None

     new_kind = _kind_of(store, new_id)
     if new_kind is None:
         raise DiffError(f"unknown artifact: {new_id}")
     if old_kind != new_kind:
         raise DiffError(f"cannot diff {old_kind} against {new_kind}")

     old: Claim | Page
     new: Claim | Page
     if old_kind == "claim":
-        old, new = store.get_claim(old_id), store.get_claim(new_id)
+        old, new = old_claim or store.get_claim(old_id), store.get_claim(new_id)
         fields, text_field = _CLAIM_FIELDS, _CLAIM_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 `@src/vouch/diff.py` around lines 86 - 96, The fallback path in diff logic is
doing a redundant claim read when new_id is omitted. Update the diff-building
flow around the existing store.get_claim call in the kb.diff path so the fetched
old_claim is reused later instead of fetching the same claim again when
constructing old, while keeping the superseded_by check and DiffError behavior
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/vouch/diff.py`:
- Around line 86-96: The fallback path in diff logic is doing a redundant claim
read when new_id is omitted. Update the diff-building flow around the existing
store.get_claim call in the kb.diff path so the fetched old_claim is reused
later instead of fetching the same claim again when constructing old, while
keeping the superseded_by check and DiffError behavior unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cdc8645d-68d0-463b-9f75-745ef1b4e124

📥 Commits

Reviewing files that changed from the base of the PR and between 5f58c69 and 9b1e733.

📒 Files selected for processing (8)
  • CHANGELOG.md
  • docs/superpowers/specs/2026-05-25-vouch-diff-design.md
  • src/vouch/capabilities.py
  • src/vouch/cli.py
  • src/vouch/diff.py
  • src/vouch/jsonl_server.py
  • src/vouch/server.py
  • tests/test_diff.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli command line interface docs documentation, specs, examples, and repo guidance mcp mcp, jsonl, and http surfaces size: S 50-199 changed non-doc lines sync sync, vault mirror, and diff flows tests tests and fixtures

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: vouch diff <id-old> <id-new> — revision diff for claims and pages

1 participant