Skip to content
Merged

Test #525

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,43 @@ All notable changes to vouch are documented here. Format follows

## [Unreleased]

### Added
- `vouch install-mcp claude-code` now registers vouch as a **local-scope**
MCP server in `~/.claude.json` (`projects[<abs project>].mcpServers`),
the same thing `claude mcp add` does. a committed `.mcp.json` is a
*project*-scope server that Claude Code loads only after a per-user
approval — and the **VS Code extension never surfaces that approval
prompt**, so `.mcp.json` alone left the `kb_*` tools stuck at "pending
approval" in the extension while the hooks quietly ran (reads as
connected, isn't). the local-scope entry is trusted on sight, so a fresh
install now connects after a window reload with no manual step. verified
end-to-end: fresh project → `install-mcp` → `claude mcp list` reports
`✔ Connected` (was `⏸ Pending approval`). declared by a manifest
`user_mcp:` block (host-neutral core; only claude-code opts in);
idempotent and never clobbers a server you added yourself; opt out with
`--no-approve`.

### Changed
- the starter config now ships `review.auto_approve_on_receipt: true`
(and `require_human_approval: false`, an advisory key no code path
reads): a fresh KB auto-approves captured claims whose byte-offset
receipts verify against their source, so recall works out of the box
with no `vouch review` pass. the gate is unchanged for everything
the receipt check cannot vouch for — pages (session summaries
included), entities, relations, and claims that cannot quote their
source still wait for a human. existing KBs keep whatever their
`.vouch/config.yaml` says; set `auto_approve_on_receipt: false` to
restore the fully human gate.
- the receipt drain now runs at every session start (`capture
finalize-all`), so verifiable claims left pending while the gate was
off are approved instead of stranded, and it is duplicate-safe: a
claim re-deriving text that is already durable is mechanically
rejected ("duplicate: identical claim already durable") rather than
crashing the drain or piling up in the review queue. a claim id held
by *different* text is a real conflict and stays pending for a human.
the same resolution now backs `capture answer`, which previously left
re-captured duplicates pending forever.

## [1.4.0] — 2026-07-17

### Added
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,9 @@ cd /path/to/your/project
vouch install-mcp claude-code # creates .vouch/ (if missing) + wires Claude Code
```

`install-mcp` initialises the KB when no `.vouch/` is discoverable (pass `--no-init` to skip; `vouch init` still exists for KB-only setup), then writes `.mcp.json` (the `kb.*` MCP tools), the `/vouch-*` slash commands, and five hooks — `SessionStart` recall, `UserPromptSubmit` per-prompt recall, `PostToolUse` capture, `Stop` answer capture, `SessionEnd` rollup. Restart Claude Code so they load.
`install-mcp` initialises the KB when no `.vouch/` is discoverable (pass `--no-init` to skip; `vouch init` still exists for KB-only setup), then writes `.mcp.json` (the `kb.*` MCP tools), the `/vouch-*` slash commands, and five hooks — `SessionStart` recall, `UserPromptSubmit` per-prompt recall, `PostToolUse` capture, `Stop` answer capture, `SessionEnd` rollup. It also registers vouch as a local-scope MCP server in `~/.claude.json` (the `⚑` line in the output). **Reload your editor window** (VS Code: *Developer: Reload Window*) so it loads.

> **Why the extra registration?** A committed `.mcp.json` is a *project*-scope server, and Claude Code only loads one after a per-user approval — which the **VS Code extension never prompts for**, so `.mcp.json` alone leaves the `kb_*` tools invisible in the extension (they sit at "pending approval", while the hooks quietly work — easy to misread as "connected"). The local-scope entry `install-mcp` writes is trusted on sight, so a fresh install just connects. Verify with `claude mcp list` (`vouch … ✔ Connected`). Pass `--no-approve` to skip it and approve `.mcp.json` yourself.
Comment on lines +126 to +128

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

scope the following approval instructions to manual installs.

this paragraph says the default command registers a trusted local server, but the next “one-time approval” block still instructs every reader to approve .mcp.json and says install-mcp cannot perform the step. Mark that block as applying only to --no-approve or manual wiring.

🤖 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 `@README.md` around lines 126 - 128, Update the README approval instructions to
apply only to manual wiring or installations using --no-approve. Clarify that
the default install-mcp flow already registers the trusted local-scope server,
and remove any wording implying install-mcp cannot perform that registration;
retain approval steps for users who skip automatic registration.


> **One-time approval.** Claude Code only loads a project's `.mcp.json` servers after a per-user approval. The terminal CLI prompts for it on the next `claude` launch — but the **VS Code extension never shows this prompt**, so the `kb.*` tools stay unavailable there while the hooks keep working (which makes it easy to think vouch is connected when it isn't). If `claude mcp list` run inside the project shows `vouch … ⏸ Pending approval`, either run `claude` in the project folder from any terminal and accept the prompt, or create `.claude/settings.local.json` (user-local, not committed) with `{"enabledMcpjsonServers": ["vouch"]}`. The same key in the committed `.claude/settings.json` is deliberately ignored — a repo can't approve its own servers — which is why `install-mcp` can't do this step for you. Verify with `claude mcp list` (`✔ Connected`), then reload the VS Code window.

Expand All @@ -146,6 +148,8 @@ compile:
vouch review # walk pending proposals one at a time
```

