diff --git a/.ctx/HANDOFF.md b/.ctx/HANDOFF.md index 54c4cd9..fa200a3 100644 --- a/.ctx/HANDOFF.md +++ b/.ctx/HANDOFF.md @@ -1,4 +1,4 @@ -# Handoff — notfiles (2026-07-07) +# Handoff — notfiles (2026-07-14) **Branch:** main | **Build:** unknown | **Tests:** unknown @@ -11,6 +11,7 @@ ## Log - 20260606.135918: done=13 running=0 pending=0 blocked=0 +- 2026-07-08: hexagonal architecture refactor with adapters and integration tests, notsecrets cleanup, config payload moved to notfiles-config repo - 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 07eaa4a..f6032de 100644 --- a/.ctx/HANDOFF.notfiles.notfiles.yaml +++ b/.ctx/HANDOFF.notfiles.notfiles.yaml @@ -1,6 +1,6 @@ project: notfiles id: notfile -updated: 2026-07-08 +updated: 2026-07-14 items: - id: uncommitted-work doob_uuid: null @@ -20,14 +20,12 @@ items: completed: null extra: [] log: -- date: 2026-07-08 - summary: hexagonal architecture refactor with adapters and integration tests, notsecrets cleanup, config payload moved to notfiles-config repo - commits: - - 6427d18 - - 25624c9 - date: '20260606.135918' summary: done=13 running=0 pending=0 blocked=0 commits: [] +- date: 2026-07-08 + summary: hexagonal architecture refactor with adapters and integration tests, notsecrets cleanup, config payload moved to notfiles-config repo + commits: [] - date: 2026-04-17 summary: Added notnet crate with YubikeySource and threaded Tailscale into notstrap; design spec complete commits: [] diff --git a/.superpowers/brainstorm/72758-1776397235/content/architecture.html b/.superpowers/brainstorm/72758-1776397235/content/architecture.html deleted file mode 100644 index 097794b..0000000 --- a/.superpowers/brainstorm/72758-1776397235/content/architecture.html +++ /dev/null @@ -1,71 +0,0 @@ -

notfiles + Tailscale — Bootstrap Architecture

-

How the crates fit together for ephemeral machine setup

