Skip to content

feat(review): headless JSON verbs + guided assumption-review console - #162

Merged
pofallon merged 2 commits into
mainfrom
053-assumption-review-console
Jul 25, 2026
Merged

feat(review): headless JSON verbs + guided assumption-review console#162
pofallon merged 2 commits into
mainfrom
053-assumption-review-console

Conversation

@pofallon

Copy link
Copy Markdown
Contributor

Implements specs/053-assumption-review-console (all 27 tasks).

Expose the assumption-review lifecycle as headless --json verbs, and add a packaged Claude Code skill that drives them — so clearing the review queue no longer requires a human sitting at a bare terminal.

What's here

US1 — headless JSON verbs. Every review-lifecycle verb is invocable with --json:

Verb Command
review.list review --list [--status|--spec|--severity]...
review.answer / review.waive review <id> --answer/--waive
review.bulk-waive review --spec <s> --waive <reason>
reconcile.run / reconcile.dry-run reconcile [--dry-run]
land.status land --status (new read-only frontier query)
land.run land

They share one envelope and error-kind registry (cli/json_output.py), and one canonical entry-row projection (assumptions/serialize.py) used verbatim by both review --list and the land report, so the two surfaces can't drift.

US2 — guided console. A packaged maverick-review skill, installed by maverick init and removed by maverick uninstall. It sweeps the open queue one entry at a time via AskUserQuestion, applies each decision immediately through the JSON verbs, and never touches jj/git/bd directly.

US3 — batched flow. One reconcile after the sweep (not per answer), then a frontier report, then landing offered only on explicit human confirmation.

maverick review without --json is unchanged (FR-018) — it remains the fallback for humans without Claude Code.

Review fixes folded in

A code review of the above surfaced 15 findings, all fixed here. The load-bearing ones:

  • JSON output was being corrupted. write_json_document went through a Rich Console with emoji substitution left on, so any :name: run inside a string value was rewritten to a unicode emoji. Agent-authored questions and answers hit this routinely, and review --answer "<adopted_answer>" then wrote the mutated text back into the ledger.
  • A committed write could be reported as a failure. The post-write projection re-read sat inside json_error_handler, so a transient bd hiccup after the ledger write turned into ok: false, kind: bd-unavailable — telling the caller a recorded decision wasn't recorded. The re-read now fails soft (degraded: true).
  • An unreadable ledger looked like a clear frontier. The gate degrades open and materializes zero entries, making frontier_clear trivially true. Added a top-level degraded flag; the skill and contracts now require frontier_clear && !degraded before offering to land.
  • bd-unavailable was reported as validation on two review paths, which the skill's preflight branches on.
  • WorkflowError.reason_code replaces prose substring-matching for precondition classification, so rewording a message can't silently reclassify it as internal.

Plus: land.run's nothing-to-land return now emits the full result shape; heuristic curation reports absorb_ran/squashed_count (absorb rewrites history without squashing anything); a new cli/commands/land_gate.py removes a private-surface import cycle between land and land --status and takes land.py back under the module-size guardrail; the skill install path is shared and written atomically; bd-readiness is one shared predicate.

Pre-existing bug found along the way: nothing ever set ctx.obj["verbosity"] — the root group sets ctx.obj["verbose"] — so verbose progress rendering was dead for every workflow, not just this feature's paths. Fixed at the source.

Verification

make ci green: format, lint, mypy, and 4379 passed / 1 xfailed. Also smoke-tested end to end against real bd/jj in a scratch repo (seeded ledger entries; exercised list/answer/waive/already-resolved/reconcile/land-status), which is how the reconcile.status documentation fix in land-report-schema.md was confirmed.

Two caveats worth naming:

  • tests/integration/workflows/test_reconcile_jj.py and tests/integration/test_assumption_ledger_flow.py intermittently hit the 30s pytest timeout under parallel load with real jj/bd subprocesses. They pass in isolation and are unaffected by this branch — pre-existing flakiness that deserves its own look.
  • The skill's guided sweep was validated by tracing its instructions against every acceptance criterion, not by a live /maverick-review session (no interactive Claude Code UI in this environment). Worth one real run against a seeded project before fully trusting the UX.

🤖 Generated with Claude Code

https://claude.ai/code/session_012av72R5SPCc84chsUNjhLC

Paul O'Fallon and others added 2 commits July 25, 2026 22:57
Expose the assumption-review lifecycle as headless `--json` verbs and add
a packaged Claude Code skill that drives them, so clearing the review
queue no longer requires a human at a bare terminal.

Implements specs/053-assumption-review-console (all 27 tasks).

US1 — headless JSON verbs:
- `review --list [--status|--spec|--severity]...` (verb `review.list`),
  filtered and canonically ordered server-side.
- `review <id> --answer/--waive --json`, `review --spec <s> --waive --json`.
- `reconcile --json` / `reconcile --dry-run --json`.
- `land --status --json` (new read-only frontier query) and `land --json`.
- One shared envelope + error-kind registry (`cli/json_output.py`); one
  canonical entry-row projection (`assumptions/serialize.py`) shared
  verbatim by `review --list` and the land report so they can't drift.

US2 — guided console:
- `maverick-review` skill, installed by `maverick init`, removed by
  `maverick uninstall`. Sweeps the open queue one entry at a time, applies
  each decision immediately through the JSON verbs above, then runs
  reconcile once and reports the frontier.

US3 — batched reconcile / frontier / landing flow, with landing offered
only on explicit human confirmation.

Human-mode output is unchanged throughout (FR-018).

Also addresses a code review of the above:
- `write_json_document` disables Rich emoji substitution — `:key:`-shaped
  runs in agent-authored questions and answers were being silently
  rewritten to unicode emoji on every JSON verb.
- Post-write projection re-reads moved outside `json_error_handler`, so a
  transient read failure after a committed ledger write is reported as
  `degraded`, not as a failed verb.
- bd-availability preconditions report `bd-unavailable` on every verb
  (was `validation` on two paths).
- `land --status`/`land --json` gained a top-level `degraded` flag: an
  unreadable ledger made `frontier_clear` trivially true and therefore
  indistinguishable from a verified frontier.
- `land.run`'s nothing-to-land early return emits the full result shape.
- Heuristic curation reports `absorb_ran`/`squashed_count` so an
  absorb-only rewrite isn't indistinguishable from a no-op.
- `WorkflowError.reason_code` replaces prose substring matching for
  precondition classification (markers kept as a fallback).
- New `cli/commands/land_gate.py` holds the gate/report logic shared by
  `land` and `land --status`, removing a private-surface import cycle and
  taking land.py back under the module-size guardrail.
- Skill install path shared via `maverick.skills`, written atomically;
  bd-readiness factored into one `bd_ready_reason()` predicate.

Fixes a pre-existing bug found along the way: nothing ever set
`ctx.obj["verbosity"]` (the root group sets `ctx.obj["verbose"]`), so
verbose progress rendering was dead for every workflow. Now resolved via
a shared `resolve_verbosity()`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012av72R5SPCc84chsUNjhLC
Rich wraps at the terminal width by default, so `maverick uninstall
--dry-run` split long file paths mid-segment and left them uncopyable.
`soft_wrap=True` keeps each path on one line.

Caught by CI, whose tmp directories are longer than a dev box's — the
existing assertion passed locally and failed there. The replacement test
constructs a path wider than Rich's default width so it fails regardless
of environment.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012av72R5SPCc84chsUNjhLC
@pofallon
pofallon merged commit d841ba3 into main Jul 25, 2026
2 checks passed
@pofallon
pofallon deleted the 053-assumption-review-console branch July 25, 2026 23:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant