# Migrasi Dotfiles Claude Code ke Plugin yang Ter-version

> Sebuah hook PostToolUse di repository ini mulai me-review setiap edit dua kali, dan penyebabnya bukan script itu — saya menyalinnya ke dalam plugin dan meninggalkan versi aslinya di settings.json. Berikut peta file per file keluar dari direktori dotfiles, plus aturan precedence yang menentukan salinan mana yang benar-benar jalan.

URL: https://www.matthewswong.com/id/blog/claude-code-dotfiles-to-plugin-migration/

- Author: Matthews Wong (https://www.matthewswong.com/id/about/)
- Published: 2026-09-09
- Updated: 2026-09-09
- Category: Software Development

> **Key takeaway:** Memigrasikan direktori dotfiles Claude Code yang sudah menumpuk ke sebuah plugin ter-version memindahkan skills, subagents, commands, hooks dan MCP servers menjadi satu bundle yang bisa di-install. Bagian yang mahal adalah sisa yang tertinggal: hook lama di settings tetap jalan berdampingan dengan salinan plugin, registrasi MCP user-scope mengalahkan milik plugin, dan file subagent lokal bernama sama menang tanpa suara.

Laporannya tercetak dua kali. Sebuah hook PostToolUse di repository ini memeriksa apakah figure sebuah artikel cocok dengan section di sebelahnya, dan setelah saya membungkus hook itu ke dalam plugin, ia mulai me-review setiap edit dua kali. Satu jam saya pakai untuk membaca script-nya. Script-nya tidak apa-apa. Saya menyalin hook itu ke plugin dan membiarkan versi aslinya tetap di settings.json, dan Claude Code menjalankan keduanya.

Begitulah bentuk seluruh migrasi ini. Memindahkan file hanya butuh satu sore dengan cp; biayanya ada di layer yang lupa Anda kosongkan. Tulisan ini adalah peta file per file dari direktori dotfiles ke sebuah plugin, empat aturan precedence yang menentukan salinan mana yang sebenarnya jalan, dan cara memastikan sebuah komponen benar-benar pindah dan bukan hilang tanpa suara. Mekanismenya dari dokumentasi Claude Code; inventarisnya dan laporan ganda itu milik saya.

## Buat inventaris dulu sebelum memindahkan apa pun

Tidak ada satu pun isi direktori dotfiles yang menumpuk itu dirancang, dan Anda baru melihatnya setelah semuanya jadi daftar. Milik saya bahkan bukan satu direktori. Folder .claude di repository ini berisi empat skills, satu subagent dan satu hook PostToolUse, sementara file user settings saya membawa enam belas key di level teratas dan hook handler pada delapan event berbeda, dari PreToolUse sampai SessionEnd.

Dua hal dalam output itu mengubah rencana. Hooks-nya terpecah antara file user dan file project, jadi memigrasikan salah satunya akan meninggalkan yang lain tetap jalan. Lalu dua backup yang duduk di sebelah settings.json bukan salinan yang saya buat sengaja, dan itu justru argumen paling jujur untuk seluruh kerja ini: sebuah plugin punya field version, sedangkan file settings yang diedit tangan hanya punya salinan dirinya sendiri berstempel waktu.

## Peta dari file lepas ke direktori plugin

Sebagian besar perpindahannya mekanis dan bentuknya tidak berubah. Sebuah skill tetap folder berisi SKILL.md, sebelum dan sesudah; sebuah subagent tetap file markdown dengan frontmatter, sebelum dan sesudah. Hanya dua baris di tabel ini yang butuh kerja sungguhan, dan keduanya adalah baris yang sumbernya settings.json, bukan sebuah direktori.

| Di direktori dotfiles | Di dalam plugin | Yang berubah |
| --- | --- | --- |
| skills/name/SKILL.md | skills/name/SKILL.md di root plugin | Cara memanggilnya jadi /plugin-name:name. Versi aslinya masih menjawab /name, jadi keduanya tetap tersedia |
| commands/name.md | commands/name.md di root plugin | Markdown datar masih jalan, tapi skills/ adalah bentuk yang disarankan untuk apa pun yang baru |
| agents/name.md | agents/name.md di root plugin | Dimuat sebagai plugin-name:name, tapi file user atau project bernama sama mengalahkannya |
| Objek hooks di dalam settings.json | hooks/hooks.json | Bentuk objeknya identik, tapi ia digabung dengan hooks di settings Anda, bukan menggantinya |
| Entri MCP di ~/.claude.json | .mcp.json di root plugin | Nama tool mendapat scope plugin, jadi permission rule yang ditulis untuk nama lama berhenti cocok |
| Sisa isi settings.json | Tetap di tempatnya | settings.json milik plugin hanya menerima key agent dan subagentStatusLine |
| CLAUDE.md dan file rules | Tidak ke mana-mana | Layout plugin yang terdokumentasi tidak punya slot untuk file memory, jadi keduanya tetap di repository |

Manifest-nya lebih kecil dari dugaan orang. Hanya name yang wajib, dan name itu sekaligus menjadi namespace untuk setiap skill di dalam bundle. version bersifat opsional, tapi menghilangkannya berarti pemasang tidak pernah menerima update ketika Anda mengubah isinya, jadi migrasi yang melewatkannya menghasilkan plugin yang tidak bisa di-upgrade siapa pun. Satu aturan struktural layak dibaca dua kali: hanya plugin.json yang masuk ke dalam .claude-plugin, dan semua direktori komponen duduk di root plugin.

## Apa yang harus tetap personal, dan apa yang rusak kalau tidak

Kegagalan di sini bukan berupa pesan error. Sebuah skill yang menyebut absolute path di bawah home directory Anda tetap ter-parse, tetap ter-aktivasi, lalu menyuruh Claude menjalankan script yang tidak ada di mesin yang memasangnya. Tidak ada bagian bundle yang invalid, jadi tidak ada validator yang protes, dan kegagalannya muncul tiga langkah kemudian sebagai tool error yang membingungkan.

Tiga variabel menghapus masalah ini sejak awal. CLAUDE_SKILL_DIR menunjuk ke direktori yang memuat SKILL.md itu, CLAUDE_PLUGIN_ROOT ke direktori pemasangan plugin, dan CLAUDE_PROJECT_DIR ke root project — dan Claude Code mensubstitusikan ketiganya baik di body skill maupun di frontmatter allowed-tools, dan itulah yang membuat script bawaan bisa jalan tanpa permission prompt. Credential mendapat perlakuan berbeda: deklarasikan di userConfig, tandai sensitive, lalu biarkan setiap pemasang memberi milik mereka sendiri.

> Jangan coba memigrasikan permission rules Anda. settings.json milik plugin hanya menerima key agent dan subagentStatusLine, jadi rule allow dan deny sama sekali tidak bisa ikut di dalam bundle — dan rule yang sudah Anda tulis untuk sebuah MCP tool berhenti cocok begitu server itu dikirim di dalam plugin, karena nama tool yang ter-scope mendapat prefix plugin. Setiap panggilan yang tadinya Anda izinkan mulai meminta konfirmasi lagi.

## Kenapa satu hook sekarang jalan dua kali, dan layer mana yang menang

Hooks tidak saling menimpa. Dokumentasinya tegas bahwa entri hook digabung antar level settings dan bukan saling mengganti, bahwa semua hook yang cocok jalan paralel, dan bahwa handler yang sama yang didefinisikan di lebih dari satu file settings hanya jalan sekali — tetapi salinan milik plugin atau skill tetap dihitung terpisah. Klausa terakhir itulah laporan ganda saya: deduplikasi tidak pernah menyeberangi batas antara file settings dan sebuah plugin.

- Hooks digabung. Hook di settings dan hook di plugin pada event yang sama dua-duanya jalan, jadi blok lama harus dihapus, bukan disimpan sebagai fallback.
- Subagents saling menimpa. File agents milik user atau project yang bernama sama mengalahkan salinan plugin, jadi versi plugin diam saja sampai versi aslinya dihapus.
- Skills hidup berdampingan. Skill plugin diberi namespace plugin-name:skill-name, jadi tidak ada yang bentrok dan tidak ada yang diganti — Anda hanya punya dua dari segalanya sampai dibersihkan. Di antara level lain, personal mengalahkan project.
- MCP servers diselesaikan berdasarkan scope. Local mengalahkan project, project mengalahkan user, dan user mengalahkan apa pun yang disediakan plugin. Seluruh entri diambil dari sumber tertinggi dan field-nya tidak pernah digabung.

Ada dua kasus yang tidak bisa saya pastikan dari dokumentasi, jadi saya berhenti menduga. Dokumentasi tidak menyebut apa yang terjadi kalau dua plugin yang aktif mengirim handler identik untuk event yang sama, dan tidak mengurutkan key agent di settings.json milik plugin melawan key yang sama di level user. Yang pertama ternyata tidak sepenting kedengarannya, karena hook yang cocok jalan paralel dan tidak ada urutan yang bisa diandalkan. Untuk yang kedua tesnya murah: set key-nya di satu tempat, baca apa yang termuat, lalu set di tempat lain — lebih lambat dari aturan yang terdokumentasi, dan jauh lebih aman dari aturan yang diasumsikan.

*Panel ketiga adalah yang memakan waktu Anda. Semua isinya adalah salinan yang sudah Anda buat, dan membiarkan salah satunya di tempat adalah cara sebuah plugin berakhir menjalankan satu hook dua kali atau menutupi MCP server-nya sendiri.*

## Hasil yang jujur adalah dua artefak, bukan satu

Saya tidak berhasil sampai ke satu bundle tunggal, dan setelah mencoba saya berhenti menginginkannya. Separuh dari yang menumpuk di konfigurasi saya secara prinsip tidak bisa dibagikan: perintah status line yang menunjuk script di home directory saya, sebuah effort level, sebuah theme, permission rules yang mencerminkan seberapa jauh saya membiarkan agent bekerja tanpa diawasi. Dorong semua itu ke team plugin dan Anda sedang mendorong kebiasaan kerja Anda ke empat orang lain.

Jadi migrasinya mendarat sebagai satu team plugin plus satu personal layer yang tipis, dan temuan yang berguna adalah personal layer itu juga bisa berupa plugin, tanpa mesin distribusi sama sekali. claude plugin init menulis sebuah manifest dan satu SKILL.md awal ke direktori personal skills Anda, dan pada session berikutnya ia dimuat sebagai name@skills-dir — tanpa marketplace dan tanpa langkah install, tapi dengan manifest sungguhan, jadi ia tetap bisa membawa agents, hooks dan MCP servers sendiri. Bagian tim ikut di-version dan di-review. Bagian personal boleh tetap berantakan.

> Uji dengan --plugin-dir sebelum meng-install apa pun. Flag ini memuat plugin langsung dari sebuah direktori, dan salinan lokal punya precedence di atas plugin ter-install yang bernama sama untuk session itu, jadi Anda bisa melatih sebuah upgrade tanpa uninstall dulu. Lalu jalankan /reload-plugins setelah setiap edit — ia membaca ulang skills, agents, hooks serta MCP dan LSP server milik plugin tanpa restart.

## Verifikasi setiap komponen lewat sinyalnya sendiri

Migrasi yang tidak memunculkan pesan error sama sekali belum membuktikan apa pun. Empat dari lima jenis komponen gagal tanpa suara ketika gagal, jadi setiap pemeriksaan harus bersifat positif: sebutkan sinyal yang hanya muncul kalau komponennya benar-benar termuat, lalu pergi mencarinya. Loop sebelum pemeriksaan itu sifatnya tetap.

1. Jalankan claude plugin validate pada direktori plugin. Ini pemeriksaan yang sama dengan yang dijalankan pipeline review komunitas, dan --strict menaikkan warning menjadi error. Ia juga bekerja pada direktori skills biasa, dan itu cara tercepat menemukan SKILL.md yang frontmatter-nya tidak lagi ter-parse.
2. Muat dengan claude --plugin-dir yang diarahkan ke direktori itu, bukan dengan meng-install-nya, supaya versi aslinya masih ada di disk saat Anda perlu membandingkan perilakunya.
3. Jalankan /reload-plugins setelah setiap edit, bukan me-restart session. Ia membaca ulang plugins, skills, agents, hooks serta MCP dan LSP server milik plugin.
4. Buka /plugin dan baca tab Errors. Komponen yang gagal dimuat terdaftar di sana, misalnya executable yang tidak ditemukan, sementara komponen dengan konfigurasi invalid justru dilewati — dan claude --debug memberi tahu alasannya.

Lalu komponennya sendiri, dan cara Anda merumuskan pemeriksaan skill lebih penting dari yang terlihat. Mengetik slash command hanya membuktikan bahwa sebuah file ada di suatu tempat; mendeskripsikan tugasnya dengan kalimat Anda sendiri itulah yang membuktikan description-nya masih layak memicu aktivasi, dan justru itu yang bisa rusak tanpa suara karena penyalinan antar direktori. Untuk sisanya, /hooks memberi jumlah per event beserta label sumbernya, /context mendaftar custom agents, dan /mcp memberi status per server.

*Baca band terakhir sebagai alasan kenapa empat yang lain belum cukup: masing-masing bisa melaporkan sukses sementara komponen yang benar-benar dipakai bukan yang Anda migrasikan.*

## Komponen yang hilang tanpa Anda sadari

Komponennya adalah MCP server. Ketika nama server yang sama ada di lebih dari satu scope, Claude Code menyambungkannya sekali saja, dan urutannya local, lalu project, lalu user, lalu apa pun yang disediakan plugin — dengan seluruh entri diambil dari sumber tertinggi dan tanpa penggabungan field. Registrasi user-scope di ~/.claude.json karena itu mengalahkan salinan yang susah payah Anda pindahkan ke .mcp.json milik plugin. Tidak ada peringatan. Statusnya terbaca Connected, tool-nya bekerja, dan definisi yang dipakai adalah yang Anda yakini sudah Anda ganti.

Kondisi itu nyaman untuk didiami berminggu-minggu, dan pecah begitu seorang rekan meng-install plugin-nya: mereka mendapat entri milik plugin, Anda masih memakai milik sendiri, dan dua konfigurasi itu melenceng tanpa sebab yang kelihatan. Pemeriksaannya claude mcp list, yang menampilkan setiap server beserta scope-nya, lalu claude mcp get untuk server yang Anda pedulikan. Jalankan sekali di akhir migrasi dan sekali lagi setelah versi aslinya Anda hapus.

Jadi aturan yang akan saya berikan kepada siapa pun yang melakukan ini: migrasi belum selesai ketika plugin-nya termuat, migrasi selesai ketika versi aslinya sudah hilang. Salinan yang tertinggal bukan fallback. Ia adalah sumber kebenaran kedua yang tidak kelihatan, dan aturan precedence Claude Code sendirilah yang menentukan mana dari keduanya yang Anda jalankan — dan aturannya berbeda untuk hooks, subagents, skills dan MCP servers.

## Sumber

- Dokumentasi Claude Code — Create plugins: langkah migrasi, kontrak direktori dan catatan tentang menghapus file aslinya
- Dokumentasi Claude Code — Plugins reference: layout direktori lengkap, skema manifest dan environment variable milik plugin
- Dokumentasi Claude Code — Hooks: di mana hooks bisa dikonfigurasi, bagaimana ia digabung antar level, dan aturan deduplikasinya
- Dokumentasi Claude Code — Skills: di mana skills disimpan, bagaimana skill bernama sama diselesaikan, dan variabel substitusi path
- Dokumentasi Claude Code — MCP: scope local, project dan user, serta urutan precedence yang menempatkan server plugin di bawahnya

## Source links

- https://code.claude.com/docs/en/plugins
- https://code.claude.com/docs/en/plugins-reference
- https://code.claude.com/docs/en/hooks
- https://code.claude.com/docs/en/skills
- https://code.claude.com/docs/en/mcp

## FAQ

### Bagaimana cara mengubah direktori dotfiles .claude saya menjadi plugin Claude Code?

Buat direktori plugin di sebelah folder .claude yang akan Anda gantikan, taruh plugin.json di dalam folder .claude-plugin, lalu copy skills, agents dan commands ke root plugin tanpa diubah. Hooks adalah pengecualian karena ia tinggal di dalam settings.json: angkat objek hooks itu secara manual ke hooks/hooks.json, yang bentuknya identik. Hanya plugin.json yang masuk ke .claude-plugin, dan semua direktori komponen duduk di root plugin.

### Kenapa hook saya jalan dua kali setelah dipindahkan ke dalam plugin?

Karena versi aslinya masih ada di settings.json. Entri hook digabung antar level settings dan bukan saling mengganti, dan walaupun handler yang sama di dua file settings hanya jalan sekali, salinan milik plugin dihitung terpisah dari salinan di settings. Hapus blok lamanya, jangan disimpan sebagai fallback, lalu pakai /hooks untuk melihat jumlah per event dan label sumber setiap handler.

### Mana yang menang kalau plugin dan config lokal saya mendefinisikan komponen yang sama?

Aturannya berbeda per komponen, jadi tidak ada satu aturan yang bisa dihafal. File agents milik user atau project yang bernama sama mengalahkan subagent milik plugin; skill plugin diberi namespace plugin-name:skill-name sehingga kedua salinan tetap tersedia; semua hook yang cocok jalan; dan MCP server diselesaikan berurutan local, project, user, baru plugin. Urutan terakhir itu berarti registrasi user-scope yang tertinggal mengalahkan salinan di dalam plugin Anda.

### Apa yang tidak boleh masuk ke dalam plugin Claude Code yang dibagikan?

Apa pun yang spesifik ke satu mesin atau bersifat rahasia. Absolute path di bawah home directory Anda akan rusak di mesin lain dan tidak memunculkan error validasi, jadi pakai CLAUDE_SKILL_DIR, CLAUDE_PLUGIN_ROOT atau CLAUDE_PROJECT_DIR. Credential sebaiknya dideklarasikan di userConfig dengan tanda sensitive supaya setiap pemasang memberi miliknya sendiri, dan permission rule sama sekali tidak bisa ikut karena settings.json milik plugin hanya menerima key agent dan subagentStatusLine.

### Bagaimana memastikan tidak ada komponen yang hilang saat migrasi?

Jalankan claude plugin validate pada direktorinya, muat dengan claude --plugin-dir alih-alih meng-install, lalu verifikasi setiap komponen lewat sinyalnya sendiri: /skills ditambah tugas yang Anda deskripsikan sendiri untuk membuktikan aktivasi, /hooks untuk jumlah per event, /context untuk custom agents dan /mcp untuk status server. Tutup dengan claude mcp list, karena server yang registrasi lamanya masih mengalahkan plugin akan melaporkan Connected sambil menjalankan entri yang Anda pikir sudah diganti.

---

- HTML version: https://www.matthewswong.com/id/blog/claude-code-dotfiles-to-plugin-migration/
- English: https://www.matthewswong.com/en/blog/claude-code-dotfiles-to-plugin-migration/
- Site index for agents: https://www.matthewswong.com/llms.txt
- Sitemap: https://www.matthewswong.com/sitemap.xml
- API description (OpenAPI): https://www.matthewswong.com/openapi.json
