Claude's chat replies to you in plain, CEFR-level English: no code unless asked, short task reports. Files, reviews and agent hand-offs keep full technical…

Claude Code mods for everyday workflows, by Efi Perez (@EfiPerez).
A mod is a Claude Code plugin with a hooks module: code that runs inside Claude Code on events such as a prompt being sent, a tool being called or a model request going out.
Add the marketplace once:
/plugin marketplace add EfiPerez/claude-code-mods
Then install only the mods you want:
| Mod | What it does | Install command |
|---|---|---|
effort-picker | Sets each request's effort level from the task, not the prompt: Claude picks a level once it understands the work, and helpers get a level per job. | /plugin install effort-picker@efi-mods |
plain-language | Claude's chat replies to you in plain, CEFR-level English (A2 or B1): no code unless asked, short task reports. Files, reviews and agent hand-offs keep full technical detail. | /plugin install plain-language@efi-mods |
Alternatively download the repo and load a mod locally using claude --plugin-dir <downloaded-repo>/<mod> on a compatible Claude Code runtime. This uses the plugin loader, not ~/.claude/skills/.
Third-party marketplace auto-update is off by default. Run /plugin marketplace update or enable auto-update for this marketplace.
baseline, default high).mcp__effort-picker__set_effort {level, category} in the same message as its next tool calls. The mod then rewrites the effort of every later request in that loop. Categories (chat, lookup, trivial edit, contained change, multi-file change, unknown root cause, review/plan, strict rules, long agentic work, architecture/security/data, prior attempt failed) each carry a default level.xhigh and max need your approval. The mod asks with a question card; a "no", a dismissed card or a headless run (claude -p) falls back to high. max is only accepted with the category prior_attempt_failed.effort per job. Start an Agent description with [effort-locked] to lock the child at that level./effort, ultrathink, a skill whose frontmatter declares effort:. Setting CLAUDE_CODE_EFFORT_LEVEL turns the mod off for the whole session.staleDays (default 90, 0 turns it off), or when a session runs on a Claude model the rules were not checked against. Either means: check for a plugin update.~/.claude/effort-picker/logs/<YYYY-MM-DD>_<session>.jsonl holding only levels, categories, ids and counts (never prompt or file text), and one $.store value (the day the stale notice was last shown).hooks/rubric.ts (checkedAt).B1 by default, A2 via the cefrLevel setting. Files Claude writes, prompts for subagents or other AI tools, and subagent results keep full technical detail.prompt.compose), and the same rules attached once as hidden context to the first accepted prompt of each conversation (again after /clear). On compaction the summarizer is asked to keep the rules.claude -p, the SDK); the system-prompt section is also skipped for --bare and teammates.cc-plugin-sec-default skips the user-mod events classic.*, prompt.section, prompt.context, prompt.compose, skill.prompt, attribution.text and settings.read. There is no user-side switch; an administrator must manage the plugin to lift it.effort-picker uses none of those events.plain-language loses its system-prompt section there and falls back to the first-prompt context path described above. The rules then reach the model as part of the first message's context rather than the system prompt.effort-picker's approval) and toasts show in the panel; do not expect terminal/Desktop UI parity..claude-plugin/types/ declarations are regenerated at load time.MIT — see LICENSE.
Built by Efi Perez.
hooks/register.ts 73 lines1// plain-language: Claude's chat replies to you are plain English at a CEFR
2// level, with no code unless asked. It lives in a mod and not in CLAUDE.md on
3// purpose: other AI tools that read CLAUDE.md (code reviewers, coding agents)
4// would pick up these rules too and give vague answers with no file:line
5// evidence. A mod's prompt text reaches Claude Code only.
6
7import type { EngineInterface, Register } from 'claude-code'
8
9export const SECTION_ID = 'plain-language:style'
10
11export function styleText(level: string): string {
12 return [
13 '# How to write to the user in chat',
14 '',
15 'These rules apply only to the messages you write to the user in this chat, and they override any',
16 'instruction that says to assume a technical reader. They do NOT apply to: files you write',
17 '(code, docs, CLAUDE.md, handoffs, review reports), prompts you write for subagents or other AI tools,',
18 'or what you return when you are a subagent. Those keep full technical detail and file:line',
19 'evidence.',
20 '',
21 `- Write in CEFR ${level} English: short sentences, common words, one idea per sentence. The user is`,
22 ' smart and busy, not a beginner; the goal is easy to follow, not talking down.',
23 '- Explain everything in plain English for a non-programmer. If a technical term is',
24 ' unavoidable, define it in a few words the first time it appears.',
25 "- Don't paste code into the chat unless the user asks; say what it does and where it lives (a file",
26 ' link is fine).',
27 '- After finishing a task, report: what changed, where, and how the user can test it. 5 lines max.',
28 '- Keep answering BLUF: the conclusion first, in one or two sentences.',
29 '- When the user asks for code, a diff, exact commands, or "the technical version", give it in full.',
30 ].join('\n')
31}
32
33// Headless runs (claude -p, the SDK) have no person reading the chat. VS Code reports
34// "not interactive", so the entrypoint is the test: headless ones are sdk-*.
35async function headless($: EngineInterface): Promise<boolean> {
36 return ((await $.env.get('CLAUDE_CODE_ENTRYPOINT')) ?? '').startsWith('sdk')
37}
38
39export const register: Register = (on, options) => {
40 const level = options?.cefrLevel === 'A2' ? 'A2' : 'B1'
41
42 on('prompt.compose', async ($, e, next) => {
43 const result = await next(e)
44 // --bare keeps its one section; a teammate writes to its lead, not to the user; print is -p / SDK.
45 if (e.traits.includes('bare') || e.traits.includes('teammate') || e.traits.includes('print')) return result
46 return { sections: [...result.sections, { id: SECTION_ID, text: styleText(level), scope: 'session' }] }
47 })
48
49 // prompt.compose above is skipped on managed / Team / Enterprise machines (cc-plugin-sec-default),
50 // so the rules also ride on the first prompt of each conversation as hidden context: the model
51 // reads them, the chat shows nothing and no extra turn is spent. Keyed on the session id because
52 // /clear starts a new conversation in the same process with no session.start; recorded only once
53 // the prompt actually entered, so a prompt another hook dropped does not use up the delivery.
54 let attachedFor = ''
55 on('prompt.submit', async ($, e, next) => {
56 const sid = await $.session.id()
57 if (attachedFor === sid || (await headless($))) return next(e)
58 const r = await next({ ...e, context: [...(e.context ?? []), styleText(level)] })
59 if (r.drop === undefined) attachedFor = sid
60 return r
61 })
62
63 // A compaction summary would drop that message; ask the summarizer to keep the rules.
64 on('session.compact', async ($, e, next) => {
65 if (e.agentId || (await headless($))) return next(e)
66 return next({ ...e, instructions: [e.instructions, keepText(level)].filter(Boolean).join('\n\n') })
67 })
68}
69
70export function keepText(level: string): string {
71 return `Copy this block word for word at the end of the summary; it is the user's standing chat style:\n\n${styleText(level)}`
72}
73