Receipt-verified claims skip the queue by default (`review.auto_approve_on_receipt: true` in the starter config): each session's captured answers become recallable memory with no review pass. What lands in `vouch review` is everything the mechanical check can't vouch for — session-summary pages, entities, relations, and claims that can't quote their source. Set the flag to `false` in `.vouch/config.yaml` to put every write behind the gate.

Comment on lines +151 to +152

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

qualify the “fully human gate” claim.

disabling review.auto_approve_on_receipt does not guarantee that every write requires human approval when the opt-in trusted-agent shortcut remains enabled.

  • README.md#L151-L152: say the flag disables receipt-based approval, while trusted-agent policy may still approve eligible proposals.
  • CHANGELOG.md#L26-L35: replace “fully human gate” with “disable receipt-based auto-approval,” or include the trusted-agent exception.
📍 Affects 2 files
  • README.md#L151-L152 (this comment)
  • CHANGELOG.md#L26-L35
🤖 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 `@README.md` around lines 151 - 152, Qualify the approval behavior in README.md
lines 151-152 by stating that setting review.auto_approve_on_receipt to false
disables receipt-based auto-approval, while trusted-agent policy may still
approve eligible proposals. Update CHANGELOG.md lines 26-35 to replace any
“fully human gate” wording with “disable receipt-based auto-approval” or
explicitly include the trusted-agent exception.

**Want a browser UI for reviewing and proposing?** The video shows the **vouch webapp** — chat, review queue, claims, and stats. Your options:

