Skip to content
Merged
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ Closes [#121](https://github.com/breferrari/shardmind/issues/121). The guard tha

- **Tests**: `tests/unit/manifest.test.ts` (range parse + reject incl. empty/whitespace → `MANIFEST_VALIDATION_FAILED` and deliberate `*`/`x` match-all; `assertEngineCompatible` matrix — satisfied / lower-bound / too-old / absent / unknown-version skip / prerelease-ahead / match-all) and Layer 1 flow tests refusing `>=99.0.0` before any write across all three commands: install (scenario 12, no `state.json`), update (scenario 18, `state.json` byte-unchanged), adopt (scenario 24, no `.shardmind/`). **Docs**: `docs/AUTHORING.md` (field reference + §6 note), `docs/ERRORS.md` (`SHARDMIND_VERSION_MISMATCH`), `schemas/shard.schema.json` (`requires.shardmind`).

### Changed (adopt values confirm-or-override — #104)

Closes [#104](https://github.com/breferrari/shardmind/issues/104). Adopt is for users who already have a populated vault, but it reused the full step-by-step install wizard to collect the values classification needs (the planner renders the shard's `.njk` against them before hashing). Being interrogated value-by-value reads as a fresh-install flow shoehorned onto a retrofit.

- **`shardmind adopt` opens on a values confirm page** (`source/components/AdoptValuesGate.tsx`) instead of `InstallWizard`. It surfaces the exact values that will drive classification — resolved defaults, computed defaults, and any `--values` prefill, each labelled with its provenance (`default` / `computed` / `from --values`) — plus the module default, with **Use these values / Override individually / Cancel**. No more hidden defaults: the values are shown before classification runs.

- **"Override individually" reuses `InstallWizard` verbatim** (per-value + module editing) rather than forking its value-iteration / computed-preview / module-review logic. A required value with no default and no prefill opens straight into the wizard (no confident set to confirm) — the predicate feeds `missingValueKeys` the *merged* map, exactly as the `--yes` path does, so the common all-defaults shard lands on the confirm page.

- **`--yes` / `--values` unchanged.** Those non-interactive paths resolve values in the machine and never reach the gate, so adopt's scripted contract is byte-identical.

- **Tests**: `tests/component/AdoptValuesGate.test.tsx` (provenance labels, Use → `onComplete` with resolved values + all-default selections, Override → wizard, Cancel, computed-resolve + computed-throw → `onError`, required-no-default → override, zero-values shard); Layer 1 adopt flow scenarios 19/20/21 re-driven through the confirm gate + new 25 (Override → wizard → adopt) and 26 (values surfaced before classification); Layer 2 PTY adopt scenario 20 drives the gate. **Docs**: `docs/ARCHITECTURE.md §10.5`, `docs/IMPLEMENTATION.md §3.5`, `CLAUDE.md` component tree.

### Added (multiselect value type — #101)

Closes [#101](https://github.com/breferrari/shardmind/issues/101). Promotes `multiselect` from a partially-wired type (engine validation existed; the wizard rendered a comma-separated text fallback flagged in-code as v0.2 polish) to a first-class value type. Lets a shard author ask one checkbox question ("Which agents do you use?") instead of N booleans. Scope is the value type + widget; gating *which modules install* on a multiselect value remains [#80](https://github.com/breferrari/shardmind/issues/80) (v0.2).
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ shardmind/
│ │ ├── CollisionReview.tsx # Install: backup / overwrite / cancel
│ │ ├── ExistingInstallGate.tsx # Install: existing-install disambiguation
│ │ ├── DiffView.tsx # Three-way diff + conflict resolution
│ │ ├── AdoptValuesGate.tsx # Adopt: values confirm-or-override page (#104)
│ │ ├── AdoptDiffView.tsx # Adopt: 2-way diff (no merge base) + per-file prompt
│ │ ├── AdoptSummary.tsx # Final adopt report (matched / mine / shard / fresh)
│ │ ├── NewValuesPrompt.tsx # Update: prompt for newly required values
Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ UX gaps surfaced during real obsidian-mind v6 install + adopt runs. None block t

- [x] Wizard scroll indicator + boolean prompt consistency (Y/n typed input → selectable Yes/No) ([#100](https://github.com/breferrari/shardmind/issues/100)) — smallest item, recommended starting point.
- [x] `multiselect` value type for module-set questions ([#101](https://github.com/breferrari/shardmind/issues/101)) — pairs with #100 (shortens module list). First-class value type + scrollable widget + per-option `default` + min/max; value→module gating stays #80.
- [ ] Adopt: replace step-by-step install wizard with confirm-or-override values flow ([#104](https://github.com/breferrari/shardmind/issues/104))
- [x] Adopt: replace step-by-step install wizard with confirm-or-override values flow ([#104](https://github.com/breferrari/shardmind/issues/104))
- [ ] Adopt batch operations (keep all mine / use all theirs / auto-merge non-conflicting) ([#120](https://github.com/breferrari/shardmind/issues/120)) — pairs with #104.
- [ ] Hook stderr presentation in Summary (truncate, dim, label as non-fatal) ([#105](https://github.com/breferrari/shardmind/issues/105))

Expand Down
10 changes: 5 additions & 5 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -872,7 +872,7 @@ Two pre-flight guards run before the network resolve, so a deterministically-wro
Phase ordering (logical; UI may interleave loading messages — see IMPLEMENTATION §3.5 for the data-flow diagram):

1. **Fetch** — resolve + download into a temp directory.
2. **Wizard** — collect values + module selections via the same `InstallWizard` component install uses. Wizard runs **before** classification because `.njk` templates need values to render before their output bytes can be hashed.
2. **Values gate** (`source/components/AdoptValuesGate.tsx`, #104) — adopt's user already has a populated vault, so it opens on a single confirm page that surfaces the values that will drive classification (resolved defaults, computed defaults, `--values` prefill, each labelled with its provenance) plus the module default, with **Use these values / Override individually / Cancel**. "Override individually" drops into the same `InstallWizard` install uses (per-value + module editing). Required values with no default and no prefill open straight into the wizard (no confident set to confirm). This step runs **before** classification because `.njk` templates need values to render before their output bytes can be hashed.
3. **Classify** (`source/core/adopt-planner.ts::classifyAdoption`) — for every shard output, render or read, hash, stat the user's vault, and assign one of four buckets:
- **matches** — byte-identical post-render → managed silently.
- **differs** — bytes differ → 2-way diff prompt (`AdoptDiffView`); user picks `keep_mine` (record user's hash, ownership=`modified`) or `use_shard` (overwrite with shard bytes, ownership=`managed`).
Expand All @@ -883,16 +883,16 @@ Phase ordering (logical; UI may interleave loading messages — see IMPLEMENTATI
6. **Re-hash** — recompute managed-file hashes per `state.ts::rehashManagedFiles` so any hook edits to managed paths land in the recorded state.

Flags:
- `--yes` — skip wizard + auto-pick `keep_mine` on every `differs`. Preserves the user's bytes on every divergence; safe default for retroactive adoption.
- `--values <file>` — prefill wizard answers (same shape as `install --values`).
- `--yes` — skip the values gate + auto-pick `keep_mine` on every `differs`. Preserves the user's bytes on every divergence; safe default for retroactive adoption.
- `--values <file>` — prefill value answers (same shape as `install --values`); shown on the gate's confirm page as `(from --values)`.
- `--verbose` — show per-file action history during the apply phase.
- `--dry-run` — run the full pipeline (fetch, wizard, classify) without touching the vault. Summary reports what *would* happen.
- `--dry-run` — run the full pipeline (fetch, values gate, classify) without touching the vault. Summary reports what *would* happen.

Volatile templates (`{# shardmind: volatile #}`) skip the differs prompt entirely — their rendered output is expected to vary across renders, so a content prompt would be meaningless. User's bytes are accepted as-is; missing-on-disk falls through to shard-only.

Excluded modules' files in the user's vault are not classified — adopt mirrors install's "module excluded → file not installed" rule, so user content at those paths stays user-content without any prompt.

Implementation modules: `source/core/adopt-planner.ts` (IMPLEMENTATION §4.17), `source/core/adopt-executor.ts` (§4.18). Orchestration lives in `source/commands/hooks/use-adopt-machine.ts`; UI components are `source/components/AdoptDiffView.tsx` + `source/components/AdoptSummary.tsx`.
Implementation modules: `source/core/adopt-planner.ts` (IMPLEMENTATION §4.17), `source/core/adopt-executor.ts` (§4.18). Orchestration lives in `source/commands/hooks/use-adopt-machine.ts`; UI components are `source/components/AdoptValuesGate.tsx` (values confirm-or-override), `source/components/AdoptDiffView.tsx` + `source/components/AdoptSummary.tsx`.

### 10.6 Install Location

Expand Down
2 changes: 1 addition & 1 deletion docs/IMPLEMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ graph TD
B["registry + download<br/>Resolve + extract to temp"] --> C
C["manifest + schema<br/>Parse new shard.yaml, shard-schema.yaml"] --> D

D["InstallWizard — Ink TUI<br/>① Value prompts from schema.groups<br/>② Module review<br/>③ Confirm<br/>(runs BEFORE classification — .njk needs values to render)"] --> E
D["AdoptValuesGate — Ink TUI (#104)<br/>Confirm page: values + provenance + module default<br/>Use these values / Override individually / Cancel<br/>Override → InstallWizard (value + module editing)<br/>(runs BEFORE classification — .njk needs values to render)"] --> E

E["adopt-planner.ts::classifyAdoption<br/>resolveModules walks shard, render or read each output<br/>per-output sha256 vs sha256(user vault path)<br/>→ matches | differs | shard-only buckets"] --> F1

Expand Down
2 changes: 1 addition & 1 deletion docs/SHARD-LAYOUT.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ Phases (logical order; UI may interleave loading messages):

Future `shardmind update` calls work normally — merge base is the adopt-time cache.

Reuses: drift detection (`core/drift.ts`), install-executor, values wizard, hook runtime. New surfaces: 2-way diff UI component (`AdoptDiffView`), adopt-planner, adopt-executor.
Reuses: drift detection (`core/drift.ts`), install-executor, value collection (`AdoptValuesGate` confirm page → `InstallWizard` on override, #104), hook runtime. New surfaces: 2-way diff UI component (`AdoptDiffView`), adopt-planner, adopt-executor.

## Naming decisions

Expand Down
21 changes: 8 additions & 13 deletions source/commands/adopt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import zod from 'zod';
import { ShardMindError, assertNever } from '../runtime/types.js';

import { Spinner, StatusMessage, Alert } from '../components/ui.js';
import InstallWizard from '../components/InstallWizard.js';
import AdoptValuesGate from '../components/AdoptValuesGate.js';
import AdoptDiffView from '../components/AdoptDiffView.js';
import AdoptSummary from '../components/AdoptSummary.js';
import CommandFrame from '../components/CommandFrame.js';
Expand Down Expand Up @@ -83,21 +83,16 @@ export default function Adopt({ args, options }: Props) {
case 'wizard':
return (
<CommandFrame dryRun={dryRun} selfUpdateBanner={banner}>
<InstallWizard
{/* Adopt opens on a values confirm-or-override page (#104)
instead of the full install wizard: the user already has a
populated vault, so a single "here's what will drive
classification" page beats a fresh-install-style interview.
"Override individually" inside the gate drops into the
InstallWizard for per-value + module editing. */}
<AdoptValuesGate
manifest={phase.ctx.manifest}
schema={phase.ctx.schema}
prefillValues={phase.ctx.prefillValues}
// Adopt's wizard reuses install's value-collection UI but
// shows file counts as zero — adopt has no clean
// "X files would be installed" guess at this point because
// the planner needs values first. We pass empty maps so the
// module-review step still renders cleanly without bogus
// counts; the Summary view shows the real bucket counts at
// the end.
moduleFileCounts={Object.fromEntries(
Object.keys(phase.ctx.schema.modules).map((id) => [id, 0]),
)}
alwaysIncludedFileCount={0}
onComplete={onWizardComplete}
onCancel={onWizardCancel}
onError={onWizardError}
Expand Down
7 changes: 7 additions & 0 deletions source/commands/hooks/use-adopt-machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@
* diff-review (loop over `differs`) → executing →
* running-hook → summary
*
* The `wizard` phase renders `AdoptValuesGate` (not `InstallWizard`
* directly): adopt opens on a values confirm-or-override page (#104) since
* the user already has a populated vault. The phase still means "collect
* values interactively", and the `onWizard*` callbacks are unchanged — the
* gate's "Override individually" path renders `InstallWizard` and returns
* the same `WizardResult`.
*
* Reuses `useSigintRollback` and `appendHookOutput` from `shared.ts`, and
* the hook orchestrator from `core`, so install / update / adopt can't
* drift on any of those concerns.
Expand Down
Loading
Loading