Skip to content

feat(cli): pearlite reconcile --commit / --adopt-all + prompt loop#71

Merged
UnbreakableMJ merged 3 commits intomainfrom
feat/m4-w1-reconcile-commit-cli
May 7, 2026
Merged

feat(cli): pearlite reconcile --commit / --adopt-all + prompt loop#71
UnbreakableMJ merged 3 commits intomainfrom
feat/m4-w1-reconcile-commit-cli

Conversation

@UnbreakableMJ
Copy link
Copy Markdown
Contributor

Summary

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(). 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.
  • 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.

Stacked on #70

Builds on Engine::reconcile_commit from #70, which builds on #69. Until those merge, this PR's diff includes their commits too.

Test plan

Refs: ADR-0014, PRD §11

🤖 Generated with Claude Code

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>
UnbreakableMJ and others added 2 commits May 7, 2026 18:20
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>
@UnbreakableMJ UnbreakableMJ force-pushed the feat/m4-w1-reconcile-commit-cli branch from d9393be to 34ca1bd Compare May 7, 2026 15:39
@UnbreakableMJ UnbreakableMJ merged commit 2c839cc into main May 7, 2026
3 checks passed
@UnbreakableMJ UnbreakableMJ deleted the feat/m4-w1-reconcile-commit-cli branch May 7, 2026 15:42
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