Skip to content

feat: add project skill and rule synchronization#59

Open
ivoidcat wants to merge 7 commits into
crossoverJie:mainfrom
ivoidcat:feat/project-rule-sync
Open

feat: add project skill and rule synchronization#59
ivoidcat wants to merge 7 commits into
crossoverJie:mainfrom
ivoidcat:feat/project-rule-sync

Conversation

@ivoidcat

Copy link
Copy Markdown

Summary

  • add project and global whole-directory skill synchronization across supported agents
  • add project-local skill repository linking and update checks
  • add one-source rule synchronization for AGENTS.md, Claude, Gemini, Copilot, Kiro, and Cursor project rules
  • preserve existing targets through preview, backup, and replacement confirmation

Validation

  • swift test (189 tests passed)
  • packaged and installed the local macOS app for verification

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 skills synchronization (with preview/backup/replace workflows).
  • Add unified rule synchronization (AGENTS.md as canonical source, Cursor .mdc wrapper) 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.

Comment on lines 75 to +77
/// 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 }

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +3 to 8
/// 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.
///

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines 11 to +12
/// 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 }

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@crossoverJie

Copy link
Copy Markdown
Owner

Hi @ivoidcat, thanks for the great work on this PR! I have two questions/points:

1. Background & use cases

Could you add a bit more context on the motivation behind this feature? Specifically:

  • What real-world scenario does whole-directory project sync solve that the previous per-skill / per-agent toggle model didn't? (e.g. teams sharing a repo-level skill set across agents, keeping AGENTS.md + agent rules in one source of truth?)
  • When would a user choose "项目" (Projects) sync vs "全局同步" (Global Sync)? A short scenario description would help reviewers and future users understand the intended workflows.

2. Localization / i18n

The 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 (L10nKeys.swift + L10n.swift + LText(...)). Please route all new user-facing strings through that system so the feature is fully bilingual — otherwise English users will see untranslated Chinese in the new views.

Thanks!

@ivoidcat

Copy link
Copy Markdown
Author

Hi @ivoidcat, thanks for the great work on this PR! I have two questions/points:

1. Background & use cases

Could you add a bit more context on the motivation behind this feature? Specifically:

  • What real-world scenario does whole-directory project sync solve that the previous per-skill / per-agent toggle model didn't? (e.g. teams sharing a repo-level skill set across agents, keeping AGENTS.md + agent rules in one source of truth?)
  • When would a user choose "项目" (Projects) sync vs "全局同步" (Global Sync)? A short scenario description would help reviewers and future users understand the intended workflows.

2. Localization / i18n

The 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 (L10nKeys.swift + L10n.swift + LText(...)). Please route all new user-facing strings through that system so the feature is fully bilingual — otherwise English users will see untranslated Chinese in the new views.

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 <project>/.agents/skills as the canonical project-level skill source. Syncing a target links its entire skills directory to that source, so adding, updating, disabling, or restoring a project skill is immediately reflected for all synced tools. This avoids partial states where different agents see different subsets of the same repository-owned skills.

The same principle applies to rules: <project>/.agents/AGENTS.md is the canonical project instruction file, while agent-specific files such as AGENTS.md, CLAUDE.md, GEMINI.md, and Cursor rules can reference that one source. This gives teams one reviewable, version-controlled place for repository instructions instead of maintaining duplicated files with drift.

The two sync modes target different scopes:

  • Projects is for repository-specific setup. For example, a team can keep iOS project skills and project instructions inside the repository, sync them to Claude Code, Codex, Cursor, and other supported tools, and have every contributor receive the same behavior after cloning the repository.
  • Global Sync is for the user’s personal shared skill collection. It uses the configured global skill directory and distributes that personal collection to the selected AI tools, without making those skills part of a particular repository.

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 L10nKeys / L10n / LText localization system before requesting another review.

Thanks again — this is a helpful catch.

@crossoverJie

crossoverJie commented Jul 21, 2026

Copy link
Copy Markdown
Owner
image

Thanks for your reply. I built and ran it locally and added a project, but I don't know how to proceed?

This interface is very confusing to me. Could you provide screenshots and explain in detail how to operate it?

@ivoidcat

Copy link
Copy Markdown
Author

Thanks for trying it. I prepared a small end-to-end example to show the intended workflow.
A project must first contain its own source files:
/
.agents/
AGENTS.md
skills/
example-project-skill/
SKILL.md

  1. Add/select the project
1

After selecting test, SkillDeck detects:
one project-local source skill: example-project-skill
one shared rule source: .agents/AGENTS.md
no tools synced yet (0/17)
The project source directory is shown at the top:
~/Downloads/test/.agents/skills
2. Preview syncing the skills directory to every supported tool
2

Click Sync All Tools. SkillDeck does not copy the skill separately into every tool. Instead, it prepares one directory symlink per tool, all pointing to the same project source directory:
/.agents/skills
The preview lists the planned target directories, such as:
/.claude/skills
/.codex/skills
/.gemini/skills
...
Confirm the preview to apply all links.
3. Or sync only one selected tool
3

If only one tool should receive the project skills, select that tool in the horizontal tool list and click Sync Entire Skills Directory.
For example, this creates:
/.claude/skills -> /.agents/skills
This is still a whole-directory link. Per-skill selection is intentionally not used, because a project should have one complete, consistent skill set for each selected tool.
4. Verify project skill synchronization
4

After syncing all tools, the project row reports 17/17 tools fully synced, and each tool in the horizontal list reports 1/1.
This means every supported tool now reads the same project-local source skill from:
/.agents/skills/example-project-skill/SKILL.md
5. Sync shared project rules
5

The Shared Rules section uses:
/.agents/AGENTS.md
as the single source of truth. Click Sync All Rules to create the supported tool-specific rule entry points. The Finder view shows examples such as:
AGENTS.md
CLAUDE.md
GEMINI.md
.claude/
.codex/
.cursor/
...
The displayed AGENTS.md, CLAUDE.md, and GEMINI.md files are symlinks, so rule content remains maintained in one place instead of being copied and drifting across tools. Cursor uses its project rules directory with a small .mdc wrapper that references the shared AGENTS.md.
In short: Projects is for repository-local skills and rules that can be committed with the repository and shared by a team. Global Sync is for the user’s personal cross-project skills.

@ivoidcat

Copy link
Copy Markdown
Author
image Thanks for your reply. I built and ran it locally and added a project, but I don't know how to proceed?

This interface is very confusing to me. Could you provide screenshots and explain in detail how to operate it?

This feature is specifically for project-local skills and rules. I am not sure whether you have used this workflow before, but it is useful when a skill applies only to one repository or team.
Unlike global skills, project-local skills live inside the repository under .agents/skills. They do not add unrelated skills to the user's global skill directories or make them appear in every project. This keeps project-specific instructions, tooling, and conventions isolated, while still allowing the same project-local source to be shared consistently across supported AI tools.

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.

3 participants