# Skills catalog

Persona-specific skills referenced by `docs/tasks/*.md` track specs in the loop planning repo. Each entry follows `library + concepts + gotchas` granularity — generic categories ("TypeScript expert", "Testing expert") are not acceptable; the persona must point at concrete libraries / domains the implementer must already know.

Entries are categorized so spec authors can scan by area. When adding a new persona, drop it in the closest category — create a new category only when 2+ entries would land in it.

---

## Language / Module structure

- **TypeScript ESM module design expert** — Node ESM (`type: "module"`), `.js` extension in import specifiers, top-level `await`, barrel re-exports, ESM ↔ CJS interop gotchas
- **TypeScript discriminated union design expert** — `z.literal` discriminators, exhaustiveness via `never`, `as const` narrowing, no-fail-open at switches
- **TypeScript compiler API expert** — `typescript` npm package, `ts.createSourceFile(name, text, ts.ScriptTarget.Latest, /*setParentNodes*/ true)`, `ts.forEachChild` recursive visitor, type guards (`ts.isCallExpression`, `ts.isPropertyAccessExpression`, `ts.isIdentifier`), `ts.SyntaxKind` enum, `SourceFile.getLineAndCharacterOfPosition(pos)` for human-readable positions (1-indexed line in display), JSDoc trivia stripped by default (the parser DOESN'T emit comment nodes — perfect for "real call site" detection), no need to add `typescript` as a runtime dep (it's already a devDependency in opensquid)
- **Rust async expert (tokio)** — `tokio::spawn`, `tokio::net`, async traits, `Arc<Mutex>` vs message passing, cancellation safety, runtime feature flags
- **Quarantine archaeology expert** — reading pre-reset code in `src.legacy/`, mapping symbols to current architecture, separating "still useful" from "obsolete", git-blame-driven recovery

## Schema / Validation

- **Zod schema design expert** — `zod` npm package, discriminated unions, `z.infer`, `.brand`, narrowing patterns, `safeParse` vs `parse`, error shape preservation
- **JSON Schema / Ajv expert** — Draft 2020-12, `ajv` npm package, schema bundling, `$ref` resolution, custom keywords, oneOf vs anyOf vs allOf semantics
- **YAML schema design expert** — `yaml` npm package, anchors + aliases pitfalls, bundled schemas, editor-friendly key ordering, forgiving validation patterns

## Architecture / Audit

