Skip to content
Open
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
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ All notable changes to vouch are documented here. Format follows

## [Unreleased]

### Added
- `kb.triage_pending` — advisory triage scoring over the pending-review queue.
Scores each pending proposal on fit, citation quality, duplication risk, and
contradiction risk, then attaches a `_meta.vouch_triage` block
(`recommendation`, `score`, `signals`, `rationale`) to help a reviewer
prioritize a long `kb.list_pending`. Read-only and advisory only: it never
calls `kb.approve` / `kb.reject` and never moves a proposal out of pending —
a human still decides. Duplication and fit reuse the propose-time embedding
similarity path and degrade to a `difflib` heuristic when the `[embeddings]`
extra isn't installed. Opt-in via `triage.enabled: true` in `config.yaml`.
`vouch triage [proposal-id...]` mirrors it on the CLI with `--json` and
`--reverse` (#322).

## [1.1.0] — 2026-07-03

### Added
Expand Down
1 change: 1 addition & 0 deletions src/vouch/capabilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"kb.list_relations",
"kb.list_sources",
"kb.list_pending",
"kb.triage_pending",
"kb.register_source",
"kb.register_source_from_path",
"kb.propose_claim",
Expand Down
46 changes: 46 additions & 0 deletions src/vouch/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,52 @@ def show(proposal_id: str) -> None:
click.echo(yaml.safe_dump(pr.model_dump(mode="json"), sort_keys=False))


@cli.command()
@click.argument("proposal_ids", nargs=-1)
@click.option(
"--json", "as_json", is_flag=True,
help="Emit machine-readable _meta.vouch_triage blocks.",
)
@click.option(
"--reverse", is_flag=True,
help="Ascending order (worst-first) instead of the default descending (best-first).",
)
def triage(proposal_ids: tuple[str, ...], as_json: bool, reverse: bool) -> None:
"""Advisory triage scoring over pending proposals (opt-in: triage.enabled).

Scores each proposal on fit, citation quality, duplication risk, and
contradiction risk, then prints a ranked table. Never approves or
rejects — a human still decides via `vouch approve` / `vouch reject`.
"""
from . import triage as triage_mod

store = _load_store()
with _cli_errors():
results = triage_mod.triage_pending(store, proposal_ids=list(proposal_ids) or None)
results.sort(key=lambda r: r["_meta"]["vouch_triage"]["score"], reverse=not reverse)

if as_json:
_emit_json(results)
return
if not results:
click.echo("no pending proposals to triage")
return
for r in results:
block = r["_meta"]["vouch_triage"]
preview = (
r["payload"].get("text")
or r["payload"].get("title")
or r["payload"].get("name")
or r["payload"].get("id")
or "-"
)
click.echo(
f"{block['score']:.2f} [{block['recommendation']:>11}] "
f"{r['id']} [{r['kind']}] {str(preview).strip()[:80]}"
)
click.echo(f" {block['rationale']}")


@cli.command()
@click.argument("proposal_ids", nargs=-1, required=True)
@click.option("--reason", default=None)
Expand Down
7 changes: 7 additions & 0 deletions src/vouch/jsonl_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,12 @@ def _h_list_pending(_: dict) -> list[dict]:
]


def _h_triage_pending(p: dict) -> list[dict]:
from . import triage as triage_mod

return triage_mod.triage_pending(_store(), proposal_ids=p.get("proposal_ids"))


def _h_register_source(p: dict) -> dict:
s = _store()
src = s.put_source(
Expand Down Expand Up @@ -691,6 +697,7 @@ def _h_propose_theme(p: dict) -> dict:
"kb.list_relations": _h_list_relations,
"kb.list_sources": _h_list_sources,
"kb.list_pending": _h_list_pending,
"kb.triage_pending": _h_triage_pending,
"kb.register_source": _h_register_source,
"kb.register_source_from_path": _h_register_source_from_path,
"kb.propose_claim": _h_propose_claim,
Expand Down
17 changes: 17 additions & 0 deletions src/vouch/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,23 @@ def kb_list_pending() -> list[dict[str, Any]]:
]


@mcp.tool()
def kb_triage_pending(proposal_ids: list[str] | None = None) -> list[dict[str, Any]]:
"""Advisory triage scoring over the pending-review queue.

Attaches `_meta.vouch_triage` (recommendation/score/signals/rationale)
to each pending proposal's view. Read-only — never approves, rejects,
or otherwise decides; a human still calls `kb_approve` / `kb_reject`.
Opt-in: disabled unless `triage.enabled: true` is set in config.yaml.
"""
from . import triage as triage_mod

try:
return triage_mod.triage_pending(_store(), proposal_ids=proposal_ids)
except (ValueError, ArtifactNotFoundError) as e:
raise ValueError(str(e)) from e


# === write tools — gated (produce proposals) =============================


Expand Down
Loading
Loading