josippapez/ai-setup/tree/main/claude/plugins/dev-core
dev-core
Everyday defaults for Claude Code: repo grounding, the always-on engineering rules, core skills, and the specialist agents.
About this mod
dev-core
Everyday defaults for Claude Code: repo grounding, the always-on engineering rules, core skills, and the specialist agents. Claude-compatible mirror of the OpenCode interactive-mcp custom plugin, which keeps the older name.
-
Repo grounding comes from the separate
repo-docsplugin, not from this one.find_docs/list_docs/read_doc/find_libs/get_blast_radius/get_file_dependentslive inclaude/plugins/repo-docs, shared withorchestrateso both use one MCP server and one tool namespace (mcp__plugin_repo-docs_repo-docs__*) instead of each bundling an identical copy.claude/install.shinstallsrepo-docsautomatically alongside this plugin; if its tools are not callable, tell the user to runclaude plugin install repo-docs@ai-setup. Seeclaude/plugins/repo-docs/README.md. -
.mcp.jsonregistersinteractive(@rawwee/interactive-mcp, a question tool subagents can reach since Claude Code withholdsAskUserQuestionfrom them;--disable-tools message_complete_notification,intensive_chatleaves onlyrequest_user_input, since the main agent is told to useAskUserQuestionand nothing here uses intensive chat, so the other tools were roster weight nobody could call), plus bundled third-party MCP servers that should be available by default in every project:context7(@upstash/context7-mcp),chrome-devtools(chrome-devtools-mcp) andcomputer-use(computer-use-mcp, mouse/keyboard/screenshot control of the real desktop for anything with no API and no DOM behind it: native app UI, OS dialogs, browser chrome outside the page, and seeing what actually rendered; it also rescues achrome-devtoolscall stuck on the native remote-debugging dialog, which no timeout cuts short). Documented by thecomputer-useskill. These ship enabled with the plugin so they need no per-machine MCP config. Project- or secret-specific servers (e.g. Figma, a local Storybook endpoint) are intentionally kept out of the plugin and live at user scope in~/.claude.json. -
WCAG lookups are a CLI, not an MCP. Accessibility criteria/techniques/failures come from
@rawwee/wcag-cli, invoked over Bash and documented by the bundledaccessibilityskill. A skill + on-demand CLI costs nothing until used, whereas an always-registered MCP spawns a subprocess every session — the same pattern asopensrcandrtk. Prefer this shape for any stateless reference lookup; keep an MCP only where the server holds real state (a warm index, a browser session), asrepo-docsandchrome-devtoolsdo. -
skills/mirrors the OpenCode skills. -
Rules come in three layers: an always-on kernel, a per-prompt digest, and tool-triggered cards. Claude Code has no native plugin "rules" loader and nothing a plugin ships reaches the system prompt except an output style, so a hook publishes the kernel as user-scope rules and the other two layers arrive as hook-injected
additionalContext.rules/is the kernel:evidence-first,external-facts,proactive-execution. Only what applies to every turn regardless of which tool runs.hooks/link-rules.cjsruns onSessionStart, refreshes everyrules/*.mdin the plugin's data dir file by file through a rename (emptying the directory first made a concurrently starting session report every rule missing) (${CLAUDE_PLUGIN_DATA}/rules/) and points~/.claude/rules/dev-coreat the copy. From the next launch Claude Code loads them natively: into the main agent and every subagent (measured with a no-tools probe subagent), listed under Memory files in/context, reloaded after compaction. The data dir is the one path Claude Code deletes on uninstall, so the rules leave with the plugin; the dangling link is skipped silently (measured).Two gaps, both measured, both closed by the same script. Rules load before
SessionStarthooks run, so the session that publishes the link has no native copy and neither do its subagents (measured: a subagent spawned after the link appeared still reported nothing): in that one session the script emits the rules asadditionalContexton bothSessionStartandSubagentStart, sharded under the 10,000-character cap, keyed on afirst-sessionmarker in the data dir that holds the publishingsession_id. Andclaude plugin disableruns nothing of the disabled plugin, so the script also sweeps~/.claude/rules/for links into~/.claude/plugins/data/*/ruleswhose pluginclaude plugin list --json(0.3 s) reports disabled or no longer lists, and removes them;rules-indexcarries a byte-identical copy so the sweep still runs when dev-core itself is the one disabled. If the user has their own real directory at~/.claude/rules/dev-core, it is left alone and the rules keep arriving as context every session.hooks.jsonregisters 3 shard slots per event (2 in use). This replacedhooks/inject-rules.cjs, which emitted the kernel as sharded context on every session and every subagent spawn.rules-digest.mdis the second layer: a ~250-token restatement of the kernel, emitted on everyUserPromptSubmitbyhooks/inject-rules-digest.cjs. The kernel is ~2.5k tokens and sits in the launch context, so repeating it every prompt is unaffordable and leaving it alone lets its pull fade. The digest carries no rule text of its own; it points at the copy already in context and says the full rules govern where the two differ.concise-outputships the identical script against its own digest, which is why a session shows two[rules-reminder]blocks.rule-cards/is the third layer: guidance that only matters next to a specific tool, delivered by the mod inhooks/rule-cards.ts, which attaches the card to the tool call's result. Its triggers (tools, afireregex, askipregex) live inrule-cards/triggers.json, checked byrule-cards/triggers.test.cjs. It replaced aPreToolUsecommand hook that started a Node process per card on every matching call (0.05 to 0.06 s each, five per Bash call with thegit mvguard). The kernel lands at turn 0 and is far from the work by turn 40; a card arrives adjacent to the action instead.| Card | Fires on | Skips when | | --- | --- | --- | |
writing-code— simplicity, surgical scope, root cause, what a reviewer checks |Edit/Write/NotebookEdit, or a Bashsed -i,tee, or redirect into a file | — | |reading-libraries—opensrcover assumption | any tool call or Bash command touching anode_modules/path | the path appears only in an exclusion (-not -path,--exclude-dir,-g '!node_modules',-prune) | |git-commit— what has to be true before it lands | a Bashgit commit| — |Naming a path is not working on it. The skip column came from this plugin's own audit, where
find . -type f -not -path "./node_modules/*"fired the opensrc card. The trigger'sskipregex suppresses it; a false skip costs nothing, a false fire costs context every two minutes, so the skip pattern wins over the fire pattern.Every card has a Bash trigger as well as a tool-name one, and that is not optional. Benchmarked 2026-09-14 over five prompts in a fixture repo: across 39 tool calls the model made zero
Edit,Write,Read,Grep, andGlobcalls. Every file read, write, search, and move went through Bash (cat -n,cat >> f <<EOF,sed -i,grep -rn,find,mv). With tool-name matchers alone exactly one card fired; with the Bash patterns added, all four fired in the same run.Each card is debounced: it reappears at most once every two minutes, per card, per agent. Injected context does not leave the conversation, it only moves further back, so re-injecting on every user prompt just piles up duplicate copies. Measured over the five-prompt bench run under the original per-prompt keying: 8 injections carrying only 4 distinct bodies, 14,240 characters, half of it a repeat of text already in context. A card may declare
requires: <path>to skip itself unless that path exists under the session cwd. The debounce is kept in the mod's memory, so/reload-pluginsre-arms every card.Cost: the always-on bundle went from 30.5 KB (~7.6k tokens) to 9.9 KB (~2.5k tokens), loaded natively at launch for the main agent and each subagent. The cards total 6.9 KB, and only the one matching the tool is ever sent.
-
git mvis enforced, not suggested.hooks/mv-guard.tsis a modtool.callhook on Bash that denies a plainmvwhose source is tracked by git, and names thegit mvto run instead. Guidance alone did not hold: the rule is read at session start and forgotten at the moment it matters, and a plainmvplusgit addrecords the move as a delete and an add. It splits the command on unquoted&&,||,;,|, and newlines and checks every segment, because the whole-command version never fired once: asked to rename a tracked file, the model wrotemv src/utils.js src/helpers.js && sed -i '' ... && echo ...on the first try. Within a segment it denies only a two-argumentmv, optional short flags, source tracked in the git work tree at the session cwd. Globs, multi-source moves, untracked or ignored sources, temp paths, and non-git directories all run untouched, because a false deny costs the user a blocked command.Verified live: the model hit the deny, read the reason, and re-ran with
git mv, landing the change asR100 src/utils.js -> src/helpers.js. Worth knowing thatgit add -Aplus git's own rename detection often recovers a plainmvanyway — in the control arm it did. The guard makes the rename deterministic rather than dependent on similarity detection.
Tests
node --test claude/plugins/dev-core/hooks/*.test.cjs
Mod
hooks/screenshots.tsx draws a chrome-devtools take_screenshot result as the picture itself in the terminal transcript (PNG only, inline or saved to a file). JPEG/WebP results and other surfaces keep the default row.
Installation
Check the author's README for the marketplace and plugin name first. Commands may change as the repository evolves.
claude plugin marketplace add josippapez/ai-setup claude plugin install dev-core
Original text / README
dev-core
Everyday defaults for Claude Code: repo grounding, the always-on engineering rules, core skills, and the specialist agents. Claude-compatible mirror of the OpenCode interactive-mcp custom plugin, which keeps the older name.
-
Repo grounding comes from the separate
repo-docsplugin, not from this one.find_docs/list_docs/read_doc/find_libs/get_blast_radius/get_file_dependentslive inclaude/plugins/repo-docs, shared withorchestrateso both use one MCP server and one tool namespace (mcp__plugin_repo-docs_repo-docs__*) instead of each bundling an identical copy.claude/install.shinstallsrepo-docsautomatically alongside this plugin; if its tools are not callable, tell the user to runclaude plugin install repo-docs@ai-setup. Seeclaude/plugins/repo-docs/README.md. -
.mcp.jsonregistersinteractive(@rawwee/interactive-mcp, a question tool subagents can reach since Claude Code withholdsAskUserQuestionfrom them;--disable-tools message_complete_notification,intensive_chatleaves onlyrequest_user_input, since the main agent is told to useAskUserQuestionand nothing here uses intensive chat, so the other tools were roster weight nobody could call), plus bundled third-party MCP servers that should be available by default in every project:context7(@upstash/context7-mcp),chrome-devtools(chrome-devtools-mcp) andcomputer-use(computer-use-mcp, mouse/keyboard/screenshot control of the real desktop for anything with no API and no DOM behind it: native app UI, OS dialogs, browser chrome outside the page, and seeing what actually rendered; it also rescues achrome-devtoolscall stuck on the native remote-debugging dialog, which no timeout cuts short). Documented by thecomputer-useskill. These ship enabled with the plugin so they need no per-machine MCP config. Project- or secret-specific servers (e.g. Figma, a local Storybook endpoint) are intentionally kept out of the plugin and live at user scope in~/.claude.json. -
WCAG lookups are a CLI, not an MCP. Accessibility criteria/techniques/failures come from
@rawwee/wcag-cli, invoked over Bash and documented by the bundledaccessibilityskill. A skill + on-demand CLI costs nothing until used, whereas an always-registered MCP spawns a subprocess every session — the same pattern asopensrcandrtk. Prefer this shape for any stateless reference lookup; keep an MCP only where the server holds real state (a warm index, a browser session), asrepo-docsandchrome-devtoolsdo. -
skills/mirrors the OpenCode skills. -
Rules come in three layers: an always-on kernel, a per-prompt digest, and tool-triggered cards. Claude Code has no native plugin "rules" loader and nothing a plugin ships reaches the system prompt except an output style, so a hook publishes the kernel as user-scope rules and the other two layers arrive as hook-injected
additionalContext.rules/is the kernel:evidence-first,external-facts,proactive-execution. Only what applies to every turn regardless of which tool runs.hooks/link-rules.cjsruns onSessionStart, refreshes everyrules/*.mdin the plugin's data dir file by file through a rename (emptying the directory first made a concurrently starting session report every rule missing) (${CLAUDE_PLUGIN_DATA}/rules/) and points~/.claude/rules/dev-coreat the copy. From the next launch Claude Code loads them natively: into the main agent and every subagent (measured with a no-tools probe subagent), listed under Memory files in/context, reloaded after compaction. The data dir is the one path Claude Code deletes on uninstall, so the rules leave with the plugin; the dangling link is skipped silently (measured).Two gaps, both measured, both closed by the same script. Rules load before
SessionStarthooks run, so the session that publishes the link has no native copy and neither do its subagents (measured: a subagent spawned after the link appeared still reported nothing): in that one session the script emits the rules asadditionalContexton bothSessionStartandSubagentStart, sharded under the 10,000-character cap, keyed on afirst-sessionmarker in the data dir that holds the publishingsession_id. Andclaude plugin disableruns nothing of the disabled plugin, so the script also sweeps~/.claude/rules/for links into~/.claude/plugins/data/*/ruleswhose pluginclaude plugin list --json(0.3 s) reports disabled or no longer lists, and removes them;rules-indexcarries a byte-identical copy so the sweep still runs when dev-core itself is the one disabled. If the user has their own real directory at~/.claude/rules/dev-core, it is left alone and the rules keep arriving as context every session.hooks.jsonregisters 3 shard slots per event (2 in use). This replacedhooks/inject-rules.cjs, which emitted the kernel as sharded context on every session and every subagent spawn.rules-digest.mdis the second layer: a ~250-token restatement of the kernel, emitted on everyUserPromptSubmitbyhooks/inject-rules-digest.cjs. The kernel is ~2.5k tokens and sits in the launch context, so repeating it every prompt is unaffordable and leaving it alone lets its pull fade. The digest carries no rule text of its own; it points at the copy already in context and says the full rules govern where the two differ.concise-outputships the identical script against its own digest, which is why a session shows two[rules-reminder]blocks.rule-cards/is the third layer: guidance that only matters next to a specific tool, delivered by the mod inhooks/rule-cards.ts, which attaches the card to the tool call's result. Its triggers (tools, afireregex, askipregex) live inrule-cards/triggers.json, checked byrule-cards/triggers.test.cjs. It replaced aPreToolUsecommand hook that started a Node process per card on every matching call (0.05 to 0.06 s each, five per Bash call with thegit mvguard). The kernel lands at turn 0 and is far from the work by turn 40; a card arrives adjacent to the action instead.| Card | Fires on | Skips when | | --- | --- | --- | |
writing-code— simplicity, surgical scope, root cause, what a reviewer checks |Edit/Write/NotebookEdit, or a Bashsed -i,tee, or redirect into a file | — | |reading-libraries—opensrcover assumption | any tool call or Bash command touching anode_modules/path | the path appears only in an exclusion (-not -path,--exclude-dir,-g '!node_modules',-prune) | |git-commit— what has to be true before it lands | a Bashgit commit| — |Naming a path is not working on it. The skip column came from this plugin's own audit, where
find . -type f -not -path "./node_modules/*"fired the opensrc card. The trigger'sskipregex suppresses it; a false skip costs nothing, a false fire costs context every two minutes, so the skip pattern wins over the fire pattern.Every card has a Bash trigger as well as a tool-name one, and that is not optional. Benchmarked 2026-09-14 over five prompts in a fixture repo: across 39 tool calls the model made zero
Edit,Write,Read,Grep, andGlobcalls. Every file read, write, search, and move went through Bash (cat -n,cat >> f <<EOF,sed -i,grep -rn,find,mv). With tool-name matchers alone exactly one card fired; with the Bash patterns added, all four fired in the same run.Each card is debounced: it reappears at most once every two minutes, per card, per agent. Injected context does not leave the conversation, it only moves further back, so re-injecting on every user prompt just piles up duplicate copies. Measured over the five-prompt bench run under the original per-prompt keying: 8 injections carrying only 4 distinct bodies, 14,240 characters, half of it a repeat of text already in context. A card may declare
requires: <path>to skip itself unless that path exists under the session cwd. The debounce is kept in the mod's memory, so/reload-pluginsre-arms every card.Cost: the always-on bundle went from 30.5 KB (~7.6k tokens) to 9.9 KB (~2.5k tokens), loaded natively at launch for the main agent and each subagent. The cards total 6.9 KB, and only the one matching the tool is ever sent.
-
git mvis enforced, not suggested.hooks/mv-guard.tsis a modtool.callhook on Bash that denies a plainmvwhose source is tracked by git, and names thegit mvto run instead. Guidance alone did not hold: the rule is read at session start and forgotten at the moment it matters, and a plainmvplusgit addrecords the move as a delete and an add. It splits the command on unquoted&&,||,;,|, and newlines and checks every segment, because the whole-command version never fired once: asked to rename a tracked file, the model wrotemv src/utils.js src/helpers.js && sed -i '' ... && echo ...on the first try. Within a segment it denies only a two-argumentmv, optional short flags, source tracked in the git work tree at the session cwd. Globs, multi-source moves, untracked or ignored sources, temp paths, and non-git directories all run untouched, because a false deny costs the user a blocked command.Verified live: the model hit the deny, read the reason, and re-ran with
git mv, landing the change asR100 src/utils.js -> src/helpers.js. Worth knowing thatgit add -Aplus git's own rename detection often recovers a plainmvanyway — in the control arm it did. The guard makes the rename deterministic rather than dependent on similarity detection.
Tests
node --test claude/plugins/dev-core/hooks/*.test.cjs
Mod
hooks/screenshots.tsx draws a chrome-devtools take_screenshot result as the picture itself in the terminal transcript (PNG only, inline or saved to a file). JPEG/WebP results and other surfaces keep the default row.
