Review system architecture (commonplace.review + commonplace.cli.review)
Type: kb/types/note.md · Status: current
The review system runs LLM-based quality reviews against KB notes using defined review gates. It tracks provenance (note and gate versions), manages acceptance state, and detects staleness.
For the review workflow and gate definitions, see ../instructions/REVIEW-SYSTEM.md. This document covers the code architecture.
Sweep commands are experimental.
review_sweep,run_gate_sweep,gate_sweep_format, andack_trivial_note_changesare not yet stabilized and are intentionally not documented in detail here. Treat their interfaces as subject to change.
Package layout
The review subsystem is split between two packages, mirroring the project-wide commonplace.cli ↔ commonplace.lib split:
commonplace.review— library code only. Pure functions, dataclasses, the SQLite layer, the runner subprocess wrappers. Nomain()functions, no argparse, noPath.cwd()at import time. Importable from any caller without pulling in CLI machinery.commonplace.cli.review— thin CLI wrappers. Each module is argparse +Path.cwd()+prepare_review_db(...)+ one library call. Thecommonplace-*review entry points inpyproject.tomlall live here.
For most modules the lib name and the CLI name match. For example commonplace.review.run_review_bundle is the library that owns the create_run → runner → record_and_finalize_run pipeline; commonplace.cli.review.run_review_bundle is the thin wrapper that argparse-parses the user's invocation and calls into the lib. Two modules (resolve_gates and review_target_selector) exist in both packages because they were split during the layout migration: lib helpers stayed in commonplace.review.*, the CLI main() moved to commonplace.cli.review.*.
This document refers to modules by their unqualified names (e.g. run_review_bundle, review_target_selector) when the distinction doesn't matter. When a particular function or class is called out, it lives in the library package unless explicitly noted as a CLI.
Architecture overview
┌─────────────────────────────────────────────────┐
│ Orchestration │
│ run_review_bundle create/finalize/ingest │
├─────────────────────────────────────────────────┤
│ Gate resolution & targeting │
│ resolve_gates review_target_selector │
│ warn_selector │
├─────────────────────────────────────────────────┤
│ Execution │
│ review_runners │
├─────────────────────────────────────────────────┤
│ Data model │
│ review_db review_model │
│ review_metadata review-schema.sql │
│ paths │
└─────────────────────────────────────────────────┘
Sweep-related modules (review_sweep, run_gate_sweep, gate_sweep_format, ack_trivial_note_changes) exist alongside these layers but are experimental — see the note above.
Data model
Database schema (review-schema.sql)
SQLite database, default location kb/reports/review-store.sqlite (override with COMMONPLACE_REVIEW_DB env var).
Core tables:
| Table | Purpose | Key columns |
|---|---|---|
review_runs |
Execution records | id, note_path, model_id, runner, status (running/completed/failed), provenance SHAs |
review_run_gates |
Gate set captured at run start | review_run_id, gate_id, gate_sha, ordinal |
gate_reviews |
Individual gate outcomes | review_run_id, note_path, gate_id, decision (pass/warn/fail/error/unknown), rationale_markdown |
acceptance_events |
Append-only acceptance log | note_path, gate_id, model_id, accepted_review_id, acceptance_kind |
Key views:
current_gate_acceptances— latest acceptance for each (note, gate, model) triplestale_gate_pairs— current acceptance states for staleness detection
Acceptance kinds: full-review, gate-migration, trivial-change-ack, migration-import, manual-override
review_model.py
Encode and normalize model identifiers with reasoning effort levels.
encode_model(model) -> str— sanitize model names (lowercase, special chars → hyphens)normalize_model_id(model_id) -> str— collapse known aliases such asopus-4-6into canonical review partitionsnormalize_reasoning_effort(raw) -> str | None— validate from {low, medium, high, xhigh}build_model_id(model, reasoning_effort) -> str— canonical ID like"claude-3-5-sonnet-xhigh"
review_metadata.py
Git-backed provenance tracking and metadata block management.
Provenance functions:
- review_note_provenance(repo_root, path) -> (blob_sha, commit | None) — get the review baseline for a note; commit=None means the baseline came from the current worktree
- committed_file_provenance(repo_root, path, *, kind) -> (blob_sha, commit) — generic file provenance
- blob_sha_at_commit(repo_root, commit, path) -> str | None — file SHA at a specific commit
- file_text_at_commit(repo_root, commit, path) -> str | None — file content at a commit
Metadata blocks:
- parse_review_metadata(review_text) -> ReviewMetadata | None — extract <!-- REVIEW-METADATA ... --> blocks
- render_review_metadata(metadata) -> str — generate metadata block
- inject_review_metadata(review_text, metadata) -> str — insert/update metadata block
paths.py
Filesystem path constants used across the review subsystem. Currently exposes GATES_ROOT = Path("kb/instructions/review-gates"). Lives in its own module so the data layer (review_db.py) doesn't have to own filesystem constants about gate locations.
review_db.py
Database operations, decision parsing, and record management. The largest module in the package.
Connection & setup:
- connect(db_path) -> Connection — open with Row factory
- resolve_db_path(repo_root, db_override=None) -> Path — resolve DB location, honoring an optional --db override
- ensure_db(repo_root, db_path) — initialize from schema if needed
- prepare_review_db(repo_root, db_override=None) -> Path — convenience helper that resolves and ensures in one call; collapses the four-step bootstrap that every CLI used to open-code
Note relocation helpers (called by commonplace.lib.relocation):
- count_note_path_records(conn, *, note_path) -> NotePathUpdateCounts — how many review_runs/gate_reviews/acceptance_events rows reference a note path
- rekey_note_path(conn, *, old_note_path, new_note_path) -> NotePathUpdateCounts — update those rows in place when a note moves
CRUD operations:
- insert_review_run(conn, ...) -> int — create run, return ID
- insert_gate_review(conn, ...) -> int — record gate outcome
- append_acceptance_event(conn, ...) -> int — record acceptance
- complete_review_run(conn, ...) / fail_review_run(conn, ...) — finalize run status
- load_review_run / load_gate_reviews_for_run / load_gate_reviews_for_note — query helpers
- load_effective_gate_review_map(conn, ...) -> dict — accepted-or-latest reviews per gate
- load_current_acceptances(conn) -> dict — current acceptance state map
Lifecycle helpers:
- create_run(conn, ...) -> int — create the run row plus its captured gate set
- attach_execution_data(conn, ...) — persist telemetry, raw bundle markdown, and debug log
- record_and_finalize_run(conn, ...) -> int — optionally insert gate reviews, rekey to the actual model, validate coverage, complete the run, and append acceptance events
Decision parsing (parse_review_decision):
Multi-strategy fallback chain for extracting decisions from review markdown:
- Explicit flagging phrases ("flagging as fail")
- Revised-result headers
- Standard result headers (
## Result: PASS) - Finding severity patterns
- Bold decision patterns
- Legacy format detection
- Falls back to
"unknown"
Core operations
Review execution flow
1. resolve_gates → expand bundle names to gate IDs, filter by note type and traits
2. create_run → insert review_run + review_run_gates (status=running)
3. review_runners → invoke claude-code or codex CLI with review prompt
4. attach_execution_data → persist runner artifacts and telemetry
5. extract results → parse gate blocks from runner output
6. record_and_finalize_run → write gate_reviews, validate coverage, mark run completed, append acceptance_events
run_review_bundle.py — Multi-gate single-note review
The bundle protocol owner for multi-gate single-note review. The subprocess path runs multiple gates against one note in a single LLM invocation; the live-agent path renders the same prompt and ingests the same sentinel-delimited bundle format.
- Resolves markdown links in the note so the reviewer sees linked content
- Builds a structured prompt with the target note identity, pre-resolved link table, and gate definitions
- Parses output delimited by
=== GATE REVIEW START/END: {gate-id} === - Records individual gate_reviews and acceptance_events
The live-agent entry points reuse this protocol without launching a nested runner:
commonplace-create-review-run --with-promptcreates the run and writes the canonical prompt tokb/reports/bundle-reviews/review-run-{id}/prompt.md(path returned asprompt_pathin the JSON payload)- the current agent reads
prompt_path, follows it, and writeskb/reports/bundle-reviews/review-run-{id}/bundle-output.md commonplace-ingest-bundle-outputparses that artifact and finalizes throughrecord_and_finalize_run
review_runners.py — LLM execution
Invokes claude-code or codex CLI processes with a review prompt. Captures:
- stdout/stderr
- Exit code
- Session telemetry (token counts, model info) from CLI session logs
Targeting & staleness
review_target_selector.py
Identifies (note, gate) pairs needing review by comparing current vs. accepted provenance:
| Reason | Meaning |
|---|---|
missing-review |
No acceptance exists for this (note, gate, model) triple |
note-changed |
Note blob SHA differs from accepted note SHA |
gate-changed |
Gate blob SHA differs from accepted gate SHA |
Also provides:
- ack_pairs(repo_root, pairs, model) — batch-acknowledge pairs with trivial-change-ack
- list_reviewable_notes(repo_root) / list_current_notes(repo_root) — top-level *.md notes across the configured scan roots (kb/notes/ and kb/reference/), non-recursive, skipping indexes and files without frontmatter
- explicit note-scope expansion for files and directories; directory operands expand direct child *.md files only and skip indexes, files without frontmatter, and content under types/ directories
- note_diff_since() — unified diff between accepted and current note
resolve_gates.py
resolve_to_gate_ids(args, gates_dir) -> list[str]— expand bundle names (e.g.,"prose") to gate files ingates_dir/prose/*.mdapplicable_gate_ids_for_note(note_path, gate_ids, gates_dir) -> list[str]— filter by notetraitsvs gaterequires_trait, and notetypevs gaterequires-type
warn_selector.py
Query effective reviews with decision="warn", skip stale gate revisions and legacy rows without a review_run_id, and extract actionable findings from the ### Findings section.
Bundle prompt format (multi-gate, single note)
=== GATE REVIEW START: {gate-id} ===
### Summary
<prose>
### Findings
- <severity>: <finding>
### Suggested Revision
<optional>
## Result: PASS|WARN|FAIL|ERROR
=== GATE REVIEW END: {gate-id} ===
Repair & migration utilities
These are operational commands for database maintenance. All support --dry-run.
| Command | Purpose |
|---|---|
repair-manual-import-review-results |
Re-infer decisions for legacy manual-import reviews |
reparse-gate-review-decisions |
Re-parse decisions from stored markdown (after parser updates) |
prune-superseded-unknown-manual-import-reviews |
Delete manual-import reviews with decision=unknown that have replacements |