diff --git a/CLAUDE.md b/CLAUDE.md index b60019f..ddc308c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -40,6 +40,10 @@ The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overla - The remote is self-hosted Gitea (`git.alexion.dev`), driven with `gitea-axi` rather than `tea`; `gh` is not installed. `gitea-axi` resolves the repository from the `origin` remote and takes credentials from the `axi` tea login, so both are implicit inside a checkout. `tea` remains installed only as that credential source. +- `~/.claude/skills` is generated by home-manager with `recursive = true`, so the directories are real and writable but every leaf file is a read-only symlink into the store. + Editing a skill in place fails; its source is `modules/claude-code/skills//` here, applied by a rebuild. + Creating a new file under `~/.claude/skills/` succeeds silently and is the trap — it stays outside the repo and reaches no other machine. + Copying out of that tree needs `cp -rL` plus `chmod -R u+w`: a plain `cp -r` copies the symlinks, putting store paths into the destination, and dereferenced files keep the store's read-only mode. - nixpkgs `vimPlugins.nord-nvim` is `shaunsingh/nord.nvim` (no `require("nord").setup()`); the config wants `gbprod/nord.nvim`, which is packaged as `vimPlugins.gbprod-nord`. - nixpkgs `vimPlugins.nvim-treesitter` tracks the rewritten `main` branch: there is no `require("nvim-treesitter.configs").setup{ensure_installed,highlight,indent}`. Under nixvim, use `plugins.treesitter` with `highlight.enable`/`indent.enable` and `grammarPackages = with config.programs.nixvim.plugins.treesitter.package.builtGrammars; [ ... ]` — the module's own `package.builtGrammars`, **not** `pkgs.vimPlugins.nvim-treesitter.*` (whose query files can mismatch). The module targets the main branch and enables features via neovim-native APIs (`vim.treesitter.start()`, `require'nvim-treesitter'.indentexpr()`). - Neovim is configured via **nixvim** (flake input `nixvim`, consumed as `inputs.nixvim.homeModules.nixvim` added to `home-manager.sharedModules`, config under `home-manager.users..programs.nixvim`). `nixvim.inputs.nixpkgs.follows = "nixpkgs"` is set; nixvim then emits a benign eval warning that its pinned nixpkgs differs from the followed one — builds and runs fine, do not "fix" it by dropping the follows. diff --git a/modules/claude-code/skills/craft-skill/SKILL.md b/modules/claude-code/skills/craft-skill/SKILL.md index 7b7f78c..98d78f9 100644 --- a/modules/claude-code/skills/craft-skill/SKILL.md +++ b/modules/claude-code/skills/craft-skill/SKILL.md @@ -23,11 +23,11 @@ If the request describes a new workflow, capability, or repeated manual process - Is there already a word — in your prompts, docs, or codebase — that names this behavior? Reach for that **leading word** before coining one. Done when every axis above has an answer, or the user says to just draft something and iterate. -2. **Write the draft.** First decide where it lives: project-local `.claude/skills/` if the workflow is tied to this one repo, `~/.claude/skills/` if it's general-purpose across projects. Then follow the **information hierarchy**: steps for what the agent does in order, in-file **reference** for facts every branch needs, and disclose the rest behind a pointer — to a sibling file, or to the existing project docs identified in step 1 rather than restating them. Done when every branch from step 1 has somewhere to live, and no sentence fails the no-op test in isolation (see `No-Op` in GLOSSARY.md). +2. **Write the draft.** First decide where it lives: project-local `.claude/skills/` if the workflow is tied to this one repo, the personal set if it's general-purpose across projects. The personal set is not authored in `~/.claude/skills/` — that tree is generated, and every file under it is a read-only symlink into the Nix store. Write it in the dotfiles repo at `modules/claude-code/skills//` and rebuild to make it live. Creating files directly under `~/.claude/skills/` looks like it works, because the directories themselves are writable, but the result is untracked by the repo and reaches no other machine. Then follow the **information hierarchy**: steps for what the agent does in order, in-file **reference** for facts every branch needs, and disclose the rest behind a pointer — to a sibling file, or to the existing project docs identified in step 1 rather than restating them. Done when every branch from step 1 has somewhere to live, and no sentence fails the no-op test in isolation (see `No-Op` in GLOSSARY.md). ## Audit an existing skill -1. **Locate it.** Check the current project's `.claude/skills/`, then `~/.claude/skills/`, then `~/.claude/skills/library/`, in that order; ask if the name is ambiguous across locations. If it's tracked in a project's `skills-lock.yaml`, mention that editing it here will make it read as locally-customized to `update-skills` — confirm that's actually intended rather than editing the library source. +1. **Locate it.** Check the current project's `.claude/skills/`, then `~/.claude/skills/`, then `~/.claude/skills/library/`, in that order; ask if the name is ambiguous across locations. A hit under `~/.claude/skills/` is a read-only symlink and cannot be edited in place: its source is the dotfiles repo, at `modules/claude-code/skills//` for a personal skill or `modules/claude-code/skills/library//` for a library one. Edit there and rebuild. If it's tracked in a project's `skills-lock.yaml`, mention that editing it here will make it read as locally-customized to `update-skills` — confirm that's actually intended rather than editing the library source. 2. **Apply the checklist.** Read the skill and its disclosed files, then check each against GLOSSARY.md, quoting the offending line for anything that fails: - **Premature completion** — is each completion criterion checkable, and does it demand what the step actually needs? @@ -44,6 +44,6 @@ If the request describes a new workflow, capability, or repeated manual process 1. Propose one realistic test prompt — reflecting the trigger phrasing gathered (draft) or the skill's existing purpose (audit) — and get it confirmed or adjusted before spending a run on it. 2. Spawn one subagent: give it the skill's path and the confirmed prompt, have it attempt the task using the skill, and report back what happened — including anywhere it hesitated, misread the skill, or did something unexpected. 3. Re-read the draft/rewrite against GLOSSARY.md's failure modes in light of that run, and fix whatever either pass turned up. If the fix is substantial, repeat from step 1; otherwise it's done. -4. Stage the specific changed or created paths — one path per file, never a wildcard — with the host project's own staging convention: plain `git add ` normally, or e.g. `dot add ` in this dotfiles setup (wrap as `fish -c "dot add "` if the invoking shell isn't fish — `dot` is a fish function, not a binary on `$PATH`). Do not commit; that's left to the user. +4. Stage the specific changed or created paths — one path per file, never a wildcard — with `git add `. Do not commit; that's left to the user. Done when the subagent's run succeeded without confusion on the confirmed prompt, the checklist raised nothing outstanding, and every changed path is staged. diff --git a/modules/claude-code/skills/setup-skills/SKILL.md b/modules/claude-code/skills/setup-skills/SKILL.md index 74d924e..a1aeb10 100644 --- a/modules/claude-code/skills/setup-skills/SKILL.md +++ b/modules/claude-code/skills/setup-skills/SKILL.md @@ -10,6 +10,12 @@ the current project's `.claude/skills/`, tracked in Only ever adds — checking already-installed skills for updates is [`update-skills`](../update-skills/SKILL.md)'s job, not this one's. +The library is a tree of read-only symlinks into the Nix store, so every +copy out of it must dereference (`cp -rL`) and then restore write +permission (`chmod -R u+w`). A plain `cp -r` copies the symlinks +themselves, putting store paths into the project that break on any other +machine. + ## Steps 1. Read `.claude/skills-lock.yaml` in the current project, if it exists. @@ -35,7 +41,8 @@ Only ever adds — checking already-installed skills for updates is skill already lives there and isn't tracked — remove or rename it first if they want the library version). - Otherwise, copy `~/.claude/skills/library//` to - `.claude/skills//` in the project, run + `.claude/skills//` in the project with + `cp -rL` followed by `chmod -R u+w`, run `~/.claude/skills/setup-skills/hash-dir.sh .claude/skills/`, and append `{name, hash: }` to `.claude/skills-lock.yaml` (create the file, an empty YAML list, if it doesn't exist yet). diff --git a/modules/claude-code/skills/update-skills/SKILL.md b/modules/claude-code/skills/update-skills/SKILL.md index 2d11a89..b0a17b3 100644 --- a/modules/claude-code/skills/update-skills/SKILL.md +++ b/modules/claude-code/skills/update-skills/SKILL.md @@ -36,8 +36,11 @@ skill that isn't already there — that's already included an explicit go-ahead argument (e.g. `-y`, `yes`), in which case apply them without asking. Applying means: delete `.claude/skills//` entirely and copy - `~/.claude/skills/library//` in its place, so no file the project - copy had but the library no longer has can survive — then recompute its + `~/.claude/skills/library//` in its place with `cp -rL` followed + by `chmod -R u+w` (the library is read-only symlinks into the Nix store; + a plain `cp -r` would put store paths into the project), so no file the + project copy had but the library no longer has can survive — then + recompute its hash and overwrite that entry's `hash` in `.claude/skills-lock.yaml` in place.