diff --git a/skills/autoskill/SKILL.md b/skills/autoskill/SKILL.md index 6b86f58..b326a55 100644 --- a/skills/autoskill/SKILL.md +++ b/skills/autoskill/SKILL.md @@ -18,10 +18,12 @@ vary), do the following: 3. On approval, write the skill: - a conversation shortcut or expansion: add it to the `shortcuts` skill table instead of creating a new skill. - - project-specific behavior: create it in the project's own skills - directory (e.g. `skills//SKILL.md` in the repository). - - portable behavior: create `skills//SKILL.md` in the operator's - skills repository. + - anything else: persist it exactly as the `autotune` skill's + **Persistence** section specifies — locate the operator's skills + repository, write `skills//SKILL.md` there (or in the current + 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, single-purpose instruction; route to an authoritative doc when one exists instead of duplicating it. diff --git a/skills/autotune/SKILL.md b/skills/autotune/SKILL.md index 7158ed7..3ada798 100644 --- a/skills/autotune/SKILL.md +++ b/skills/autotune/SKILL.md @@ -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 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//SKILL.md` in that repository + (portable skills), or `skills//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//` and `~/.agents/skills//`, and + for a project-specific skill additionally to that project's + `.claude/skills//` and `.agents/skills//`. 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 -- Portable skills go to `skills//SKILL.md` in the operator's skills - repository; project-specific ones to that project's skills directory. +- Write the skill to every location the **Persistence** section lists. - 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 every session, so it decides whether the skill ever fires. diff --git a/tests/test_autotune.py b/tests/test_autotune.py index 65652f8..ceb8986 100644 --- a/tests/test_autotune.py +++ b/tests/test_autotune.py @@ -37,6 +37,20 @@ class TestAutotuneSkill(unittest.TestCase): 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//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): def setUp(self): self.tmp = tempfile.TemporaryDirectory()