On status/audit prompts: require doc + file:line citations and show which sources Claude actually read

Claude Code mods (function-hook plugins). Each folder is one plugin; .claude-plugin/marketplace.json makes this repo a marketplace.
| Mod | What it does |
|---|---|
cheap-worker | /worker spins up Claude Code workers from a picker; /workers lists, messages and closes them. Team mode lets workers find each other (ListAgents) and talk (SendMessage). |
agent-progress | /progress: animated progress bar per agent (main, subagents, workers) from their task lists. |
agent-map | /map: project file tree; red 🔨 = an agent is editing it, green ✓ = done, ready for review. |
context-bar | Context-window breakdown above the prompt. |
usage-meter | 5-hour / weekly usage in the footer. |
quick-settings | ⚙ footer button for model and effort. |
skins | /skins: colour themes (Tokyo Night, Dracula, Gruvbox, …). |
cw-pane, driver-seat-guard, evidence-band | For the driver-seat workflow; skip them elsewhere. |
/plugin marketplace add ianwilson97/claude-mods
/plugin install cheap-worker@ianwilson-mods
/plugin install agent-progress@ianwilson-mods
/plugin install agent-map@ianwilson-mods
On the machine where this folder is the live copy (loaded through CLAUDE_CODE_PLUGIN_DIRS), don't also install from the marketplace: each mod would load twice.
Set per machine in /config (or pluginConfigs.cheap-worker.options in ~/.claude/settings.json):
| Option | Values | |
|---|---|---|
provider | openrouter (default) | Any OpenRouter model; key read from $CW_OPENROUTER_API_KEY (export it in ~/.zshenv). Workers skip user settings, hooks and plugins. |
bedrock | Claude only (Sonnet / Opus / Haiku). Workers keep your user settings, so the Bedrock env there (CLAUDE_CODE_USE_BEDROCK, AWS_REGION, AWS_PROFILE, …) applies. No call to openrouter.ai. | |
launch | panes (default) | All workers tiled in tmux session cw: tmux new -A -s cw. |
tabs | Each worker gets a tmux session of its own and opens in a background tab: a cmux tab when Claude Code runs inside cmux, else a Ghostty tab (focus stays where you were). |
Work laptop: provider = bedrock, launch = tabs.
crewThe mod installs crew into ~/.local/bin (that folder must be on your PATH; it never overwrites a different crew).
crew # list running workers, pick one by number
crew ab12c # attach to cw-ab12c
If Ghostty can't be scripted (macOS asks once to let Claude control Ghostty; a managed Mac may refuse), the worker still runs: press CMD+T and run crew. Closing a tab only detaches; the worker keeps running until you close it from /workers.
Needs: tmux; for auto-opened tabs, cmux or Ghostty 1.3+.
/plugin lists the mods as enabled. Managed settings can block marketplaces or plugins; if so, nothing loads./worker, launch one with task say hi; a Ghostty tab opens (or the toast says CMD+T, then crew …)./workers send one a message: messaging is Claude Code's own (ListAgents / SendMessage) and works under Bedrock unless policy turns it off.hooks/register.tsx 88 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { Evidence } from '../types'
5
6const evidence = atom({ plugin: 'evidence-band', key: 'evidence' } as const, {
7 isAudit: false,
8 code: [],
9 docs: [],
10} as Evidence)
11
12const AUDIT = /\b(status|audit|sprint|smells?|review|tracker|is .* (done|complete)|what'?s left)\b/i
13// ponytail: path heuristic; extend when a project keeps requirements elsewhere
14const DOC = /(^|\/)(docs?|adr|specs?|\.planning|\.driver-seat)\/|(^|\/)(README|CHANGELOG|ROADMAP|TODO|CLAUDE)[^/]*$|tracker|\.mdx?$/i
15const READ_CMD = /^\s*(cat|head|tail|sed|grep|rg|less|bat|wc)\b/
16const PATHISH = /^[\w.~\/-]+\.[\w]+$|\//
17
18const CITE = `# Evidence rule (enforced by a plugin)
19This prompt asks for status, an audit or a review. Before answering:
20- Read the project's docs (README, docs/, specs, tracker) AND the code they describe.
21- Every finding or status claim cites both: the doc section and the file:line.
22- Drop any finding a doc explicitly requires. Say plainly what you did not verify.`
23
24export function sort(paths: readonly string[], e: Evidence): Evidence {
25 const add = (list: string[], p: string) => (list.includes(p) ? list : [...list, p])
26 return paths.reduce(
27 (acc, p) => (DOC.test(p) ? { ...acc, docs: add(acc.docs, p) } : { ...acc, code: add(acc.code, p) }),
28 e,
29 )
30}
31
32export function bashPaths(command: string): string[] {
33 if (!READ_CMD.test(command)) return []
34 return command
35 .split(/[\s|;&]+/)
36 .slice(1)
37 .filter(t => !t.startsWith('-') && PATHISH.test(t))
38}
39
40export const register: Register = on => {
41 on('prompt.submit', async ($, e, next) => {
42 await update($, evidence, () => ({ isAudit: AUDIT.test(e.text), code: [], docs: [] }))
43 return next(e)
44 })
45
46 on('prompt.compose', async ($, e, next) => {
47 const composed = await next(e)
48 if (!(await read($, evidence)).isAudit) return composed
49 return {
50 sections: [...composed.sections, { id: 'evidence-band:cite', text: CITE, scope: 'session' as const }],
51 }
52 })
53
54 on('tool.call', { tool: 'Read' }, async ($, e, next) => {
55 await update($, evidence, ev => sort([e.file_path], ev))
56 return next(e)
57 })
58
59 on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
60 const paths = bashPaths(e.command)
61 if (paths.length > 0) await update($, evidence, ev => sort(paths, ev))
62 return next(e)
63 })
64
65 on('turn.complete', async ($, e, next) => {
66 const ev = await read($, evidence)
67 if (ev.isAudit && (ev.docs.length === 0 || ev.code.length === 0)) {
68 $.ui.toast(`Audit answer without reading ${ev.docs.length === 0 ? 'docs' : 'code'}. Ask for evidence.`)
69 }
70 return next(e)
71 })
72
73 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
74 const ev = await read($, evidence)
75 if (!ev.isAudit || e.props.hasSurvey) return next(e)
76 const { Box, Text } = $.ui.resolve(e)
77 const mark = (n: number) => (n > 0 ? `✓${n}` : '✗')
78 return (
79 <Box flexDirection="column">
80 <Text dimColor>
81 evidence · code {mark(ev.code.length)} · docs {mark(ev.docs.length)}
82 </Text>
83 {await next(e)}
84 </Box>
85 )
86 })
87}
88types/index.d.ts 8 lines1export type Evidence = { isAudit: boolean; code: string[]; docs: string[] }
2
3declare module 'claude-code' {
4 interface PluginState {
5 'evidence-band': { evidence: Evidence }
6 }
7}
8