docs: update CLAUDE.md for workspace layout, add HANDOFF.md
This commit is contained in:
64
CLAUDE.md
64
CLAUDE.md
@@ -9,36 +9,60 @@ A modern dotfiles manager written in Rust — a pure Rust alternative to GNU Sto
|
|||||||
## Build & Test Commands
|
## Build & Test Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo build # build
|
cargo build # build all workspace crates
|
||||||
cargo test # run all tests (unit + integration)
|
cargo build -p notfiles # build just the notfiles binary
|
||||||
cargo test --lib # unit tests only
|
cargo test # run all tests across workspace
|
||||||
cargo test --test integration # integration tests only
|
cargo test -p notcore # test notcore only
|
||||||
cargo test <test_name> # run a single test by name
|
cargo test -p notfiles # test notfiles only
|
||||||
cargo clippy # lint
|
cargo test -p notsecrets # test notsecrets only
|
||||||
cargo fmt --check # check formatting
|
cargo test -p nothooks # test nothooks only
|
||||||
|
cargo clippy --workspace # lint all crates
|
||||||
|
cargo fmt --check # check formatting
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Workspace Structure
|
||||||
|
|
||||||
|
This is a Cargo workspace with 5 crates under `crates/`:
|
||||||
|
|
||||||
|
| Crate | Purpose |
|
||||||
|
|-------|---------|
|
||||||
|
| `notcore` | Shared types: `Config`, `NotfilesError`, `expand_tilde`, `HookPhase`, `HookSpec`, `Report`, `StepStatus` |
|
||||||
|
| `notfiles` | Dotfiles linker — lib + `notfiles` binary (symlink/copy, init, status) |
|
||||||
|
| `notsecrets` | Age key retrieval via Bitwarden, file, or prompt; SOPS decryption |
|
||||||
|
| `nothooks` | Nushell hook runner with dot/setup phases and state persistence |
|
||||||
|
| `notstrap` | New-machine bootstrap orchestrator — ties all crates together |
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
The binary has four subcommands: `init`, `link`, `unlink`, `status`. CLI parsing uses clap derive in `src/cli.rs`; command dispatch happens in `src/main.rs`.
|
### notfiles (primary user-facing tool)
|
||||||
|
|
||||||
**Core flow for `link`:** `main` → `resolve_packages` (discover subdirs or validate requested names) → `collect_files` (recursive walk with ignore filtering) → `linker::link_package` (create symlinks or copies, record in state).
|
Four subcommands: `init`, `link`, `unlink`, `status`. CLI parsing in `crates/notfiles/src/cli.rs`; dispatch in `src/main.rs`.
|
||||||
|
|
||||||
Key modules:
|
**Core flow for `link`:** `main` → `resolve_packages` → `collect_files` (recursive walk with ignore filtering) → `linker::link_package` (create symlinks or copies, record in state).
|
||||||
- **linker** — Creates/removes symlinks or copies. Manages `State` (serialized to `.notfiles-state.toml` in the dotfiles dir) which tracks every linked file with source, target, method, and timestamp. Handles conflict detection, `--force` backups, and empty-parent cleanup on unlink.
|
|
||||||
- **config** — Parses `notfiles.toml`. Provides per-package overrides for method (symlink/copy), target directory, and ignore patterns. Falls back to `[defaults]` section values.
|
|
||||||
- **package** — Discovers packages (non-hidden subdirectories of the dotfiles dir) and recursively collects files, filtering through `IgnoreMatcher`.
|
|
||||||
- **ignore** — Glob-based ignore matching using `globset`. Matches both full relative paths and individual path components.
|
|
||||||
- **paths** — `expand_tilde` utility.
|
|
||||||
- **status** — Compares expected state (files on disk + config) against actual state (symlinks/copies + state file) to report linked/copied/missing/conflict/orphan.
|
|
||||||
- **error** — `NotfilesError` enum via `thiserror`.
|
|
||||||
|
|
||||||
Integration tests (`tests/integration.rs`) run the compiled binary against temp directories using `tempfile`.
|
Key modules in `crates/notfiles/src/`:
|
||||||
|
- **linker** — Creates/removes symlinks or copies. Manages `State` (`.notfiles-state.toml`) tracking every linked file. Handles conflict detection, `--force` backups, empty-parent cleanup on unlink.
|
||||||
|
- **config** — Parses `notfiles.toml`. Per-package overrides for method, target, ignore. Falls back to `[defaults]`.
|
||||||
|
- **package** — Discovers packages (non-hidden subdirs) and recursively collects files via `IgnoreMatcher`.
|
||||||
|
- **ignore** — Glob-based ignore matching using `globset`.
|
||||||
|
- **status** — Compares expected vs actual state: linked/copied/missing/conflict/orphan.
|
||||||
|
|
||||||
|
### notsecrets
|
||||||
|
|
||||||
|
`AgeKeySource` trait with three implementations: `BitwardenSource` (bw CLI), `FileSource`, `PromptSource`. `resolve_age_key()` tries each in order. `install_age_key()` writes to `~/.config/sops/age/keys.txt` (mode 0600). `decrypt_sops()` shells out to `sops --decrypt`.
|
||||||
|
|
||||||
|
### nothooks
|
||||||
|
|
||||||
|
`HookRunner` executes `.nu` scripts via `nu <script>`. Two phases: `HookPhase::Dot` (always runs) and `HookPhase::Setup` (runs once, tracked in `.nothooks-state.toml`). `--force` flag reruns setup hooks.
|
||||||
|
|
||||||
|
### notstrap
|
||||||
|
|
||||||
|
Orchestrates in order: prereqs check → load config → clone dotfiles → age key → decrypt SOPS → link dotfiles → run hooks → final report. Config file: `notstrap.toml`.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
The config file is `notfiles.toml` at the dotfiles directory root. Each subdirectory of the dotfiles dir is a "package". Per-package config can override the link method (`symlink` or `copy`), target directory, and additional ignore patterns.
|
`notfiles.toml` lives at the dotfiles directory root. Each subdirectory is a "package". Per-package config can override: `method` (symlink/copy), `target` directory, additional `ignore` patterns.
|
||||||
|
|
||||||
## Edition
|
## Edition
|
||||||
|
|
||||||
Uses Rust edition 2024.
|
Rust edition 2024. All hook scripts are Nushell (`.nu`) — no `.sh` scripts.
|
||||||
|
|||||||
37
HANDOFF.md
Normal file
37
HANDOFF.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# HANDOFF.md
|
||||||
|
|
||||||
|
State of the `notfiles` repo as of 2026-03-31.
|
||||||
|
|
||||||
|
## What Was Done
|
||||||
|
|
||||||
|
Restructured from a flat `src/` layout into a Cargo workspace of 5 crates:
|
||||||
|
- `notcore` — shared library (types, config, paths, errors)
|
||||||
|
- `notfiles` — dotfiles linker, migrated from `src/`
|
||||||
|
- `notsecrets` — age key retrieval (Bitwarden/file/prompt) + SOPS decrypt
|
||||||
|
- `nothooks` — Nushell hook runner with phase/state tracking
|
||||||
|
- `notstrap` — new-machine bootstrap orchestrator
|
||||||
|
|
||||||
|
39 tests passing, 0 clippy warnings. Committed to `main`, not yet pushed to `gitea/main`.
|
||||||
|
|
||||||
|
## Pending Issues
|
||||||
|
|
||||||
|
### 1. Push to remote
|
||||||
|
```bash
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
8 commits ahead of `gitea/main`. Nothing blocking this.
|
||||||
|
|
||||||
|
### 2. notstrap has an empty lib.rs
|
||||||
|
`crates/notstrap/src/lib.rs` is empty — created as a stub. `notstrap` is binary-only so this is harmless, but it's dead weight. Either delete `[lib]` from `notstrap/Cargo.toml` (if there is one) or remove the file.
|
||||||
|
|
||||||
|
### 3. notstrap config not documented
|
||||||
|
`notstrap.toml` format is defined inline in `notstrap/src/main.rs` via serde structs but never written down. A sample `notstrap.toml` should be created and documented.
|
||||||
|
|
||||||
|
### 4. notsecrets has no binary
|
||||||
|
`notsecrets` is library-only. A small `notsecrets get-key` CLI binary could be useful for manual debugging/testing of the key retrieval chain, but not blocking.
|
||||||
|
|
||||||
|
### 5. Integration test coverage for nothooks assumes nu in PATH
|
||||||
|
`nothooks` integration tests call `nu` directly. If `nu` is not on PATH (e.g. on CI), the success/failure tests will fail for the wrong reason. CI should either install `nu` or the tests should be marked `#[ignore]` with a feature flag.
|
||||||
|
|
||||||
|
### 6. notstrap sops_file path is always joined to dotfiles_dir
|
||||||
|
If `sops_file` in `notstrap.toml` is an absolute path, the join will silently override the base. This is a minor footgun in `repo.clone_if_missing` usage — low priority.
|
||||||
Reference in New Issue
Block a user