From b4cba74ea39af00fbf98b14814dbec1e8f1e516c Mon Sep 17 00:00:00 2001 From: Joseph O'Brien <98370624+89jobrien@users.noreply.github.com> Date: Wed, 1 Apr 2026 20:58:09 -0400 Subject: [PATCH] =?UTF-8?q?docs:=20update=20HANDOFF.md=20=E2=80=94=20age-n?= =?UTF-8?q?ative=20redesign=20progress=20(tasks=201=E2=80=937=20complete)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 562 ++++++++++++++++++++++++- HANDOFF.md | 68 ++- crates/notcore/src/config.rs | 10 +- crates/notcore/src/types.rs | 9 +- crates/notfiles/src/ignore.rs | 4 +- crates/notfiles/src/lib.rs | 6 +- crates/notfiles/src/linker.rs | 43 +- crates/notfiles/src/main.rs | 7 +- crates/notfiles/src/package.rs | 2 +- crates/notfiles/src/status.rs | 12 +- crates/notfiles/tests/integration.rs | 36 +- crates/nothooks/README.md | 121 ++++++ crates/nothooks/src/lib.rs | 8 +- crates/nothooks/src/main.rs | 2 +- crates/nothooks/src/runner.rs | 21 +- crates/nothooks/src/state.rs | 4 +- crates/nothooks/tests/integration.rs | 4 +- crates/notstrap/src/lib.rs | 60 ++- crates/notstrap/src/main.rs | 9 +- crates/notstrap/src/prereqs.rs | 15 +- mise.toml | 94 +++++ scripts/check-dep-boundaries.py | 56 +++ tests/integration/tests/bootstrap.rs | 4 +- tests/integration/tests/cross_crate.rs | 11 +- 24 files changed, 1069 insertions(+), 99 deletions(-) create mode 100644 crates/nothooks/README.md create mode 100644 mise.toml create mode 100644 scripts/check-dep-boundaries.py diff --git a/Cargo.lock b/Cargo.lock index bb3bf91..fcb296e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,16 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -97,12 +107,39 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bech32" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" + [[package]] name = "bitflags" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bstr" version = "1.12.1" @@ -135,6 +172,30 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.44" @@ -146,6 +207,17 @@ dependencies = [ "windows-link", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + [[package]] name = "clap" version = "4.6.0" @@ -192,12 +264,27 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -223,12 +310,73 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "difflib" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + [[package]] name = "dirs" version = "6.0.0" @@ -256,6 +404,30 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "780955b8b195a21ab8e4ac6b60dd1dbdcec1dc6c51c0617964b08c81785e12c9" +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + [[package]] name = "either" version = "1.15.0" @@ -290,6 +462,12 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -302,6 +480,16 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -371,6 +559,24 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "iana-time-zone" version = "0.1.65" @@ -429,6 +635,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "integration" version = "0.1.0" @@ -465,6 +680,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + [[package]] name = "leb128fmt" version = "0.1.0" @@ -477,6 +701,12 @@ version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "libredox" version = "0.1.15" @@ -548,12 +778,24 @@ name = "notsecrets" version = "0.1.0" dependencies = [ "anyhow", + "base64", + "bech32", + "chacha20poly1305", + "curve25519-dalek", "dirs", + "ed25519-dalek", + "hkdf", + "hmac", "notcore", + "rand", "rpassword", + "rsa", + "scrypt", + "sha2", "tempfile", "thiserror", - "which", + "x25519-dalek", + "zeroize", ] [[package]] @@ -571,6 +813,42 @@ dependencies = [ "which", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -578,6 +856,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -592,12 +871,89 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "option-ext" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "predicates" version = "3.1.4" @@ -659,6 +1015,36 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + [[package]] name = "redox_users" version = "0.5.2" @@ -698,6 +1084,27 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "sha2", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rtoolbox" version = "0.0.3" @@ -708,6 +1115,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.1.4" @@ -727,6 +1143,15 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + [[package]] name = "same-file" version = "1.0.6" @@ -736,6 +1161,18 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "password-hash", + "pbkdf2", + "salsa20", + "sha2", +] + [[package]] name = "semver" version = "1.0.27" @@ -794,18 +1231,67 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.117" @@ -897,6 +1383,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "unicode-ident" version = "1.0.24" @@ -909,12 +1401,28 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "walkdir" version = "2.5.0" @@ -1302,6 +1810,58 @@ dependencies = [ "wasmparser", ] +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core", + "serde", + "zeroize", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zmij" version = "1.0.21" diff --git a/HANDOFF.md b/HANDOFF.md index 615ac70..1b6da97 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -1,9 +1,11 @@ # HANDOFF.md -State of the `notfiles` repo as of 2026-03-31. +State of the `notfiles` repo as of 2026-04-01. ## What Was Done +### Previous session (2026-03-31) + 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/` @@ -11,27 +13,61 @@ Restructured from a flat `src/` layout into a Cargo workspace of 5 crates: - `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`. +### This session (2026-04-01) -## Pending Issues +Began age-native redesign of `notsecrets` — replacing sops shell-out and Bitwarden CLI dependency with a self-contained age encryption/decryption library implemented in pure Rust (modeled after rage, no dependency on it). + +**Spec and plan written:** +- `docs/superpowers/specs/2026-04-01-notsecrets-age-redesign.md` +- `docs/superpowers/plans/2026-04-01-notsecrets-age-redesign.md` + +**Implementation progress (Tasks 1–7 of 12 complete):** + +| Task | Status | Commit | +|------|--------|--------| +| 1: Scaffold (Cargo.toml, error.rs, domain types, legacy shim) | ✅ | `34d9e3d` | +| 2: format.rs (age wire format parser/serializer) | ✅ | `2476daa` | +| 3: X25519 identity + recipient | ✅ | `6202c5d` | +| 4: Scrypt identity + recipient | ✅ | `b899198` | +| 5: SSH Ed25519 identity + recipient | ✅ | `566501c` | +| 6: SSH RSA identity + recipient | ✅ | `a0fe6f7` | +| 7: EncryptedIdentity (stub, real impl needs Decryptor) | ✅ | `545a45b` | +| 8: Encryptor | ⏳ not started | | +| 9: Decryptor + wire EncryptedIdentity | ⏳ not started | | +| 10: sources/ migration (IdentitySource trait) | ⏳ not started | | +| 11: resolve_identities() public API | ⏳ not started | | +| 12: notstrap migration + cargo check --workspace | ⏳ not started | | + +**Current state:** +- `notsecrets` compiles and passes all existing tests +- Legacy API (`AgeKeySource`, `resolve_age_key`, `install_age_key`, `decrypt_sops`) preserved in `_legacy.rs` — `notstrap` still works +- `EncryptedIdentity::unwrap_file_key` is a stub returning an error until `Decryptor` is wired in Task 9 + +## Resuming + +To continue the age-native redesign, pick up at Task 8 (Encryptor). The plan is at: +`docs/superpowers/plans/2026-04-01-notsecrets-age-redesign.md` + +Tasks 8–12 need to be executed in order (each depends on the previous). Use the subagent-driven-development skill to dispatch implementer subagents per task. + +Key context for resuming: +- `EncryptedIdentity` stub in `src/identities/encrypted.rs` must have its `unwrap_file_key` body replaced after `Decryptor` exists (end of Task 9) +- The `EnvInjector` type in `notstrap` changes from `Box` to `Box` in Task 12 +- `_legacy.rs` is deleted in Task 10 + +## Pending Issues (carried from previous session) ### 1. Push to remote -```bash -git push -``` -8 commits ahead of `gitea/main`. Nothing blocking this. +Several commits ahead of `gitea/main`. Push when ready. ### 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. +`crates/notstrap/src/lib.rs` is empty — created as a stub. Either delete `[lib]` from `notstrap/Cargo.toml` 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. +`notstrap.toml` format is defined inline via serde structs but never written down. A sample config would help. -### 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. +### 4. Integration test coverage for nothooks assumes nu in PATH +`nothooks` integration tests call `nu` directly. CI needs `nu` installed or tests need `#[ignore]` + feature flag. -### 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. +### 5. notstrap sops_file path footgun +If `sops_file` in `notstrap.toml` is an absolute path, the `join` will silently override the base. Low priority. diff --git a/crates/notcore/src/config.rs b/crates/notcore/src/config.rs index 8b7a3f9..9f2320a 100644 --- a/crates/notcore/src/config.rs +++ b/crates/notcore/src/config.rs @@ -77,10 +77,12 @@ impl Config { if !config_path.exists() { return Ok(Config::default()); } - let content = std::fs::read_to_string(&config_path) - .map_err(|e| NotfilesError::Config(format!("reading {}: {e}", config_path.display())))?; - let config: Config = toml::from_str(&content) - .map_err(|e| NotfilesError::Config(format!("parsing {}: {e}", config_path.display())))?; + let content = std::fs::read_to_string(&config_path).map_err(|e| { + NotfilesError::Config(format!("reading {}: {e}", config_path.display())) + })?; + let config: Config = toml::from_str(&content).map_err(|e| { + NotfilesError::Config(format!("parsing {}: {e}", config_path.display())) + })?; Ok(config) } diff --git a/crates/notcore/src/types.rs b/crates/notcore/src/types.rs index 98376e0..2715ae4 100644 --- a/crates/notcore/src/types.rs +++ b/crates/notcore/src/types.rs @@ -48,7 +48,10 @@ pub struct Report { impl Report { pub fn add(&mut self, name: impl Into, status: StepStatus) { - self.steps.push(Step { name: name.into(), status }); + self.steps.push(Step { + name: name.into(), + status, + }); } pub fn print(&self) { @@ -67,7 +70,9 @@ impl Report { } pub fn has_failures(&self) -> bool { - self.steps.iter().any(|s| matches!(s.status, StepStatus::Failed(_))) + self.steps + .iter() + .any(|s| matches!(s.status, StepStatus::Failed(_))) } } diff --git a/crates/notfiles/src/ignore.rs b/crates/notfiles/src/ignore.rs index 880ffcd..fd7d137 100644 --- a/crates/notfiles/src/ignore.rs +++ b/crates/notfiles/src/ignore.rs @@ -14,7 +14,9 @@ impl IgnoreMatcher { // Match the pattern as a filename component and also as a path suffix. let glob = Glob::new(pattern) .or_else(|_| Glob::new(&format!("**/{pattern}"))) - .map_err(|e| NotfilesError::Other(format!("invalid ignore pattern '{pattern}': {e}")))?; + .map_err(|e| { + NotfilesError::Other(format!("invalid ignore pattern '{pattern}': {e}")) + })?; builder.add(glob); // Also add a recursive variant so "foo" matches "a/foo" etc. #[allow(clippy::collapsible_if)] diff --git a/crates/notfiles/src/lib.rs b/crates/notfiles/src/lib.rs index 449e191..1950429 100644 --- a/crates/notfiles/src/lib.rs +++ b/crates/notfiles/src/lib.rs @@ -10,11 +10,7 @@ use std::path::Path; pub use linker::{LinkOptions, State}; pub use package::resolve_packages; -pub fn link( - dotfiles_dir: &Path, - packages: &[String], - opts: &LinkOptions, -) -> Result { +pub fn link(dotfiles_dir: &Path, packages: &[String], opts: &LinkOptions) -> Result { let config = notcore::Config::load(dotfiles_dir)?; let mut state = State::load(dotfiles_dir)?; let pkgs = resolve_packages(dotfiles_dir, packages)?; diff --git a/crates/notfiles/src/linker.rs b/crates/notfiles/src/linker.rs index 6f5419e..a08b6d1 100644 --- a/crates/notfiles/src/linker.rs +++ b/crates/notfiles/src/linker.rs @@ -4,8 +4,8 @@ use std::path::{Path, PathBuf}; use chrono::Utc; use serde::{Deserialize, Serialize}; -use notcore::{Config, Method, NotfilesError, expand_tilde}; use crate::package::collect_files; +use notcore::{Config, Method, NotfilesError, expand_tilde}; const STATE_FILE: &str = ".notfiles-state.toml"; @@ -46,7 +46,10 @@ impl State { } pub fn entries_for_package(&self, package: &str) -> Vec<&StateEntry> { - self.entries.iter().filter(|e| e.package == package).collect() + self.entries + .iter() + .filter(|e| e.package == package) + .collect() } pub fn remove_package(&mut self, package: &str) { @@ -55,7 +58,8 @@ impl State { pub fn add_entry(&mut self, entry: StateEntry) { // Remove existing entry for same source+target, then add new - self.entries.retain(|e| !(e.source == entry.source && e.target == entry.target)); + self.entries + .retain(|e| !(e.source == entry.source && e.target == entry.target)); self.entries.push(entry); } } @@ -113,10 +117,18 @@ pub fn link_package( if !opts.no_backup { let backup = backup_path(&target); if opts.dry_run { - println!(" \x1b[33mwould backup\x1b[0m {} -> {}", target.display(), backup.display()); + println!( + " \x1b[33mwould backup\x1b[0m {} -> {}", + target.display(), + backup.display() + ); } else { if opts.verbose { - println!(" \x1b[33mbackup\x1b[0m {} -> {}", target.display(), backup.display()); + println!( + " \x1b[33mbackup\x1b[0m {} -> {}", + target.display(), + backup.display() + ); } fs::rename(&target, &backup)?; } @@ -147,7 +159,10 @@ pub fn link_package( }; if opts.dry_run { - println!(" \x1b[36mwould {action_word}\x1b[0m {source_display} -> {}", target.display()); + println!( + " \x1b[36mwould {action_word}\x1b[0m {source_display} -> {}", + target.display() + ); } else { match method { Method::Symlink => { @@ -161,7 +176,10 @@ pub fn link_package( } } if opts.verbose { - println!(" \x1b[32m{action_word}\x1b[0m {source_display} -> {}", target.display()); + println!( + " \x1b[32m{action_word}\x1b[0m {source_display} -> {}", + target.display() + ); } state.add_entry(StateEntry { @@ -183,7 +201,11 @@ pub fn unlink_package( package: &str, opts: &LinkOptions, ) -> Result<(), NotfilesError> { - let entries: Vec = state.entries_for_package(package).into_iter().cloned().collect(); + let entries: Vec = state + .entries_for_package(package) + .into_iter() + .cloned() + .collect(); if entries.is_empty() { if opts.verbose { @@ -278,7 +300,10 @@ fn cleanup_empty_parents(path: &Path) { if Some(parent.to_path_buf()) == dirs::home_dir() || parent == Path::new("/") { break; } - if fs::read_dir(parent).map(|mut d| d.next().is_none()).unwrap_or(false) { + if fs::read_dir(parent) + .map(|mut d| d.next().is_none()) + .unwrap_or(false) + { let _ = fs::remove_dir(parent); dir = parent.parent(); } else { diff --git a/crates/notfiles/src/main.rs b/crates/notfiles/src/main.rs index f447c29..ec7562d 100644 --- a/crates/notfiles/src/main.rs +++ b/crates/notfiles/src/main.rs @@ -2,11 +2,11 @@ use anyhow::{Context, Result}; use clap::Parser; use std::fs; +use notcore::Config; use notfiles::cli::{Cli, Command}; use notfiles::linker::{LinkOptions, State}; use notfiles::package::resolve_packages; use notfiles::{linker, status}; -use notcore::Config; fn main() -> Result<()> { let cli = Cli::parse(); @@ -46,7 +46,10 @@ fn main() -> Result<()> { if !cli.dry_run { state.save(&dotfiles_dir)?; - let count: usize = pkgs.iter().map(|p| state.entries_for_package(p).len()).sum(); + let count: usize = pkgs + .iter() + .map(|p| state.entries_for_package(p).len()) + .sum(); println!( "\x1b[32mLinked {count} file{} across {} package{}.\x1b[0m", if count == 1 { "" } else { "s" }, diff --git a/crates/notfiles/src/package.rs b/crates/notfiles/src/package.rs index a88ac35..e2b981b 100644 --- a/crates/notfiles/src/package.rs +++ b/crates/notfiles/src/package.rs @@ -1,8 +1,8 @@ use std::fs; use std::path::{Path, PathBuf}; -use notcore::{Config, NotfilesError}; use crate::ignore::IgnoreMatcher; +use notcore::{Config, NotfilesError}; /// Discover available packages (subdirectories of the dotfiles dir). pub fn discover_packages(dotfiles_dir: &Path) -> Result, NotfilesError> { diff --git a/crates/notfiles/src/status.rs b/crates/notfiles/src/status.rs index fdd0b11..9fc525a 100644 --- a/crates/notfiles/src/status.rs +++ b/crates/notfiles/src/status.rs @@ -1,9 +1,9 @@ use std::fs; use std::path::{Path, PathBuf}; -use notcore::{Config, Method, expand_tilde}; use crate::linker::State; use crate::package::collect_files; +use notcore::{Config, Method, expand_tilde}; #[derive(Debug, PartialEq)] pub enum FileStatus { @@ -69,8 +69,7 @@ pub fn package_status( } Method::Copy => { let has_state = state.entries.iter().any(|e| { - e.package == package - && e.target == target.to_string_lossy().as_ref() + e.package == package && e.target == target.to_string_lossy().as_ref() }); if has_state && target.exists() { FileStatus::Copied @@ -122,6 +121,11 @@ pub fn print_status(package: &str, entries: &[StatusEntry]) { } println!(" \x1b[1m{package}\x1b[0m:"); for entry in entries { - println!(" {} {} -> {}", entry.status, entry.source_display, entry.target.display()); + println!( + " {} {} -> {}", + entry.status, + entry.source_display, + entry.target.display() + ); } } diff --git a/crates/notfiles/tests/integration.rs b/crates/notfiles/tests/integration.rs index 56014b9..af8915d 100644 --- a/crates/notfiles/tests/integration.rs +++ b/crates/notfiles/tests/integration.rs @@ -94,7 +94,13 @@ fn test_link_and_status() { let gitconfig = target.join(".gitconfig"); assert!(gitconfig.exists()); - assert!(gitconfig.symlink_metadata().unwrap().file_type().is_symlink()); + assert!( + gitconfig + .symlink_metadata() + .unwrap() + .file_type() + .is_symlink() + ); // State file should exist assert!(dotfiles.join(".notfiles-state.toml").exists()); @@ -199,7 +205,14 @@ fn test_force_with_backup() { assert!(ok); // The link should now exist - assert!(target.join(".gitconfig").symlink_metadata().unwrap().file_type().is_symlink()); + assert!( + target + .join(".gitconfig") + .symlink_metadata() + .unwrap() + .file_type() + .is_symlink() + ); // A backup should exist let backups: Vec<_> = fs::read_dir(&target) @@ -232,11 +245,7 @@ fn test_force_no_backup() { let backups: Vec<_> = fs::read_dir(&target) .unwrap() .filter_map(|e| e.ok()) - .filter(|e| { - e.file_name() - .to_string_lossy() - .contains("notfiles-backup") - }) + .filter(|e| e.file_name().to_string_lossy().contains("notfiles-backup")) .collect(); assert_eq!(backups.len(), 0); } @@ -272,8 +281,17 @@ method = "copy" let ssh_config = target.join(".ssh/config"); assert!(ssh_config.exists()); // Should NOT be a symlink - assert!(!ssh_config.symlink_metadata().unwrap().file_type().is_symlink()); - assert_eq!(fs::read_to_string(&ssh_config).unwrap(), "Host *\n AddKeysToAgent yes"); + assert!( + !ssh_config + .symlink_metadata() + .unwrap() + .file_type() + .is_symlink() + ); + assert_eq!( + fs::read_to_string(&ssh_config).unwrap(), + "Host *\n AddKeysToAgent yes" + ); // Status should show "copied" let (stdout, _, _) = run(&dotfiles, &["status", "ssh"]); diff --git a/crates/nothooks/README.md b/crates/nothooks/README.md new file mode 100644 index 0000000..2d26718 --- /dev/null +++ b/crates/nothooks/README.md @@ -0,0 +1,121 @@ +# nothooks + +Nushell hook runner for the notfiles workspace. Executes `.nu` scripts in two +distinct lifecycle phases — **dot** (always) and **setup** (once) — with +state persistence so setup hooks are not re-run across invocations. + +## Concepts + +### HookPhase + +Each hook belongs to exactly one phase: + +| Phase | When it runs | Typical use | +|-------|-------------|-------------| +| `dot` | Every time nothooks is invoked | Apply shell config, set env vars, refresh symlinks | +| `setup` | Once per machine (tracked in state) | Install packages, create dirs, first-time configuration | + +### HookSpec + +A hook is described by three fields (defined in `notcore`): + +```toml +[[hooks]] +name = "install-starship" +script = "hooks/setup/install-starship.nu" +phase = "setup" + +[[hooks]] +name = "set-default-shell" +script = "hooks/dot/shell.nu" +phase = "dot" +``` + +### State tracking + +Setup hooks are tracked in `.nothooks-state.toml` in the state directory +(typically the dotfiles root). Once a setup hook completes successfully its +name is recorded and it will be **skipped** on all future runs. + +```toml +# .nothooks-state.toml (auto-generated, do not edit manually) +completed_setup_hooks = ["install-starship", "configure-git"] +``` + +Dot hooks are **never** recorded in state — they run unconditionally on every +invocation. + +### --force re-run + +To re-run setup hooks (e.g. after a machine rebuild or to apply changes), +construct the runner with `HookRunner::with_force`. This bypasses the state +check so all setup hooks execute regardless of prior completion. + +The notstrap CLI exposes this as `--force`. + +## Hook script lifecycle + +``` +notstrap / nothooks invoked +│ +├── dot phase +│ └── for each hook where phase == "dot" +│ └── nu