Skip to content

feat(skill): add explicit project scope - #643

Open
ousamabenyounes wants to merge 4 commits into
Panniantong:mainfrom
ousamabenyounes:fix/issue-333
Open

feat(skill): add explicit project scope#643
ousamabenyounes wants to merge 4 commits into
Panniantong:mainfrom
ousamabenyounes:fix/issue-333

Conversation

@ousamabenyounes

@ousamabenyounes ousamabenyounes commented Aug 21, 2026

Copy link
Copy Markdown

摘要 / Summary

根据维护者关闭 #390 时提出的方向,本 PR 实现了显式的 scope 参数:--scope=user|project。默认值仍为 user,不进行隐式推断。

Adds an explicit --scope=user|project parameter for skill registration. user
remains the default, and nothing is inferred from the working directory.

Addresses #333.

Design rationale

Issue #333 asked for project-local skill installation, and listed an explicit
--scope flag as one of its two options. #390 (@StevanusPangau) explored the
feature and identified the same target path, but derived the scope from the
working directory; it was closed in favour of designing an explicit scope
parameter. This PR implements that project-local target with an explicit flag
rather than inference, on the current installer.

Keeping user as the default is deliberate: an implicit write into the working
directory would contradict the workspace-isolation rules in docs/install.md
and the read-only-by-default install introduced in #577.

On agent-reach install, project scope is already covered by the --system
gate from #577install --scope=project without --system writes nothing.
Only agent-reach skill --install / --uninstall is ungated, and it is ungated
today for user scope too, so this PR did not add a second authorization flag
there. Instead the resolved absolute destination is printed before the first
write, so a project-scoped install is never silent about where it lands. Happy
to put the skill subcommand behind --system as well if you prefer.

Changes

  • --scope=user|project on agent-reach install and
    agent-reach skill --install / --uninstall, defaulting to user
  • project scope resolves <cwd>/.claude/skills/agent-reach through one helper
    shared by install and uninstall, which refuses to proceed when .claude or
    .claude/skills is a symlink
  • the resolved absolute destination is printed before the first write
  • skill --uninstall --scope=project removes that directory, so a
    project-scoped install can be undone by the CLI that created it;
    agent-reach uninstall stays user-scoped and never touches the working
    directory
  • the workspace-policy wording in docs/install.md is kept byte-identical and
    the exception added as a separate line; agent_reach/skill/SKILL.md,
    SKILL_en.md and docs/update.md state the same rule and are updated in the
    same PR, so the agent-facing instructions do not tell an agent to refuse a
    supported flag
  • CHANGELOG.md entry under ## [Unreleased] — happy to drop or re-target it
    if you keep the changelog on a different cadence

Documentation diffs are additions only: docs/install.md +19/-0,
docs/update.md +2/-0, SKILL.md +2/-0, SKILL_en.md +3/-0.

Test verification (RED → GREEN)

tests/test_skill_command.py from this branch applied to upstream/main with
no production changes (RED):

FAILED test_install_skill_rejects_unknown_scope
FAILED test_project_scope_announces_target_before_writing
FAILED test_project_scope_installs_only_in_current_project
FAILED test_project_scope_preserves_existing_skill_when_not_forced
FAILED test_project_scope_rejects_symlinked_skill_parent
FAILED test_project_scope_reports_expected_destination_copy_failure
FAILED test_project_scope_reports_expected_directory_creation_failure
FAILED test_project_scope_uninstall_rejects_symlinked_skill_parent
FAILED test_project_scope_uninstall_removes_only_project_skill
FAILED test_project_scope_uninstall_reports_removal_failure
FAILED test_project_scope_uninstall_reports_when_nothing_installed
FAILED test_project_scope_uninstall_unlinks_symlinked_target
FAILED test_skill_cli_accepts_project_scope
FAILED test_skill_cli_accepts_project_scope_uninstall
FAILED test_skill_command_forwards_explicit_scope
FAILED test_skill_command_forwards_uninstall_scope
FAILED test_system_install_forwards_project_scope
FAILED test_system_install_keeps_default_user_scope_call_compatible
FAILED test_uninstall_skill_rejects_unknown_scope
19 failed, 12 passed, 16 subtests passed

Patched branch (GREEN):

606 passed, 16 subtests passed

upstream/main baseline is 586 passed, measured in a clean worktree; this PR
adds 20 tests and removes none.

Each project-uninstall branch was mutation-checked rather than only covered —
breaking the scope guard, the symlinked-target unlink, the removal-failure
report or the not-found message each fails a test:

mutate: drop uninstall scope guard        1 failed, 30 passed
mutate: follow symlinked target           1 failed, 30 passed
mutate: swallow removal error             1 failed, 30 passed
mutate: drop not-found message            1 failed, 30 passed

Verification

  • ruff check agent_reach tests — All checks passed
  • mypy agent_reach — Success: no issues found in 35 source files
  • pytest -q — 606 passed

The workspace-isolation wording in docs/install.md is restored exactly as
written, with the project-scope exception added as a separate line instead of
a rewrite. SKILL.md, SKILL_en.md and docs/update.md carried the same rule and
would otherwise instruct agents to refuse a supported flag.
A project-scoped install had no matching removal: _uninstall_skill() only
swept user directories, so files the CLI created in a project could not be
removed by the CLI. Both operations now resolve the target through one helper
that refuses symlinked path components, and the absolute destination is
printed before the first write. 'agent-reach uninstall' stays user-scoped.
The scope guard, the symlinked-target unlink, the removal-failure report and
the not-found message were all reachable but unasserted: each one survived
being broken. Also restores a blank line dropped with the old argument
validation, and narrows the _project_skill_paths docstring, which claimed more
containment than a pre-flight symlink check can give.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant