短答案
先讀取 .claude/skills 的 LinkType 與 Target。目錄已有
Claude-only 內容時,保留實體目錄,只替 portable Skill 建立 Link:
.agents/skills/ # 可攜 Skill 的來源
.claude/skills/<portable-skill> # 指向來源的 Junction / symlink
.claude/skills/<claude-only> # Claude 專屬實體 Skill
AGENTS.md # 共用專案規則
CLAUDE.md # @AGENTS.md + Claude-only 補充
.claude/skills 尚不存在、而且所有 Skills 都能共用時,整棵 Junction 才是
合理選項。規則檔優先使用 @AGENTS.md;Hard Link 只留給固定在
Windows/NTFS、同一 volume,且兩個檔名必須永遠同內容的環境。
Claude Code 沒有掃 .agents/skills
Agent Skills 規格定義 SKILL.md 的結構,各個 client 仍可決定安裝位置。
許多工具採用 .agents/skills;Claude Code 的專案路徑則是
.claude/skills/<skill-name>/SKILL.md。檔案可讀,不等於原生 discovery
會找到它。
內容留一份
.agents/skills 保存可攜版本,避免兩份 SKILL.md 漂移。
補原生路徑
Link 把同一份內容露出在 .claude/skills,不靠模型臨時搜尋。
回到 Claude 測
檢查 slash autocomplete、auto-invocation 與 /context,不只看檔案存在。
既有實體目錄現在會被擋下
vibeLinker 把 .agents\skills 定為唯一 Skill root,再為 Claude 與
Gemini 建立 Junction。舊版遇到實體目錄時只會跳過;目前版本會核對 Link 的實際 target,
把實體副本或錯誤 target 記成契約違規,最後以 exit code 1 結束。
$directoryLinks = [ordered]@{
".claude\skills" = ".agents\skills"
".gemini\skills" = ".agents\skills"
}
$instructionLinks = [ordered]@{
"AGENTS.md" = "AGENTS.md"
"CLAUDE.md" = "AGENTS.md"
"GEMINI.md" = "AGENTS.md"
}
2026-08-29 重驗結果
.agents\skills是實體 canonical 目錄。.claude\skills是 Junction,target 正確指向.agents\skills。setup_links.ps1與remove_links.ps1的 AST 語法錯誤數皆為 0。- 本次沒有執行 setup/remove,也沒有建立受檢 repo 缺少的
.gemini\skills。
- bootstrap 解析 canonical Skill root 與 host 端 target。
- 既有 Junction 會比對實際 target;相符才視為已接好。
- 實體副本或錯誤 target 會留下違規紀錄並讓 setup 失敗。
舊失敗模式仍值得保留:發現實體 .claude\skills 時,先分類 portable 與
Claude-only Skill,完成差異與備份後才遷移。直接刪除可能把 Claude-only Skill 一併移除。
動手前,先確認 .claude\skills 是什麼
- 以下命令都在專案根目錄執行。
.agents\skills必須存在,每個 Skill 目錄內要有SKILL.md。- 查清楚
.claude\skills是實體目錄、Junction,還是 Symbolic Link,並核對 target。 - Hard Link 的來源與目標必須位於同一個 volume。
- Junction 可跨本機 volume,但不能指向映射的網路磁碟。
$repoRoot = (Resolve-Path ".").Path
$agentsSkills = Join-Path $repoRoot ".agents\skills"
$claudeSkills = Join-Path $repoRoot ".claude\skills"
if (-not (Test-Path -LiteralPath $agentsSkills)) {
throw "找不到 Skill 來源目錄:$agentsSkills"
}
Get-Item -LiteralPath $agentsSkills -Force |
Select-Object FullName, Attributes, LinkType, Target
if (Test-Path -LiteralPath $claudeSkills) {
Get-Item -LiteralPath $claudeSkills -Force |
Select-Object FullName, Attributes, LinkType, Target
}
ReparsePoint,而且 Target 正確。
.claude\skills
是實體目錄或指到錯誤 target,先比較內容。兩種接法怎麼選
整個目錄做 Junction
適合所有 Skill 都能共用,而且 .claude\skills 尚不存在的專案。
- 結構簡單。
- 無法保留 Claude-only Skill。
- 建立後仍要到 Claude Code 裡驗收。
只連需要共用的 Skill
保留實體 .claude\skills,只替可攜 Skill 建立 Link。
- 可攜與 Claude-only Skill 能並存。
- 可以只拿一個 Skill 試跑。
- 需要時再用 bootstrap script 重建。
| 現況 | 建議 |
|---|---|
| 兩邊 Skills 完全同質 | 整棵 Junction |
| 已有 Claude-only Skill | 逐 Skill Junction/symlink |
| 跨 Windows、macOS、Linux | 逐 Skill Symbolic Link + bootstrap script |
| Cloud session 或 remote container | 提交 project Skill 或安裝 plugin;不依賴本機外部 Link |
決策流程 · 動畫只標示資料流向 · 手機可左右滑動
/context。
接法 A:整個 Skills 目錄做 Junction
適用條件:.claude\skills 尚不存在,且不需要 Claude-only
Skills。
$repoRoot = (Resolve-Path ".").Path
$source = Join-Path $repoRoot ".agents\skills"
$claudeDir = Join-Path $repoRoot ".claude"
$target = Join-Path $claudeDir "skills"
if (-not (Test-Path -LiteralPath $source)) {
throw "找不到來源:$source"
}
New-Item -ItemType Directory -Path $claudeDir -Force | Out-Null
if (Test-Path -LiteralPath $target) {
throw "目標已存在,未建立 Link:$target"
}
New-Item `
-ItemType Junction `
-Path $target `
-Target (Resolve-Path $source) |
Select-Object FullName, LinkType, Target
看到這些才算建立成功:.claude\skills → .agents\skills,
輸出同時顯示 LinkType = Junction 與正確 target。
接法 B:只連需要共用的 Skill
.claude 已有內容時,從一個可攜 Skill 開始最安全。這個目錄之外的設定與
Claude-only Skills 都不會被碰到。
$repoRoot = (Resolve-Path ".").Path
$skillName = "my-skill"
$source = Join-Path $repoRoot ".agents\skills\$skillName"
$claudeSkills = Join-Path $repoRoot ".claude\skills"
$target = Join-Path $claudeSkills $skillName
if (-not (Test-Path -LiteralPath (Join-Path $source "SKILL.md"))) {
throw "來源不是有效 Skill:$source"
}
New-Item -ItemType Directory -Path $claudeSkills -Force | Out-Null
if (Test-Path -LiteralPath $target) {
throw "Claude Skill entry 已存在,未覆蓋:$target"
}
New-Item `
-ItemType Junction `
-Path $target `
-Target (Resolve-Path $source) |
Select-Object FullName, LinkType, Target
Windows 已開啟 Developer Mode,或目前 shell 具備 symlink 權限時,也可以建立 Symbolic Link:
New-Item `
-ItemType SymbolicLink `
-Path $target `
-Target (Resolve-Path $source)
.claude\skills\my-skill。
SKILL.md,其他 Skills 未變。
SKILL.md、target 已存在或 symlink 權限不足。
規則檔不必再複製一份
@AGENTS.md import
AGENTS.md 繼續當共用來源,CLAUDE.md 只放 import
與 Claude-only 補充。跨平台,也沒有同 volume 限制。
Hard Link
只適合固定 Windows/NTFS、同一 volume,而且兩個檔名永遠必須同內容的環境。
方案 A:薄 CLAUDE.md import
@AGENTS.md
## Claude Code
- 把只適用 Claude Code 的補充規則放在這裡。
方案 B:以 Hard Link 共用規則檔
$repoRoot = (Resolve-Path ".").Path
$agentsFile = Join-Path $repoRoot "AGENTS.md"
$claudeFile = Join-Path $repoRoot "CLAUDE.md"
if (-not (Test-Path -LiteralPath $agentsFile)) {
throw "找不到 AGENTS.md 來源檔"
}
if (Test-Path -LiteralPath $claudeFile) {
throw "CLAUDE.md 已存在,未建立 Hard Link"
}
New-Item `
-ItemType HardLink `
-Path $claudeFile `
-Target $agentsFile
Hard Link 建立後,兩個檔名指向同一個檔案實體。從 CLAUDE.md
編輯也會改到 AGENTS.md;需要 Claude-only 內容時改用 import。
驗收要看 Link 身分,也要看內容
1. Link 型態與 target
Get-Item -LiteralPath ".claude\skills\my-skill" -Force |
Select-Object FullName, Attributes, LinkType, Target
預期 Attributes 包含 ReparsePoint,LinkType
為 Junction 或 SymbolicLink,且 Target
指向預期來源。
2. Entrypoint 與 SHA-256
Test-Path -LiteralPath ".claude\skills\my-skill\SKILL.md"
Get-FileHash ".agents\skills\my-skill\SKILL.md"
Get-FileHash ".claude\skills\my-skill\SKILL.md"
Test-Path 應回傳 True,兩個 SHA-256 應一致。
3. Hard Link 身分
fsutil hardlink list ".\AGENTS.md"
預期清單同時包含 AGENTS.md 與 CLAUDE.md。內容相同的兩份副本不能當成
Hard Link 證據。
回到 Claude Code 做最後三項驗收
claude --version
- 如果 session 啟動時還沒有頂層
.claude\skills,建立後重啟 Claude Code。 - 輸入
/my-skill,確認 slash autocomplete 能看到該 Skill。 - 用符合 frontmatter
description的自然語言任務,確認 auto-invocation。 - 執行
/context,確認 Memory files 已載入CLAUDE.md。
一般 Read tool 能看到檔案,只能證明檔案可讀。slash autocomplete、
auto-invocation 與 /context 都通過,才能證明原生 discovery 已接上。
Link bootstrap 會碰哪些檔案
整套 bootstrap 會處理 metadata、wrapper、專案根目錄標記與清理流程。只想讓 Claude Code 看見一個 portable Skill 時,手動建立單一 Link 的變更面較小。
| 程式行為 | 風險 | 執行前檢查 |
|---|---|---|
| Junction 已存在 | Link 可能指到錯誤來源 | 檢查 Get-Item ... Target;目前腳本也會解析實際 target |
.claude\skills 是實體目錄或錯誤 Link |
setup 記錄契約違規並回傳 exit code 1 | 分類 portable 與 Claude-only Skill,備份後再修復 |
| 規則檔內容相同就顯示 Hard Link 已存在 | 相同副本被誤認為同一檔案 | 執行 fsutil hardlink list 或
scripts/validate_host_contract.py
|
| 規則內容不同時改名備份再建立 Link | 目前專案的檔案配置會改變 | review 差異與備份名稱 |
remove_links.ps1 拆除 Junction |
目標辨識錯誤會讓清理範圍失真 | 核對 LinkType 與 target;腳本以非遞迴刪除移除 Link 本身 |
AST parse 只能證明 PowerShell 語法可解析,不能證明檔案變更符合預期。 在隔離的測試專案完成建立與清理流程前,不要把語法檢查當成安全驗收。
卡住時,從這裡查
| 狀況 | 可能原因 | 處理順序 |
|---|---|---|
.claude\skills 已是實體目錄 |
過去人工複製,或內含 Claude-only Skill | 先比較並備份;vibeLinker setup 會回傳契約違規,不會自動覆蓋 |
| Claude 看不到 Skill | 路徑、檔名、description、target 或 session 時機錯誤 | 依序檢查 .claude/skills/<name>/SKILL.md、
Test-Path 與重啟
|
| Symbolic Link 權限不足 | 未開 Developer Mode,shell 也沒有 symlink 權限 | 在 Windows 本機改用 Junction,或調整權限後重試 |
| Hard Link 建立失敗 | AGENTS.md 與 CLAUDE.md 不在同一 volume |
改用 @AGENTS.md import |
修改 CLAUDE.md 後 AGENTS.md 也變了 |
這是 Hard Link 的正常行為 | 需要 Claude-only 內容時改採 import loader |
| 本機可用,Cloud session 看不到 | 外部 Junction 不會隨 clone 出現 | 提交 project Skill、使用 bootstrap 或 plugin |
交付前逐項確認
.agents/skills/<name>/SKILL.md是可攜 Skill 的唯一來源。.claude/skills/<name>的LinkType與Target正確。- Claude path 的
SKILL.md可讀,SHA-256 與來源一致。 /my-skill出現在 slash autocomplete。- 自然語言任務能觸發該 Skill。
/context顯示CLAUDE.md。- 使用 Hard Link 時,
fsutil hardlink list同時列出兩個檔名。 - bootstrap 或團隊文件已記錄 clone 後如何重建 Link。
資料來源與驗證邊界
- Claude Code:Extend Claude with skills
- Claude Code:Manage Claude's memory
- Agent Skills:Adding skills support to your agent
- Microsoft:Hard Links and Junctions
- PowerShell:New-Item
2026-08-29 已重查 Claude Code 官方文件、vibeLinker 控制流程與本機 Link
狀態;本機 Claude Code 版本為 2.1.247。這次沒有執行 setup/remove,也沒有替讀者環境
建立任何 Link。實際變更前,請先在可還原的測試專案驗證。