feat(cli): pearlite reconcile --commit / --adopt-all + prompt loop#71
Merged
UnbreakableMJ merged 3 commits intomainfrom May 7, 2026
Merged
feat(cli): pearlite reconcile --commit / --adopt-all + prompt loop#71UnbreakableMJ merged 3 commits intomainfrom
UnbreakableMJ merged 3 commits intomainfrom
Conversation
Adds two `Vec<String>` fields to `pearlite_state::ReconciliationEntry`, both `#[serde(default)]` so pre-ADR `[[reconciliations]]` rows deserialize cleanly with empty vectors. Tightens `package_count`'s docstring to name it as the audit denominator (ADR-0014 §9). Backfills three round-trip tests in `reconciliation::tests`: - legacy entry without decision vectors deserializes with empty `[]`, - populated decision vectors round-trip through TOML, - AdoptAll with empty `skipped` round-trips. Schema change is additive; the `ReconciliationAction` unit-variant enum (AdoptAll / Interactive / Skipped) keeps its existing TOML representation per ADR-0014 §7. Refs: ADR-0014, PRD §7.3 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 tasks
Adds the write-side of reconcile (ADR-0014, M4 W1):
- `Engine::reconcile_commit(state_path, &decisions, threshold)` —
probes via the existing adapter, classifies Manual drift through
`pearlite_diff::{classify_pacman, classify_cargo}` against an empty
declared `PackageSet` (the fresh-import path from PRD §11), enforces
the `Some(N)` threshold defensively, unions adopted names into
`state.adopted.{pacman,cargo}`, and appends one `[[reconciliations]]`
row with a fresh `Uuid::now_v7()`. Atomic write via `StateStore`;
`state.last_modified` set, `state.last_apply` deliberately untouched.
- `Engine::probe_manual_drift(state_path)` — read-only helper that
returns the merged sorted-deduplicated Manual list. The CLI uses it
to drive the threshold pre-check and per-package prompt loop without
reaching into the engine's private probe accessor.
- `ReconcileDecisions` enum (`AdoptAll` / `Selective { adopt }`) and
`ReconcileCommitOutcome` mirror the ADR §7 split: enum carries the
*policy*, vectors carry the *decisions*.
- `ReconcileCommitError::{Probe, State, ThresholdExceeded}` for the
three failure modes; threshold variant carries `count` and
`threshold` so the CLI can surface ADR-0014 §2 wording verbatim.
Module doc now describes both reconcile entry points (read-only
import + commit). Ten new tests in `reconcile::tests`: AdoptAll happy
path, Selective partition, threshold exceeded refuses without
writing, threshold boundary allowed, `state.managed` packages stay
classified as Forgotten (not adopted), existing `state.adopted`
preserved on union, no-Manual-drift still records empty entry,
probe / state-read failure propagation, fresh `plan_id` per call.
Refs: ADR-0014, PRD §11
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires the engine's reconcile-commit through the CLI (ADR-0014, M4 W1): - New `agents` module (`pearlite_cli::agents::is_non_interactive`) per ADR-0014 §6 — M4 stub returning `!stdin.is_terminal() || AI_AGENT.is_some()`. The full AGENT/CI/CLAUDECODE matrix lands in M5 W2 (TODO comment marks the spot). - `Reconcile` arg becomes a struct variant: `--commit`, `--adopt-all` (`requires = "commit"`), `--commit-threshold <Option<u32>>`. Clap enforces the requires-relationship; help text points at ADR-0014. - `dispatch_reconcile_commit` — non-interactive refusal first (`RECONCILE_REQUIRES_INTERACTIVE`, exit 2), then either bulk (`AdoptAll`) → engine, or interactive: probe + classify via `Engine::probe_manual_drift` → CLI threshold gate (`RECONCILE_THRESHOLD_EXCEEDED`, exit 2, ADR-0014 §2 wording — count + threshold + fresh-install case + `--adopt-all` pointer) → per-package prompt loop with `[y]es / [N]o (default) / [a]dopt-all / [s]kip-all / [q]uit` (ADR-0014 §4) → engine call with `Selective`. `q` aborts byte-identically; bare-Enter defaults to skip; EOF mid-loop is treated as quit. - `effective_threshold()` codifies the four-way ADR §3 mapping: bare `--commit` → default 5; `--commit-threshold N` → Some(N); bare `--adopt-all` → unbounded; `--adopt-all --commit-threshold N` → Some(N). - `ReconcileCommitError` → typed `ErrorPayload` so engine-side `ThresholdExceeded` (e.g. `--adopt-all --commit-threshold 3`) surfaces with the same `RECONCILE_THRESHOLD_EXCEEDED` code as the CLI-side gate. Prompt loop is generic over `<R: BufRead, W: Write>` so tests script stdin/stderr without a TTY. 18 new tests cover the four ADR table entries, all five prompt menu paths (y/N/a/s/q), EOF-as-quit, and end-to-end dispatch arms (non-interactive refusal, threshold exceeded refuses without writing, threshold boundary allowed via `a`, `q` aborts byte-identically, label reflects `--commit`). Refs: ADR-0014, PRD §11 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
d9393be to
34ca1bd
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Wires the engine's reconcile-commit through the CLI (ADR-0014, M4 W1):
agentsmodule (pearlite_cli::agents::is_non_interactive) per ADR-0014 §6 — M4 stub returning!stdin.is_terminal() || AI_AGENT.is_some(). Full AGENT/CI/CLAUDECODE matrix lands in M5 W2 (TODO comment marks the spot).Reconcilearg becomes a struct variant:--commit,--adopt-all(requires = "commit"),--commit-threshold <Option<u32>>. Clap enforces the requires-relationship.dispatch_reconcile_commit— non-interactive refusal first (RECONCILE_REQUIRES_INTERACTIVE, exit 2), then either bulk (AdoptAll) → engine, or interactive: probe + classify viaEngine::probe_manual_drift→ CLI threshold gate (RECONCILE_THRESHOLD_EXCEEDED, exit 2, ADR-0014 §2 wording — count + threshold + fresh-install case +--adopt-allpointer) → per-package prompt loop with[y]es / [N]o (default) / [a]dopt-all / [s]kip-all / [q]uit(ADR-0014 §4) → engine call withSelective.qaborts byte-identically; bare-Enter defaults to skip; EOF mid-loop is treated as quit.effective_threshold()codifies the four-way ADR §3 mapping: bare--commit→ default 5;--commit-threshold N→ Some(N); bare--adopt-all→ unbounded;--adopt-all --commit-threshold N→ Some(N).ReconcileCommitError→ typedErrorPayloadso engine-sideThresholdExceeded(e.g.--adopt-all --commit-threshold 3) surfaces with the sameRECONCILE_THRESHOLD_EXCEEDEDcode as the CLI-side gate.Prompt loop is generic over
<R: BufRead, W: Write>so tests script stdin/stderr without a TTY.Stacked on #70
Builds on
Engine::reconcile_commitfrom #70, which builds on #69. Until those merge, this PR's diff includes their commits too.Test plan
cargo test -p pearlite-cli— 57/57 passing (18 new: 7 prompt loop, 1 effective-threshold table, 7 dispatch arms, plus the agents smoke test, the--adopt-all-with-threshold engine-error mapping, and the label-reflects---commitassertion).cargo clippy --workspace --all-targets -- -D warnings— clean.cargo run -p pearlite-audit -- check .— 0 violations.pearlite reconcile --helpshows the new flags;pearlite reconcile --adopt-allerrors with--commit required(clap's requires).Refs: ADR-0014, PRD §11
🤖 Generated with Claude Code