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:
@@ -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.
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user