feat: add project skill and rule synchronization#59
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces whole-directory synchronization for skills and one-source rule synchronization across supported agents, adding a dedicated Projects workflow (plus a Global Sync screen) and separating project-local skill update metadata from the global canonical store.
Changes:
- Add project management + global sync UI/logic for whole-directory
skillssynchronization (with preview/backup/replace workflows). - Add unified rule synchronization (
AGENTS.mdas canonical source, Cursor.mdcwrapper) for both projects and global targets. - Make the global canonical skills directory configurable and update install/import/scan flows to rely on that canonical store (removing per-agent per-skill toggle assignment UI).
Reviewed changes
Copilot reviewed 33 out of 33 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| Tests/SkillDeckTests/SkillManagerToggleTests.swift | Removes per-agent toggle/symlink unit tests (no longer applicable with directory sync). |
| Tests/SkillDeckTests/RuleSyncFileSystemTests.swift | Adds coverage for rule sync planning, backup/replace, removal, and Cursor .mdc generation. |
| Tests/SkillDeckTests/ProjectSyncFileSystemTests.swift | Adds coverage for whole-directory sync planning, backup/replace, and managed link removal. |
| Tests/SkillDeckTests/ProjectSkillUpdateServiceTests.swift | Adds coverage ensuring project lock metadata is stored under the project, not the global store. |
| Tests/SkillDeckTests/GlobalSkillStorageTests.swift | Adds coverage for configurable canonical storage affecting scanner + lock/cache paths. |
| Tests/SkillDeckTests/AgentTypeTests.swift | Updates supported agent count expectations. |
| Sources/SkillDeck/Views/Sidebar/SidebarView.swift | Adds “全局同步” and “项目” navigation entries. |
| Sources/SkillDeck/Views/SettingsView.swift | Adds UI to select/reset the canonical global skills directory and refresh storage-backed services. |
| Sources/SkillDeck/Views/Projects/ProjectSkillDetailView.swift | New project skill detail UI with repo linking, update checks, and update actions scoped to the project. |
| Sources/SkillDeck/Views/Projects/ProjectManagementView.swift | New project list + detail pane with agent rail, skill list, and preview/apply flows for sync + rules. |
| Sources/SkillDeck/Views/Projects/GlobalSkillSourceList.swift | New global-sync source list UI (configured global skills directory as a source). |
| Sources/SkillDeck/Views/Install/SkillInstallView.swift | Removes per-agent install targeting; installs into canonical global directory only. |
| Sources/SkillDeck/Views/Install/LocalImportView.swift | Removes per-agent import targeting; imports into canonical global directory only. |
| Sources/SkillDeck/Views/Detail/SkillDetailView.swift | Removes per-agent assignment UI section from skill details. |
| Sources/SkillDeck/Views/ContentView.swift | Wires new global sync + projects screens into the existing three-column navigation. |
| Sources/SkillDeck/Views/Components/AgentToggleView.swift | Deletes per-agent toggle UI component. |
| Sources/SkillDeck/ViewModels/SkillInstallViewModel.swift | Removes agent selection state and plumbs installs into canonical global storage only. |
| Sources/SkillDeck/ViewModels/SkillDetailViewModel.swift | Removes toggle-assignment API from the detail VM. |
| Sources/SkillDeck/ViewModels/ProjectSkillDetailViewModel.swift | New VM for project-scoped repo linking, update checks, and updates with status feedback. |
| Sources/SkillDeck/ViewModels/ProjectManager.swift | New project/global sync manager + filesystem sync planner + rule sync planner + preview/apply orchestration. |
| Sources/SkillDeck/ViewModels/LocalImportViewModel.swift | Removes agent selection and updates import flow semantics to canonical directory import only. |
| Sources/SkillDeck/ViewModels/ClawHubBrowserViewModel.swift | Updates ClawHub install call to no longer pass target agents. |
| Sources/SkillDeck/Utilities/Constants.swift | Adds styling for the newly supported agents. |
| Sources/SkillDeck/Services/SymlinkManager.swift | Removes per-skill create/remove APIs; positions as inspection support for sync model. |
| Sources/SkillDeck/Services/SkillScanner.swift | Scans only the configured canonical directory; uses installation inspection for reporting synchronized tools. |
| Sources/SkillDeck/Services/SkillManager.swift | Updates delete/install/import flows for the canonical store and adds reloadConfiguredStorage(). |
| Sources/SkillDeck/Services/RuleSyncFileSystem.swift | New rule synchronization planner/applier with Cursor .mdc support and backup behavior. |
| Sources/SkillDeck/Services/ProjectSkillUpdateService.swift | New project-local update service using a project-local lock file. |
| Sources/SkillDeck/Services/LockFileManager.swift | Moves default lock file path derivation to SkillStorageSettings.lockFileURL. |
| Sources/SkillDeck/Services/CommitHashCache.swift | Moves default cache file path derivation to SkillStorageSettings.cacheFileURL. |
| Sources/SkillDeck/Services/AgentPathSettings.swift | Adds SkillStorageSettings for canonical global skill directory configuration. |
| Sources/SkillDeck/Models/AgentType.swift | Adds new supported agents and introduces project-relative skills paths. |
| docs/PROJECT_MANAGEMENT.md | Documents project/global sync workflows, directory layout, backup rules, and current boundaries. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// Default path for the cache file: ~/.agents/.skilldeck-cache.json | ||
| /// `static let` is a compile-time constant, similar to Java's static final | ||
| static let defaultPath: URL = { | ||
| let home = NSString(string: "~/.agents/.skilldeck-cache.json").expandingTildeInPath | ||
| return URL(fileURLWithPath: home) | ||
| }() | ||
| static var defaultPath: URL { SkillStorageSettings.cacheFileURL } |
There was a problem hiding this comment.
Thanks. This is intentional: ~/.agents/.skilldeck-cache.json remains the fallback default, while SkillStorageSettings.cacheFileURL redirects it only when the user explicitly changes the canonical global skills directory. defaultPath must be a computed static var so each new cache instance resolves the current setting instead of retaining a stale path. The static let sentence is generic Swift background text, not a claim about the current implementation. No functional change is needed here.
| /// SymlinkManager provides filesystem inspection for whole-directory skill synchronization. | ||
| /// | ||
| /// Core Concepts: | ||
| /// - The "real copy" of all skills is stored in ~/.agents/skills/ (canonical location) | ||
| /// - Each Agent references the shared skill via symlink | ||
| /// - Example: ~/.claude/skills/agent-notifier -> ~/.agents/skills/agent-notifier | ||
| /// - The canonical directory is selected in SkillDeck settings. | ||
| /// - Each Agent can reference it through a single directory symlink. | ||
| /// |
There was a problem hiding this comment.
Thanks. isSymlink intentionally describes the leaf skill path stored in SkillInstallation.path. With whole-directory sync, the symlink is the parent skills directory, so the child skill path is correctly not itself a symlink. Directory-link ownership and synchronization status are handled by ProjectSyncFileSystem / ProjectInspection, while the dashboard uses isInherited, not isSymlink, for its presentation. No behavior depends on treating children of a directory symlink as leaf symlinks.
| /// Default path for lock file | ||
| static let defaultPath: URL = { | ||
| let home = NSString(string: "~/.agents/.skill-lock.json").expandingTildeInPath | ||
| return URL(fileURLWithPath: home) | ||
| }() | ||
| static var defaultPath: URL { SkillStorageSettings.lockFileURL } |
There was a problem hiding this comment.
Thanks. The documented ~/.agents/.skill-lock.json location remains the fallback default. SkillStorageSettings.lockFileURL intentionally resolves a different location only after the user changes the canonical global skills directory. defaultPath is computed so new instances use the current setting; no caller relies on a fixed path. No functional change is needed here.
|
Hi @ivoidcat, thanks for the great work on this PR! I have two questions/points: 1. Background & use casesCould you add a bit more context on the motivation behind this feature? Specifically:
2. Localization / i18nThe new UI strings (Projects, Global Sync, rule sync, project management, etc.) appear to be hardcoded in Chinese only. The app already has an English + Simplified Chinese localization system ( Thanks! |
Thanks for the thoughtful review — the motivation is to make project-level configuration behave as a coherent, portable unit rather than a collection of unrelated per-skill toggles. 1. Background and intended workflows The previous per-skill / per-agent model works well for selectively installing individual global skills, but it is not a good fit for repository-owned configuration. In a real project, the skill set and instruction files are usually maintained together and should be consumed consistently by multiple AI tools. The project sync flow uses The same principle applies to rules: The two sync modes target different scopes:
In short: use Projects when the source of truth belongs to one repository and should travel with it; use Global Sync when the source of truth belongs to the user environment and should be available across repositories. 2. Localization Agreed. The first implementation focused on the sync behavior and left a number of new UI strings hardcoded in Simplified Chinese. I will move all user-facing strings introduced by this feature — including project/global sync, rule sync, project management, previews, confirmations, status messages, and project skill actions — into the existing Thanks again — this is a helpful catch. |







Summary
Validation