The harness cockpit, a viewer: Cortex memory health and grooming, the context window against the Stop guard's thresholds (from zetetic-autopilot), the…

Claude Code mods for the ai-architect.tools harness, one concern per mod, state shared through dependencies:
| Mod | Owns | Depends on |
|---|---|---|
cortex-guard | refusals: wiki pages only through wiki_write, worktrees inside <repo>/.claude/worktrees/ | nothing |
cortex-wiki | /wiki <wiki/**.md> to PDF | nothing |
zetetic-genius | state: the request grade (task class → effort), the genius patterns and skills it matches | nothing |
zetetic-autopilot | context, policy: effort ladder, model routing, pressure, lean results | zetetic-genius |
cortex-cockpit | stats, ledger, tally, stages, hygiene; the /cortex pane draws the rest | the three above |
harness-fleet | fleet: 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 model | guard, genius, autopilot |
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.
.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.
hooks/register.tsx 270 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { CortexEntry, CortexStats, HygieneSnapshot, StageTally, TurnTally } from '../types'
5import type { ContextHealth, GeniusState, PolicyState, Refusal } from './deps'
6import { OWNERSHIP_PATH, parseOwnership, testProcesses, worktrees } from './hygiene'
7import { begin, emptyTally, finish, group, isCortexTool, parseStats, shortTool } from './model'
8import { bump, emptyStages, stageOf } from './pipeline'
9import { inputGist, outputText, parseRecall } from './recall'
10import { RecallResultView, UseRowView } from './rows'
11import { type Surface, tone } from './tokens'
12import { Cockpit } from './view'
13
14const PANE = 'cortex-cockpit'
15const LEDGER_CAP = 200 // source: bounds $.state size; the pane shows the last rows only
16const SLOW_MS = 2000 // source: memory_stats states ~75 ms; a call ten times slower is worth a word
17const RETRY_MS = [2000, 5000, 15000] // source: own choice, MCP servers connect after the mod loads
18
19const ledger = atom({ plugin: 'cortex-cockpit', key: 'ledger' } as const, [] as CortexEntry[])
20const stats = atom({ plugin: 'cortex-cockpit', key: 'stats' } as const, null as CortexStats | null)
21const statsError = atom({ plugin: 'cortex-cockpit', key: 'statsError' } as const, null as string | null)
22const tally = atom({ plugin: 'cortex-cockpit', key: 'tally' } as const, emptyTally())
23const stages = atom({ plugin: 'cortex-cockpit', key: 'stages' } as const, emptyStages() as Record<string, StageTally>)
24const hygiene = atom({ plugin: 'cortex-cockpit', key: 'hygiene' } as const, null as HygieneSnapshot | null)
25
26// Tools after which the memory population changes, so the headline numbers are re-read.
27// source: Cortex handlers that write (mcp_server/handlers/*: remember, forget, consolidate, ...).
28const MUTATING = new Set(['remember', 'forget', 'consolidate', 'checkpoint', 'import_sessions'])
29
30const surfaceOf = (value: unknown): Surface => (value === 'paper' ? 'paper' : 'ink')
31
32// Module state the readers share: where the repo is and which Cortex server to call.
33let repo = ''
34let server = 'plugin_hypermnesia-mcp_cortex'
35
36const expandHome = async ($: EngineInterface, path: string): Promise<string> =>
37 path.startsWith('~/') ? `${(await $.env.get('HOME')) ?? ''}/${path.slice(2)}` : path
38
39// The other mods' state, as their contracts declare it (plugin.json `dependencies`); a mod not
40// loaded reads as undefined. The references are literals here, as the validator lists them.
41const GUARD_REFUSALS = { plugin: 'cortex-guard', key: 'refusals' } as const
42const AUTOPILOT_CONTEXT = { plugin: 'zetetic-autopilot', key: 'context' } as const
43const AUTOPILOT_POLICY = { plugin: 'zetetic-autopilot', key: 'policy' } as const
44const GENIUS_STATE = { plugin: 'zetetic-genius', key: 'state' } as const
45
46async function peekRefusals($: EngineInterface): Promise<readonly Refusal[]> {
47 try {
48 return (await $.state.get(GUARD_REFUSALS)).value ?? []
49 } catch {
50 return []
51 }
52}
53
54async function peekContext($: EngineInterface): Promise<ContextHealth | null> {
55 try {
56 return (await $.state.get(AUTOPILOT_CONTEXT)).value ?? null
57 } catch {
58 return null
59 }
60}
61
62async function peekPolicy($: EngineInterface): Promise<PolicyState | null> {
63 try {
64 return (await $.state.get(AUTOPILOT_POLICY)).value ?? null
65 } catch {
66 return null
67 }
68}
69
70async function peekGenius($: EngineInterface): Promise<GeniusState | null> {
71 try {
72 return (await $.state.get(GENIUS_STATE)).value ?? null
73 } catch {
74 return null
75 }
76}
77
78// memory_stats is read-only and takes no arguments (handlers/memory_stats.py, schema). A server
79// not yet connected answers with an error; the read re-arms itself a few times.
80async function refreshStats($: EngineInterface, attempt = 0): Promise<void> {
81 let parsed: ReturnType<typeof parseStats>
82 try {
83 parsed = parseStats(await $.mcp.call(server, 'memory_stats'), await $.clock.now())
84 } catch (error) {
85 parsed = { error: `${server} unreachable: ${String(error).slice(0, 140)}` }
86 }
87 if ('stats' in parsed) {
88 await update($, stats, () => parsed.stats)
89 await update($, statsError, () => null)
90 return
91 }
92 await update($, statsError, () => parsed.error)
93 const wait = RETRY_MS[attempt]
94 if (wait !== undefined) $.clock.after(wait, () => refreshStats($, attempt + 1))
95}
96
97async function refreshHygiene($: EngineInterface): Promise<void> {
98 const readAt = await $.clock.now()
99 let snapshot: HygieneSnapshot
100 try {
101 const list = await $.process.run(['git', 'worktree', 'list', '--porcelain'], { cwd: repo })
102 let ownership: string | undefined
103 try {
104 ownership = await $.fs.read(await expandHome($, OWNERSHIP_PATH))
105 } catch {
106 ownership = undefined
107 }
108 const ps = await $.process.run(['ps', '-eo', 'pid,etime,command'])
109 snapshot =
110 list.exitCode === 0
111 ? {
112 worktrees: worktrees(list.stdout, ownership === undefined ? undefined : parseOwnership(ownership)),
113 testProcesses: testProcesses(ps.stdout),
114 error: null,
115 readAt,
116 }
117 : { worktrees: [], testProcesses: [], error: list.stderr.slice(0, 120), readAt }
118 } catch (error) {
119 snapshot = { worktrees: [], testProcesses: [], error: String(error).slice(0, 120), readAt }
120 }
121 await update($, hygiene, () => snapshot)
122}
123
124async function consolidateNow($: EngineInterface): Promise<void> {
125 $.ui.toast('consolidate: running')
126 try {
127 await $.mcp.call(server, 'consolidate')
128 } finally {
129 void refreshStats($)
130 }
131}
132
133export const register: Register = (on, options) => {
134 server = String(options.cortex_server ?? server)
135 const surface = surfaceOf(options.surface)
136
137 on('session.start', async ($, e, next) => {
138 repo = (await $.session.repo())?.root ?? e.cwd
139 void refreshStats($)
140 void refreshHygiene($)
141 try {
142 await $.command.register({ name: 'cortex', description: 'Open the Cortex cockpit pane', immediate: true })
143 } catch (error) {
144 $.ui.toast(`/cortex is taken by another plugin: ${String(error).slice(0, 80)}`)
145 }
146
147 return next(e)
148 })
149
150 on('command.run', { command: 'cortex' }, async ($) => {
151 await $.ui.open({ id: PANE, title: 'Cortex' })
152 void refreshStats($)
153 void refreshHygiene($)
154
155 return { text: 'Cortex cockpit opened.' }
156 })
157
158 // Observer: tallies every tool, places it on the pipeline, and keeps the Cortex ledger.
159 on('tool.call', async ($, e, next) => {
160 const name = String(e.tool)
161 const short = shortTool(name)
162 const stage = stageOf(name, e as { command?: string })
163 await update($, tally, (t: TurnTally) => ({ ...t, tools: { ...t.tools, [short]: (t.tools[short] ?? 0) + 1 } }))
164 const entry =
165 isCortexTool(name) && e.tool_use_id !== undefined ? begin(e.tool_use_id, name, await $.clock.now()) : undefined
166 if (entry !== undefined) await update($, ledger, (list) => [...list, entry].slice(-LEDGER_CAP))
167
168 const ran = await next(e)
169 const now = await $.clock.now()
170 const isBlocked = ran.deny !== undefined || ran.isError === true
171 if (stage !== undefined) await update($, stages, (s) => bump(s, stage, name, now, isBlocked))
172 if (entry !== undefined) {
173 const done = finish(entry, now, ran.text ?? '', isBlocked)
174 await update($, ledger, (list) => list.map((one) => (one.id === entry.id ? done : one)))
175 if ((done.ms ?? 0) >= SLOW_MS) $.ui.status(`cortex ${short} slow: ${group(done.ms ?? 0)} ms`)
176 if (MUTATING.has(short)) void refreshStats($)
177 }
178
179 return ran
180 })
181
182 on('turn.complete', async ($, e, next) => {
183 const u = e.usage
184 if (u !== undefined && e.agentId === undefined) {
185 await update($, tally, (t: TurnTally) => ({
186 ...t,
187 turns: t.turns + 1,
188 input: t.input + u.input_tokens,
189 output: t.output + u.output_tokens,
190 cacheRead: t.cacheRead + u.cache_read_input_tokens,
191 cacheWrite: t.cacheWrite + u.cache_creation_input_tokens,
192 }))
193 }
194
195 return next(e)
196 })
197
198 on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) =>
199 Cockpit($.ui.resolve(e) as never, {
200 surface,
201 now: await $.clock.now(),
202 columns: e.props.bodyColumns,
203 repo,
204 stats: await read($, stats),
205 statsError: await read($, statsError),
206 ledger: await read($, ledger),
207 tally: await read($, tally),
208 stages: await read($, stages),
209 hygiene: await read($, hygiene),
210 context: await peekContext($),
211 policy: await peekPolicy($),
212 genius: await peekGenius($),
213 refusals: await peekRefusals($),
214 onRefresh: () => {
215 void refreshStats($)
216 void refreshHygiene($)
217 },
218 onConsolidate: () => void consolidateNow($),
219 onCurateWiki: () =>
220 void $.prompt.submit({ text: 'Call curate_wiki, then write each authoring job it returns with wiki_write.' }),
221 }),
222 )
223
224 // Transcript rows, Cortex tools only; everything else is the engine's own row.
225 on('ui.render', { component: 'ToolUse' }, async ($, e, next) => {
226 if (!isCortexTool(e.props.tool)) return next(e)
227 const entry = (await read($, ledger)).find((one) => one.id === e.props.tool_use_id)
228
229 return UseRowView($.ui.resolve(e) as never, {
230 surface,
231 tool: shortTool(e.props.tool),
232 gist: inputGist(e.props.input),
233 isRunning: e.props.isRunning,
234 isErrored: e.props.isErrored,
235 entry,
236 })
237 })
238
239 on('ui.render', { component: 'ToolResult' }, async ($, e, next) => {
240 if (!isCortexTool(e.props.tool) || e.props.isErrored) return next(e)
241 const view = parseRecall(outputText(e.props.output))
242 if (view === undefined) return next(e)
243
244 return RecallResultView($.ui.resolve(e) as never, shortTool(e.props.tool), view)
245 })
246
247 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
248 const s = await read($, stats)
249 const failure = await read($, statsError)
250 const c = await peekContext($)
251 if (e.props.hasSurvey || (s === null && failure === null && c === null)) return next(e)
252 const { Box, Button, Text } = $.ui.resolve(e)
253 const refused = (await peekRefusals($)).length
254 const pressure = (await peekPolicy($))?.pressure ?? 'none'
255 const calls = (await read($, ledger)).length
256 const ctx = c === null || c.percent === null ? 'ctx: no reading' : `ctx ${c.percent} %`
257 const mem = s === null ? 'stats blocked' : `${group(s.total_memories)} memories`
258
259 return (
260 <Box>
261 <Text color={s === null ? tone(surface, 'danger') : undefined} dimColor={s !== null}>
262 cortex · {ctx} · {mem} · {group(calls)} calls · {refused === 0 ? 'nothing refused' : `${group(refused)} refused`}
263 {pressure === 'none' ? '' : ` · pressure ${pressure}`}{' '}
264 </Text>
265 <Button key="open" label="Cockpit" onPress={() => void $.ui.open({ id: PANE, title: 'Cortex' })} />
266 </Box>
267 )
268 })
269}
270hooks/deps.ts 11 lines1// The state this viewer reads from the mods it depends on (plugin.json `dependencies`), typed
2// by their contracts as the engine lays them: nothing is copied from another mod's folder.
3
4import type { PluginState } from 'claude-code'
5
6export type ContextHealth = NonNullable<PluginState['zetetic-autopilot']['context']>
7export type PolicyState = PluginState['zetetic-autopilot']['policy']
8export type PolicyDecision = PolicyState['decisions'][number]
9export type GeniusState = PluginState['zetetic-genius']['state']
10export type Refusal = PluginState['cortex-guard']['refusals'][number]
11hooks/hygiene.ts 68 lines1import type { Worktree } from '../types'
2// source: ~/.claude/CLAUDE.md rule 2 and Cortex CLAUDE.md: <repo>/.claude/worktrees/<name>/,
3// .Codex/worktrees/<name>/ for Codex. The same rule cortex-guard enforces; a viewer only reads it.
4const WORKTREE_ROOTS = ['/.claude/worktrees/', '/.Codex/worktrees/'] as const
5const isInsideWorktreeRoot = (path: string): boolean => WORKTREE_ROOTS.some((root) => path.includes(root))
6
7// source: ~/.local/state/disk-hygiene/ownership.json, the registry disk_hygiene.py keeps
8// (its --state default); one entry per registered path, `owner` is "<host>:<session>".
9export const OWNERSHIP_PATH = '~/.local/state/disk-hygiene/ownership.json'
10
11export type Ownership = Record<string, { owner?: string; pr?: string; kind?: string }>
12
13export const parseOwnership = (text: string): Ownership | undefined => {
14 try {
15 const raw = JSON.parse(text) as unknown
16 return raw !== null && typeof raw === 'object' ? (raw as Ownership) : undefined
17 } catch {
18 return undefined
19 }
20}
21
22// `git worktree list --porcelain`: stanzas separated by a blank line, the first is main.
23export const parsePorcelain = (text: string): { path: string; branch: string | null }[] =>
24 text
25 .split(/\n\s*\n/)
26 .map((stanza) => stanza.trim())
27 .filter((stanza) => stanza.startsWith('worktree '))
28 .map((stanza) => {
29 const lines = stanza.split('\n')
30 const path = (lines[0] ?? '').slice('worktree '.length)
31 const branch = lines.find((l) => l.startsWith('branch '))?.slice('branch '.length) ?? null
32 return { path, branch }
33 })
34
35export const worktrees = (porcelain: string, ownership: Ownership | undefined): Worktree[] =>
36 parsePorcelain(porcelain).map((w, i) => {
37 const entry = ownership?.[w.path]
38 return {
39 path: w.path,
40 branch: w.branch,
41 isMain: i === 0,
42 isInsideRepoRule: i === 0 || isInsideWorktreeRoot(w.path),
43 isRegistered: entry !== undefined,
44 owner: entry?.owner ?? null,
45 pr: entry?.pr ?? null,
46 }
47 })
48
49// source: the test runners the Cortex and ai-architect projects use (pytest, vitest, cargo).
50const TEST_RUNNER = /\b(pytest|vitest|jest|cargo test|go test|mutmut|hypothesis)\b/
51
52// `ps -eo pid,etime,command` rows that are a test runner; the header row never matches.
53export const testProcesses = (
54 ps: string,
55): { pid: number; elapsed: string; command: string }[] =>
56 ps
57 .split('\n')
58 .map((line) => line.trim())
59 .filter((line) => TEST_RUNNER.test(line) && !/\bgrep\b/.test(line))
60 .map((line) => {
61 const [pid, elapsed, ...rest] = line.split(/\s+/)
62 return { pid: Number(pid), elapsed: elapsed ?? '', command: rest.join(' ').slice(0, 80) }
63 })
64 .filter((p) => Number.isFinite(p.pid))
65
66export const shortPath = (path: string, repo: string): string =>
67 path.startsWith(repo) ? path.slice(repo.length).replace(/^\//, '') || '.' : path
68hooks/model.ts 105 lines1import type { CortexEntry, CortexStats, TurnTally } from '../types'
2
3// Pure functions: no engine access, so the test file exercises them directly.
4
5// source: the Cortex plugin exposes its tools to the model as
6// mcp__plugin_hypermnesia-mcp_cortex__<tool> (seen in this build's tool list).
7const CORTEX_PREFIX = 'mcp__plugin_hypermnesia-mcp_cortex__'
8const STATS_KEYS = ['total_memories', 'avg_heat', 'grooming_staleness'] as const
9
10export const isCortexTool = (tool: string): boolean => tool.startsWith(CORTEX_PREFIX)
11
12export const shortTool = (tool: string): string =>
13 isCortexTool(tool) ? tool.slice(CORTEX_PREFIX.length) : tool
14
15// Exact counts, thousands grouped by a space: "119 304" (gate G10).
16export const group = (n: number): string =>
17 String(Math.trunc(n)).replace(/\B(?=(\d{3})+(?!\d))/g, ' ')
18
19export const emptyTally = (): TurnTally => ({
20 turns: 0,
21 input: 0,
22 output: 0,
23 cacheRead: 0,
24 cacheWrite: 0,
25 tools: {},
26})
27
28export const begin = (id: string, tool: string, now: number): CortexEntry => ({
29 id,
30 tool: shortTool(tool),
31 startedAt: now,
32 status: 'pending',
33})
34
35// recall answers {"count": n, "receipt_id": m, ...}; absent means we say so, not 0.
36const numberAfter = (text: string, key: string): number | undefined => {
37 const hit = new RegExp(`"${key}"\\s*:\\s*(\\d+)`).exec(text)
38 return hit === null ? undefined : Number(hit[1])
39}
40
41export const finish = (
42 entry: CortexEntry,
43 now: number,
44 text: string,
45 isError: boolean,
46): CortexEntry => ({
47 ...entry,
48 status: isError ? 'blocked' : 'measured',
49 ms: Math.max(0, now - entry.startedAt),
50 count: numberAfter(text, 'count'),
51 receipt: numberAfter(text, 'receipt_id'),
52})
53
54export const countBy = (ledger: readonly CortexEntry[]): Record<string, number> => {
55 const out: Record<string, number> = {}
56 for (const one of ledger) out[one.tool] = (out[one.tool] ?? 0) + 1
57 return out
58}
59
60type Block = { type: string; text?: string }
61export type StatsParse = { stats: CortexStats } | { error: string }
62
63export const parseStats = (
64 result: { content: readonly Block[]; isError: boolean; structuredContent?: unknown },
65 now: number,
66): StatsParse => {
67 const first = result.content.find((b) => b.type === 'text')?.text ?? ''
68 if (result.isError) return { error: first.slice(0, 160) || 'server reported an error' }
69 let raw: unknown = result.structuredContent
70 if (raw === undefined) {
71 try {
72 raw = JSON.parse(first)
73 } catch {
74 return { error: 'memory_stats did not return JSON' }
75 }
76 }
77 const obj = raw as Record<string, unknown> | null
78 const missing = STATS_KEYS.filter((k) => obj === null || typeof obj !== 'object' || !(k in obj))
79 if (missing.length > 0) return { error: `memory_stats lacks ${missing.join(', ')}` }
80 return { stats: { ...(obj as Omit<CortexStats, 'readAt'>), readAt: now } }
81}
82
83// Heat track of `width` cells: how many are filled and each cell's rung 0..3.
84export const heatCells = (value: number, width: number) => {
85 const v = Number.isFinite(value) ? Math.min(1, Math.max(0, value)) : 0
86 const filled = Math.round(v * width)
87 return Array.from({ length: width }, (_, i) => ({
88 isFilled: i < filled,
89 rung: Math.min(3, Math.floor((i / width) * 4)),
90 }))
91}
92
93export const ageLabel = (ms: number): string => {
94 const s = Math.round(ms / 1000)
95 if (s < 60) return `${s}s`
96 const m = Math.floor(s / 60)
97 return m < 60 ? `${m}m ${s % 60}s` : `${Math.floor(m / 60)}h ${m % 60}m`
98}
99
100// Share of input served from the prompt cache; undefined when nothing was read.
101export const cacheShare = (t: TurnTally): number | undefined => {
102 const all = t.input + t.cacheRead + t.cacheWrite
103 return all === 0 ? undefined : t.cacheRead / all
104}
105hooks/pipeline.ts 94 lines1import type { StageTally } from '../types'
2
3// The spec-to-code pipeline as the harness runs it, read off the tools the model calls.
4// Stage order and membership are the servers' own published orderings:
5// source: prd-gen server instructions (coordinate_context_budget → start_pipeline →
6// get_pipeline_state → submit_action_result; plan_* → submit_action_result →
7// conclude_verification; validate_prd_*).
8// source: ai-architect server instructions (stages 1 findings, 2 verification, 3 code
9// intelligence, 4/6 PRD grounding, 8/9 gates).
10// source: Cortex server instructions (recall/remember, wiki_*).
11
12export const STAGES = [
13 'recall',
14 'spec',
15 'spec-verify',
16 'code-intel',
17 'grounding',
18 'gates',
19 'subagents',
20 'remember',
21 'wiki',
22 'git',
23] as const
24
25export type Stage = (typeof STAGES)[number]
26
27const PRD = 'mcp__plugin_ai-architect-mcp-spec_prd-gen__'
28const ARCH = 'mcp__plugin_ai-architect-mcp-codebase_ai-architect__'
29const CORTEX = 'mcp__plugin_hypermnesia-mcp_cortex__'
30
31const BY_TOOL: Record<string, Stage> = {
32 [`${CORTEX}recall`]: 'recall',
33 [`${CORTEX}recall_hierarchical`]: 'recall',
34 [`${CORTEX}unified_search`]: 'recall',
35 [`${CORTEX}recall_skills`]: 'recall',
36 [`${CORTEX}remember`]: 'remember',
37 [`${CORTEX}checkpoint`]: 'remember',
38 [`${PRD}coordinate_context_budget`]: 'spec',
39 [`${PRD}start_pipeline`]: 'spec',
40 [`${PRD}get_pipeline_state`]: 'spec',
41 [`${PRD}submit_action_result`]: 'spec',
42 [`${PRD}validate_prd_section`]: 'spec-verify',
43 [`${PRD}validate_prd_document`]: 'spec-verify',
44 [`${PRD}plan_section_verification`]: 'spec-verify',
45 [`${PRD}plan_document_verification`]: 'spec-verify',
46 [`${PRD}conclude_verification`]: 'spec-verify',
47 [`${ARCH}prepare_prd_input`]: 'grounding',
48 [`${ARCH}validate_prd_against_graph`]: 'grounding',
49 [`${ARCH}check_doc_claims`]: 'grounding',
50 [`${ARCH}check_security_gates`]: 'gates',
51 [`${ARCH}verify_semantic_diff`]: 'gates',
52 [`${ARCH}start_verification`]: 'gates',
53 [`${ARCH}finalize_verification`]: 'gates',
54}
55
56const GIT_WRITE = /(^|[\s;&|])git\s+(commit|push|merge|rebase|worktree\s+add|tag)\b/
57
58export const stageOf = (tool: string, input?: { command?: string }): Stage | undefined => {
59 const direct = BY_TOOL[tool]
60 if (direct !== undefined) return direct
61 if (tool.startsWith(ARCH)) return 'code-intel'
62 if (tool.startsWith(`${CORTEX}wiki_`)) return 'wiki'
63 if (tool === 'Agent') return 'subagents'
64 if (tool === 'Bash' && input?.command !== undefined && GIT_WRITE.test(input.command))
65 return 'git'
66 return undefined
67}
68
69export const emptyStages = (): Record<string, StageTally> =>
70 Object.fromEntries(
71 STAGES.map((s) => [s, { calls: 0, blocked: 0, lastAt: null, lastTool: null }]),
72 )
73
74export const bump = (
75 stages: Record<string, StageTally>,
76 stage: Stage,
77 tool: string,
78 now: number,
79 isBlocked: boolean,
80): Record<string, StageTally> => {
81 const s = stages[stage] ?? { calls: 0, blocked: 0, lastAt: null, lastTool: null }
82 return {
83 ...stages,
84 [stage]: {
85 calls: s.calls + 1,
86 blocked: s.blocked + (isBlocked ? 1 : 0),
87 lastAt: now,
88 lastTool: tool,
89 },
90 }
91}
92
93export const shortName = (tool: string): string => tool.replace(/^mcp__[^_]+(_[^_]+)*__/, '')
94hooks/recall.ts 91 lines1// Pure reading of a Cortex tool call for the transcript rows (ToolUse, ToolResult).
2
3export type RecallHit = {
4 id: string
5 score?: number
6 heat?: number
7 domain?: string
8 source?: string
9 content: string
10}
11
12export type RecallView = {
13 count: number
14 receipt?: number
15 intent?: string
16 dropped?: number
17 shown: RecallHit[]
18 hidden: number
19}
20
21// source: own choice, the rows a result shows before "n more"; the full list stays in ctrl+o
22export const SHOWN_HITS = 5
23// source: own choice, one transcript line per hit
24const GIST_CHARS = 100
25
26const oneLine = (text: string, max: number): string => {
27 const flat = text.replace(/\s+/g, ' ').trim()
28 return flat.length <= max ? flat : `${flat.slice(0, max - 1)}…`
29}
30
31const num = (v: unknown): number | undefined => (typeof v === 'number' ? v : undefined)
32const str = (v: unknown): string | undefined => (typeof v === 'string' ? v : undefined)
33
34// An MCP result reaches the row as text, as MCP content blocks, or as an object.
35export const outputText = (output: unknown): string => {
36 if (typeof output === 'string') return output
37 const content = (output as { content?: unknown } | null)?.content
38 if (Array.isArray(content)) {
39 return content
40 .map((b) => (b as { type?: string; text?: string }).text)
41 .filter((t): t is string => typeof t === 'string')
42 .join('\n')
43 }
44 return output === undefined ? '' : JSON.stringify(output)
45}
46
47// Shape-driven: anything without a `memories` array is left to the engine's own row.
48export const parseRecall = (text: string): RecallView | undefined => {
49 let raw: unknown
50 try {
51 raw = JSON.parse(text)
52 } catch {
53 return undefined
54 }
55 const obj = raw as Record<string, unknown> | null
56 const list = obj?.memories
57 if (obj === null || typeof obj !== 'object' || !Array.isArray(list)) return undefined
58 const hits: RecallHit[] = list.map((m) => {
59 const r = m as Record<string, unknown>
60 return {
61 id: String(r.memory_id ?? '?'),
62 score: num(r.score),
63 heat: num(r.heat),
64 domain: str(r.domain),
65 source: str(r.source),
66 content: oneLine(str(r.content) ?? '', GIST_CHARS),
67 }
68 })
69 return {
70 count: num(obj.count) ?? hits.length,
71 receipt: num(obj.receipt_id),
72 intent: str(obj.intent),
73 dropped: num(obj.low_signal_dropped),
74 shown: hits.slice(0, SHOWN_HITS),
75 hidden: Math.max(0, hits.length - SHOWN_HITS),
76 }
77}
78
79// The input field that says what the call is about, first present wins.
80const GIST_KEYS = ['query', 'content', 'title', 'domain', 'memory_id'] as const
81
82export const inputGist = (input: unknown): string => {
83 const obj = (input ?? {}) as Record<string, unknown>
84 for (const key of GIST_KEYS) {
85 const v = obj[key]
86 if (typeof v === 'string' && v !== '') return oneLine(v, 80)
87 if (typeof v === 'number') return String(v)
88 }
89 return ''
90}
91hooks/rows.tsx 82 lines1import type { CortexEntry } from '../types'
2import { group } from './model'
3import type { RecallView } from './recall'
4import { HEAT_TRACK, type Surface, tone } from './tokens'
5import type { Ui } from './view'
6
7export type UseRow = {
8 surface: Surface
9 tool: string
10 gist: string
11 isRunning: boolean
12 isErrored: boolean
13 entry?: CortexEntry
14}
15
16const heatRung = (heat: number): number => Math.min(3, Math.max(0, Math.floor(heat * 4)))
17
18// One ruled line per call: what was asked, and the lexicon word for where it stands.
19export const UseRowView = (ui: Ui, r: UseRow) => {
20 const { Box, Text } = ui
21 const status = r.isErrored ? 'blocked' : r.isRunning ? 'pending' : 'measured'
22 const color = r.isErrored
23 ? tone(r.surface, 'danger')
24 : r.isRunning
25 ? tone(r.surface, 'warn')
26 : undefined
27 const e = r.entry
28 const facts = [
29 status,
30 e?.ms === undefined ? undefined : `${group(e.ms)} ms`,
31 e?.receipt === undefined ? undefined : `rcpt ${e.receipt}`,
32 ].filter((f): f is string => f !== undefined)
33
34 return (
35 <Box>
36 <Text dimColor>cortex ▸ </Text>
37 <Text bold>{r.tool}</Text>
38 <Text dimColor>{r.gist === '' ? '' : ` ${r.gist}`}</Text>
39 <Text color={color} dimColor={color === undefined}>
40 {' '}
41 {facts.join(' · ')}
42 </Text>
43 </Box>
44 )
45}
46
47export const RecallResultView = (ui: Ui, tool: string, v: RecallView) => {
48 const { Box, Text } = ui
49 const head = [
50 `${group(v.count)} rows`,
51 v.receipt === undefined ? undefined : `rcpt ${v.receipt}`,
52 v.intent === undefined ? undefined : `intent ${v.intent}`,
53 v.dropped === undefined ? undefined : `${group(v.dropped)} low-signal dropped`,
54 ].filter((f): f is string => f !== undefined)
55
56 return (
57 <Box flexDirection="column">
58 <Text>
59 <Text bold>{tool}</Text>
60 <Text dimColor> · {head.join(' · ')}</Text>
61 </Text>
62 {v.count === 0 && <Text dimColor>no memory matched</Text>}
63 {v.shown.map((hit) => (
64 <Text>
65 {hit.heat === undefined ? (
66 <Text dimColor>· </Text>
67 ) : (
68 <Text color={HEAT_TRACK[heatRung(hit.heat)]}>■ </Text>
69 )}
70 <Text dimColor>
71 {hit.score === undefined ? ' ' : hit.score.toFixed(2)}{' '}
72 {hit.heat === undefined ? '' : `heat ${hit.heat.toFixed(2)} `}
73 {hit.domain ?? 'no domain'}{' '}
74 </Text>
75 {hit.content}
76 </Text>
77 ))}
78 {v.hidden > 0 && <Text dimColor>{group(v.hidden)} more not shown here</Text>}
79 </Box>
80 )
81}
82hooks/tokens.ts 41 lines1// Colour tokens of the AI Architect design system, resolved for a terminal.
2// A terminal has no CSS variables, so each token is converted from its oklch
3// definition to sRGB (OKLab matrices, Ottosson 2020) and written here once.
4// Chrome stays greyscale: it uses the terminal's own theme keys and dimColor.
5// Only the single accent and data/status tones are coloured (gate G3, G4).
6// A value marked "clipped" was outside the sRGB gamut and is clamped.
7const DS = 'AI Architect Design System/tokens/colors.css'
8
9export type Tone = 'accent' | 'ok' | 'warn' | 'danger' | 'info'
10export type Surface = 'ink' | 'paper'
11
12// source: DS :L27 --accent (terracotta; selection only, never a data category)
13export const ACCENT = '#cf6e39'
14
15// source: DS :L85-88 --ok --warn --danger --info (lifted tones, dark terminal)
16const LIFTED: Record<Exclude<Tone, 'accent'>, string> = {
17 ok: '#65c98c',
18 warn: '#e8aa4e',
19 danger: '#e86154',
20 info: '#57b8e3',
21}
22
23// source: DS :L57-61 --accent-deep --ok-deep --warn-deep --danger-deep --info-deep
24// (warn, info and accent-deep clipped to sRGB), deep inks for a light terminal
25const DEEP: Record<Tone, string> = {
26 accent: '#a53e00',
27 ok: '#0a693c',
28 warn: '#845b00',
29 danger: '#a52a24',
30 info: '#006185',
31}
32
33export const tone = (surface: Surface, t: Tone): string =>
34 surface === 'paper' ? DEEP[t] : t === 'accent' ? ACCENT : LIFTED[t]
35
36// source: DS :L65-68 --heat-hot --heat-warm --heat-cool --heat-cold
37// Heat track, the one gradient the gate allows (G6): cold to hot.
38export const HEAT_TRACK = ['#645d51', '#9c896a', '#d99c68', '#fd9976'] as const
39
40export const SOURCE = DS
41hooks/view.tsx 184 lines1import type { Elements, RenderChildren } from 'claude-code'
2
3import type { CortexEntry, CortexStats, HygieneSnapshot, StageTally, TurnTally } from '../types'
4import type { ContextHealth, GeniusState, PolicyState, Refusal } from './deps'
5import { ageLabel, cacheShare, countBy, group, heatCells } from './model'
6import { ContextPanel, HygienePanel, PipelinePanel, PolicyPanel, RefusalsPanel, clock } from './panels'
7import { HEAT_TRACK, type Surface, tone } from './tokens'
8
9export type Ui = Elements['terminal']
10
11export type CockpitData = {
12 surface: Surface
13 now: number
14 columns: number
15 repo: string
16 stats: CortexStats | null
17 statsError: string | null
18 ledger: readonly CortexEntry[]
19 tally: TurnTally
20 context: ContextHealth | null
21 stages: Record<string, StageTally>
22 refusals: readonly Refusal[]
23 hygiene: HygieneSnapshot | null
24 policy: PolicyState | null
25 genius: GeniusState | null
26 onRefresh: () => void
27 onConsolidate: () => void
28 onCurateWiki: () => void
29}
30
31const SHOWN = 8 // source: rows of the ledger kept on screen; older rows stay in state
32
33// Micro-label: mono UPPERCASE is the one place the voice allows caps (gate G13).
34const Label = (ui: Ui, text: string) => {
35 const { Text } = ui
36 return (
37 <Text bold dimColor>
38 {text}
39 </Text>
40 )
41}
42
43const HeatTrack = (ui: Ui, value: number) => {
44 const { Text } = ui
45 return (
46 <Text>
47 {heatCells(value, 20).map((c) =>
48 c.isFilled ? <Text color={HEAT_TRACK[c.rung]}>█</Text> : <Text dimColor>·</Text>,
49 )}
50 </Text>
51 )
52}
53
54const Memory = (ui: Ui, d: CockpitData) => {
55 const { Box, Button, Text } = ui
56 const s = d.stats
57 if (s === null) {
58 return d.statsError === null ? (
59 <Text color={tone(d.surface, 'warn')}>stats: pending</Text>
60 ) : (
61 <Text color={tone(d.surface, 'danger')}>stats: blocked, {d.statsError}</Text>
62 )
63 }
64 const stale = Object.entries(s.grooming_staleness)
65 const anyStale = stale.some(([, g]) => g.stale)
66 return (
67 <Box flexDirection="column">
68 <Text>
69 {group(s.total_memories)} memories · episodic {group(s.episodic_count)} · semantic{' '}
70 {group(s.semantic_count)} · active {group(s.active_count)} · stale {group(s.stale_count)}
71 </Text>
72 <Box>
73 <Text>heat {s.avg_heat.toFixed(4)} </Text>
74 {HeatTrack(ui, s.avg_heat)}
75 <Text dimColor>
76 {' '}
77 · {group(s.total_entities)} entities · {group(s.total_relationships)} relationships
78 </Text>
79 </Box>
80 <Text dimColor>
81 last consolidation {s.last_consolidation ?? 'none recorded'} · vector search{' '}
82 {s.has_vector_search ? 'available' : 'absent'}
83 </Text>
84 {stale.map(([kind, g]) => (
85 <Text color={g.stale ? tone(d.surface, 'warn') : undefined} dimColor={!g.stale}>
86 grooming {kind}:{' '}
87 {g.days_since_last_run === null ? 'never run' : `${g.days_since_last_run.toFixed(1)} d`}
88 {g.stale ? ` (stale, threshold ${s.grooming_staleness_threshold_days} d)` : ''}
89 </Text>
90 ))}
91 {anyStale && (
92 <Box gap={1}>
93 <Button key="consolidate" label="Consolidate now" hotkey="c" onPress={d.onConsolidate} />
94 <Button key="curate" label="Queue wiki curation" hotkey="w" onPress={d.onCurateWiki} />
95 </Box>
96 )}
97 <Text dimColor>read {ageLabel(d.now - s.readAt)} ago</Text>
98 </Box>
99 )
100}
101
102const statusColor = (surface: Surface, e: CortexEntry): string | undefined =>
103 e.status === 'blocked'
104 ? tone(surface, 'danger')
105 : e.status === 'pending'
106 ? tone(surface, 'warn')
107 : undefined
108
109const Ledger = (ui: Ui, d: CockpitData) => {
110 const { Box, Text } = ui
111 if (d.ledger.length === 0) return <Text dimColor>no Cortex call this session</Text>
112 const summary = Object.entries(countBy(d.ledger))
113 .sort((a, b) => b[1] - a[1])
114 .map(([tool, n]) => `${group(n)} ${tool}`)
115 .join(' · ')
116 return (
117 <Box flexDirection="column">
118 <Text>{summary}</Text>
119 {d.ledger.slice(-SHOWN).map((e) => (
120 <Text color={statusColor(d.surface, e)} dimColor={e.status === 'measured'}>
121 {clock(e.startedAt)} {e.tool.padEnd(18)}{' '}
122 {(e.ms === undefined ? '…' : `${group(e.ms)} ms`).padStart(9)}{' '}
123 {(e.count === undefined ? 'no count' : `${group(e.count)} rows`).padStart(10)}{' '}
124 {e.receipt === undefined ? '' : `rcpt ${e.receipt} `}
125 {e.status}
126 </Text>
127 ))}
128 </Box>
129 )
130}
131
132const Turns = (ui: Ui, d: CockpitData) => {
133 const { Box, Text } = ui
134 const t = d.tally
135 if (t.turns === 0) return <Text dimColor>no turn completed yet</Text>
136 const share = cacheShare(t)
137 const tools = Object.entries(t.tools)
138 .sort((a, b) => b[1] - a[1])
139 .slice(0, 6)
140 .map(([name, n]) => `${name} ${group(n)}`)
141 .join(' · ')
142 return (
143 <Box flexDirection="column">
144 <Text>
145 {group(t.turns)} turns · in {group(t.input)} · out {group(t.output)} · cache read{' '}
146 {group(t.cacheRead)} · cache write {group(t.cacheWrite)}
147 {share === undefined ? '' : ` · ${(share * 100).toFixed(1)} % from cache`}
148 </Text>
149 <Text dimColor>tools: {tools || 'none'}</Text>
150 </Box>
151 )
152}
153
154const Section = (ui: Ui, label: string, body: RenderChildren) => {
155 const { Box } = ui
156 return (
157 <Box flexDirection="column">
158 {Label(ui, label)}
159 {body}
160 </Box>
161 )
162}
163
164export const Cockpit = (ui: Ui, d: CockpitData) => {
165 const { Box, Button, Text } = ui
166 return (
167 <Box flexDirection="column" gap={1}>
168 <Box flexDirection="column">
169 <Text bold>Cortex cockpit</Text>
170 <Text dimColor>{'─'.repeat(Math.max(8, d.columns))}</Text>
171 </Box>
172 {Section(ui, 'CONTEXT', ContextPanel(ui, d.surface, d.context, d.now))}
173 {Section(ui, 'MEMORY', Memory(ui, d))}
174 {Section(ui, 'PIPELINE SPEC → CODE', PipelinePanel(ui, d.surface, d.stages))}
175 {Section(ui, 'HYGIENE', HygienePanel(ui, d.surface, d.hygiene, d.repo, d.now))}
176 {Section(ui, 'POLICY', PolicyPanel(ui, d.surface, d.policy, d.genius))}
177 {Section(ui, 'ENFORCED', RefusalsPanel(ui, d.surface, d.refusals))}
178 {Section(ui, 'CORTEX CALLS', Ledger(ui, d))}
179 {Section(ui, 'TURNS', Turns(ui, d))}
180 <Button key="refresh" label="Refresh" hotkey="r" onPress={d.onRefresh} />
181 </Box>
182 )
183}
184hooks/panels.tsx 219 lines1import type { HygieneSnapshot, StageTally } from '../types'
2import { bar, tokensLeft } from './contextview'
3import type { ContextHealth, GeniusState, PolicyDecision, PolicyState, Refusal } from './deps'
4import { shortPath } from './hygiene'
5import { ageLabel, group } from './model'
6import { STAGES, shortName } from './pipeline'
7import { type Surface, tone } from './tokens'
8import type { Ui } from './view'
9
10const pad2 = (n: number): string => String(n).padStart(2, '0')
11export const clock = (ms: number): string => {
12 const d = new Date(ms)
13 return `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`
14}
15
16const BAR_WIDTH = 30 // source: own choice, fits the narrowest docked pane the engine seats
17const TOP = 5 // source: own choice, the rows a one-line summary can carry
18
19export const ContextPanel = (ui: Ui, surface: Surface, c: ContextHealth | null, now: number) => {
20 const { Box, Text } = ui
21 if (c === null) return <Text dimColor>context: pending (no response measured yet)</Text>
22 const b = c.band
23 const color =
24 b === 'hard' ? tone(surface, 'danger') : b === 'warn' ? tone(surface, 'warn') : undefined
25 const left = tokensLeft(c)
26 const limits = c.rateLimits.map((r) => `${r.kind} ${r.percentUsed} %`).join(' · ')
27 return (
28 <Box flexDirection="column">
29 <Box>
30 <Text color={color}>{bar(c, BAR_WIDTH)} </Text>
31 <Text color={color}>
32 {c.tokens === null ? 'no reading' : `${group(c.tokens)} / ${group(c.window)}`}
33 {c.percent === null ? '' : ` · ${c.percent} %`} · {b}
34 </Text>
35 </Box>
36 <Text dimColor>
37 {c.warn === null
38 ? `no thresholds for ${c.model} (${c.thresholdsSource})`
39 : `warn ${group(c.warn)} · hard ${group(c.hard ?? 0)} · ${
40 left.toWarn === null ? '' : left.toWarn > 0 ? `${group(left.toWarn)} to warn` : 'past warn'
41 }${left.toHard === null ? '' : left.toHard > 0 ? ` · ${group(left.toHard)} to hard` : ' · past hard'} · ${c.model}`}
42 </Text>
43 <Text dimColor>
44 {limits === '' ? 'rate limits: no reading' : limits}
45 {c.usd === null ? '' : ` · cost ${c.usd.toFixed(2)} USD`} · read {ageLabel(now - c.readAt)}{' '}
46 ago
47 </Text>
48 {c.categories.length > 0 && (
49 <Text dimColor>
50 by category:{' '}
51 {c.categories
52 .filter((k) => k.tokens > 0)
53 .slice(0, TOP)
54 .map((k) => `${k.name} ${group(k.tokens)}`)
55 .join(' · ')}
56 </Text>
57 )}
58 {c.mcpServers.length > 0 && (
59 <Text dimColor>
60 MCP schemas:{' '}
61 {c.mcpServers
62 .slice(0, TOP)
63 .map((s) => `${s.server} ${group(s.tokens)} (${s.tools} tools)`)
64 .join(' · ')}
65 </Text>
66 )}
67 </Box>
68 )
69}
70
71export const PipelinePanel = (ui: Ui, surface: Surface, stages: Record<string, StageTally>) => {
72 const { Box, Text } = ui
73 const any = STAGES.some((s) => (stages[s]?.calls ?? 0) > 0)
74 if (!any) return <Text dimColor>no pipeline tool called this session</Text>
75 return (
76 <Box flexDirection="column">
77 {STAGES.map((name) => {
78 const s = stages[name] ?? { calls: 0, blocked: 0, lastAt: null, lastTool: null }
79 const idle = s.calls === 0
80 const color = s.blocked > 0 ? tone(surface, 'danger') : undefined
81 return (
82 <Text color={color} dimColor={idle}>
83 {idle ? '○' : s.blocked > 0 ? '✗' : '●'} {name.padEnd(12)}
84 {idle
85 ? 'not reached'
86 : `${group(s.calls)} calls${s.blocked > 0 ? ` · ${group(s.blocked)} blocked` : ''} · last ${
87 s.lastTool === null ? '' : shortName(s.lastTool)
88 } ${s.lastAt === null ? '' : clock(s.lastAt)}`}
89 </Text>
90 )
91 })}
92 </Box>
93 )
94}
95
96export const HygienePanel = (
97 ui: Ui,
98 surface: Surface,
99 snap: HygieneSnapshot | null,
100 repo: string,
101 now: number,
102) => {
103 const { Box, Text } = ui
104 if (snap === null) return <Text dimColor>hygiene: pending</Text>
105 if (snap.error !== null)
106 return <Text color={tone(surface, 'danger')}>hygiene: blocked, {snap.error}</Text>
107 const extra = snap.worktrees.filter((w) => !w.isMain)
108 return (
109 <Box flexDirection="column">
110 {extra.length === 0 && <Text dimColor>no worktree beside the main checkout</Text>}
111 {extra.map((w) => {
112 const bad = !w.isInsideRepoRule || !w.isRegistered
113 return (
114 <Text color={bad ? tone(surface, 'danger') : undefined}>
115 {bad ? '✗' : '●'} {shortPath(w.path, repo)}
116 {w.branch === null ? '' : ` · ${w.branch.replace('refs/heads/', '')}`}
117 {w.isInsideRepoRule ? '' : ' · outside <repo>/.claude/worktrees/'}
118 {w.isRegistered ? ` · ${w.owner ?? 'owner unknown'}` : ' · not registered with disk_hygiene'}
119 {w.pr === null ? '' : ` · ${w.pr}`}
120 </Text>
121 )
122 })}
123 {snap.testProcesses.length === 0 ? (
124 <Text dimColor>no test runner running</Text>
125 ) : (
126 snap.testProcesses.map((p) => (
127 <Text color={tone(surface, 'warn')}>
128 ▶ pid {p.pid} · {p.elapsed} · {p.command}
129 </Text>
130 ))
131 )}
132 <Text dimColor>read {ageLabel(now - snap.readAt)} ago</Text>
133 </Box>
134 )
135}
136
137const SHOWN_REFUSALS = 6 // source: own choice, the newest refusals on screen
138
139export const RefusalsPanel = (ui: Ui, surface: Surface, refusals: readonly Refusal[]) => {
140 const { Box, Text } = ui
141 if (refusals.length === 0) return <Text dimColor>no call refused this session</Text>
142 return (
143 <Box flexDirection="column">
144 <Text>
145 {group(refusals.length)} refused · wiki pages only through wiki_write · worktrees inside the
146 repo
147 </Text>
148 {refusals.slice(-SHOWN_REFUSALS).map((r) => (
149 <Text color={tone(surface, 'danger')}>
150 {clock(r.at)} {r.tool.padEnd(12)} {r.rule.padEnd(22)} {r.target}
151 </Text>
152 ))}
153 </Box>
154 )
155}
156
157const SHOWN_DECISIONS = 6 // source: own choice, the newest decisions on screen
158
159type AnyDecision = PolicyDecision | GeniusState['decisions'][number]
160const decisionRow = (d: AnyDecision): string =>
161 `${clock(d.at)} ${d.kind.padEnd(6)} ${d.subject.padEnd(28)} ${'from' in d ? `${d.from} → ` : '→ '}${d.to}${
162 d.applied ? '' : ' · observed'
163 }`
164
165const GeniusLine = (ui: Ui, g: GeniusState | null) => {
166 const { Text } = ui
167 if (g === null) return <Text dimColor>genius: not loaded</Text>
168 const c = g.classified
169 const picks = c === null ? [] : [...c.geniuses.map((p) => `${p.agent} · ${p.shape}`), ...c.shapes]
170 const last =
171 c === null
172 ? 'no request graded yet'
173 : `last: ${c.taskClass} → effort ${c.effort} · ${picks.length === 0 ? 'no pattern' : picks.join(' · ')} · ${c.ms} ms`
174 return (
175 <Text dimColor>
176 genius {g.mode} · classifier {g.classifierModel} · {g.skillShapesLoaded} skills · {g.geniusShapesLoaded} genius
177 shapes · {last}
178 {g.classifierError === null ? '' : ` · ${g.classifierError}`}
179 </Text>
180 )
181}
182
183export const PolicyPanel = (ui: Ui, surface: Surface, p: PolicyState | null, g: GeniusState | null) => {
184 const { Box, Text } = ui
185 if (p === null)
186 return (
187 <Box flexDirection="column">
188 <Text dimColor>autopilot: not loaded</Text>
189 {GeniusLine(ui, g)}
190 </Box>
191 )
192 const pressureColor = p.pressure === 'none' ? undefined : tone(surface, 'warn')
193 const decisions: AnyDecision[] = [...p.decisions, ...(g?.decisions ?? [])].sort((a, b) => a.at - b.at)
194 return (
195 <Box flexDirection="column">
196 <Text>
197 <Text bold>{p.mode}</Text>
198 <Text dimColor>
199 {' '}
200 · ladder from the graded rung, loop at medium, pressure at low · quota pressure from {p.quotaPercent} % of
201 5 h · results capped at {group(p.resultCapChars)} chars
202 </Text>
203 </Text>
204 <Text color={pressureColor}>
205 pressure: {p.pressure}
206 {p.charsCut > 0
207 ? ` · ${group(p.charsCut)} chars kept out, about ${group(Math.round(p.charsCut / 4))} tokens (estimated)`
208 : ''}
209 </Text>
210 {GeniusLine(ui, g)}
211 {decisions.length === 0 ? (
212 <Text dimColor>no decision yet</Text>
213 ) : (
214 decisions.slice(-SHOWN_DECISIONS).map((d) => <Text dimColor={!d.applied}>{decisionRow(d)}</Text>)
215 )}
216 </Box>
217 )
218}
219hooks/contextview.ts 25 lines1// Display-only readings of the context window the autopilot measures.
2
3import type { ContextHealth } from './deps'
4
5// A bar of `width` cells: the fill, and where the warn and hard marks fall.
6export const bar = (c: ContextHealth, width: number): string => {
7 const cell = (n: number | null) =>
8 n === null ? -1 : Math.min(width - 1, Math.round((n / c.window) * width))
9 const fill = c.tokens === null ? 0 : Math.round((c.tokens / c.window) * width)
10 const warnAt = cell(c.warn)
11 const hardAt = cell(c.hard)
12 let out = ''
13 for (let i = 0; i < width; i++) {
14 if (i === hardAt) out += '┃'
15 else if (i === warnAt) out += '│'
16 else out += i < fill ? '█' : '·'
17 }
18 return out
19}
20
21export const tokensLeft = (c: ContextHealth): { toWarn: number | null; toHard: number | null } => ({
22 toWarn: c.tokens === null || c.warn === null ? null : c.warn - c.tokens,
23 toHard: c.tokens === null || c.hard === null ? null : c.hard - c.tokens,
24})
25types/index.d.ts 83 lines1export type CortexStatus = 'pending' | 'measured' | 'blocked'
2
3export type CortexEntry = {
4 id: string
5 tool: string
6 startedAt: number
7 status: CortexStatus
8 ms?: number
9 count?: number
10 receipt?: number
11}
12
13export type GroomingKind = {
14 days_since_last_run: number | null
15 stale: boolean
16}
17
18export type CortexStats = {
19 total_memories: number
20 episodic_count: number
21 semantic_count: number
22 active_count: number
23 archived_count: number
24 stale_count: number
25 protected_count: number
26 avg_heat: number
27 total_entities: number
28 total_relationships: number
29 active_triggers: number
30 last_consolidation: string | null
31 has_vector_search: boolean
32 grooming_staleness: Record<string, GroomingKind>
33 grooming_staleness_threshold_days: number
34 readAt: number
35}
36
37export type TurnTally = {
38 turns: number
39 input: number
40 output: number
41 cacheRead: number
42 cacheWrite: number
43 tools: Record<string, number>
44}
45
46// One stage of the spec-to-code pipeline, as observed from the tools the model called.
47export type StageTally = {
48 calls: number
49 blocked: number
50 lastAt: number | null
51 lastTool: string | null
52}
53
54export type Worktree = {
55 path: string
56 branch: string | null
57 isMain: boolean
58 isInsideRepoRule: boolean
59 isRegistered: boolean
60 owner: string | null
61 pr: string | null
62}
63
64export type HygieneSnapshot = {
65 worktrees: Worktree[]
66 testProcesses: { pid: number; elapsed: string; command: string }[]
67 error: string | null
68 readAt: number
69}
70
71declare module 'claude-code' {
72 interface PluginState {
73 'cortex-cockpit': {
74 ledger: CortexEntry[]
75 stats: CortexStats | null
76 statsError: string | null
77 tally: TurnTally
78 stages: Record<string, StageTally>
79 hygiene: HygieneSnapshot | null
80 }
81 }
82}
83