From b71e5b6e37fdae053d2126627465abf6d329ed46 Mon Sep 17 00:00:00 2001 From: fullstackjam Date: Sun, 2 Aug 2026 10:10:36 +0800 Subject: [PATCH 1/2] docs: align sync/snapshot AGENTS docs, README, and changelog for v1.0.0 --- CHANGELOG.md | 4 +++- README.md | 1 + internal/snapshot/AGENTS.md | 47 +++++++++++++++++++++++-------------- internal/sync/AGENTS.md | 28 ++++++++++++---------- 4 files changed, 50 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bac956d..af099d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## v1.0 (unreleased) +## v1.0.0 OpenBoot v1.0 narrows the core workflow to two verbs: **`install`** (add things to your Mac) and **`snapshot`** (save your current state somewhere). The supporting **`doctor`** and **`update`** maintenance commands remain available; everything else is either cloud-config CRUD or independent tooling. @@ -37,6 +37,8 @@ No aliases are kept — silent aliasing would regress behavior invisibly (the ol - **`snapshot --publish`**: direct non-interactive cloud upload. Respects the sync source (updates it) or creates a new config with a prompt. Does not ask for name/desc/visibility when updating existing. - **`snapshot` in pipe**: piping `openboot snapshot` to another command now emits JSON to stdout automatically (TTY detection). - **Shell capture**: snapshots now include the Oh-My-Zsh state, theme, and plugins (previously the field was defined but never populated — publishing silently dropped shell data). +- **Interactive dry-run**: `install --dry-run` on a TTY now walks the same full-screen wizard as a real install and previews the reviewed plan (DRY-RUN banner, no changes made). Non-TTY dry-run stays linear. +- **`install --update` help corrected**: the flag has always meant "update Homebrew and exit"; the help text now says so instead of implying it combines with an install. ### Philosophy diff --git a/README.md b/README.md index 202fd9a..6cb17a7 100644 --- a/README.md +++ b/README.md @@ -185,6 +185,7 @@ Removed in v1.0: `pull`, `push`, `diff`, `clean`, `log`, `restore`, `init`, `set --from FILE Install from a local config or snapshot JSON file -s, --silent Non-interactive mode (requires env vars) --dry-run Preview what would be installed + --pick NAMES Comma-separated packages to install from a remote config --packages-only Install packages only, skip system config --update Update Homebrew before installing --shell MODE Shell setup: install, skip diff --git a/internal/snapshot/AGENTS.md b/internal/snapshot/AGENTS.md index c05eb3b..b98392c 100644 --- a/internal/snapshot/AGENTS.md +++ b/internal/snapshot/AGENTS.md @@ -1,30 +1,36 @@ # SNAPSHOT PACKAGE -Environment capture, matching, and restoration. 8 files (4 source + 4 test), 1,783 lines. +Environment capture, matching, and restoration. 6 source files + tests. ## FILES | File | Lines | Purpose | |------|-------|---------| -| `capture.go` | 528 | Capture formulae/casks/taps/npm/prefs/shell/git/devtools | +| `capture.go` | 562 | Capture pipeline: formulae/casks/taps/npm/bun/prefs/dock/login items/git/dotfiles/devtools/shell | +| `dock.go` | 268 | Dock persistent-apps capture (`com.apple.dock` plist parsing) | +| `loginitems.go` | 60 | Login items capture via `osascript` | | `match.go` | 112 | Match captured packages against catalog, Jaccard similarity for preset detection | -| `local.go` | 62 | Read/write snapshots to `~/.openboot/snapshot.json` | -| `snapshot.go` | 61 | Data structures: Snapshot, PackageSnapshot, MacOSPrefs, ShellConfig | +| `local.go` | 74 | Read/write snapshots to `~/.openboot/snapshot.json`; `LoadFile`/`ParseBytes` | +| `snapshot.go` | 202 | Data structures + `PackageSnapshot` JSON codec accepting legacy shapes | ## CAPTURE PIPELINE -`CaptureWithProgress()` runs 8 sequential steps, each reporting via callback: +`CaptureWithProgress()` runs 12 sequential steps (`captureSteps` in capture.go), each reporting via callback: 1. Homebrew Formulae → `brew leaves` (top-level only, excludes dependencies) 2. Homebrew Casks → `brew list --cask` 3. Homebrew Taps → `brew tap` -4. npm Packages → `npm list -g --json` -5. macOS Preferences → reads known defaults keys -6. Shell Config → detects shell, oh-my-zsh, plugins, aliases -7. Git Config → user.name, user.email, core.editor, etc. -8. Dev Tools → version detection for node, go, python, rust, docker, etc. - -Each step is independent. Failures are non-fatal (captured as empty). +4. NPM Global Packages → `npm list -g --json` +5. Bun Global Packages +6. macOS Preferences → reads known defaults keys +7. Dock Apps → `com.apple.dock` persistent-apps +8. Login Items → `osascript` +9. Git Configuration → user.name, user.email, core.editor, etc. +10. Dotfiles → repo URL of `~/.dotfiles` +11. Dev Tools → version detection for node, go, python, rust, docker, etc. +12. Shell Config → detects shell, oh-my-zsh, plugins, aliases + +Each step is independent. Failures are non-fatal (recorded in `failed_steps`, snapshot marked `partial`). ## MATCHING LOGIC (match.go) @@ -43,12 +49,19 @@ Each step is independent. Failures are non-fatal (captured as empty). "formulae": ["curl", "wget"], "casks": ["visual-studio-code"], "taps": ["homebrew/core"], - "npm": ["typescript"] + "npm": ["typescript"], + "bun": [] }, - "macos_prefs": { ... }, - "shell_config": { ... }, - "git_config": { ... }, - "dev_tools": { ... } + "macos_prefs": [ ... ], + "shell": { ... }, + "git": { ... }, + "dotfiles": { ... }, + "dev_tools": [ ... ], + "matched_preset": "developer", + "catalog_match": { ... }, + "dock_apps": ["/Applications/Safari.app"], + "login_items": [{ "name": "Raycast", "path": "...", "hidden": false }], + "health": { "failed_steps": [], "partial": false } } ``` diff --git a/internal/sync/AGENTS.md b/internal/sync/AGENTS.md index e24d087..3511739 100644 --- a/internal/sync/AGENTS.md +++ b/internal/sync/AGENTS.md @@ -1,29 +1,29 @@ # SYNC PACKAGE -Compute diff + execute plan for syncing a remote config with the local system. 3 source files + 3 test files, 1,040 lines. +Compute diff + execute plan for syncing a remote config with the local system. ## FILES | File | Lines | Purpose | |------|-------|---------| -| `source.go` | 95 | `SyncSource` struct + Load/Save/Delete to `~/.openboot/sync_source.json` | -| `diff.go` | 239 | `SyncDiff` struct + `ComputeDiff()` comparing remote config vs local system | -| `plan.go` | 196 | `SyncPlan` struct + `Execute()` applying selected changes via brew/npm/shell/macos | +| `source.go` | 102 | `SyncSource` struct + Load/Save/Delete to `~/.openboot/sync_source.json` | +| `diff.go` | 280 | `SyncDiff` struct + `ComputeDiff()` comparing remote config vs local system | +| `plan.go` | 212 | `SyncPlan` struct + `Execute()`/`ExecuteContext()` applying selected changes via brew/npm/shell/macos | ## HOW IT WORKS ``` openboot install user/config → saves SyncSource to disk ↓ -openboot sync → loads SyncSource +openboot install (no args) → loads SyncSource (cli/install.go runSyncInstall) ↓ → fetches latest RemoteConfig ↓ → ComputeDiff(rc) compares remote vs local ↓ - → user selects changes in TUI (cli/sync.go) + → 3-way prompt: install / customize / cancel (cli/sync_helpers.go) ↓ - → Execute(plan, dryRun) applies changes + → ExecuteContext(ctx, plan, dryRun) applies additions ``` ## SOURCE PERSISTENCE (source.go) @@ -49,11 +49,11 @@ Key types: - `ShellDiff` — theme/plugins changes - `MacOSPrefDiff` — per-preference domain/key/value diff -Helper: `ToSet([]string) map[string]bool` — exported for use in `cli/sync.go` +Helper: `ToSet([]string) map[string]bool` — exported wrapper around the `diff` package's `ToSet` ## PLAN EXECUTION (plan.go) -`Execute(plan *SyncPlan, dryRun bool) (*SyncResult, error)`: +`Execute(plan *SyncPlan, dryRun bool)` / `ExecuteContext(ctx, plan, dryRun) (*SyncResult, error)`: Execution order (dependency-aware): 1. Install taps (other packages may depend on them) @@ -63,6 +63,10 @@ Execution order (dependency-aware): 5. Update shell (theme + plugins via `shell.RestoreFromSnapshot`) 6. Apply macOS preferences (via `macos.Configure`) +The uninstall branches remain implemented, but since v1.0 no CLI path +populates the `Uninstall*` fields — install is additive +(`buildInstallPlan` in `cli/sync_helpers.go` never sets them). + Error handling: Collects all errors via `errors.Join` (continues on failure). ## REUSED FUNCTIONS @@ -83,11 +87,11 @@ Error handling: Collects all errors via `errors.Join` (continues on failure). - Pure logic functions (diffLists, ToSet, HasChanges, Totals, TotalActions, IsEmpty) have 100% coverage - `getLocalDotfilesURL` tested with temp git repo at 80% -- `ComputeDiff` and `Execute` depend on external commands (brew, npm, git) — not unit-testable without interface refactoring. Exercise them via real-subprocess tests in `test/integration/` (run as part of L1, `make test-unit`). +- `Execute` is unit-tested with faked `brew.Runner`/`npm.Runner` doubles (`execute_test.go`). `ComputeDiff` still captures via real commands (brew, npm, git) — exercise it via real-subprocess tests in `test/integration/` (run as part of L1, `make test-unit`). - Source persistence tested with `t.TempDir()` + `t.Setenv("HOME", tmpDir)` pattern ## WHEN MODIFYING -- Adding a new diff category: Add fields to `SyncDiff`, update `HasChanges/TotalMissing/TotalExtra/TotalChanged`, add capture in `ComputeDiff`, update `cli/sync.go` TUI -- Adding a new plan action: Add fields to `SyncPlan`, update `TotalActions`, add execution branch in `Execute`, update `cli/sync.go` `buildSyncPlan` +- Adding a new diff category: Add fields to `SyncDiff`, update `HasChanges/TotalMissing/TotalExtra/TotalChanged`, add capture in `ComputeDiff`, update the printed diff in `cli/sync_helpers.go` +- Adding a new plan action: Add fields to `SyncPlan`, update `TotalActions`, add execution branch in `Execute`, update `buildInstallPlan` in `cli/sync_helpers.go` - Changing persistence format: Update `SyncSource` struct — JSON tags are the wire format From f7dca231f1f6d1b2074b8643123c0c6aebc483e2 Mon Sep 17 00:00:00 2001 From: fullstackjam Date: Sun, 2 Aug 2026 10:18:45 +0800 Subject: [PATCH 2/2] docs: fix --update description and stale totalSteps reference (review follow-up) --- README.md | 2 +- internal/snapshot/AGENTS.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6cb17a7..bb2505f 100644 --- a/README.md +++ b/README.md @@ -187,7 +187,7 @@ Removed in v1.0: `pull`, `push`, `diff`, `clean`, `log`, `restore`, `init`, `set --dry-run Preview what would be installed --pick NAMES Comma-separated packages to install from a remote config --packages-only Install packages only, skip system config - --update Update Homebrew before installing + --update Update Homebrew and exit --shell MODE Shell setup: install, skip --macos MODE macOS prefs: configure, skip --dotfiles MODE Dotfiles: clone, link, skip diff --git a/internal/snapshot/AGENTS.md b/internal/snapshot/AGENTS.md index b98392c..235032a 100644 --- a/internal/snapshot/AGENTS.md +++ b/internal/snapshot/AGENTS.md @@ -79,7 +79,7 @@ Snapshot data is mapped to `config.SnapshotGitConfig` and `config.SnapshotShellC ## WHEN MODIFYING -- Adding capture step: Add to `CaptureWithProgress()`, update `totalSteps`, add to `Snapshot` struct +- Adding capture step: Add an entry to `captureSteps` in `capture.go`, add to `Snapshot` struct - Adding restore step: Add to `installer.RunFromSnapshot()`, create `config.Snapshot*Config` type, wire in `cli/snapshot.go` - Adding preset detection: Modify `DetectBestPreset()` scoring in `match.go` - Tests: Table-driven with testify. `capture_test.go` mocks command output. `match_test.go` tests Jaccard scoring.