Manage the user's global Claude config in the Module
Bring the declarative half of ~/.claude into modules/claude-code and apply
it when the Module is enabled: the global agent instructions (context =
./CLAUDE.md), the skills tree (skills = ./skills), the attention-bell hook,
and settings.json (model = opus plus the Stop/Notification/SessionStart
hook wiring).
Runtime state (projects, plugins, cache, history, sessions) and the
.credentials.json secret are left out, so login survives rebuilds and no
secret enters the repo. Verified against the built home-files that
~/.claude/{CLAUDE.md,settings.json,skills,hooks/attention-bell.sh} are
generated, the hook executable.
This commit is contained in:
53
modules/claude-code/skills/setup-skills/LOCKFILE.md
Normal file
53
modules/claude-code/skills/setup-skills/LOCKFILE.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Skills Lockfile
|
||||
|
||||
`.claude/skills-lock.yaml`, at the root of a project, tracks which library
|
||||
skills (from `~/.claude/skills/library/`) have been copied into that
|
||||
project's `.claude/skills/`, so [`setup-skills`](SKILL.md),
|
||||
[`update-skills`](../update-skills/SKILL.md), and
|
||||
[`remove-skills`](../remove-skills/SKILL.md) all agree on what's installed
|
||||
without re-deriving it from the filesystem.
|
||||
|
||||
## Schema
|
||||
|
||||
A YAML list of entries, one per installed skill:
|
||||
|
||||
```yaml
|
||||
- name: nbdev
|
||||
hash: 3f2a9b8c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a
|
||||
- name: terraform-conventions
|
||||
hash: 9c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a3f2a9b
|
||||
```
|
||||
|
||||
- `name` — matches both the skill's directory name in the library
|
||||
(`skills/library/<name>`) and its copied directory name in the project
|
||||
(`.claude/skills/<name>`).
|
||||
- `hash` — the output of `hash-dir.sh` run against that one skill's
|
||||
directory contents, recorded at the moment it was last copied or
|
||||
confirmed up to date. Never a hash of anything else — not the whole
|
||||
project, not the whole library, just that one skill's own directory
|
||||
tree.
|
||||
|
||||
## What a mismatch means
|
||||
|
||||
To classify a skill's state, compare three values: the lockfile's stored
|
||||
`hash`, `hash-dir.sh` on the project's current copy
|
||||
(`.claude/skills/<name>`), and `hash-dir.sh` on the library's current
|
||||
source (`~/.claude/skills/library/<name>`).
|
||||
|
||||
| stored vs. project copy | stored vs. library source | meaning |
|
||||
|--------------------------|----------------------------|--------------------------------------|
|
||||
| match | match | nothing to do |
|
||||
| match | differs | library moved on — safe to update |
|
||||
| differs | match | project customized on purpose — leave it |
|
||||
| differs | differs | conflict — report, don't touch |
|
||||
|
||||
## Writing to the lockfile
|
||||
|
||||
- Adding a skill: append a new `{name, hash}` entry.
|
||||
- Applying a safe update: overwrite that entry's `hash` in place with the
|
||||
library's current hash.
|
||||
- Removing a skill: delete its entry entirely.
|
||||
|
||||
Never reorder or restructure existing entries beyond what an add, update,
|
||||
or remove requires — this file is meant to diff cleanly in a project's
|
||||
git history.
|
||||
46
modules/claude-code/skills/setup-skills/SKILL.md
Normal file
46
modules/claude-code/skills/setup-skills/SKILL.md
Normal file
@@ -0,0 +1,46 @@
|
||||
---
|
||||
name: setup-skills
|
||||
description: Add relevant skills from the shared skills library to the current project.
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
Adds opt-in, project-specific skills from `~/.claude/skills/library/` into
|
||||
the current project's `.claude/skills/`, tracked in
|
||||
`.claude/skills-lock.yaml` (see [LOCKFILE.md](LOCKFILE.md) for its schema).
|
||||
Only ever adds — checking already-installed skills for updates is
|
||||
[`update-skills`](../update-skills/SKILL.md)'s job, not this one's.
|
||||
|
||||
## Steps
|
||||
|
||||
1. Read `.claude/skills-lock.yaml` in the current project, if it exists.
|
||||
Note every skill name already listed — these are already installed and
|
||||
must not be re-proposed.
|
||||
|
||||
2. List every skill under `~/.claude/skills/library/*/SKILL.md` and read
|
||||
each one's `name` and `description`.
|
||||
|
||||
3. Inspect the current project (file tree, manifests like
|
||||
`pyproject.toml`/`package.json`, file extensions present, etc.) and
|
||||
judge which library skills — excluding ones already installed — seem
|
||||
relevant, the same way you'd reason about any unfamiliar codebase.
|
||||
Propose that shortlist to the user with your reasoning, one line per
|
||||
skill. If the user asks to see the full catalog instead, list every
|
||||
library skill (minus already-installed ones) with its description.
|
||||
|
||||
4. Let the user confirm, adjust, or pick freely from the full list.
|
||||
|
||||
5. For each confirmed skill:
|
||||
- If `.claude/skills/<name>/` already exists in the project and is
|
||||
*not* in the lockfile, skip it and tell the user why (a same-named
|
||||
skill already lives there and isn't tracked — remove or rename it
|
||||
first if they want the library version).
|
||||
- Otherwise, copy `~/.claude/skills/library/<name>/` to
|
||||
`.claude/skills/<name>/` in the project, run
|
||||
`~/.claude/skills/setup-skills/hash-dir.sh .claude/skills/<name>`,
|
||||
and append `{name, hash: <output>}` to `.claude/skills-lock.yaml`
|
||||
(create the file, an empty YAML list, if it doesn't exist yet).
|
||||
|
||||
6. Report what was added and what was skipped, and why.
|
||||
|
||||
Done when every confirmed skill is either copied and recorded in the
|
||||
lockfile, or explicitly skipped with a stated reason.
|
||||
23
modules/claude-code/skills/setup-skills/hash-dir.sh
Executable file
23
modules/claude-code/skills/setup-skills/hash-dir.sh
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
# Deterministic recursive hash of a directory's file contents.
|
||||
#
|
||||
# Hashes relative paths, not absolute ones, so two directories with
|
||||
# identical contents hash identically regardless of where they live on
|
||||
# disk (needed to compare a project's copied skill against the library
|
||||
# source it was copied from).
|
||||
#
|
||||
# Usage: hash-dir.sh <directory>
|
||||
set -euo pipefail
|
||||
|
||||
if [ $# -ne 1 ]; then
|
||||
echo "Usage: hash-dir.sh <directory>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
dir="$1"
|
||||
if [ ! -d "$dir" ]; then
|
||||
echo "Not a directory: $dir" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
(cd "$dir" && find . -type f -print0 | sort -z | xargs -0 -r sha256sum) | sha256sum | awk '{print $1}'
|
||||
Reference in New Issue
Block a user