feat(autotune): persist skills to repo and mirrors

A skill written only under .claude/skills is lost on the next install, and
one written only into the skills repository is not live until the next
restart, so both writers now do both: locate the operator's skills
repository through an ordered first-hit lookup, write the source of truth
there, then mirror the whole skill directory into the live locations so
hooks and references come along.

The lookup ends in a question to the operator rather than a silent skip
when no repository matches. autoskill routes to the same section instead
of carrying its own copy of the paths.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-02 19:39:43 +02:00
parent 168a307e4f
commit 89e088b6f4
3 changed files with 47 additions and 6 deletions

View File

@@ -18,10 +18,12 @@ vary), do the following:
3. On approval, write the skill: 3. On approval, write the skill:
- a conversation shortcut or expansion: add it to the `shortcuts` skill - a conversation shortcut or expansion: add it to the `shortcuts` skill
table instead of creating a new skill. table instead of creating a new skill.
- project-specific behavior: create it in the project's own skills - anything else: persist it exactly as the `autotune` skill's
directory (e.g. `skills/<name>/SKILL.md` in the repository). **Persistence** section specifies — locate the operator's skills
- portable behavior: create `skills/<name>/SKILL.md` in the operator's repository, write `skills/<name>/SKILL.md` there (or in the current
skills repository. project for project-specific behavior), then mirror the skill directory
into `~/.claude/skills/` and `~/.agents/skills/`, plus the project's
`.claude/skills/` and `.agents/skills/` when it is project-specific.
Follow the local naming conventions and keep the skill a thin, Follow the local naming conventions and keep the skill a thin,
single-purpose instruction; route to an authoritative doc when one single-purpose instruction; route to an authoritative doc when one
exists instead of duplicating it. exists instead of duplicating it.

View File

@@ -65,10 +65,35 @@ for a final `Write it` / `Revise it` / `Discard` confirmation before touching
disk. Rewrites of an existing skill additionally show a before/after diff of disk. Rewrites of an existing skill additionally show a before/after diff of
the changed lines in that confirmation. the changed lines in that confirmation.
## Persistence
A skill that exists only under `.claude/skills` is lost on the next install, and
one that exists only in the repository is not live until the next restart — so
every skill is written to **both**, in this order:
1. **Locate the operator's skills repository.** Take the first hit: the
`SKILLS_REPO` environment variable; the current repository, when it holds
`skills-lock.json` and a `skills/` directory; `pkgmgr path skills`; a git
checkout named `skills` under the usual roots
(`~/Repositories/*/*/skills`, `~/git`, `~/src`, `~/Projects`) carrying
`skills-lock.json`. If none matches, ask the operator for the path with a
single-select question — never skip the repository and write only the live
copies.
2. **Write the source of truth**: `skills/<name>/SKILL.md` in that repository
(portable skills), or `skills/<name>/SKILL.md` in the current project
(project-specific ones). This is the copy that gets committed.
3. **Mirror it live**, so it works without a reinstall: copy the skill's whole
directory to `~/.claude/skills/<name>/` and `~/.agents/skills/<name>/`, and
for a project-specific skill additionally to that project's
`.claude/skills/<name>/` and `.agents/skills/<name>/`. Copy the directory,
not just `SKILL.md`, so hooks and references come along.
Report all written paths. Deleting or renaming a skill follows the same list in
reverse — remove the mirrors too, or the old name keeps firing.
## Step 3: write ## Step 3: write
- Portable skills go to `skills/<name>/SKILL.md` in the operator's skills - Write the skill to every location the **Persistence** section lists.
repository; project-specific ones to that project's skills directory.
- Frontmatter carries `name` and a `description` that states what the skill - Frontmatter carries `name` and a `description` that states what the skill
does **and** when to trigger it - the description is the only part loaded into does **and** when to trigger it - the description is the only part loaded into
every session, so it decides whether the skill ever fires. every session, so it decides whether the skill ever fires.

View File

@@ -37,6 +37,20 @@ class TestAutotuneSkill(unittest.TestCase):
self.assertIn("multiSelect: false", text) self.assertIn("multiSelect: false", text)
class TestSkillPersistence(unittest.TestCase):
"""Both skill writers must persist to the skills repository AND the live dirs."""
def test_autotune_names_every_target(self):
text = SKILL.read_text(encoding="utf-8")
for target in ("SKILLS_REPO", "skills/<name>/SKILL.md", "~/.claude/skills/", "~/.agents/skills/"):
self.assertIn(target, text)
def test_autoskill_routes_to_the_same_persistence(self):
text = (REPO_ROOT / "skills" / "autoskill" / "SKILL.md").read_text(encoding="utf-8")
for target in ("Persistence", "~/.claude/skills/", "~/.agents/skills/"):
self.assertIn(target, text)
class TestAutotuneHook(unittest.TestCase): class TestAutotuneHook(unittest.TestCase):
def setUp(self): def setUp(self):
self.tmp = tempfile.TemporaryDirectory() self.tmp = tempfile.TemporaryDirectory()