Skip to content

Commit 588199e

Browse files
committed
refactor: remove SKILL_DIR_EXCLUSIVE flag — SKILL_DIR=None is the only signal needed
If SKILL_DIR is set, sync_skills_dir() runs. If None (Copilot), it skips. No boolean flag required.
1 parent c65d334 commit 588199e

7 files changed

Lines changed: 11 additions & 22 deletions

File tree

src/appliers/base.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,6 @@ class BaseApplier(ABC):
6666
SKILL_DIR: Optional[Path] = None
6767
TOOL_NAME: str = ""
6868

69-
# When True, apc sync replaces the entire SKILL_DIR with a single symlink
70-
# → ~/.apc/skills/ so any future `apc install` is immediately live.
71-
# All tools default to True. Set False only for tools that cannot use a
72-
# dir-level symlink (e.g. Copilot, which has no dedicated skills dir).
73-
SKILL_DIR_EXCLUSIVE = True
74-
7569
# Subclasses that support LLM-based memory sync should override this
7670
# with a description of how the tool expects its memory files.
7771
MEMORY_SCHEMA: str = ""
@@ -149,15 +143,15 @@ def link_skills(self, skills: List[Dict], source_dir: Path, manifest: ToolManife
149143
def sync_skills_dir(self) -> bool:
150144
"""Establish a dir-level symlink: SKILL_DIR → ~/.apc/skills/.
151145
152-
Only applies when SKILL_DIR_EXCLUSIVE=True (dir is entirely apc-managed).
146+
Only applies when SKILL_DIR is set on the applier.
153147
After this runs once, any future `apc install` is immediately live in
154148
this tool without re-running sync.
155149
156150
Returns True if the symlink was established, False if not applicable.
157151
"""
158152
from skills import get_skills_dir
159153

160-
if not self.SKILL_DIR_EXCLUSIVE or self.SKILL_DIR is None:
154+
if self.SKILL_DIR is None:
161155
return False
162156

163157
skills_source = get_skills_dir()

src/appliers/copilot.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,6 @@ def _vscode_mcp_json() -> Path:
8787

8888
class CopilotApplier(BaseApplier):
8989
TOOL_NAME = "github-copilot"
90-
# Copilot has no dedicated skills dir — it uses a single instructions file.
91-
# Dir-level symlink is not applicable.
92-
SKILL_DIR_EXCLUSIVE = False
9390
MEMORY_SCHEMA = COPILOT_MEMORY_SCHEMA
9491

9592
@property # type: ignore[override]

src/appliers/cursor.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,8 @@ def _cursor_mcp_json() -> Path:
7676

7777
class CursorApplier(BaseApplier):
7878
TOOL_NAME = "cursor"
79-
# ~/.cursor/rules/ is symlinked → ~/.apc/skills/ (SKILL_DIR_EXCLUSIVE=True default).
80-
# Cursor reads skill dirs directly from ~/.cursor/rules/<name>/SKILL.md.
81-
# Note: .mdc per-file format is superseded by this dir-level approach.
79+
# ~/.cursor/rules/ → ~/.apc/skills/ symlink (dir-level sync, same as all tools).
80+
# Skills appear as <name>/SKILL.md subdirs; .mdc per-file format superseded.
8281
MEMORY_SCHEMA = CURSOR_MEMORY_SCHEMA
8382

8483
@property # type: ignore[override]

src/install.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,15 @@ def _resolve_targets(target_args: tuple, yes: bool) -> List[str]:
8686
def _note_per_skill_tools(target_list: list) -> None:
8787
"""Warn if any synced target uses per-skill symlinks (not dir-level).
8888
89-
For SKILL_DIR_EXCLUSIVE tools (OpenClaw, Claude Code), the dir symlink is
90-
already live — new skills in ~/.apc/skills/ appear immediately.
91-
For mixed-dir tools (Cursor, Copilot), the user must re-run `apc sync`.
89+
For tools with SKILL_DIR set, the dir symlink is already live after
90+
apc sync — new skills in ~/.apc/skills/ appear immediately.
91+
For Copilot (no SKILL_DIR), re-run `apc sync` to pick up new skills.
9292
"""
9393
needs_sync = []
9494
for target_name in target_list:
9595
try:
9696
applier = get_applier(target_name)
97-
if not applier.SKILL_DIR_EXCLUSIVE and applier.SKILL_DIR is not None:
97+
if applier.SKILL_DIR is None:
9898
needs_sync.append(target_name)
9999
except Exception:
100100
pass

src/sync_helpers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ def sync_skills(tool_list: List[str]) -> Tuple[int, int]:
9393
total_dir += 1
9494
success(f"{tool_name}: skills dir symlinked → ~/.apc/skills/")
9595
else:
96-
success(f"{tool_name}: no skills dir to sync (SKILL_DIR_EXCLUSIVE=False)")
96+
success(f"{tool_name}: no skills dir (SKILL_DIR=None) — skipping")
9797

9898
except Exception as e:
9999
error(f"Failed to sync skills to {tool_name}: {e}")
@@ -192,7 +192,7 @@ def sync_all(tool_list: List[str], no_memory: bool = False, override_mcp: bool =
192192
# Establish dir-level symlink: SKILL_DIR → ~/.apc/skills/
193193
if applier.sync_skills_dir():
194194
manifest.record_dir_sync(str(applier.SKILL_DIR), str(skills_dir))
195-
s, lk = (1, 0) if applier.SKILL_DIR_EXCLUSIVE and applier.SKILL_DIR else (0, 0)
195+
s, lk = (1, 0) if applier.SKILL_DIR is not None else (0, 0)
196196

197197
# MCP servers
198198
secrets = _resolve_all_mcp_secrets(mcp_servers)

tests/test_docker_integration.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ def test_out_of_sync_when_skill_deleted(
409409
"""Removing a synced skill link/file causes 'out of sync' for that tool.
410410
411411
Skills now live in ~/.apc/skills/ (source of truth).
412-
- SKILL_DIR_EXCLUSIVE tools (claude-code, openclaw): their skills dir is a
412+
- Tools with SKILL_DIR set (claude-code, openclaw, cursor): their skills dir is a
413413
single symlink → ~/.apc/skills/. Out-of-sync = symlink removed/broken.
414414
- Per-skill tools (cursor): individual link_path entries in the manifest.
415415
Out-of-sync = linked file deleted.

tests/test_sync_helpers.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ def _mock_applier(tmpdir: Path, tool: str = "cursor"):
1717
"""Return a MagicMock that satisfies the applier interface."""
1818
applier = MagicMock()
1919
applier.get_manifest.return_value = _make_manifest(tmpdir, tool)
20-
applier.SKILL_DIR_EXCLUSIVE = True # all tools use dir-level symlink by default
2120
applier.SKILL_DIR = tmpdir / "skills"
2221
applier.sync_skills_dir.return_value = True # dir-level symlink succeeds
2322
applier.apply_skills.return_value = 3

0 commit comments

Comments
 (0)