diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..ddc1af1 Binary files /dev/null and b/.DS_Store differ diff --git a/.claude/.DS_Store b/.claude/.DS_Store new file mode 100644 index 0000000..3a51aa2 Binary files /dev/null and b/.claude/.DS_Store differ diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..ca90aa8 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,27 @@ +{ + "permissions": { + "allow": [ + "Bash(doob:*)", + "Bash(echo:*)", + "Bash(gh:*)", + "Bash(op:*)", + "Bash(obfsck:*)", + "Bash(head:*)", + "Bash(git ls-files:*)", + "WebSearch", + "WebFetch(domain:www.apache.org)", + "Skill(todo:*)", + "Bash(PATH=\"/Users/joe/.local/share/mise/shims:$PATH\" which nu)", + "Bash(PATH=\"/Users/joe/.local/share/mise/shims:$PATH\" nu --version 2>&1 | head -1)", + "Bash(doob todo:*)", + "Bash(handoff-detect 2>&1; echo \"exit: $?\")", + "Bash(handoff-detect --name && handoff-detect --project)", + "Bash(rtk gain:*)", + "Bash(rtk rewrite:*)", + "Bash(rtk grep:*)", + "Bash(nu -c '\nlet result = \\(do { python3 -c \"import json; print\\(json.dumps\\({\\\\\"ok\\\\\": True}\\)\\)\" } | complete\\)\nprint $result.stdout\n')", + "Bash(echo '{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"grep -n \\\\\"^foo\\\\\" /file.md\"}}' | nu /Users/joe/.claude/hooks/nu/pre/grep-to-grep-tool.nu)", + "Bash(echo '{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"grep -c pattern file.txt\"}}' | nu /Users/joe/.claude/hooks/nu/pre/grep-to-grep-tool.nu\necho '{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"grep -i \\\\\"Hello World\\\\\" /dir/\"}}' | nu /Users/joe/.claude/hooks/nu/pre/grep-to-grep-tool.nu\necho '{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"git status\"}}' | nu /Users/joe/.claude/hooks/nu/pre/grep-to-grep-tool.nu; echo \"exit: $?\")" + ] + } +} diff --git a/.codex/hooks.json b/.codex/hooks.json new file mode 100644 index 0000000..e6284b9 --- /dev/null +++ b/.codex/hooks.json @@ -0,0 +1,25 @@ +{ + "hooks": { + "PostToolUse": [ + { + "matcher": "Edit|Write", + "hooks": [ + { + "type": "command", + "command": "bash -c 'if [ -f Cargo.toml ]; then cargo check --quiet 2>&1 | head -20; fi'" + } + ] + } + ], + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "nu /Users/joe/dev/notfiles/scripts/preflight.nu" + } + ] + } + ] + } +} diff --git a/.ctx/HANDOFF.md b/.ctx/HANDOFF.md index 4adaeb6..54c4cd9 100644 --- a/.ctx/HANDOFF.md +++ b/.ctx/HANDOFF.md @@ -1,4 +1,4 @@ -# Handoff — notfiles (2026-06-06) +# Handoff — notfiles (2026-07-07) **Branch:** main | **Build:** unknown | **Tests:** unknown @@ -6,9 +6,11 @@ | ID | P | Status | Title | |---|---|---|---| +| uncommitted-work | P1 | open | Uncommitted changes (3 files) | ## Log +- 20260606.135918: done=13 running=0 pending=0 blocked=0 - 2026-04-17: Added notnet crate with YubikeySource and threaded Tailscale into notstrap; design spec complete - 2026-04-15: Hardened bootstrap and hook-state failure handling, made notfiles FileStore usage real, and fixed copy-drift safety coverage - 2026-04-15: Pruned completed items from handoff; GitHub issues plus doob are now the source of truth for active work diff --git a/.ctx/HANDOFF.notfiles.notfiles.yaml b/.ctx/HANDOFF.notfiles.notfiles.yaml index 4948220..0b9d98b 100644 --- a/.ctx/HANDOFF.notfiles.notfiles.yaml +++ b/.ctx/HANDOFF.notfiles.notfiles.yaml @@ -1,8 +1,28 @@ project: notfiles id: notfile -updated: 2026-06-06 -items: [] +updated: 2026-07-07 +items: +- id: uncommitted-work + doob_uuid: null + name: uncommitted-work + priority: P1 + status: open + title: Uncommitted changes (3 files) + description: |- + Working tree has uncommitted changes: + .claude/worktrees/agent-a27fe5f2 + .claude/settings.local.json + .superpowers/brainstorm/72758-1776397235/state/server-stopped + files: + - .claude/worktrees/agent-a27fe5f2 + - .claude/settings.local.json + - .superpowers/brainstorm/72758-1776397235/state/server-stopped + completed: null + extra: [] log: +- date: '20260606.135918' + summary: done=13 running=0 pending=0 blocked=0 + commits: [] - date: 2026-04-17 summary: Added notnet crate with YubikeySource and threaded Tailscale into notstrap; design spec complete commits: [] @@ -12,8 +32,3 @@ log: - date: 2026-04-15 summary: Pruned completed items from handoff; GitHub issues plus doob are now the source of truth for active work commits: [] -branch: main -state: - tests: passing - build: clean - notes: Handoff is now ephemeral; track active work via GitHub issues synced through doob. diff --git a/.notfiles-state.toml b/.notfiles-state.toml new file mode 100644 index 0000000..7b71b67 --- /dev/null +++ b/.notfiles-state.toml @@ -0,0 +1,76 @@ +[[entries]] +package = "nushell" +source = "/Users/joe/dev/notfiles/nushell/.config/nushell/autoload/aliases.nu" +target = "/Users/joe/.config/nushell/autoload/aliases.nu" +method = "symlink" +linked_at = "2026-06-14T04:23:50.728685+00:00" + +[[entries]] +package = "nushell" +source = "/Users/joe/dev/notfiles/nushell/.config/nushell/autoload/audit.nu" +target = "/Users/joe/.config/nushell/autoload/audit.nu" +method = "symlink" +linked_at = "2026-06-14T04:23:50.728734+00:00" + +[[entries]] +package = "nushell" +source = "/Users/joe/dev/notfiles/nushell/.config/nushell/autoload/did-you-mean.nu" +target = "/Users/joe/.config/nushell/autoload/did-you-mean.nu" +method = "symlink" +linked_at = "2026-06-14T04:23:50.728773+00:00" + +[[entries]] +package = "nushell" +source = "/Users/joe/dev/notfiles/nushell/.config/nushell/autoload/functions.nu" +target = "/Users/joe/.config/nushell/autoload/functions.nu" +method = "symlink" +linked_at = "2026-06-14T04:23:50.728809+00:00" + +[[entries]] +package = "nushell" +source = "/Users/joe/dev/notfiles/nushell/.config/nushell/autoload/nu_libs.nu" +target = "/Users/joe/.config/nushell/autoload/nu_libs.nu" +method = "symlink" +linked_at = "2026-06-14T04:23:50.728847+00:00" + +[[entries]] +package = "nushell" +source = "/Users/joe/dev/notfiles/nushell/.config/nushell/autoload/nuenv.nu" +target = "/Users/joe/.config/nushell/autoload/nuenv.nu" +method = "symlink" +linked_at = "2026-06-14T04:23:50.728879+00:00" + +[[entries]] +package = "nushell" +source = "/Users/joe/dev/notfiles/nushell/.config/nushell/autoload/session-guard.nu" +target = "/Users/joe/.config/nushell/autoload/session-guard.nu" +method = "symlink" +linked_at = "2026-06-14T04:23:50.728915+00:00" + +[[entries]] +package = "nushell" +source = "/Users/joe/dev/notfiles/nushell/.config/nushell/autoload/settings.nu" +target = "/Users/joe/.config/nushell/autoload/settings.nu" +method = "symlink" +linked_at = "2026-06-14T04:23:50.728947+00:00" + +[[entries]] +package = "nushell" +source = "/Users/joe/dev/notfiles/nushell/.config/nushell/autoload/toolkit-hook.nu" +target = "/Users/joe/.config/nushell/autoload/toolkit-hook.nu" +method = "symlink" +linked_at = "2026-06-14T04:23:50.728981+00:00" + +[[entries]] +package = "nushell" +source = "/Users/joe/dev/notfiles/nushell/.config/nushell/config.nu" +target = "/Users/joe/.config/nushell/config.nu" +method = "symlink" +linked_at = "2026-06-14T04:23:50.729023+00:00" + +[[entries]] +package = "nushell" +source = "/Users/joe/dev/notfiles/nushell/.config/nushell/env.nu" +target = "/Users/joe/.config/nushell/env.nu" +method = "symlink" +linked_at = "2026-06-14T04:23:50.729055+00:00" diff --git a/.sccignore b/.sccignore new file mode 100644 index 0000000..43f2eb5 --- /dev/null +++ b/.sccignore @@ -0,0 +1,11 @@ +.ctx +.kgx +.github +.claude +docs +examples +fuzz +target +*.md +*.json +tests/ diff --git a/.superpowers/brainstorm/72758-1776397235/.DS_Store b/.superpowers/brainstorm/72758-1776397235/.DS_Store new file mode 100644 index 0000000..9ad8929 Binary files /dev/null and b/.superpowers/brainstorm/72758-1776397235/.DS_Store differ diff --git a/.superpowers/brainstorm/72758-1776397235/state/server-stopped b/.superpowers/brainstorm/72758-1776397235/state/server-stopped new file mode 100644 index 0000000..ac70b7f --- /dev/null +++ b/.superpowers/brainstorm/72758-1776397235/state/server-stopped @@ -0,0 +1 @@ +{"reason":"idle timeout","timestamp":1776399095365} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..d97f87a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,110 @@ +# AGENTS.md + +This file provides guidance to Codex (Codex.ai/code) when working with code in this repository. + +## What is notfiles? + +A modern dotfiles manager written in Rust — a pure Rust alternative to GNU Stow. It symlinks (or copies) files from organized "package" directories into a target location (typically `~`). + +## Build & Test Commands + +```bash +cargo build # build all workspace crates +cargo build -p notfiles # build just the notfiles binary +cargo test # run all tests across workspace +cargo test -p notcore # test notcore only +cargo test -p notfiles # test notfiles only +cargo test -p notsecrets # test notsecrets only +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 7 crates under `crates/`: + +| Crate | Purpose | +| ------------ | -------------------------------------------------------------------------- | +| `notcore` | Shared types: `Config`, `NotfilesError`, `Reporter`, `LinkEvent`, | +| | `expand_tilde`, `suggest_package`, `HookPhase`, `HookSpec`, `Report` | +| `notfiles` | Dotfiles linker — lib + `notfiles` binary. Subcommands: `init`, `link`, | +| | `unlink`, `status`, `check`, `diff`, `adopt`, `completions` | +| `notsecrets` | Multi-provider secret resolution (`SecretResolver`), age encryption/ | +| | decryption, identity management | +| `nothooks` | Nushell hook runner with dot/setup phases and state persistence | +| `notnet` | Network utilities — Tailscale integration, YubikeySource | +| `notstrap` | New-machine bootstrap orchestrator — ties all crates together | +| `notgraph` | Dependency/import graph for Rust files — HTML/MD/JSON/Mermaid output, | +| | heatmap, cycle detection | + +## Architecture + +### notfiles (primary user-facing tool) + +Eight subcommands: `init`, `link`, `unlink`, `status`, `check`, `diff`, +`adopt`, `completions`. Global flags: `--dry-run`, `--verbose`, `--json`. +CLI parsing in `crates/notfiles/src/cli.rs`; dispatch in `src/main.rs`. + +**Core flow for `link`:** `main` → `config.validate()` → +`resolve_packages_filtered` (include/exclude + platform filtering) → +`collect_files` (recursive walk with ignore filtering) → +`linker::link_package` (create symlinks or copies, record in state, +return `LinkResult` with counters). + +**Architecture**: Hexagonal (ports/adapters). `FileStore` trait abstracts +filesystem I/O; `Reporter` trait abstracts output. Adapters: +`FileStoreImpl` (real fs), `InMemoryFileStore` (testing), +`TerminalReporter` (ANSI), `JsonReporter` (NDJSON). + +Key modules in `crates/notfiles/src/`: + +- **linker** — Creates/removes symlinks or copies via `FileStore` port. + Manages `State` (`.notfiles-state.toml`). Returns `LinkResult` with + linked/copied/skipped/backed_up counts. Also provides `adopt_files`. +- **package** — Discovers packages with include/exclude and platform + filtering. Recursively collects files via `IgnoreMatcher`. Typo + suggestions via `suggest_package` on not-found errors. +- **ignore** — Glob-based ignore matching using `globset`. +- **status** — Compares expected vs actual state: + linked/copied/missing/conflict/orphan. Also provides `diff_package` + for copy-method divergence detection. +- **adapters/** — `FileStoreImpl`, `InMemoryFileStore`, + `TerminalReporter`, `JsonReporter`. +- **ports** — `FileStore` trait definition. + +### notsecrets + +Multi-provider secret resolution via `SecretResolver`. Providers include +`EnvSource`, `OpSource` (1Password), `BitwardenSource`, `FileSource`, +`DotenvxSource`, `SopsSource`, and others. Native age encryption/decryption +with x25519, SSH ed25519/RSA, and scrypt identity support. No external +`sops` or `age` binaries required. + +### nothooks + +`HookRunner` executes `.nu` scripts via `nu