- **No setup**: the Docker demo above
Expand Down
2 changes: 1 addition & 1 deletion adapters/claude-code/.claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
],
"Stop": [
{
"comment": "save this turn's answer as durable, recallable knowledge — receipt-backed claims, auto-approved only under the review opt-in (trusted-agent / auto_approve_on_receipt); fires every turn but skips short/duplicate answers; never blocks the turn",
"comment": "save this turn's answer as durable, recallable knowledge — receipt-verified claims auto-approve under the starter-config default (review.auto_approve_on_receipt; set false to keep every write behind vouch review); fires every turn but skips short/duplicate answers; never blocks the turn",
"matcher": "*",
"hooks": [
{
Expand Down
8 changes: 5 additions & 3 deletions adapters/claude-code/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ pipx install vouch-kb
Make sure `vouch` is on the `PATH` Claude Code will see.

The one-command path — `vouch install-mcp claude-code` from your project
root — does everything below in one go, and initialises the `.vouch/` KB
root — does everything below in one go, initialises the `.vouch/` KB
first when the project doesn't have one yet (`vouch init` also does that
on its own; `--no-init` skips it). The rest of this file is the manual
equivalent.
on its own; `--no-init` skips it), **and registers vouch in
`~/.claude.json` so the VS Code extension loads it without a manual
approval it never prompts for** (see step 2). The rest of this file is
the manual equivalent.

## 2. Drop the MCP server into your project

Expand Down
21 changes: 21 additions & 0 deletions adapters/claude-code/install.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,32 @@
# T4 = `.claude/settings.json`: SessionStart (kb status + capture review banner +
# recall digest of approved knowledge), PostToolUse (capture observe),
# SessionEnd (capture finalize), plus read-only kb_* auto-allow.
#
# user_mcp = a local-scope MCP registration written to the user's
# `~/.claude.json` under `projects[<abs project>].mcpServers`. The `.mcp.json`
# from T1 is a *project*-scope server, and Claude Code only loads a
# project-scope server after a per-user approval — which the VS Code
# extension never surfaces a prompt for, so `.mcp.json` alone leaves the
# `kb_*` tools invisible in the extension (they sit at "pending approval").
# A local-scope entry is trusted on sight (no approval gate), so writing one
# is what makes a fresh install actually connect in the extension. This is
# exactly what `claude mcp add vouch -- vouch serve` does; install-mcp does
# it for you (opt out with `--no-approve`).
host: claude-code
pretty: Claude Code
fence:
begin: "<!-- BEGIN vouch -->"
end: "<!-- END vouch -->"
user_mcp:
config: .claude.json # relative to the user's home directory
scope: project # keyed under projects[<abs project>].mcpServers
name: vouch
spec:
type: stdio
command: vouch
args: ["serve"]
env:
VOUCH_AGENT: claude-code
tiers:
T1:
- { src: .mcp.json, dst: .mcp.json }
Expand Down
15 changes: 10 additions & 5 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,16 @@ vouch approve prop-abc123 --reason "matches the meeting notes"
The claim is now durable. The proposal moves to
`.vouch/decided/prop-abc123.yaml` (committed, for audit).

**Note on approval:** By default, vouch requires human approval and
prevents self-approval (a proposer cannot approve their own proposal).
For local testing, you can add `approver_role: trusted-agent` to
`.vouch/config.yaml`. Production deployments should keep the default
`require_human_approval: true` to preserve the review gate.
**Note on approval:** vouch prevents blanket self-approval (a proposer
cannot approve their own proposal), with one mechanical exception that
the starter config enables by default: a claim whose byte-offset
receipts all verify against their source is auto-approved — the receipt
is the reviewer (`review.auto_approve_on_receipt: true`). Everything
else — pages, entities, relations, and any claim that cannot quote its
source — still waits for a human. Set `auto_approve_on_receipt: false`
in `.vouch/config.yaml` to put every write behind `vouch review`, or
`approver_role: trusted-agent` to let an agent approve anything (local
testing only).

```bash
git add .vouch && git commit -m "kb: approve auth-uses-jwt"
Expand Down
41 changes: 30 additions & 11 deletions src/vouch/capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -481,8 +481,9 @@ def capture_answer(
receipt-backed claim per quotable span (``extract.extract_receipt_claims``),
and approves each one the review gate allows — self-approval clears under
``review.approver_role: trusted-agent`` or, for these verbatim-quoting
claims, ``review.auto_approve_on_receipt``. With neither gate on the claims
stay pending: the review gate is honoured, never bypassed.
claims, ``review.auto_approve_on_receipt`` (the starter-config default).
With neither gate on the claims stay pending: the review gate is
honoured, never bypassed.

Idempotent and quiet by design: an answer already ingested (same bytes) is
skipped, and answers shorter than ``min_answer_chars`` (acknowledgements)
Expand All @@ -493,7 +494,6 @@ def capture_answer(

from . import extract as extract_mod
from . import proposals as proposals_mod
from .proposals import ProposalError
from .storage import ArtifactNotFoundError, sha256_hex

# vouch's own LLM subprocesses set this so the agent session they spawn does
Expand Down Expand Up @@ -531,15 +531,14 @@ def capture_answer(
)
approved = 0
for result in filed:
try:
proposals_mod.approve(
store, result.proposal.id, approved_by=ANSWER_ACTOR,
reason="auto-captured session answer (receipt verified)",
)
# approves under the gate, rejects duplicates of durable claims,
# leaves everything else pending — see resolve_pending_receipt_claim.
claim = proposals_mod.resolve_pending_receipt_claim(
store, result.proposal, actor=ANSWER_ACTOR,
reason="auto-captured session answer (receipt verified)",
)
if claim is not None:
approved += 1
except ProposalError:
# gate closed (no trusted-agent, no receipt opt-in): leave pending.
pass
return {
"captured": True, "skipped": None, "session_id": session_id,
"source": source.id, "filed": len(filed), "approved": approved,
Expand Down Expand Up @@ -568,6 +567,23 @@ def is_stale_buffer(
return age > max_age_seconds


def _drain_receipt_backlog(store: KBStore) -> int:
"""Auto-approve pending receipt-verified claims; count them.

With ``review.auto_approve_on_receipt`` on (the starter-config default)
this clears any backlog of verifiable claims left pending while the gate
was off — e.g. a kb that flipped the flag after capturing. No-op when the
gate is off, and never fatal: it runs from a SessionStart hook that must
not break the session.
"""
from . import proposals as proposals_mod

try:
return len(proposals_mod.auto_approve_receipts(store))
except Exception:
return 0


def finalize_all_except(
store: KBStore,
current_session_id: str,
Expand All @@ -582,6 +598,7 @@ def finalize_all_except(
- finalized: [session_id1, session_id2, ...] session IDs that were finalized
- skipped_recent: [id3, id4, ...] sessions too recent to finalize
- skipped_current: [id5] the current session (always skipped)
- auto_approved: count of pending receipt-verified claims drained on the way
"""
finalized: list[str] = []
skipped_recent: list[str] = []
Expand All @@ -594,6 +611,7 @@ def finalize_all_except(
"finalized": finalized,
"skipped_recent": skipped_recent,
"skipped_current": skipped_current,
"auto_approved": _drain_receipt_backlog(store),
}

for path in sorted(caps_dir.glob("*.jsonl")):
Expand Down Expand Up @@ -621,4 +639,5 @@ def finalize_all_except(
"finalized": finalized,
"skipped_recent": skipped_recent,
"skipped_current": skipped_current,
"auto_approved": _drain_receipt_backlog(store),
}
33 changes: 30 additions & 3 deletions src/vouch/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2419,8 +2419,9 @@ def capture_answer_cmd(session_id: str | None) -> None:

The Stop hook emits {session_id, transcript_path} on stdin; the session's
answer is ingested as a source and its receipt-backed claims are
auto-approved under the review opt-in (trusted-agent / auto_approve_on_receipt),
else left pending. Always exits 0 so a capture failure can never break the turn.
auto-approved when review.auto_approve_on_receipt is on (the
starter-config default) or under trusted-agent, else left pending.
Always exits 0 so a capture failure can never break the turn.
"""
if sys.stdin.isatty():
return
Expand Down Expand Up @@ -3923,13 +3924,23 @@ def pr_cache_show(repo: str, state: str, limit: int, as_json: bool, cache_dir: s
help="Initialise a .vouch/ KB at the target when none is discoverable, "
"so install-mcp is a one-command setup.",
)
@click.option(
"--approve/--no-approve",
"approve",
default=True,
show_default=True,
help="Register vouch as a local-scope MCP server in ~/.claude.json so the "
"Claude Code extension loads it without a manual approval it never prompts "
"for. --no-approve leaves only the project .mcp.json (needs manual approval).",
)
def install_mcp(
host: str | None,
list_hosts: bool,
path: str,
target_alias: str | None,
tier: str,
auto_init: bool,
approve: bool,
) -> None:
"""Install vouch into HOST (claude-code, cursor, …) idempotently.

Expand Down Expand Up @@ -4006,7 +4017,7 @@ def install_mcp(
if kb_root != target:
click.echo(f"Using existing KB at {kb_root / '.vouch'}")
try:
result = install_mod.install(host, target=target, tier=tier)
result = install_mod.install(host, target=target, tier=tier, approve=approve)
except install_mod.AdapterError as e:
raise click.ClickException(str(e)) from e

Expand All @@ -4016,16 +4027,32 @@ def install_mcp(
click.echo(f" ~ {f} (appended fenced block)")
for f in result.merged:
click.echo(f" ~ {f} (merged into existing)")
for f in result.registered:
click.echo(f" ⚑ {f} (registered in ~/.claude.json)")
for f in result.skipped:
click.echo(f" · {f} (already present)")
for f in result.failed:
click.echo(f" ✗ {f} (could not install — left unchanged)")
click.echo(
f"Done — {len(result.written)} written, "
f"{len(result.appended)} appended, {len(result.merged)} merged, "
f"{len(result.registered)} registered, "
f"{len(result.skipped)} skipped, {len(result.failed)} failed "
f"under {target}"
)
if result.registered:
# the extension reads MCP servers once, at window start — a running
# window won't see the new registration until it reloads.
click.echo(
"Reload your editor window so it picks up the vouch MCP server "
"(VS Code: Developer: Reload Window)."
)
elif approve and host == "claude-code" and not result.failed:
# already registered on a prior run — still remind, harmlessly.
click.echo(
"vouch is registered for Claude Code. If the kb_* tools aren't "
"visible, reload your editor window."
)
if result.failed:
# a failed install is not a no-op: exit non-zero so scripts (and the
# user) notice vouch was NOT wired into these files.
Expand Down
Loading
Loading