SLOPSHOPPER

zetetic-genius

Grades every request before its turn starts (task class → effort rung, in one low-effort completion) and routes it to the genius reasoning patterns and…

newbandpromptmodel
v0.1.0no licenseupdated 2026-10-07cdeust/claude-mods/mods/zetetic-genius
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · zetetic-genius
› fix the failing auth test and add an audit log call ⏺ Read(src/auth.ts) ⎿ Read 6 lines ⏺ 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 genius observe · no request graded · classifier answered off-contract: OK ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
genius observe · no request graded · classifier answered off-contract: OK
README

claude-mods

Claude Code mods for the ai-architect.tools harness, one concern per mod, state shared through dependencies:

ModOwnsDepends on
cortex-guardrefusals: wiki pages only through wiki_write, worktrees inside <repo>/.claude/worktrees/nothing
cortex-wiki/wiki <wiki/**.md> to PDFnothing
zetetic-geniusstate: the request grade (task class → effort), the genius patterns and skills it matchesnothing
zetetic-autopilotcontext, policy: effort ladder, model routing, pressure, lean resultszetetic-genius
cortex-cockpitstats, ledger, tally, stages, hygiene; the /cortex pane draws the restthe three above
harness-fleetfleet: the owner's plugins, installed vs offered version, open PRs with CI, open issues (a defect is taken and fixed, a feature request goes to the owner); /fleet. Reads only (gh pr list, gh issue list, git remote): every outward action is a button the owner presses, which puts a prompt in front of the modelguard, genius, autopilot

Developing

Each mod is validated, tested and type-checked on its own:

cd mods/<mod> && claude plugin validate . && claude plugin test && npx -y -p typescript tsc -p .

tsc needs the engine to have loaded the mod once (it lays .claude-plugin/types/). For hot reload in a session, link the mod into that session's ~/.claude/dev-mods/<session>/ folder; the engine watches the folder a link names.

Installing

.claude-plugin/marketplace.json lists each mod as "source": "./mods/<mod>", so the repository is the marketplace. From another machine:

/plugin install <mod> --marketplace cdeust/claude-mods

y adds the marketplace, then the user scope. On this machine a folder marketplace reads the mods from the checkout, with no copy: claude plugin marketplace add <this folder>, then /plugin install <mod> and /reload-plugins after an edit.

Source 4 files
hooks/register.tsx 216 lines
1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { Classified, GeniusDecision, GeniusState } from '../types'
5import {
6  SHAPES_PATH,
7  SHAPE_SKILL_PREFIX,
8  type Shape,
9  classifierPrompt,
10  classifierSystem,
11  isClassifiable,
12  parseClassification,
13  parseShapes,
14  sameRequest,
15  shapeContext,
16  toClassified,
17} from './classify'
18import {
19  GENIUS_DIR,
20  GENIUS_INDEX,
21  GENIUS_PLUGIN,
22  INSTALLED_PLUGINS_PATH,
23  type GeniusRow,
24  geniusContext,
25  installPathOf,
26  parseGeniusIndex,
27} from './genius'
28
29// source: platform.claude.com/docs/en/models/haiku-5-5/whats-new-haiku-5-5: adaptive thinking is on by
30// default and its tokens count toward max_tokens, so a small cap can end after the thinking block;
31// the JSON answer itself is under 60 tokens, unused room costs nothing.
32const CLASSIFIER_MAX_TOKENS = 1024
33const CLASSIFIER_TIMEOUT_MS = 4000 // source: own choice, a prompt must not wait longer on its classifier
34const DECISIONS_CAP = 50 // source: bounds $.state size; a viewer shows the last rows only
35
36const state = atom({ plugin: 'zetetic-genius', key: 'state' } as const, {
37  mode: 'observe',
38  classifierModel: 'haiku',
39  classified: null,
40  classifierError: null,
41  skillShapesLoaded: 0,
42  geniusShapesLoaded: 0,
43  geniusDir: null,
44  decisions: [],
45} as GeniusState)
46
47// The two option lists, read at start; module state a hot reload rebuilds through session.start.
48let skills: Shape[] = []
49let genius: GeniusRow[] = []
50let geniusDir: string | null = null
51
52const expandHome = async ($: EngineInterface, path: string): Promise<string> =>
53  path.startsWith('~/') ? `${(await $.env.get('HOME')) ?? ''}/${path.slice(2)}` : path
54
55const fail = async ($: EngineInterface, what: string, error: unknown): Promise<void> => {
56  await update($, state, (s) => ({ ...s, classifierError: `${what}: ${String(error).slice(0, 100)}` }))
57}
58
59// The skill shapes from the generated routing table; absent, the classifier grades effort alone.
60async function loadSkills($: EngineInterface): Promise<void> {
61  try {
62    skills = parseShapes(await $.fs.read(await expandHome($, SHAPES_PATH)))
63    await update($, state, (s) => ({ ...s, skillShapesLoaded: skills.length }))
64  } catch (error) {
65    skills = []
66    await fail($, SHAPES_PATH, error)
67  }
68}
69
70// The genius shapes from the installed plugin's INDEX.md, found through installed_plugins.json.
71async function loadGenius($: EngineInterface): Promise<void> {
72  try {
73    const installed = await $.fs.read(await expandHome($, INSTALLED_PLUGINS_PATH))
74    const root = installPathOf(installed, GENIUS_PLUGIN)
75    if (root === undefined) throw new Error(`${GENIUS_PLUGIN} is not installed`)
76    geniusDir = `${root}/${GENIUS_DIR}`
77    genius = parseGeniusIndex(await $.fs.read(`${geniusDir}/${GENIUS_INDEX}`))
78    await update($, state, (s) => ({ ...s, geniusDir, geniusShapesLoaded: genius.length }))
79  } catch (error) {
80    genius = []
81    geniusDir = null
82    await fail($, 'genius index', error)
83  }
84}
85
86// One low-effort completion grades the request; anything but a strict answer is no decision.
87async function classify($: EngineInterface, text: string): Promise<Classified | undefined> {
88  const s = await read($, state)
89  const started = await $.clock.now()
90  const r = await $.model.complete({
91    model: s.classifierModel,
92    effort: 'low',
93    system: [{ text: classifierSystem(skills, genius), cache: true }],
94    prompt: classifierPrompt(text),
95    maxTokens: CLASSIFIER_MAX_TOKENS,
96    timeoutMs: CLASSIFIER_TIMEOUT_MS,
97  })
98  const now = await $.clock.now()
99  if (!r.isAnswered) {
100    await fail($, 'classifier', r.reason)
101    return undefined
102  }
103  const c = parseClassification(r.text, skills, genius)
104  if (c === undefined) {
105    await fail($, 'classifier answered off-contract', r.text.slice(0, 80))
106    return undefined
107  }
108  const classified = toClassified(c, text, started, now)
109  await update($, state, (st) => ({ ...st, classified, classifierError: null }))
110  return classified
111}
112
113// The context blocks a graded prompt carries: each genius pattern's procedure, then the skills.
114async function contextFor($: EngineInterface, c: Classified): Promise<string[]> {
115  const blocks: string[] = []
116  for (const pick of c.geniuses) {
117    if (geniusDir === null) break
118    const path = `${geniusDir}/${pick.agent}.md`
119    try {
120      blocks.push(geniusContext(pick.agent, await $.fs.read(path), genius, path))
121    } catch (error) {
122      await fail($, path, error)
123    }
124  }
125  if (c.shapes.length > 0) blocks.push(shapeContext(c.shapes))
126  return blocks
127}
128
129const decide = (s: GeniusState, d: GeniusDecision): GeniusState => ({
130  ...s,
131  decisions: [...s.decisions, d].slice(-DECISIONS_CAP),
132})
133
134export const register: Register = (on, options) => {
135  const mode: GeniusState['mode'] = options.mode === 'enforce' ? 'enforce' : 'observe'
136  const classifierModel = String(options.classifier_model ?? 'haiku')
137  // A hot reload keeps $.state from the previous load: the configured fields are set afresh.
138  const configured = (s: GeniusState): GeniusState => ({ ...s, mode, classifierModel })
139
140  on('session.start', async ($, e, next) => {
141    await update($, state, configured)
142    await loadSkills($)
143    await loadGenius($)
144
145    return next(e)
146  })
147
148  // /clear, /resume and /branch reset $.state and never fire session.start again.
149  on('classic.SessionStart', { source: ['clear', 'resume', 'fork'] }, async ($, e, next) => {
150    await update($, state, configured)
151    await loadSkills($)
152    await loadGenius($)
153
154    return next(e)
155  })
156
157  // Observed, the prompt goes through untouched; enforced, the patterns ride beside it.
158  on('prompt.submit', async ($, e, next) => {
159    if (!isClassifiable(e.text, e.origin)) return next(e)
160    const c = await classify($, e.text)
161    if (c === undefined || (c.geniuses.length === 0 && c.shapes.length === 0)) return next(e)
162    const applied = (await read($, state)).mode === 'enforce'
163    for (const g of c.geniuses)
164      await update($, state, (s) =>
165        decide(s, { at: c.at, kind: 'genius', subject: g.shape, to: `${g.agent} pattern`, applied }),
166      )
167    if (c.shapes.length > 0)
168      await update($, state, (s) =>
169        decide(s, {
170          at: c.at,
171          kind: 'shape',
172          subject: c.shapes.join(' + '),
173          to: c.shapes.map((id) => `${SHAPE_SKILL_PREFIX}${id}`).join(', '),
174          applied,
175        }),
176      )
177    if (!applied) return next(e)
178    const blocks = await contextFor($, c)
179    return blocks.length === 0 ? next(e) : next({ ...e, context: [...(e.context ?? []), ...blocks] })
180  })
181
182  // prompt.submit's turnId is the turn that was running, not the one the prompt starts: the
183  // grade binds to its turn here, by the prompt's opening; a turn of its own carries none.
184  on('turn.start', async ($, e, next) => {
185    const s = await read($, state)
186    const c = s.classified
187    const isOurs = c !== null && c.turnId === null && sameRequest(c.text, e.text)
188    const bound = isOurs && c !== null ? { ...c, turnId: e.turnId } : c?.turnId === null ? c : null
189    await update($, state, (st) => ({ ...st, classified: bound }))
190
191    return next(e)
192  })
193
194  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
195    const s = await read($, state)
196    const c = s.classified
197    if (e.props.hasSurvey || (c === null && s.classifierError === null)) return next(e)
198    const { Box, Text } = $.ui.resolve(e)
199    const picks = [...(c?.geniuses.map((g) => `${g.agent} · ${g.shape}`) ?? []), ...(c?.shapes ?? [])]
200    const graded =
201      c === null
202        ? 'no request graded'
203        : `${c.taskClass} → effort ${c.effort} · ${picks.length === 0 ? 'no pattern' : picks.join(' · ')} · ${c.ms} ms`
204    const trouble = s.classifierError === null ? '' : ` · ${s.classifierError}`
205
206    return (
207      <Box>
208        <Text dimColor>
209          genius {s.mode} · {graded}
210          {trouble}
211        </Text>
212      </Box>
213    )
214  })
215}
216
hooks/classify.ts 134 lines
1// Pure half of the request classifier: what the cheap model is asked, how its answer is read,
2// and what each answer means for effort. The two option lists come from files read at start.
3
4import type { Classified, Effort, GeniusPick, TaskClass } from '../types'
5import type { GeniusRow } from './genius'
6
7// source: ~/.claude/reference/skill-routing-table.md, generated from the problem-shaped skills'
8// `shapes:` frontmatter (zetetic-team-subagents); read at session start, never copied here.
9export const SHAPES_PATH = '~/.claude/reference/skill-routing-table.md'
10// source: the skills are installed under this plugin name (the Skill tool lists them so).
11export const SHAPE_SKILL_PREFIX = 'zetetic-team-subagents:'
12
13export type Shape = { id: string; description: string }
14
15// `| shape | skill | description |` rows; the header and the separator carry no shape.
16export const parseShapes = (table: string): Shape[] =>
17  table
18    .split('\n')
19    .map((line) => line.split('|').map((cell) => cell.trim()))
20    .filter((cells) => cells.length >= 4 && /^[a-z][a-z-]+$/.test(cells[1] ?? ''))
21    .map((cells) => ({ id: cells[1] ?? '', description: (cells[3] ?? '').replace(/\s+/g, ' ') }))
22
23// source: effort-calibration.md task table: reading and I/O low; a fully specified plan low; a
24// clear bug fix low–medium (medium here, the higher bound); architecture, PRD, research
25// synthesis medium; correctness-critical work high; "genuinely stuck" high is the autopilot's
26// stuck signal, not the classifier's.
27export const TASK_CLASSES: readonly TaskClass[] = ['routine', 'planned', 'bugfix', 'analysis', 'critical']
28const CLASS_EFFORT: Record<TaskClass, Effort> = {
29  routine: 'low',
30  planned: 'low',
31  bugfix: 'medium',
32  analysis: 'medium',
33  critical: 'high',
34}
35export const effortForClass = (cls: TaskClass): Effort => CLASS_EFFORT[cls]
36
37export type Classification = { taskClass: TaskClass; shapes: string[]; geniuses: GeniusPick[] }
38
39// source: own choice, under this length the text continues the previous task ("yes", "go on")
40// and carries no shape of its own.
41export const MIN_CLASSIFIABLE_CHARS = 20
42// source: own choice, the opening of a request names its task; the rest is pasted material.
43export const PROMPT_HEAD_CHARS = 4000
44export const MAX_PICKS = 2 // source: /genius:route recommends 1–3 agents; two patterns at most per prompt
45
46// A slash command runs through command.run; a plugin's own submit is not the person's request.
47export const isClassifiable = (text: string, origin: { kind: string }): boolean =>
48  origin.kind !== 'plugin' && !text.trimStart().startsWith('/') && text.trim().length >= MIN_CLASSIFIABLE_CHARS
49
50export const classifierSystem = (shapes: readonly Shape[], genius: readonly GeniusRow[]): string =>
51  [
52    'You classify one request addressed to a coding agent. Answer with one JSON object and nothing else:',
53    '{"task_class": <class>, "shapes": [<skill id>, ...], "geniuses": [{"agent": <agent>, "shape": <shape>}, ...]}',
54    '',
55    'task_class is exactly one of:',
56    '- routine: reading, listing, searching, formatting, a one-line change, a question answered from files.',
57    '- planned: implementing a plan or spec that is already written out in the request.',
58    '- bugfix: a bug whose cause is clear or quickly located.',
59    '- analysis: an architecture decision, a design, a research synthesis, a multi-file feature.',
60    '- critical: formal correctness, concurrency, security, billing, data loss; a wrong answer is worse than a slow one.',
61    '',
62    `shapes and geniuses together hold at most ${MAX_PICKS} picks whose trigger the request clearly matches; both [] when none does.`,
63    // source: agents/genius/INDEX.md, its rule, verbatim.
64    'Rule: if no shape below matches the problem, do not force a genius agent.',
65    'A pick fits only when the request has that structure, never because the topic is adjacent.',
66    'Prefer a genius shape (a precise reasoning procedure) over a skill when both would fit; never pick both for the same structure.',
67    '',
68    'Skills (problem-shaped methods):',
69    ...shapes.map((s) => `- ${s.id}: ${s.description}`),
70    '',
71    'Genius shapes (agent in parentheses, then the trigger):',
72    ...genius.map((g) => `- ${g.shape} (${g.agent}): ${g.trigger}`),
73  ].join('\n')
74
75export const classifierPrompt = (text: string): string => text.slice(0, PROMPT_HEAD_CHARS)
76
77const asPick = (v: unknown, known: readonly GeniusRow[]): GeniusPick | undefined => {
78  const o = v as { agent?: unknown; shape?: unknown } | null
79  if (o === null || typeof o !== 'object' || typeof o.agent !== 'string' || typeof o.shape !== 'string') return undefined
80  return known.some((g) => g.agent === o.agent && g.shape === o.shape) ? { agent: o.agent, shape: o.shape } : undefined
81}
82
83// Strict: a JSON object, fenced or bare; an unknown class, skill or genius pair voids the answer.
84export const parseClassification = (
85  answer: string,
86  shapes: readonly Shape[],
87  genius: readonly GeniusRow[],
88): Classification | undefined => {
89  const match = /\{[\s\S]*\}/.exec(answer)
90  if (match === null) return undefined
91  let raw: unknown
92  try {
93    raw = JSON.parse(match[0])
94  } catch {
95    return undefined
96  }
97  const obj = raw as { task_class?: unknown; shapes?: unknown; geniuses?: unknown }
98  const cls = obj.task_class
99  if (typeof cls !== 'string' || !(TASK_CLASSES as readonly string[]).includes(cls)) return undefined
100  const knownSkills = new Set(shapes.map((s) => s.id))
101  const skills = Array.isArray(obj.shapes) ? obj.shapes : []
102  if (!skills.every((s) => typeof s === 'string' && knownSkills.has(s))) return undefined
103  const picks = Array.isArray(obj.geniuses) ? obj.geniuses.map((g) => asPick(g, genius)) : []
104  if (picks.some((p) => p === undefined)) return undefined
105  const geniuses = (picks as GeniusPick[]).filter((p, i, all) => all.findIndex((q) => q.agent === p.agent) === i)
106  const uniqueSkills = [...new Set(skills as string[])]
107  const room = Math.max(0, MAX_PICKS - geniuses.slice(0, MAX_PICKS).length)
108  return { taskClass: cls as TaskClass, shapes: uniqueSkills.slice(0, room), geniuses: geniuses.slice(0, MAX_PICKS) }
109}
110
111export const toClassified = (c: Classification, text: string, startedAt: number, now: number): Classified => ({
112  turnId: null,
113  text,
114  taskClass: c.taskClass,
115  effort: effortForClass(c.taskClass),
116  shapes: c.shapes,
117  geniuses: c.geniuses,
118  ms: now - startedAt,
119  at: now,
120})
121
122// What the model reads beside the prompt when a skill fits: load it first.
123export const shapeContext = (ids: readonly string[]): string =>
124  [
125    'zetetic-genius: this request matches a reasoning shape. Before answering, invoke the Skill tool for',
126    ...ids.map((id) => `  ${SHAPE_SKILL_PREFIX}${id}`),
127    'and follow its method. Say in one line which shape you applied.',
128  ].join('\n')
129
130// source: own choice, the opening of a prompt identifies it at turn.start.
131export const BIND_HEAD_CHARS = 200
132export const sameRequest = (a: string, b: string): boolean =>
133  a.slice(0, BIND_HEAD_CHARS) === b.slice(0, BIND_HEAD_CHARS)
134
hooks/genius.ts 77 lines
1// Pure side of the genius patterns: where they are installed, how INDEX.md routes by shape, and
2// what part of a pattern file rides beside the prompt.
3
4// source: ~/.claude/plugins/installed_plugins.json, the engine's record of installed plugins;
5// `plugins["<name>@<marketplace>"][0].installPath` is the folder a session runs.
6export const INSTALLED_PLUGINS_PATH = '~/.claude/plugins/installed_plugins.json'
7export const GENIUS_PLUGIN = 'zetetic-team-subagents@zetetic-marketplace'
8// source: the plugin's layout, agents/genius/<name>.md and agents/genius/INDEX.md.
9export const GENIUS_DIR = 'agents/genius'
10export const GENIUS_INDEX = 'INDEX.md'
11
12export const installPathOf = (installedPluginsJson: string, plugin: string): string | undefined => {
13  let parsed: { plugins?: Record<string, { installPath?: unknown }[]> }
14  try {
15    parsed = JSON.parse(installedPluginsJson) as typeof parsed
16  } catch {
17    return undefined
18  }
19  const path = parsed.plugins?.[plugin]?.[0]?.installPath
20  return typeof path === 'string' && path !== '' ? path : undefined
21}
22
23// One INDEX.md row: `| **shape** | trigger | [agent](agent.md) | key move |`.
24export type GeniusRow = { shape: string; trigger: string; agent: string; keyMove: string }
25
26const ROW = /^\|\s*\*\*([a-z0-9-]+)\*\*\s*\|\s*(.*?)\s*\|\s*\[([a-z0-9-]+)\]\([^)]*\)\s*\|\s*(.*?)\s*\|\s*$/
27
28export const parseGeniusIndex = (md: string): GeniusRow[] =>
29  md
30    .split('\n')
31    .map((line) => ROW.exec(line))
32    .filter((m): m is RegExpExecArray => m !== null)
33    .map((m) => ({ shape: m[1] ?? '', trigger: m[2] ?? '', agent: m[3] ?? '', keyMove: m[4] ?? '' }))
34
35// The sections of a pattern file are XML-like tags; the procedure the model needs is the
36// identity's opening, the workflow and the output format (about 4–5 K characters), not the
37// canonical moves (11 K median), which stay one Read away.
38export const sectionOf = (file: string, tag: string): string | undefined => {
39  const m = new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`).exec(file)
40  return m === null ? undefined : (m[1] ?? '').trim()
41}
42
43export const identityOpening = (file: string): string => {
44  const identity = sectionOf(file, 'identity') ?? ''
45  return identity.split(/\n\s*\n/).find((p) => p.trim() !== '') ?? ''
46}
47
48// The pattern's own effort, from its frontmatter; the owner decides whether it floors a turn.
49export const patternEffort = (file: string): string | undefined => {
50  const m = /^effort:\s*([a-z]+)\s*$/m.exec(file.split(/^---\s*$/m)[1] ?? '')
51  return m === null ? undefined : m[1]
52}
53
54export const geniusContext = (
55  agent: string,
56  file: string,
57  rows: readonly GeniusRow[],
58  filePath: string,
59): string => {
60  const matched = rows.filter((r) => r.agent === agent)
61  return [
62    `zetetic-genius: apply the ${agent} reasoning pattern to this request, before anything else.`,
63    '',
64    identityOpening(file),
65    '',
66    ...matched.map((r) => `Shape ${r.shape}: ${r.trigger}. Key move: ${r.keyMove}`),
67    '',
68    'Workflow:',
69    sectionOf(file, 'workflow') ?? '(no workflow section)',
70    '',
71    'Output format:',
72    sectionOf(file, 'output-format') ?? '(no output-format section)',
73    '',
74    `The full pattern, with its canonical moves and blind spots, is ${filePath}; read it when a step needs more than the workflow says. Say in one line which pattern you applied.`,
75  ].join('\n')
76}
77
types/index.d.ts 48 lines
1// The request classifier's contract: what it decided about the current prompt.
2
3// source: ~/.claude/reference/agent-reference/effort-calibration.md § Effort levels (task table).
4export type TaskClass = 'routine' | 'planned' | 'bugfix' | 'analysis' | 'critical'
5export type Effort = 'low' | 'medium' | 'high' | 'xhigh' | 'max'
6
7// One genius reasoning pattern picked for the request, by the shape that matched.
8export type GeniusPick = { agent: string; shape: string }
9
10// What the classifier said about one prompt: pending (turnId null) until turn.start binds it.
11export type Classified = {
12  turnId: string | null
13  text: string
14  taskClass: TaskClass
15  effort: Effort
16  shapes: string[]
17  geniuses: GeniusPick[]
18  ms: number
19  at: number
20}
21
22export type GeniusDecision = {
23  at: number
24  kind: 'shape' | 'genius'
25  subject: string
26  to: string
27  applied: boolean
28}
29
30export type GeniusState = {
31  mode: 'observe' | 'enforce'
32  classifierModel: string
33  classified: Classified | null
34  classifierError: string | null
35  skillShapesLoaded: number
36  geniusShapesLoaded: number
37  geniusDir: string | null
38  decisions: GeniusDecision[]
39}
40
41declare module 'claude-code' {
42  interface PluginState {
43    'zetetic-genius': {
44      state: GeniusState
45    }
46  }
47}
48