Commit 2c839cc
feat(cli): pearlite reconcile --commit / --adopt-all + prompt loop (#71)
* feat(state): extend ReconciliationEntry with adopted/skipped vectors
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>
* feat(engine): Engine::reconcile_commit and probe_manual_drift
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>
* feat(cli): pearlite reconcile --commit / --adopt-all + prompt loop
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>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent 34f75f0 commit 2c839cc
4 files changed
Lines changed: 899 additions & 25 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
165 | 165 | | |
166 | 166 | | |
167 | 167 | | |
168 | | - | |
169 | | - | |
170 | | - | |
171 | | - | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
172 | 172 | | |
173 | 173 | | |
174 | 174 | | |
175 | | - | |
176 | | - | |
177 | | - | |
178 | | - | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
179 | 203 | | |
180 | 204 | | |
181 | 205 | | |
| |||
0 commit comments