Kurasi Marketplace Plugin Claude Code: Apa yang Layak Masuk

Foto oleh Diliff via Wikimedia Commons (CC BY 2.5)
Terima sebuah plugin kalau ia mengunci keputusan yang sudah diambil tim dan sudah harus dijelaskan lebih dari sekali, misalnya aturan domain yang tidak bisa disimpulkan engineer baru dari code. Tolak apa pun yang hanya membungkus perintah yang tidak pernah menyulitkan siapa pun, karena itu hanya menambah nama untuk diingat tanpa mengunci pilihan apa pun. Kalau satu rule linter atau satu job CI bisa menegakkan hal yang sama secara deterministik, itu rumah yang lebih kuat daripada sebuah entry katalog.
Field name sebuah plugin adalah identifier stabil yang dirujuk di enabledPlugins dan di perintah install, jadi menghapus entry-nya langsung akan memberi error plugin-not-found ke semua pengguna lama. Umumkan pemensiunannya dulu dengan mengubah displayName dan description tanpa menyentuh name, lalu set defaultEnabled ke false supaya install baru datang dalam keadaan disabled. Baru setelah masa tenggang, hapus entry-nya dan tambahkan renames map di level atas yang mengarahkan nama lama ke penggantinya, atau ke null kalau plugin-nya sudah tidak ada.
Plugin internal yang tidak dirawat lebih buruk daripada tidak ada plugin, karena skill basi kelihatan persis sama dengan skill baru dan model membaca keduanya dengan otoritas yang sama. Tulis satu orang, bukan satu tim, di object author, karena tim adalah cara sebuah entry berakhir tanpa pemilik tanpa ada yang sadar. Object metadata bersifat bebas dan tidak dibaca Claude Code, sehingga itu tempat yang tepat untuk nama pemilik dan tanggal review.
Lebih sedikit daripada yang terasa pantas. Listing text sebuah plugin, yakni description skill dan agent serta nama command-nya, adalah context always-on di setiap session entah ada yang berjalan atau tidak, jadi entry yang tidak dipakai tetap ditagihkan ke semua orang selamanya. Jalankan claude plugin details untuk melihat proyeksi biaya always-on per entry, dan set defaultEnabled ke false pada plugin spesialis supaya terpasang dalam keadaan disabled sampai ada yang mengaktifkannya.
Claude Code memeriksa update marketplace dan plugin setelah session dimulai, dengan jeda acak sampai sepuluh menit, dan session yang sedang berjalan tetap memakai version yang dimuat saat launch. Entry dengan source git yang tidak mendeklarasikan version mengambil version dari commit SHA, jadi setiap push ke repository marketplace adalah update yang diterima tim. Deklarasikan version di setiap entry dan bump pada setiap perubahan, supaya bedanya jadi sesuatu yang bisa dibandingkan manusia.

