Offer to save a correction as a CLAUDE.md rule

Function-hook mods for Claude Code: shell safety, secret guarding, session telemetry, context panels, and workflow tools. Each mod is an isolated plugin with its own tests, and you enable only the ones you want.
| Mod | Description |
|---|---|
guarda | Intercepts risky Bash commands (rm -rf, git reset --hard, force push, new dependencies) and asks for confirmation explaining the impact. |
lixeira | Sends rm / Remove-Item targets to the Windows Recycle Bin instead of deleting them permanently. |
cofre | Keeps API keys out of the conversation: /chave stores them in .env, secret file reads display only variable names, outputs are masked, and pasted keys are quarantined. |
atomic-commit-guard | Blocks commits that mix top-level directories, violate Conventional Commits, or lack a Co-Authored-By trailer. |
universal-audit-log | A hook on * that logs every event (tool calls, prompts, turns, fs/http/process calls from other plugins) with secret redaction. |
| Mod | Description |
|---|---|
context-monitor | Context weather and token usage above the prompt, replay and activity panels, plain-language explanation before each edit, and ai-memory notes. |
cache-frio | Confirms before submitting a prompt to a large conversation whose prompt cache has gone cold. |
session-telemetry | Timestamp on each response, TTFT and tok/s per step, and visual notice whenever the responding model switches. |
session-time-machine | /timemachine: renders the session as a timeline of prompts, tool calls, and turn completions. |
turn-inspector | Side pane displaying the session turn-by-turn: every model request and what it carried. |
clean-view | Collapses each successful tool call into a single concise line (Edit file +12 −3); errors and in-flight calls retain the full block. |
webfetch-cache | Serves repeated WebFetch requests (identical URL and prompt) directly from the session cache. |
reflexo | Detects corrections and constraints in prompts, offering to save them as rules in CLAUDE.md. |
| Mod | Description |
|---|---|
agent-flow | Live diagram of agent flow: main loop context, subagent tree, and context passed/returned by each agent. |
agent-radar | One live line per running subagent above the prompt; /radar opens a pane with messages from each. |
delegation-dock | Status line indicator showing in-flight delegations (agy/codex/ollama) and alerting when one returns empty. |
phase-tracker | Tracks and displays the current development phase (Spec → Red Test → Surgical Code → Verification). |
next-prompts | Band with 3 next-step suggestions generated by a free Ollama cloud model; click one to draft. |
git-sidebar | Lazygit-style side pane: worktrees, local branches, branch switching, and worktree checkout. |
wait-quiz | Quizzes a flashcard in a band above the prompt while Claude is working. |
meus-mods | Central hub: quick-access bar above the prompt and /mods command displaying the status of each mod. |
Requires Claude Code with function-hook mods support.
git clone https://github.com/Covre81/claude-code-mods.git ~/.claude/mods
Enable the mods by listing their absolute paths in CLAUDE_CODE_PLUGIN_DIRS inside ~/.claude/settings.json. On Windows, paths are separated by ;:
{
"env": {
"CLAUDE_CODE_PLUGIN_DIRS": "C:/Users/me/.claude/mods/guarda;C:/Users/me/.claude/mods/cofre;C:/Users/me/.claude/mods/session-telemetry"
}
}
Order matters: the last plugin in the list sits closest to the engine. For instance, lixeira should come after guarda so guarda inspects the original rm -rf first.
Several mods include a command to toggle them on or off without restarting (/webfetch-cache on|off, /radar-toggle, /telemetria…); /mods displays the status of all mods.
Each mod follows the same structure:
<mod>/
.claude-plugin/plugin.json manifest
.claude-plugin/types/ engine types (git-ignored, see below)
hooks/hooks.json hooks registered by the mod
hooks/register.ts(x) code
hooks/*.test.ts(x) tests
Engine types are not committed to the repository. To typecheck and test a mod, copy .claude-plugin/types from an existing mod or generate them with your Claude Code instance.
Tests run directly within Claude Code, not via vitest/jest:
claude plugin test guarda # single mod
for d in */hooks/hooks.json; do claude plugin test "${d%%/*}"; done # all mods
Commits follow Conventional Commits, scoped to one mod per commit (enforced by atomic-commit-guard).
Six mods (agent-flow, git-sidebar, session-time-machine, turn-inspector, universal-audit-log, webfetch-cache) originate from davila7/claude-code-templates, and agent-radar originates from hamzafer/claude-code-mods, both under the MIT License and adapted here. Copyright notices are preserved in THIRD_PARTY_NOTICES.md.
hooks/register.tsx 84 lines1import { atom, read, update } from 'claude-code'
2import type { Register, EngineInterface } from 'claude-code'
3import { looksLikeRule } from './rule'
4
5const isEnabledRef = atom({ plugin: 'reflexo', key: 'isEnabled' } as const, true)
6
7const PROJECT = 'Projeto (CLAUDE.md)'
8const GLOBAL = 'Global (~/.claude/CLAUDE.md)'
9const IGNORE = 'Ignorar'
10const HEADING = '## Regras capturadas'
11// Only what a person typed is a correction; task notifications and other agents' prompts are not.
12const HUMAN = new Set(['composer', 'bridge'])
13
14export const register: Register = on => {
15 // The rule waits from prompt.submit to the end of that turn; one session runs one turn at a time, so one slot is enough.
16 let queuedRule: string | null = null
17
18 on('session.start', async ($, e, next) => {
19 const saved = await $.store.get('isEnabled').catch(() => undefined)
20 await update($, isEnabledRef, () => (typeof saved === 'boolean' ? saved : isEnabledRef.initial))
21
22 await $.command.register({
23 name: 'reflexo',
24 description: 'Liga/desliga o reflexo (salvar correções como regras).',
25 argumentHint: 'on|off',
26 })
27 return next(e)
28 })
29
30 on('command.run', { command: 'reflexo' }, async ($, e) => {
31 const arg = e.args.trim().toLowerCase()
32 if (arg !== 'on' && arg !== 'off') {
33 const isEnabled = await read($, isEnabledRef)
34 return { text: `reflexo esta ${isEnabled ? 'ativado' : 'desativado'}. Uso: /reflexo on · /reflexo off` }
35 }
36 const isEnabled = arg === 'on'
37 await update($, isEnabledRef, () => isEnabled)
38 await $.store.set('isEnabled', isEnabled)
39 return { text: `reflexo ${isEnabled ? 'ativado' : 'desativado'}.` }
40 })
41
42 on('prompt.submit', async ($, e, next) => {
43 if (HUMAN.has(e.origin.kind) && (await read($, isEnabledRef)) && looksLikeRule(e.text)) queuedRule = e.text
44 return next(e) // never blocks or drops the prompt
45 }).catch(($, e, next) => next(e))
46
47 on('turn.complete', async ($, e, next) => {
48 if (e.agentId) return next(e)
49 const rule = queuedRule
50 queuedRule = null
51 if (rule === null) return next(e)
52 const answer = await $.ui.ask(`Salvar como regra: "${rule}"?`, { header: 'Reflexo', options: [PROJECT, GLOBAL, IGNORE] })
53 if (answer === PROJECT) await appendRule($, 'CLAUDE.md', rule) // relative = session cwd
54 else if (answer === GLOBAL) {
55 const home = (await $.env.get('USERPROFILE')) ?? (await $.env.get('HOME'))
56 if (home) await appendRule($, `${home}/.claude/CLAUDE.md`, rule)
57 }
58 return next(e) // IGNORE or free text: nothing written
59 }).catch(($, e, next) => next(e)) // fail open: a dismissed ask (rejects) or a write error never blocks the turn
60}
61
62/** Appends one line under the heading; never rewrites what is already in the file. */
63async function appendRule($: EngineInterface, path: string, rule: string): Promise<void> {
64 // Only a missing file counts as empty; any other read error rethrows so nothing is written (fail open upstream).
65 const current = await $.fs.read(path).then(r => (typeof r === 'string' ? r : ''), (err: unknown) => {
66 const e = err as { code?: string; message?: string }
67 if (e?.code === 'ENOENT' || /ENOENT/.test(e?.message ?? '')) return ''
68 throw err
69 })
70 const nl = current.includes('\r\n') ? '\r\n' : '\n'
71 const lines = current.split('\n')
72 const at = lines.findIndex(l => l.trim() === HEADING)
73 let end = at === -1 ? -1 : lines.findIndex((l, i) => i > at && /^#{1,6}\s/.test(l))
74 if (end !== -1) {
75 // Another section follows: insert after the section's last non-blank line, keep every other line as is.
76 while (end > at + 1 && lines[end - 1]!.trim() === '') end--
77 lines.splice(end, 0, `- ${rule}${nl === '\r\n' ? '\r' : ''}`)
78 return $.fs.write(path, lines.join('\n'))
79 }
80 const eol = current === '' || current.endsWith('\n') ? '' : nl
81 const head = at !== -1 ? '' : `${current === '' ? '' : nl}${HEADING}${nl}`
82 await $.fs.write(path, `${current}${eol}${head}- ${rule}${nl}`)
83}
84hooks/rule.ts 9 lines1export function looksLikeRule(text: string): boolean {
2 // One line only: a newline would land in CLAUDE.md as a line of its own, maybe a fake "## " heading.
3 if (!text || text.length > 200 || /[\r\n]/.test(text)) return false
4 // "não sei", "não entendi", "não funcionou" and similar are answers, not rules
5 if (/^(n[ãa]o (sei|entendi|funcion|consegui|deu|rodou)|i don't know)/i.test(text)) return false
6 // 'no,' needs the comma: bare 'no' is Portuguese "in the" ("No arquivo X...")
7 return /^(?:(?:não|nao|nunca|sempre|pare de|prefiro|don't|always|never|stop)\b|no,)/i.test(text)
8}
9types/index.d.ts 8 lines1declare module 'claude-code' {
2 interface PluginState {
3 reflexo: {
4 isEnabled: boolean
5 }
6 }
7}
8