- **Architectural design expert** — boundary discipline, substrate vs consumer separation, dependency direction, file-size enforcement, async vs sync surface choice
- **Architectural-invariant test design expert** — production-grade enforcement of architectural decisions via test (single-caller invariants, layer-boundary invariants, "no caller in X may call Y" rules); the source-grep vs AST-walk trade-off (grep matches strings in JSDoc/comments — fragile; AST-walk parses the real call graph — ~100ms per file but robust); test placement convention (name the file after the invariant — e.g. `appendturn_sole_caller.test.ts` — not as a dumping-ground `architecture.test.ts`); INCLUDE-and-assert-location pattern (count == 1 AND location == expected) vs EXCLUDE-and-count (looser, passes at count==0); failure-message discipline (cite the offender file + line so triage is one-step + suggest the fix path); stale-FIXME-as-drift discipline (FIXMEs describing already-shipped work are documentation drift — recharacterize as architectural-contract JSDoc, don't delete the prose); the "JSDoc IS the architecture doc" principle — a separate architecture.md drifts; per-class JSDoc is the canonical statement
- **Audit / code review expert** — fail-open detection, file-size discipline, idiom-check, no-`console.log`, no-`process.exit` outside CLI, license-grep, engine-vocabulary leak
- **Two-stage validation gate design expert** — capture-stage user validation + promotion-stage outcome validation; eviction immunity invariants; block-only-testable patterns
- **Tool-sequence FSM design expert** — Letta-style finite-state-machine over agent tool-call sequences ("after X tool, next tool must be Y or Z") adapted for opensquid via `session_tool_history` primitive + Stop-hook + exit-2 retry-loop pattern; heuristic-detection limitations (regex-only consumption check is imperfect — false-negatives slip drift through, false-positives cost a recovery turn); the ~1s visible-flash trade-off (Claude Code has no PreAssistantMessage hook, so Stop+exit-2 fires AFTER prose is emitted); plugin-vs-direct-install Stop hook split (GitHub issue #10412 — plugin Stop hooks halt, direct `.claude/hooks/` Stop hooks loop); when to pair with `destination_check` rule for LLM-judged consumption detection vs deterministic-heuristic-only
- **Security capability model expert** — capability-grant patterns, denylist sealing, override env vars, audit log row shape, blast-radius reasoning, sandbox depth/step caps, proto-pollution defense via `Object.hasOwn`, allow-list discipline, fail-closed error paths
- **Performance / overhead analysis expert** — hot-path branch cost, cold-start vs warm-path, allocation budgets, fs-read budgets per hook fire, microsecond-class budgets
- **TypeScript exhaustiveness checking expert** — discriminated-union switches with `never` default, `as const` narrowing, no-fail-open at every switch, `assertNever(x: never): never` helper pattern
- **Regression testing expert** — preserve every test in a contract suite, identify the single justified rewrite, audit-grep for orphan symbols, behavior-change documentation in CHANGELOG

## Parser / Interpreter / DSL

- **chevrotain npm package expert** — `chevrotain` v11 (pin against opensquid Node 20+), `createToken` token primitives, `Lexer`, `CstParser` LL(k) grammar DSL, perf-guide singleton-parser pattern, longest-match token ordering (multi-char operators first), `performSelfAnalysis()` lock against left-recursion, error recovery, `getBaseCstVisitorConstructor()` for CST→AST
- **Tree-walking interpreter design expert** — AST node dispatch via discriminated union, scope binding via `Map<string, unknown>`, short-circuit evaluation for `&&`/`||`, sandbox primitives (depth-cap, step-cap, prototype-pollution defense via `Object.hasOwn`), error class hierarchy (`InterpreterLimitError` / `InterpreterRuntimeError`), strict vs loose equality semantics
- **CST→AST visitor pattern expert** — chevrotain `BaseCstVisitor` subclassing, `validateVisitor()` lock against typo'd method names, position-preserving lowering (chevrotain token `startOffset` → AST `offset` field), left-fold associativity for `MANY`-produced rhs sequences, exhaustive node-kind dispatch
- **TypeScript function allow-list design expert** — frozen `Record<string, FnHandler>` map via `Object.freeze`, no spread / no late mutation invariant, audit-grep for `FUNCTIONS\[` assignment, 5-point expansion checklist (cite-skill / pure / attack-surface / coercion-table / test-gate), JSDoc coercion tables
- **ReDoS mitigation expert** — catastrophic backtracking patterns (`(a+)+$`-class), V8 RegExp engine pathologies, comparable-project landscape (Cerbos Go regexp, Cloudflare re2-wasm, OPA Rego), threat-model scoping by trust boundary (first-party vs third-party pack ecosystem), node `vm` module rejection (CVE-2023-29017 / VM2)
- **re2-wasm npm package expert** — `re2-wasm` / `node-re2` / `re2js` package landscape + maintenance verification, `new RE2(pattern).test(s)` API, PCRE-subset that RE2 rejects (backreferences `\1`, lookarounds `(?=...)`/`(?<=...)`, possessive quantifiers `a++`), WASM cold-start (~50ms), bundle-size delta (~250KB)

## MCP / Tool servers

- **MCP SDK expert (`@modelcontextprotocol/sdk` npm package, TS)** — `Server` + `ListToolsRequestSchema` + `CallToolRequestSchema`, `StdioServerTransport`, version handshake, `zodToJsonSchema` integration, read-only-by-default tool design
- **MCP write-tool design expert** — idempotency keys, side-effect surface (storage row inserts vs in-memory state), dispatcher-bypass auditing, harness re-prompt safety, capability gating on write tools
- **JSON-RPC 2.0 over stdio expert** — line-delimited framing, request/response id pairing, error code conventions (-32700..-32603 reserved, -32000..-32099 custom), `id: null` notification edge cases
- **loop-engine JSON-RPC client expert** — `EngineClient` wrapper from `src/engine/client.ts`, custom `ENGINE_ERROR` codes (-32000..-32004), `ManifestAssembleParams` side-effect quirks, LOOP_HOME pinning, stderr drain

## Storage / RAG / Embeddings

- **lru-cache npm package expert** — `lru-cache` v11 (already a dep in opensquid at `package.json:69`), `max` sizing, `get`/`set` API, `cache.size` property (not method) in v11, `cache.max` read-only, `clear()` for hot-reload paths, manual hit/miss counting (v11 doesn't expose counters by default)
- **libsql migration expert** — `@libsql/client` npm package, schema migrations via `db.execute(...)` + version pragma, idempotent ingest (`INSERT OR IGNORE`, `UNIQUE` constraints), scope tagging, Turso remote URL vs local file URL
- **Turso replication / cloud sync expert** — embedded replica + remote URL pattern, LWW per-row conflict policy, scope-keyed selective sync, replication interval tuning, opt-in config surface, sensitive-row exclusion
- **Vector search / RRF expert** — Reciprocal Rank Fusion (RRF_K=60 industry default), hybrid semantic + text fusion, score normalization caveats, top-k vs candidate-set sizing
- **Qwen3 embedder integration expert** — Qwen3-Embedding-4B via Ollama (`http://localhost:11434/api/embed`), 2560-dim vector shape, batch vs per-call cost, fallback-on-Ollama-down patterns
- **HNSW persistence + cold-start expert** — `hnsw_rs` crate, rehydrate-on-start patterns, status-aware failure surface, `HNSW_MAX_ELEMENTS` caps, .vec sidecar discipline

## Hooks / Harness integration

- **Claude Code hooks expert** — `~/.claude/settings.json` hooks contract, `SessionStart`/`SessionEnd`/`Stop`/`PreToolUse`/`UserPromptSubmit` event shapes, stdin JSON payload, stderr-for-drift convention, exit-code semantics (0=allow, 2=block), `bin` entry vs inline `node` command, fail-open invariant
- **System integration test / CI fixtures expert** — spawning bin entries with constructed stdin in CI, exit-code + stdout + stderr assertions, no-op detection via observable side effect, fixture stability across Node versions
- **harness Monitor stream-source expert** — authoring a long-running command for the Claude Code `Monitor` tool (`persistent: true`): one stdout LINE = one in-chat event, events arrive on their own schedule (NOT user replies), child-side buffering is the #1 gotcha (a Node child `process.stdout.write(line + '\n')` per event flushes per call — no `stdbuf` needed, unlike piped C stdio), graceful run-forever (no exit), the session-scoped lifecycle limitation (dies at session end → agent re-starts at session start); contrast with cron/interval polling (`CronCreate`/`ScheduleWakeup`) which this explicitly replaces

## CLI / UX

- **commander v12 CLI argument parsing expert** — `commander` npm package v12, subcommand action chaining, `parseAsync`, `--help` output discipline, error exit codes
- **CLI + UX expert** — clear `--help` strings, idempotent verbs, dry-run flags, `--yes` confirmation policy, TTY-vs-non-TTY behavior, exit-code conventions

## Cross-platform / Lifecycle

- **Unix domain socket singleton design expert** — `node:net` + `tokio::net::UnixListener`, path-byte limits (macOS 104, Linux 108), stale-socket cleanup, `proper-lockfile` serialization, `detached + stdio:'ignore'` patterns
- **Subprocess lifecycle expert** — `node:child_process.spawn`, stderr drain, SIGTERM → SIGKILL escalation, pidfile patterns, detached daemon survival across parent exit
- **opensquid hook-bin fast-path expert** — the four universal hook bins (`pre-tool-use.ts`, `user-prompt-submit.ts`, `stop.ts`, `session-end.ts`) at `src/runtime/hooks/`: their unconditional sequence (parse → side-effect writes → `loadActivePacks` → `buildRegistry` → `dispatchEvent`); where the idle-skip slot is (AFTER existing side effects to preserve observable behavior, BEFORE `loadActivePacks` to avoid the cost); the stat-based liveness probe pattern (cheap `fs.stat` on `automation.flag` + `active-task.json`); when to short-circuit vs always-run (state-transitioning tools like `TaskCreate`/`TaskUpdate` must NEVER skip dispatch since they may write the active-task signal); fail-open discipline at the fast-path boundary (stat-error of unknown kind → assume engaged, run the dispatcher — never silently swallow a gate by exiting early); shared regex constants kept in sync with personal-pack rules via a SUPERSET discipline (false-positive = cheap pack load; false-negative = missed gate)
- **session-liveness probe expert** — answering "is this session-id plausible RIGHT NOW?" from filesystem evidence alone: stat-based freshness checks on session-state files (`<home>/sessions/<id>/active-task.json` and `state/tool-ledger.json`), mtime threshold semantics (default ~30min, env-configurable), `--force` escape hatch for first-fire-before-any-hook scenarios, fail-loud-not-silent when a CLI is about to write state to an implausible session-id (the cross-process-coordination failure mode behind silent-gate-disablement); contrast with active-task-presence checks (which answer "is a task loaded?", not "is the session itself alive?")
- **cross-platform app-config path resolution expert** — locating per-host config files across macOS/Windows/Linux: macOS `~/Library/Application Support/<App>/`, Windows `%APPDATA%\<App>\`, Linux `~/.config/<App>/` (XDG); `process.platform` branching, `homedir()` + `env.APPDATA` fallbacks, `path.join` (never string concat); "app installed?" detection via parent-dir existence (create file only if dir exists, skip otherwise — don't fabricate an app's config tree)
- **chokidar file-tail / append-stream expert** — `chokidar` npm package (already an opensquid dep, see `src/runtime/agent_bridge/transport_bridge.ts`): watching an append-only JSONL file that may not exist yet (fires `add` on creation, `change` on append), `{ ignoreInitial: true }` for new-only (the `tail -n 0 -F` equivalent — skip backlog), `awaitWriteFinish: { stabilityThreshold, pollInterval }` to coalesce partial-write events, byte-offset + leftover-buffer pattern for reading only appended bytes (stat size vs lastOffset; size < offset ⇒ truncation/rotation reset), partial-trailing-line carry across events; FSEvents quirks on macOS, polling fallback

## Domain-specific (opensquid runtime)

- **Pack / cartridge loader expert** — `src/cartridge/`, manifest.yaml schema, preload vs lazy load, scope hierarchy (user / project / skill / global / team)
- **opensquid pack loader expert** — `src/packs/loader.ts` + `src/packs/yaml.ts` (`parseYamlFile` error formatter at lines 86-93), schema-failure surfacing with source path, load-time vs eval-time validation timing, Zod field-path threading
- **opensquid pack-format expert** — `packs/builtin/` directory structure, `manifest.yaml` + `SKILL.md` + fixtures layout, `# Example — not load-bearing` header convention, BEFORE.md/SKILL.md/manifest.yaml triple-file pedagogical pattern
- **Evaluator + rule process expert** — `src/runtime/evaluator.ts` unified rule + checkpoint + memo paths, `ProcessStep[]` interpreter, durable wrap + memo wrap order, no-eval policy, `verdict` primitive semantics
- **opensquid runtime evaluator integration expert** — `src/runtime/evaluator.ts` unified rule + checkpoint + memo paths, `evalCondition` delegate boundary, header-docstring discipline, no-eval policy, regex-constant deletion targets (`EQ_PATTERN`/`BARE_PATTERN`/`NUM_CMP_PATTERN`/`BOOL_CMP_PATTERN`/`STR_PATH_EQ_PATTERN`)
- **Recall-injection / context-pre-prepend expert** — `UserPromptSubmit` hook pattern, top-K recall selection, system-reminder framing, configurable per-codex thresholds, token-budget guards
- **opensquid skill.yaml author expert** — authoring `skills/<name>/skill.yaml` against `schemas/skill.schema.json`: `track_check` vs `destination_check` rules, `ProcessStep` chains (`call`/`args`/`as`/`if`/`on_empty`), `when_to_load` triggers (`event_type` / `tool_match`), `if:` clause authoring against the frozen 5-function allow-list (`len`/`contains`/`startsWith`/`endsWith`/`match`), `parseExpression()` pre-write validation discipline, verdict-level selection (`pass`/`block`/`warn`/`surface`), copy-precedent from `workflow`/`git`/`d9-guard` skills, no-agent-loop constraint (skills emit verdicts, never orchestrate)
- **opensquid chain-state machine expert** — the per-session chain state machine at `~/.opensquid/sessions/<id>/chain-state.json`: 7 stages (`idle → scoping → researched → spec_authored → tasks_loaded → phases_in_flight → phases_complete`), idempotent same-stage writes, append-only `history` audit trail, enrichment fields (`pre_research_path`, `spec_path`, `task_ids`) that accumulate across transitions, the writer-distribution pattern (5 transition points: UserPromptSubmit scope-intent, PreToolUse for research/spec/TaskCreate, log_phase MCP tool), session-scoped lifecycle (`SessionEnd` clears), session_state.ts-style helper pattern (`transitionChainStage` / `readChainState` / `readChainStage` / `clearChainState`), no-throw read posture (absent/malformed ⇒ null ⇒ caller defaults to `'idle'`); contrast with the active-task signal (single value, no history) — chain-state is a multi-stage progression with audit
- **opensquid directive verdict / structured-protocol expert** — the `level: 'directive'` extension to the `Verdict` schema: `next_action: { skill?, tool?, args?, rationale }` shape (skill XOR tool, args is arbitrary JSON, rationale is mandatory and human-readable), the dispatcher aggregation pattern (mirrors `contextInjections: string[]` for `directives: Directive[]`), the UserPromptSubmit envelope serialization (`additionalContext` carries a fenced JSON block with a clear marker `⛔ DIRECTIVE` so the agent's reader surfaces it AND a future enforcer can parse it without re-parsing English), the no-agent-loop invariant (opensquid still does NOT invoke the named skill — it emits a structured recommendation; the agent dispatches), the coexistence semantics with `block` verdicts (block wins on exitCode 2; directives still ride through on stdout so the next-prompt context carries both); copies the `inject_context` aggregation precedent at `src/runtime/hooks/dispatch.ts:202-214` but with stronger typing
- **opensquid dispatcher-boundary integration expert** — adding a new gate at the dispatcher's per-skill walk boundary (`src/runtime/hooks/dispatch.ts` between `matchesEvent(...)` and `for (const rule of skill.rules)`): per-fire caching of disk-derived state (one stat per session-scoped file across N skills' precondition evaluations — the cache key is `(sessionId, fire-id)`); fail-open in the engaged direction (ENOENT = absent, any other error = assume present, run the rules); the load-bearing distinction from rule-level `if: auto.value == true` guards (dispatcher-boundary is ONE eval per skill, rule-level is one eval per `process` step); AND-semantics for the new field (every entry must hold; an empty list trivially holds; back-compat with every existing skill); the migration arithmetic — hoisting a skill's leading `is_automation_mode` call into `requires:` saves both that primitive call AND every subsequent step's `if: auto.value == true` guard re-evaluation; integration with the existing trigger filter + `unloads_when` gates (the new boundary slots BETWEEN them); contrast with the hook-bin fast-path (bin-level, all-or-nothing; this is per-skill, declarative)
- **opensquid agent-bridge daemon expert** — the WAB warm-agent-chat-bridge (`src/runtime/agent_bridge/`): `daemon.ts` (long-running process; PID lock `~/.opensquid/agent-bridge.lock`; per-project chokidar inbox watcher; api-mode lazily constructs the Anthropic SDK client, subscription-mode materializes `~/.opensquid/agent-bridge/mcp-config.json` + spawns Claude with subscription auth, NO ANTHROPIC_API_KEY/metered cost), `transport_bridge.ts` (inbox JSONL → typed events), `dispatcher.ts` (`ChatDispatcher.handleInbound` → batch/coalesce → `runAgentTurn` (api) | `runAgentTurnSubscription` (subscription) → `onReply`; `projectUuidBySlug` mapping), `agent_loop.ts` (`runAgentTurn` — full Anthropic Messages API turn, tool-use loop ≤8, prompt-cache, persisted history via `session_manager.ts`), CLI `agent-bridge {start|stop|status|restart|run-foreground}` (`resolveProjectUuidFromEnv` → cwd-walk); the daemon is autonomous + project-scoped — distinct from the harness-Monitor `chat watch` path which pushes into one live interactive session
- **opensquid MCP setup-wizard / config-writer expert** — `src/setup/cli/mcp.ts` (`runMcpWizard` testable core; `--dry-run`/`--opensquid-root`/`--no-detect-project-cleanup` flags; injectable `McpCliDeps` for tests) + `src/setup/wizard/mcp-writer.ts` (`writeOpensquidMcp(configPath, repoRoot)` — PATH-generic, not Code-specific; `buildDesiredEntries(root)` = the two server entries → `dist/mcp/server.js` + `dist/mcp/chat-bridge-server.js`; `projectOpensquidMcp` merge that PRESERVES every non-`mcpServers` key via spread round-trip; `'@opensquid': true` marker for idempotent re-run + scoped upgrade; `.bak` snapshot before mutation); the writer works on ANY host config file with the `{mcpServers:{}}` shape (Claude Code `~/.claude.json`, Claude Desktop, Cursor) — host knowledge is just the path
- **opensquid function primitive author expert** — authoring `src/functions/<name>.ts` primitives: `FunctionDef<TArgs,TResult>` shape (`name`/`argSchema` Zod `.strict()`/`durable`/`memoizable`/`costEstimateMs`/`execute`), `Result<T,FunctionError>` no-throw error model (`ok`/`err` from `../runtime/result.js`), `EvalCtx` access (`event`/`event.cwd`/`bindings`/`sessionId`), read-only vs side-effecting discipline, registration in `buildRegistry()` (`src/runtime/bootstrap.ts`), distinction from the frozen `if:` 5-function allow-list (primitives are `call:`-able, NOT `if:` functions)
- **opensquid active-task lifecycle expert** — the `active-task.json` signal (`~/.opensquid/sessions/<id>/active-task.json` = `{id,subject,started_at}` | absent) per the unified-evaluator design: written on TaskCreate/TaskUpdate(in_progress), cleared on completed, archived at SessionEnd (rule #16); the harness-TaskCreate vs `mcp__opensquid__TaskCreate` distinction (opensquid must write its own signal); the d9-guard.sh anti-pattern (reading tasks from Stop stdin, which CC doesn't populate); the gate-reads-it / writer-writes-it coupling that the G-track severed; fail-closed-not-open on a missing/malformed signal
- **loop-engine RPC handler author expert (Rust)** — adding a JSON-RPC method to `engine/src/serve.rs`: the `match method { "memory.create" => … }` dispatch arm pattern, `#[derive(Deserialize)]` param structs, `DispatchError::InvalidParams`, threading the dispatch context deps (`storage`/`llm`/`embedder`/`vector_index`), wrapping an existing `engine::memory::*` library fn (NOT reimplementing), `serde_json::json!` result shaping; the engine pre-push gate (fmt + clippy -D warnings + public-api snapshot + nightly + test) — a new public RPC shifts the public-api snapshot, update deliberately; engine-vocabulary discipline (substrate types only, no consumer concepts)
- **memory-compression lifecycle expert** — `engine/src/engine/memory/{compress,lifecycle,cycle,store}.rs`: `compress(window, config) -> Mc` (detail→gist LLM summary), `derived_from` predecessor chains + `mem-c-<hex>` ids, citation transfer (`consumed_by_user_lessons = Σ predecessors`), `recompute_citation_counts` / `get_by_id_chasing_derived_from` (citations survive predecessor deletion), cycle/depth guards (max 16), recursive compression; the append-only + user-immunity invariant (`delete(force)` refuses engine-initiated deletes of user-cited memories); the LOCKED policy (D2) — predecessor deletion is the verified, satisfaction-gated, immunity-safe TERMINAL step, never automatic; recall-replay as the compression-quality gate
- **opensquid skill-lifecycle expert** — the skill load/unload machinery: `unloads_when` (skill field, `types.ts`), `tick.ts` (`createTick`/`advanceTick`/`resetTick` — `prompt_submit` bumps `turnsSinceActivation`, `stop`/`session_end` latch `taskCompleted`/`sessionEnded`, `tool_call` no-op), `unload_conditions.ts` (`shouldUnload` OR-walk over `UnloadCondition` = `active_task_completes`|`session_ends`|`idle_n_turns:n`; `TickState` shape), `pinned_skills.ts` (universal+preload = pinned/resident, exempt from unload; dynamic = unloadable); the "Phase 4 wiring" gap (machinery has ZERO live call sites — `shouldUnload`/`advanceTick` never called by the dispatcher); per-PROCESS persistence requirement (hooks are separate processes, so tick state lives in `session_state.ts` like the tool-ledger, NOT an in-memory Map); the recall-load side (`recall_pre_inject.ts`) is SEPARATE + out of scope (per-turn-fresh `additionalContext`, harness-owned window — not accumulated)
- **LLM-classifier integration expert** — `llm_classify` primitive (`src/functions/llm.ts`): args `{model(alias), prompt, allowed_labels, timeout_ms?}`, returns one label or `'UNCERTAIN'`, `durable+memoizable` `costEstimateMs:3000`; model-neutral alias resolution (`fast_classifier`, never literal model names); cheap-first composition (gate the LLM step behind a deterministic `if:` so it only fires on a pre-filter hit — zero per-turn cost); fail-quiet on `UNCERTAIN`; the regex-pre-filter → `llm_classify` → `verdict` single-rule pattern (d9-guard / pause-prompt-extended precedent)

## Migration / Cleanup

- **Auto-memory migration expert** — `~/.claude/projects/<slug>/memory/*.md` reader, frontmatter extraction, idempotent ingest via stable external_id, scope mapping (`scope=user` default), dry-run + diff surfaces
- **Quarantine cleanup expert** — safe deletion of unimported code, import-graph traversal (`tsc --noEmit` + `eslint-plugin-import` unused detection), `git rm` discipline, per-file commit rationale, rollback safety via single-commit chunks

## Documentation / Pedagogy

- **Technical writing expert (developer docs)** — reference-doc structure (operator tables, function tables, gotchas), runnable code samples (extract + verify via parser), cross-link discipline, tone matched to comparable projects (Cerbos conditions, OPA Rego, GitHub Actions expressions), keep-a-changelog CHANGELOG conventions
- **Pedagogical example design expert** — before/after compression demos (BEFORE.md prose vs YAML compressed form), real-vs-toy scenario selection, fixture/expected-output pairing in `fixtures/` subdirs, operator-family variety across examples (logical / regex / indexing), `# Example — not load-bearing` header to prevent calcification

## Cross-cutting (every track touches these)

- **Engine-vocabulary discipline expert** — substrate types stay at substrate boundary, no `Authorship` / `BlockReason` / `PromotionConfig` leak into user-facing strings or MCP descriptions
- **Pre-push checklist expert** — opensquid: typecheck + lint + format:check + test + build; engine: fmt + clippy + public-api +nightly + test; gate enforcement before commit
- **Testing + build expert** — `vitest`, fixture discipline, skip-if-no-binary patterns for live integration, `pnpm build` clean, no leaked test artifacts