Foto oleh Diliff via Wikimedia Commons (CC BY 2.5)
Ringkasan Utama
Marketplace plugin Claude Code internal itu sebuah dokumen kebijakan: setiap entry adalah instruksi yang diikuti agent seluruh tim. Terima sebuah plugin hanya kalau ia mengunci keputusan yang memang sudah diambil tim, beri setiap entry satu maintainer bernama, lalu pensiunkan lewat renames map marketplace supaya tidak ada install yang rusak.
Kebijakan ini lahir dari satu pertanyaan pendek: dari semua ini, yang mana yang harus saya install? Seorang rekan baru menambahkan marketplace internal kami, membuka plugin browser, dan menemukan daftar yang begitu panjang sampai memilih dari daftar itu jadi pekerjaan tersendiri. Setiap entry punya description. Tidak satu pun memberi tahu dia apakah masih ada yang memakainya, siapa yang bisa ditanya soal itu, atau apa yang akan berubah dari cara agent-nya bekerja di code kami.
Saya mengelola satu marketplace Claude Code publik yang sekarang sudah lebih dari enam puluh entry, dan satu marketplace internal yang jauh lebih kecil. Katalog publik boleh longgar, karena memasang sesuatu dari sana adalah pilihan satu orang. Katalog internal tidak boleh, karena semua isinya mengubah cara agent rekan kerja bekerja di code yang kami rilis bersama. Tulisan ini adalah kebijakan editorial yang saya pakai untuk katalog internal itu. Format manifest dan mekanisme publikasi sengaja saya lewati, karena dokumentasi resmi dan satu tulisan sebelumnya sudah membahasnya.
Setiap entry di marketplace internal adalah instruksi yang dieksekusi di mesin orang lain, di session orang lain, terhadap code yang kalian miliki bersama. Sebuah skill adalah prosa yang dibaca model sebagai panduan. Sebuah hook adalah perintah shell yang tetap jalan entah agent-nya setuju atau tidak. Sebuah subagent membawa system prompt dan batasan tool-nya sendiri. Menerbitkan satu entry lebih dekat ke merge perubahan pada konfigurasi linter bersama daripada ke menambah satu link di wiki.
Karena itu pertanyaan yang menarik sejak awal bukan cara menerbitkan sebuah entry. Manifest-nya cuma satu file JSON, dan tooling-nya sudah memvalidasi untuk kita: claude plugin validate memeriksa schema dan syntax lalu keluar dengan status bukan nol kalau ada yang salah. Yang tidak bisa ia periksa adalah apakah plugin itu mengunci sesuatu yang benar, masih berlaku, dan layak dibaca seluruh tim. Bagian itu murni editorial, dan ketika tidak ada yang memiliki pekerjaan editorial itu, sebuah katalog memburuk ke satu arah yang selalu sama, yaitu membesar.
Ujian yang saya pakai adalah apakah plugin itu mengunci keputusan yang benar-benar sudah diambil tim dan sudah harus dijelaskan dua kali. Aturan closed period di module finance adalah sebuah keputusan: pernah diperdebatkan, salah menerapkannya berakibat di ledger, dan tidak ada engineer baru yang bisa menyimpulkannya dari code. Sebuah slash command yang menjalankan test suite bukan keputusan. Tidak ada yang kesulitan dengan itu, tidak ada yang dipilih, dan satu-satunya sumbangan plugin tersebut adalah satu nama tambahan yang harus diingat untuk sesuatu yang sudah rutin diketik semua orang.
Ujian keempat menolak paling banyak usulan dan paling sering dibantah, jadi alasannya perlu dinyatakan terang-terangan. Pemeriksaan deterministik jalan entah agent-nya menganggapnya relevan atau tidak. Sebuah instruksi baru bekerja kalau ia sampai ke model pada momen yang tepat. Jadi jawaban jujur untuk cukup banyak usulan adalah bahwa aturannya seharusnya di CI, dan menaruhnya juga di katalog hanya akan membuat salinan yang lebih lemah menjadi yang dipercaya orang.
Minta pengusul menyebut pull request, komentar review atau incident yang akan dicegah oleh plugin ini. Kalau dia bisa menunjuk satu, entry itu mengunci sesuatu yang nyata. Kalau contohnya masih hipotetis, entry-nya juga akan begitu, dan tetap memakan biaya di setiap session yang memuatnya.
Populer bukan berarti sudah direview. Sebuah bundle yang dipasang banyak orang memecahkan masalah orang lain dengan meyakinkan, dan justru itu yang membuatnya menggoda sekaligus otomatis gugur: yang ia kunci adalah keputusan mereka, bukan keputusan kita. Audit keamanan sebuah bundle pihak ketiga adalah pass tersendiri dengan checklist sendiri, dan itu saya jalankan terpisah. Pertanyaan editorialnya lebih sempit dan lebih mudah dilewatkan. Apakah bundle ini masih akan mengatakan hal yang kita inginkan enam bulan lagi, setelah penulisnya mengubahnya?
Dua fakta dari plugin reference membuat pertanyaan itu bisa dijawab, bukan sekadar soal selera. Perintah claude plugin details mencetak component inventory sebuah plugin beserta dua angka token: always-on, yaitu yang ditambahkan listing text plugin ke setiap session entah ada yang jalan atau tidak, dan on-invoke, yaitu biaya sebuah component ketika ia benar-benar berjalan. Lalu, sebuah entry dengan source git yang tidak mendeklarasikan version akan mengambil version dari commit SHA, sehingga setiap push di upstream adalah update yang diterima tim kalian. Itulah dua komitmen yang diminta sebuah entry dari luar: biaya context yang permanen, dan upstream tanpa batas.
# Two questions an outside bundle answers before it gets an entry.
# 1. What does it add to every session, whether or not anybody invokes it?
claude plugin details security-review@vendor-tools
# Component inventory
# Skills (2) threat-model, review-diff
# Agents (0)
# Hooks (1) PreToolUse (harness-only — no model context cost)
# Projected token cost
# Always-on: added to EVERY session by the listing text alone
#
# Always-on is the catalogue's number. It is charged per session, per member,
# for as long as the entry exists — including to everyone who never uses it.
# On-invoke is printed per component rather than as a plugin total, because a
# real session fires only a subset. Do not add the column up.
# 2. Does an entry already do this? Read the shape once, then search it.
# --available is what makes the list include plugins you have NOT
# installed, which is the half a duplication check cares about, and it
# only works together with --json.
claude plugin list --available --json > /tmp/catalogue.json
jq '.[0]' /tmp/catalogue.json # the fields your version emits
grep -i -e review -e threat /tmp/catalogue.jsonPlugin internal yang tidak dirawat lebih buruk daripada tidak ada plugin sama sekali. Halaman wiki yang setahun tidak disentuh kelihatan kedaluwarsa; sebuah skill yang setahun tidak disentuh kelihatan persis seperti skill yang baru direvisi minggu lalu. Model membaca keduanya dengan otoritas yang sama, agent bertindak atas keduanya dengan keyakinan yang sama, dan pembacanya tidak punya petunjuk untuk membedakan. Panduan basi yang dipercaya semua orang adalah artefak paling mahal yang bisa disimpan sebuah katalog.
Jadi sebuah entry tidak masuk tanpa maintainer bernama, dan nama itu tinggal di file marketplace, bukan di spreadsheet. Object author adalah field schema yang nyata dengan name wajib serta email dan url opsional. Object metadata didokumentasikan sebagai data bebas untuk field kalian sendiri yang tidak dibaca Claude Code, dan itu membuatnya tempat yang tepat untuk fakta yang penting bagi kami dan tidak bagi siapa pun: siapa pemiliknya, kapan terakhir direview, dan kapan jatuh tempo review berikutnya. Satu orang, bukan satu tim, karena tim adalah cara sebuah entry berakhir tanpa pemilik tanpa ada yang sadar.
// marketplace.json — the two places a maintainer's name can actually live.
{
"name": "erp-invariants",
"source": "./plugins/erp-invariants",
"description": "Closed-period and branch-scoping rules for the finance module",
// Declared here so a later behaviour change has a number to compare.
"version": "1.4.0",
// A real schema field: name is required, email and url are optional.
// This is the name a confused teammate needs, so it is not a mailing list.
"author": { "name": "Matthews Wong", "email": "[email protected]" },
// Documented as a free-form object for your own fields, which Claude Code
// does not read. That is exactly what makes it safe for governance data:
// it can never change how the plugin behaves, only who answers for it.
"metadata": {
"owner": "matthews",
"reviewedOn": "2026-09-02",
"reviewDue": "2026-12-01",
"decisionRecord": "docs/adr/0031-closed-period.md"
}
}# claude plugin validate checks schema and syntax. It has no opinion about
# ownership, so that gate is ours to write. One jq expression in CI on the
# marketplace repository, run on every pull request that touches the file.
jq -e '
.plugins
| map(select((.author.name // "") == "" or (.metadata.owner // "") == ""))
| if length == 0 then true
else "unowned entries: " + (map(.name) | join(", ")) | halt_error(1)
end
' .claude-plugin/marketplace.json
# unowned entries: legacy-linter, screenshot-helper
# The same run, second gate: nothing may sit past its own review date.
jq -e --arg today "$(date -u +%F)" '
.plugins
| map(select((.metadata.reviewDue // "9999-12-31") < $today))
| if length == 0 then true
else "review overdue: " + (map(.name) | join(", ")) | halt_error(1)
end
' .claude-plugin/marketplace.json
# Then the schema check, which is the cheap one and still worth failing on.
claude plugin validate .
Field name sebuah plugin adalah identifier yang stabil. Pengguna merujuknya di enabledPlugins, di pluginConfigs dan di perintah install, jadi mengganti nama atau menghapus sebuah entry akan merusak semua install yang sudah ada dengan error plugin-not-found. Mengubah hanya label yang dilihat orang itu gratis: set displayName dan biarkan name apa adanya. Menghapus entry-nya sungguh-sungguh butuh satu field yang memang dibuat untuk ini, yaitu renames map di level atas yang memetakan nama lama ke nama sekarang, atau ke null kalau plugin-nya sudah tidak ada.
// Retirement in the order that breaks nobody. Three commits, not one.
// 1. Announce it in the catalogue itself, where the reader already is.
// displayName is only the label in the UI; name is the identifier that
// every existing install references, so it must not move.
{
"name": "legacy-linter",
"displayName": "legacy-linter (deprecated, removal 2026-11-01)",
"description": "Superseded by code-formatter. Do not adopt for new work.",
"source": "./plugins/legacy-linter"
}
// 2. Freeze it. defaultEnabled false means a new install arrives disabled,
// and the marketplace entry takes precedence over the same field in the
// plugin's own manifest — so this decision is the catalogue owner's, not
// the plugin author's. An enabledPlugins entry a user already has wins
// over it and persists across updates, so this flips nobody's session.
{
"name": "legacy-linter",
"defaultEnabled": false,
"source": "./plugins/legacy-linter"
}
// 3. Only after the grace period: drop the entry AND record the removal.
// Without the renames line, every remaining install reports
// plugin-not-found, which reads to the user as a broken tool.
{
"name": "acme-tools",
"owner": { "name": "Platform" },
"plugins": [
{ "name": "code-formatter", "source": "./plugins/code-formatter" }
],
"renames": {
"legacy-linter": null,
"formatter": "code-formatter"
}
}
// null -> Claude Code drops the stale key and reports the removal
// a name -> it loads under the new name and rewrites the key once, in the
// user, project and local scopes, so the notice appears once
// remote -> a github or npm source reports plugin-cache-miss after the
// rename, and the user runs install once to fetch it againDua sifat map itu yang tercatat di dokumentasi menentukan kebijakan di sekitarnya. Ia adalah riwayat yang hanya boleh ditambah, jadi entry lama tetap di tempatnya lama setelah kalian yakin semua orang sudah migrasi, dan rename kedua mendapat entry kedua alih-alih mengedit yang pertama, karena Claude Code mengikuti rantainya. Lalu claude plugin validate menolak map yang rantainya membentuk cycle atau tidak berakhir di null maupun di plugin yang masih terdaftar, sehingga pemensiunan jadi sesuatu yang bisa direview di CI ketimbang diperiksa dengan mata.
Managed settings dan policy settings bersifat read-only bagi Claude Code, jadi plugin yang diaktifkan organisasi kalian di sana tidak bisa ditulis ulang otomatis. Plugin yang sudah di-rename tetap dimuat setiap session, tetapi notice rename-nya terus muncul sampai administrator memperbarui enabledPlugins di file managed. Memensiunkan entry managed berarti dua perubahan dengan urutan yang benar, atau seluruh tim mendapat notice yang tidak bisa mereka hilangkan.
Tiga puluh entry yang hanya empat di antaranya dipakai bukan masalah dokumentasi, dan tidak ada README yang memperbaikinya. Itu biaya yang dibayar dua kali. Sekali oleh setiap pembaca yang harus menilai dua puluh enam plugin tidak relevan sebelum sampai ke satu yang dia butuhkan, dan sekali per session oleh setiap anggota tim, karena listing text sebuah plugin, yakni description skill, description agent dan nama command-nya, adalah context always-on entah ada yang pernah berjalan atau tidak.
Jadi katalognya punya plafon, dan mengusulkan entry kedua belas berarti berargumen bahwa ia mengalahkan salah satu dari sebelas yang sudah ada. Dua mekanisme membuat plafon itu bisa dijalani. Menyetel defaultEnabled ke false pada sebuah entry marketplace memasang plugin spesialis dalam keadaan disabled sampai ada yang memilih mengaktifkannya, dan entry marketplace menang atas field yang sama di manifest plugin itu sendiri, jadi pemilik katalog bisa mengambil keputusan itu untuk seluruh tim. Lalu claude plugin prune, dengan alias autoremove, mendaftar dependency yang terpasang otomatis dan sudah tidak dibutuhkan plugin mana pun lalu meminta konfirmasi sebelum menghapusnya, dan itu layak dijalankan dengan dry-run setelah setiap pemensiunan.
Perintah claude plugin details adalah yang menyelesaikan perdebatan soal ukuran katalog. Ia mencetak component inventory dan proyeksi biaya always-on untuk satu plugin, dihitung memakai API count_tokens untuk model yang sedang aktif dan turun ke estimasi berbasis karakter kalau API itu tidak terjangkau. Jalankan pada semua entry sekali per kuartal dan harga katalog kalian berhenti jadi soal pendapat.
Kegagalannya punya ciri khas. Ada yang bilang agent-nya mulai berperilaku aneh, semua orang berspekulasi soal model, dan tidak ada yang menghubungkannya dengan update yang tidak pernah mereka lihat. Itu bukan kelalaian, itu memang bentuk jalur update-nya. Claude Code memeriksa update marketplace dan plugin setelah session kalian mulai, dengan jeda acak sampai sepuluh menit, dan session yang sedang berjalan tetap memakai version yang dimuat saat launch. Kalian mendapat notifikasi yang meminta menjalankan reload-plugins, atau version baru itu tiba begitu saja pada launch berikutnya.
Dua default membuat kedatangan diam-diam itu sangat mungkin justru pada konfigurasi yang paling sering dipakai tim. Auto-update di background mati untuk marketplace pihak ketiga kecuali ada yang menyetel autoUpdate ke true pada entry extraKnownMarketplaces, dan administrator memang melakukannya, justru supaya tidak ada yang tertinggal di katalog basi. Lalu entry dengan source git yang tidak mendeklarasikan version mengambil version dari commit SHA, yang oleh dokumentasi disebut sebagai setup paling sederhana untuk plugin internal yang aktif dikembangkan. Kedua pilihan itu masuk akal. Bersama-sama keduanya berarti satu baris suntingan di sebuah file skill sampai ke session berikutnya semua orang tanpa satu pun nomor version untuk dibandingkan.
# The change is invisible because there is nothing to compare. Give it a
# number, and the incident becomes a diff instead of a conversation.
claude plugin list --json | jq -r '.[] | [.name, .version] | @tsv'
# erp-invariants@acme-tools 1.4.0
# release-guard@acme-tools 0.9.2
# What actually changed, from the only place that keeps a record: the repo
# behind the marketplace. Skill prose is the diff that matters here, and it
# shows up in no lock file, because there is no lock file.
git -C ~/work/acme-claude-plugins log --oneline -- plugins/erp-invariants
git -C ~/work/acme-claude-plugins diff v1.3.0..v1.4.0 -- plugins/erp-invariants
# Bisect the behaviour rather than re-reading the prose. Disable the suspect
# and reproduce the odd turn. This also fails when another enabled plugin
# depends on it, which is itself the answer to: is this entry load-bearing.
claude plugin disable erp-invariants@acme-tools
# And a baseline when nobody can name a suspect: turn the catalogue off for
# one session, reproduce, then re-enable the four entries people really use.
claude plugin disable --all
Ini cukup pendek untuk ditempel ke template pull request, dan di situlah ia paling berguna, karena keputusannya diambil sekali oleh pengusul dan direview sekali oleh siapa pun yang memiliki katalog.
Langkah yang paling sering dilewati adalah yang ketiga, dan itu justru yang menentukan apakah katalognya masih benar setahun kemudian. Sisanya bisa diperbaiki nanti oleh siapa pun yang memiliki entry itu, dan itu hanya mungkin kalau ada yang memilikinya.
Aturan yang saya bawa dari semua ini adalah bahwa sebuah entry marketplace adalah instruksi permanen dengan nama seseorang di atasnya, atau ia bukan entry. Standar review, anggaran token, disiplin version dan renames map semuanya turun dari satu kalimat itu. Semuanya adalah mekanisme untuk menjaga katalog tetap lebih kecil, dan lebih jujur, daripada antusiasme yang mengisinya.
Sumber dan bacaan lanjutan