/** * checks/registry.ts — pluggable check registry. * * A `CheckDefinition` describes one hygiene check (e.g. `comments`, `complexity`). * The registry is a module-level `Map` so that adding a check only requires a * new file in `src/checks/` plus one `registerCheck(def)` call — no changes to * `index.ts` command wiring. At startup, `index.ts` iterates the registry and * auto-registers a `/pygienium-` command per definition. * * Lifecycle of a single check run (orchestrated by `src/modes/check-runner.ts`): * Q0 recon (shared) → analysis sub-agent (buildScanTask) → * fix sub-agent (buildFixTask, only with --fix) → verify gate → cleanup. * * @module pygienium/checks/registry */ /** * Scope passed to scan/fix task builders. Resolved from the command args: * a positional path (absolute or relative to `cwd`) plus parsed flags. */ export interface CheckScope { /** Absolute working directory the check operates on. */ cwd: string; /** Target path (absolute) the check scans; defaults to `cwd` when none given. */ target: string; /** Whether fixes should be applied (the `--fix` flag). */ fix: boolean; /** Remaining raw tokens after flag parsing, for check-specific use. */ rest: string[]; } /** * Verifies preconditions before a check runs its analysis phase. Returns an * error string when the check cannot proceed (e.g. no source files match), * or `undefined` when the gate passes. Implemented per-check so generic * checks can bail early without spawning an agent. */ export type CheckGate = ( cwd: string, ) => Promise | string | undefined; /** * Optional post-analysis (+ optional fix) verify hook. Confirms the check * actually produced its artifacts (e.g. `findings.md`/`changes.md`). Returns an * error string to fail the verify phase, or `undefined` to pass. When omitted, * the verify phase falls back to re-running {@link CheckDefinition.gate}, * preserving the historical behaviour for checks that have nothing to verify. */ export type CheckVerify = ( scope: CheckScope, ) => Promise | string | undefined; /** * The structured task string handed to a sub-agent. `buildScanTask` produces * the analysis prompt; `buildFixTask` consumes the findings text the scan * agent emitted and produces a fix prompt. * * Task builders may be async: a check can pre-compute deterministic candidates * (e.g. an import-graph scan) before assembling the prompt, so the sub-agent's * job is to verify/refine rather than re-derive everything from scratch. */ export type BuildScanTask = ( cwd: string, scope: CheckScope, ) => string | Promise; export type BuildFixTask = ( cwd: string, scope: CheckScope, findings: string, ) => string | Promise; /** * Definition of a single pluggable hygiene check. */ export interface CheckDefinition { /** Lowercase kebab command suffix → `/pygienium-`. Must be unique. */ name: string; /** Human label shown in help and status strips. */ label: string; /** One-line description for `/pygienium-help`. */ description: string; /** * Name of the agent definition (from `agents/*.md`) used for the analysis * phase. The fix phase uses the `fixer` agent unless `fixAgentName` * overrides it. */ agentName: string; /** Optional override for the fix-phase agent (defaults to `fixer`). */ fixAgentName?: string; /** Identifier of the phase-strip phase this check belongs to (task 05). */ phaseId: string; /** Builds the analysis sub-agent task. */ buildScanTask: BuildScanTask; /** Builds the fix sub-agent task from scan findings. */ buildFixTask: BuildFixTask; /** * Precondition gate. Returning a string skips the check (recorded as * `skipped`); returning `undefined` proceeds normally. */ gate: CheckGate; /** * Optional verify hook confirming artifacts landed (see {@link CheckVerify}). * Falls back to re-running `gate` when omitted. */ verify?: CheckVerify; } const registry = new Map(); /** * Register a check. Throws on duplicate names so wiring mistakes surface * loudly at startup rather than silently shadowing a command. */ export function registerCheck(def: CheckDefinition): void { if (!def.name || !/^[a-z0-9][a-z0-9-]*$/.test(def.name)) { throw new Error( `Invalid check name "${def.name}": must be lowercase kebab (e.g. "comments").`, ); } if (registry.has(def.name)) { throw new Error(`Duplicate pygienium check name: "${def.name}".`); } registry.set(def.name, def); } /** Look up a registered check by name. */ export function getCheck(name: string): CheckDefinition | undefined { return registry.get(name); } /** All registered checks in insertion order. */ export function getAllChecks(): CheckDefinition[] { return [...registry.values()]; } /** Test-only: reset the registry between tests. */ export function clearChecks(): void { registry.clear(); }