Tracks and displays the current execution phase (Spec -> Red Test -> Surgical Code -> Verification)

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 107 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { PhaseId } from '../types'
5import { formatBadge, nextPhase, parsePhaseArgs } from './phases'
6
7const isEnabledRef = atom({ plugin: 'phase-tracker', key: 'isEnabled' } as const, true)
8const currentRef = atom({ plugin: 'phase-tracker', key: 'current' } as const, 'idle' as PhaseId)
9const taskRef = atom({ plugin: 'phase-tracker', key: 'task' } as const, null as string | null)
10
11/** Store key of a project's phase: the store is global, so each folder gets its own. */
12export const phaseKey = (cwd: string) => `phase:${cwd.replace(/\\/g, '/').replace(/\/+$/, '').toLowerCase()}`
13
14// Set at session.start (which re-fires on reload); saves go to the project the phase was loaded from, even after a /cd.
15// ponytail: keyed by the start folder, a session opened in a subfolder is a separate project; walk up to the git root if that bites.
16let projectKey = phaseKey('')
17
18// $.state is per session: phase and task are mirrored to the store and restored at session.start.
19async function savePhase($: EngineInterface) {
20 await $.store.set(projectKey, { current: await read($, currentRef), task: await read($, taskRef) })
21}
22
23export const register: Register = on => {
24 on('session.start', async ($, e, next) => {
25 const saved = await $.store.get('isEnabled').catch(() => undefined)
26 await update($, isEnabledRef, () => (typeof saved === 'boolean' ? saved : isEnabledRef.initial))
27 projectKey = phaseKey(e.cwd)
28 const phase = (await $.store.get(projectKey).catch(() => undefined)) as { current?: PhaseId; task?: string | null } | undefined
29 await update($, currentRef, () => phase?.current ?? 'idle')
30 await update($, taskRef, () => phase?.task ?? null)
31 await $.command.register({
32 name: 'fase',
33 description: 'Acompanha e gerencia a fase da tarefa atual (spec, test, code, verify, done, next, reset)',
34 argumentHint: '<fase|next|reset|on|off> [descricao]',
35 })
36 return next(e)
37 })
38
39 on('command.run', { command: 'fase' }, async ($, e) => {
40 const parsed = parsePhaseArgs(e.args)
41 if (parsed.error) return { text: parsed.error }
42
43 if (parsed.action === 'on' || parsed.action === 'off') {
44 const isEnabled = parsed.action === 'on'
45 await update($, isEnabledRef, () => isEnabled)
46 await $.store.set('isEnabled', isEnabled) // $.state is per session; the store survives a restart
47 return { text: `phase-tracker ${isEnabled ? 'ativado' : 'desativado'}.` }
48 }
49
50 if (parsed.action === 'reset') {
51 await update($, currentRef, () => 'idle')
52 await update($, taskRef, () => null)
53 await savePhase($)
54 return { text: 'Fase resetada para idle.' }
55 }
56
57 if (parsed.action === 'next') {
58 const curr = await read($, currentRef)
59 const next = nextPhase(curr)
60 await update($, currentRef, () => next)
61 await savePhase($)
62 const task = await read($, taskRef)
63 return { text: `Avançado para a fase: ${formatBadge(next, task)}` }
64 }
65
66 if (parsed.phase) {
67 await update($, currentRef, () => parsed.phase!)
68 await update($, taskRef, () => parsed.task ?? null)
69 await savePhase($)
70 return { text: `Fase definida: ${formatBadge(parsed.phase, parsed.task ?? null)}` }
71 }
72
73 const curr = await read($, currentRef)
74 const task = await read($, taskRef)
75 return { text: `Fase atual: ${formatBadge(curr, task) || 'idle'}` }
76 })
77
78 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
79 if (e.props.hasSurvey) return next(e)
80 const isEnabled = await read($, isEnabledRef)
81 if (!isEnabled) return next(e)
82
83 const current = await read($, currentRef)
84 const task = await read($, taskRef)
85 const badge = formatBadge(current, task)
86 if (!badge) return next(e)
87
88 const { Box, Text } = $.ui.resolve(e)
89 const below = await next(e)
90
91 const mine = (
92 <Box width={e.props.bodyColumns} flexDirection="row">
93 <Text color="#4aa3df" bold>
94 {badge}
95 </Text>
96 </Box>
97 )
98
99 return below.type === 'engine' ? mine : (
100 <Box flexDirection="column">
101 {below}
102 {mine}
103 </Box>
104 )
105 })
106}
107hooks/phases.ts 59 lines1import type { PhaseId } from '../types'
2
3const ORDER: PhaseId[] = ['idle', 'spec', 'test', 'code', 'verify', 'done']
4
5export function nextPhase(current: PhaseId): PhaseId {
6 const idx = ORDER.indexOf(current)
7 if (idx === -1 || idx === ORDER.length - 1) return 'idle'
8 return ORDER[idx + 1]!
9}
10
11export type ParsedPhase = {
12 phase?: PhaseId
13 task?: string
14 action?: 'next' | 'reset' | 'off' | 'on'
15 error?: string
16}
17
18const PHASES: Record<string, PhaseId> = {
19 spec: 'spec',
20 test: 'test',
21 code: 'code',
22 verify: 'verify',
23 done: 'done',
24}
25
26export function parsePhaseArgs(input: string): ParsedPhase {
27 const trimmed = input.trim()
28 if (!trimmed) return { error: 'Fase ou ação desconhecida: comando vazio' }
29
30 const parts = trimmed.split(/\s+/)
31 const head = parts[0]!.toLowerCase()
32 const task = parts.slice(1).join(' ').trim() || undefined
33
34 if (head === 'next' || head === 'reset' || head === 'off' || head === 'on') {
35 return { action: head }
36 }
37
38 if (head in PHASES) {
39 return { phase: PHASES[head], task }
40 }
41
42 return { error: `Fase ou ação desconhecida: ${head}` }
43}
44
45const LABELS: Record<PhaseId, string> = {
46 idle: '',
47 spec: '[1/4 Spec]',
48 test: '[2/4 Test]',
49 code: '[3/4 Code]',
50 verify: '[4/4 Verify]',
51 done: '[✓ Concluído]',
52}
53
54export function formatBadge(phase: PhaseId, task: string | null): string {
55 if (phase === 'idle') return ''
56 const prefix = LABELS[phase]
57 return task ? `${prefix} ${task}` : prefix
58}
59types/index.d.ts 13 lines1export type PhaseId = 'idle' | 'spec' | 'test' | 'code' | 'verify' | 'done'
2
3// The validator wants each key named here, not behind an alias.
4declare module 'claude-code' {
5 interface PluginState {
6 'phase-tracker': {
7 isEnabled: boolean
8 current: PhaseId
9 task: string | null
10 }
11 }
12}
13