- -
-
- -
- - -
-
New Machine
-
TS_AUTHKEY env var
-
YubiKey (PIV slot 9d)
-
→ interactive prompt
-
- -
- - -
-
notstrap
-
1. prereqs check
-
2. → notnet
-
3. clone Gitea
-
4. → notsecrets
-
5. link dotfiles
-
6. run hooks
-
- -
- -
-
- -
-
notnet NEW
-
detect existing Tailscale
-
install if missing
-
resolve auth key
-
join tailnet
-
verify peer reachable
-
-
-
-
Tailnet
-
minibox (VPS)
-
Gitea repo
-
-
- -
-
- -
-
notsecrets +YubiKey
-
YubikeySource (PIV 9c)
-
BitwardenSource
-
FileSource
-
PromptSource
-
-
- -
- -
- -
- Already on Tailnet? notstrap detects active Tailscale → skips notnet entirely -
-
-
diff --git a/.superpowers/brainstorm/72758-1776397235/state/server-stopped b/.superpowers/brainstorm/72758-1776397235/state/server-stopped deleted file mode 100644 index ac70b7f..0000000 --- a/.superpowers/brainstorm/72758-1776397235/state/server-stopped +++ /dev/null @@ -1 +0,0 @@ -{"reason":"idle timeout","timestamp":1776399095365} diff --git a/.superpowers/brainstorm/72758-1776397235/state/server.log b/.superpowers/brainstorm/72758-1776397235/state/server.log deleted file mode 100644 index 584bbb4..0000000 --- a/.superpowers/brainstorm/72758-1776397235/state/server.log +++ /dev/null @@ -1,3 +0,0 @@ -{"type":"server-started","port":63245,"host":"127.0.0.1","url_host":"localhost","url":"http://localhost:63245","screen_dir":"/Users/joe/dev/notfiles/.superpowers/brainstorm/72758-1776397235/content","state_dir":"/Users/joe/dev/notfiles/.superpowers/brainstorm/72758-1776397235/state"} -{"type":"screen-added","file":"/Users/joe/dev/notfiles/.superpowers/brainstorm/72758-1776397235/content/architecture.html"} -{"type":"server-stopped","reason":"idle timeout"} diff --git a/.superpowers/brainstorm/72758-1776397235/state/server.pid b/.superpowers/brainstorm/72758-1776397235/state/server.pid deleted file mode 100644 index 436a082..0000000 --- a/.superpowers/brainstorm/72758-1776397235/state/server.pid +++ /dev/null @@ -1 +0,0 @@ -72766 diff --git a/CLAUDE.md b/CLAUDE.md index 2b6e0fc..7418e3a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,5 +1,11 @@ # CLAUDE.md + + Read: + - @docs/DESIGN.md + - @.ctx/HANDOFF.md + + This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## What is notfiles? @@ -24,19 +30,19 @@ cargo fmt --check # check formatting 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 | +| 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 diff --git a/Cargo.lock b/Cargo.lock index e52b1aa..cc7c348 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1084,7 +1084,6 @@ dependencies = [ name = "notcore" version = "0.1.0" dependencies = [ - "anyhow", "dirs", "serde", "strsim", @@ -1114,9 +1113,7 @@ dependencies = [ name = "notforge" version = "0.1.0" dependencies = [ - "anyhow", "base64", - "clap", "miette", "notcore", "serde", @@ -1173,7 +1170,6 @@ dependencies = [ "bech32", "chacha20poly1305", "curve25519-dalek", - "dirs", "ed25519-dalek", "hkdf", "hmac", @@ -1188,7 +1184,6 @@ dependencies = [ "toml", "x25519-dalek", "yubikey", - "zeroize", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index fb2e762..d40e6ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ homepage = "https://github.com/89jobrien/notfiles" keywords = ["dotfiles", "symlink", "stow", "config"] categories = ["command-line-utilities", "filesystem"] readme = "README.md" +rust-version = "1.87.0" [workspace] members = [ @@ -22,31 +23,33 @@ resolver = "2" [workspace.dependencies] anyhow = "1" +assert_fs = "1" base64 = "0.22" +chrono = { version = "0.4", default-features = false, features = ["clock"] } clap = { version = "4", features = ["derive"] } clap_complete = "4" -chrono = { version = "0.4", default-features = false, features = ["clock"] } dirs = "6" globset = "0.4" +miette = { version = "7", features = ["derive"] } +notcore = { path = "crates/notcore" } +notfiles = { path = "crates/notfiles" } +notforge = { path = "crates/notforge" } +nothooks = { path = "crates/nothooks" } +notnet = { path = "crates/notnet" } +notsecrets = { path = "crates/notsecrets" } +notstrap = { path = "crates/notstrap", version = "^0.1.0" } #unified +rpassword = "7" serde = { version = "1", features = ["derive"] } serde_json = "1" strsim = "0.11" +tempfile = "3" thiserror = "2" toml = "0.8" -miette = { version = "7", features = ["derive"] } ureq = { version = "2", features = ["json"] } which = "7" -rpassword = "7" -tempfile = "3" -assert_fs = "1" -notcore = { path = "crates/notcore" } -notfiles = { path = "crates/notfiles" } -notsecrets = { path = "crates/notsecrets" } -nothooks = { path = "crates/nothooks" } -notnet = { path = "crates/notnet" } -notforge = { path = "crates/notforge" } [profile.release] +codegen-units = 1 opt-level = 3 lto = "thin" strip = true diff --git a/HANDOFF.md b/HANDOFF.md deleted file mode 100644 index 4da5ec7..0000000 --- a/HANDOFF.md +++ /dev/null @@ -1,24 +0,0 @@ -# HANDOFF.md - -Legacy note as of 2026-04-15. - -This repo no longer uses `HANDOFF.md` as a running history log. - -Active workflow: - -1. Sync GitHub issues into `doob` -2. Read short-lived handoff context from `.ctx/HANDOFF.*.*.yaml` -3. Update issue state in `doob` -4. Sync `doob` back to GitHub issues - -Rules: - -- Handoff context should be brief and ephemeral -- Only open work should appear in handoff files -- Closed issues should be removed from handoff files after sync -- Historical status belongs in GitHub issues and git history, not here - -Current state: - -- No open handoff items are tracked locally -- Use GitHub issues plus `doob` as the task source of truth diff --git a/cliff.toml b/cliff.toml new file mode 100644 index 0000000..81e398f --- /dev/null +++ b/cliff.toml @@ -0,0 +1,93 @@ +# git-cliff ~ configuration file +# https://git-cliff.org/docs/configuration + +[remote.github] +owner = "89jobrien" +repo = "notfiles" + +[changelog] +body = """ +{% if version %}\ + ## {{ version | trim_start_matches(pat="v") }} - {{ timestamp | date(format="%Y-%m-%d") }} +{% else %}\ + ## Unreleased +{% endif %}\ +{% for group, commits in commits | group_by(attribute="group") %} + ### {{ group | upper_first }} + {% for commit in commits %} + - {% if commit.scope %}**{{ commit.scope }}:** {% endif %}\ + {{ commit.message | split(pat="\n") | first | upper_first | trim_end }}\ + {% if commit.links %} \ + ({% for link in commit.links %}[#{{ link.text }}]({{ link.href }}){% if not loop.last %}, {% endif %}{% endfor %})\ + {% endif %}\ + {% endfor %} +{% endfor %}\n +""" + +footer = """ +{%- macro remote_url() -%} + https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} +{%- endmacro -%} + +{% for release in releases -%} + {% if release.version -%} + {% if release.previous.version -%} + [{{ release.version | trim_start_matches(pat="v") }}]: \ + {{ self::remote_url() }}/compare/{{ release.previous.version }}...{{ release.version }} + {% endif -%} + {% else -%} + [unreleased]: {{ self::remote_url() }}/compare/{{ release.previous.version }}...HEAD + {% endif -%} +{% endfor %} + + +""" + +trim = true +render_always = true +output = "CHANGELOG.md" + +postprocessors = [ + { pattern = '', replace = "https://github.com/89jobrien/notfiles" }, +] + + +[git] +conventional_commits = true +filter_unconventional = true +require_conventional = false +split_commits = false +protect_breaking_commits = false +filter_commits = false +fail_on_unmatched_commit = false +use_branch_tags = false +topo_order = false +topo_order_commits = true +sort_commits = "oldest" +recurse_submodules = false + +commit_preprocessors = [ + { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](/issues/${2}))" }, +] + +commit_parsers = [ + { message = "^feat", group = "Features" }, + { message = "^fix", group = "Bug Fixes" }, + { message = "^refactor", group = "Refactor" }, + { message = "^doc", group = "Documentation" }, + { message = "^perf", group = "Performance" }, + { message = "^style", group = "Styling" }, + { message = "^test", group = "Testing" }, + { message = "^chore\\(release\\):.*", skip = true }, + { message = "^chore\\(deps.*\\)", skip = true }, + { message = "^chore\\(pr\\)", skip = true }, + { message = "^chore\\(pull\\)", skip = true }, + { message = "^chore|^ci", group = "Miscellaneous" }, + { body = ".*security", group = "Security" }, + { message = "^revert", group = "Reverts" }, + { message = ".*", group = "Other" }, +] + +link_parsers = [ + { pattern = "#(\\d+)", href = "https://github.com/89jobrien/notfiles/issues/$1", text = "$1" }, +] diff --git a/crates/notcore/Cargo.toml b/crates/notcore/Cargo.toml index 42a6b54..1bd452f 100644 --- a/crates/notcore/Cargo.toml +++ b/crates/notcore/Cargo.toml @@ -10,7 +10,6 @@ categories.workspace = true description = "Shared types and config for the notfiles dotfiles manager" [dependencies] -anyhow = { workspace = true } dirs = { workspace = true } serde = { workspace = true } strsim = { workspace = true } diff --git a/crates/notforge/Cargo.toml b/crates/notforge/Cargo.toml index e1c1979..9db30b1 100644 --- a/crates/notforge/Cargo.toml +++ b/crates/notforge/Cargo.toml @@ -18,9 +18,7 @@ name = "notforge" path = "src/main.rs" [dependencies] -anyhow = { workspace = true } base64 = { workspace = true } -clap = { workspace = true } miette = { workspace = true } notcore = { workspace = true } serde = { workspace = true } diff --git a/crates/notsecrets/Cargo.toml b/crates/notsecrets/Cargo.toml index 7977b1b..8ca2b2e 100644 --- a/crates/notsecrets/Cargo.toml +++ b/crates/notsecrets/Cargo.toml @@ -10,7 +10,6 @@ base64 = "0.22" bech32 = "0.11" chacha20poly1305 = "0.10" curve25519-dalek = "4" -dirs = { workspace = true } ed25519-dalek = "2" hkdf = "0.12" hmac = "0.12" @@ -24,7 +23,6 @@ thiserror = { workspace = true } toml = { workspace = true } x25519-dalek = { version = "2", features = ["static_secrets"] } yubikey = { version = "0.8", optional = true } -zeroize = "1" [features] default = [] diff --git a/preflight.md b/preflight.md index 42f7b8a..71040b7 100644 --- a/preflight.md +++ b/preflight.md @@ -3,7 +3,7 @@ A `rust-script` preflight that surfaces repo context in one shot: shell detection, git state, tracked-file tree, and HANDOFF document read/update. -```rust +````rust #!/usr/bin/env rust-script //! ```cargo //! [dependencies] @@ -238,7 +238,7 @@ fn main() -> Result<()> { println!("\n{}", "╚══ done ══╝".bold().yellow()); Ok(()) } -``` +```` ## Usage @@ -253,13 +253,13 @@ rust-script preflight.rs ## What it surfaces -| Section | Details | -|---------|---------| -| Environment | Current shell (`$SHELL`), cwd, git root | -| Git status | Branch name, short status (clean or dirty file list) | -| Git history | Last 7 commits, one-line with decoration | -| Tracked file tree | `git ls-files` output rendered as an indented tree, depth ≤ 3 | -| HANDOFF docs | Full content (first 40 lines) of any `HANDOFF.*` file at repo root; stamps `last_preflight:` timestamp | +| Section | Details | +| ----------------- | ------------------------------------------------------------------------------------------------------ | +| Environment | Current shell (`$SHELL`), cwd, git root | +| Git status | Branch name, short status (clean or dirty file list) | +| Git history | Last 7 commits, one-line with decoration | +| Tracked file tree | `git ls-files` output rendered as an indented tree, depth ≤ 3 | +| HANDOFF docs | Full content (first 40 lines) of any `HANDOFF.*` file at repo root; stamps `last_preflight:` timestamp | ## Dependencies diff --git a/tests/integration/Cargo.toml b/tests/integration/Cargo.toml index 77e1240..2edae83 100644 --- a/tests/integration/Cargo.toml +++ b/tests/integration/Cargo.toml @@ -3,7 +3,7 @@ name = "integration" version = "0.1.0" edition = "2024" license.workspace = true -publish = false +publish = true [[test]] name = "bootstrap" @@ -16,9 +16,9 @@ path = "tests/cross_crate.rs" [dev-dependencies] anyhow = { workspace = true } assert_fs = { workspace = true } -notcore = { path = "../../crates/notcore" } -notfiles = { path = "../../crates/notfiles" } -nothooks = { path = "../../crates/nothooks" } -notsecrets = { path = "../../crates/notsecrets" } -notstrap = { path = "../../crates/notstrap" } +notcore = { workspace = true } #unified +notfiles = { workspace = true } #unified +nothooks = { workspace = true } #unified +notsecrets = { workspace = true } #unified +notstrap = { workspace = true } #unified tempfile = { workspace = true }