SLOPSHOPPER

evidence-band

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

newbandguardtoastprompt
v0.1.0no licenseupdated 2026-10-10ianwilson97/claude-mods/evidence-band
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · evidence-band
› fix the failing auth test and add an audit log call ╭────────────────────────────────────────────╮ │ evidence-band │ ⏺ Read(src/auth.ts) │ Audit answer without reading docs. Ask for │ ⎿ Read 6 lines │ evidence. │ ⏺ Update(src/auth.ts) ╰────────────────────────────────────────────╯ ⎿ Added 2 lines, removed 1 line ⏺ Bash(bun test) ⎿ 3 pass, 1 fail ● Done. refresh now rejects expired claims and logs an audit event. ✻ Worked for 42s · done 4:20 PM evidence · code ✓1 · docs ✗ ⟨Claude Code's own drawing⟩ ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
evidence · code ✓1 · docs ✗ ⟨Claude Code's own drawing⟩
README

ianwilson-mods

Claude Code mods (function-hook plugins). Each folder is one plugin; .claude-plugin/marketplace.json makes this repo a marketplace.

ModWhat 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-barContext-window breakdown above the prompt.
usage-meter5-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-bandFor the driver-seat workflow; skip them elsewhere.

Install

/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.

cheap-worker: two switches

Set per machine in /config (or pluginConfigs.cheap-worker.options in ~/.claude/settings.json):

OptionValues
provideropenrouter (default)Any OpenRouter model; key read from $CW_OPENROUTER_API_KEY (export it in ~/.zshenv). Workers skip user settings, hooks and plugins.
bedrockClaude 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.
launchpanes (default)All workers tiled in tmux session cw: tmux new -A -s cw.
tabsEach 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.

Reaching a worker: crew

The 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+.

First run on a new machine: checks

  1. /plugin lists the mods as enabled. Managed settings can block marketplaces or plugins; if so, nothing loads.
  2. /worker, launch one with task say hi; a Ghostty tab opens (or the toast says CMD+T, then crew …).
  3. Turn on team, launch two workers, then from /workers send one a message: messaging is Claude Code's own (ListAgents / SendMessage) and works under Bedrock unless policy turns it off.
Source 2 files
hooks/register.tsx 88 lines
1import { 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}
88
types/index.d.ts 8 lines
1export type Evidence = { isAudit: boolean; code: string[]; docs: string[] }
2
3declare module 'claude-code' {
4  interface PluginState {
5    'evidence-band': { evidence: Evidence }
6  }
7}
8