feat(skill-install): repo-scoped install so curated skills don't leak across repos#127
Merged
Merged
Conversation
… across repos (#125) Curated skills are per-project but were installed into the global ~/.claude/skills / ~/.codex/skills, so every project's skills were visible in every repo and the agent's own skill matching could suggest a skill from an unrelated repo. (watchmen's statusline hint is already project-scoped; the leak is the global install feeding the agent's native suggestions.) - skill_install: add a `scope` (project|global) + `skill_base()`. Project scope targets <source_repo>/.claude/skills (resolved from the tracked repo, or a passed `repo`). The low-level install_skill defaults to global (keeps the primitive simple); install_project / auto-install / the CLI default to project so curated skills land in their origin repo. - Manifest records `scope`; uninstall is now manifest-driven so it finds links in either location, with project_key to disambiguate. - migrate_to_project_scope(): relocate existing global links into their repos. Robust to manifest loss — also sweeps the global dirs on disk and infers project/slug from the bundle target path. Links with no resolvable repo are left global, never dropped; user-made dirs never touched. - CLI: `watchmen install --scope project|global` (default project) and `watchmen install --migrate`. Prints a one-line .gitignore heads-up for repo-local links but never writes .gitignore (user's call). - viewer install/uninstall endpoints go through the same project-scoped path. - Two-project same-slug collision is no longer possible under project scope. Tests: project-scope target, no-repo skip, collision-free, manifest-driven and filesystem-sweep migration, repo-unresolvable left-global, CLI default + migrate dispatch, hermetic viewer endpoints. Full suite green (561). Also fixes a pre-existing SyntaxWarning in skill_install (raw docstring). Follow-up: shared-skill distribution across related repos (#126). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dex review) Codex review of #127 flagged two paths where the file-moving operations could touch content watchmen doesn't own. - blocker: _is_managed trusted a manifest entry unconditionally, so a stale row could let uninstall/migrate rmtree user content that had replaced our symlink at the same path. Ownership is now decided from disk only: the live object must be a symlink resolving into BUNDLES_DIR (this already survived manifest loss; the manifest claim was the unsafe part). uninstall now reports skipped_conflict and preserves user content at a stale path. - should-fix: the migration filesystem sweep inferred slug from parts[-1], so a symlink to a skill SUB-path could migrate as a bogus slug pointing at the wrong source. Now requires exactly <key>/skills/<slug> (len(parts) == 3). Tests: user content preserved at a stale manifest path under both uninstall and migrate; sub-path symlink ignored by the sweep. Full suite green (564). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #125.
Why
Curated skills are curated per-project but were installed globally (
~/.claude/skills,~/.codex/skills), so every project's skills were visible in every repo and the agent's own skill matching could suggest a skill from an unrelated repo. watchmen's statusline hint is already project-scoped (verified: clean index + correctresolve_project_key); the leak is the global install feeding the agent's native suggestions.What changed
scope(project | global) on the install path, withskill_base()resolving the target. Project scope →<source_repo>/.claude/skills. The low-levelinstall_skillkeeps aglobaldefault (simple primitive);install_project, the curator auto-install, and the CLI default to project so curated skills land in their origin repo.scope;uninstall_skillis now manifest-driven (finds links in either location) withproject_keyto disambiguate.migrate_to_project_scope()relocates existing global links into their repos. Robust to manifest loss: it also sweeps the global dirs on disk and infers project/slug from the bundle target path. Links with no resolvable repo are left global (reported, never dropped); user-made dirs are never touched.watchmen install --scope project|global(default project) andwatchmen install --migrate. Prints a one-line.gitignoreheads-up for repo-local links but never writes.gitignore(the user's call, per the issue).SyntaxWarninginskill_install(raw docstring).Migrating an existing install
watchmen install --migratemoves your current global links into their repos. Then per repo you only see that repo's curated skills; new installs are repo-scoped by default.Testing
--migratedispatch, hermetic viewer endpoints.plugin/payload touched, so no plugin version bump needed.Follow-up
Shared-skill distribution across related repos (e.g. the kai-* family) is tracked separately in #126.