140 lines
4.8 KiB
TypeScript
140 lines
4.8 KiB
TypeScript
/**
|
|
* 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-<name>` 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> | 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> | 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<string>;
|
|
export type BuildFixTask = (
|
|
cwd: string,
|
|
scope: CheckScope,
|
|
findings: string,
|
|
) => string | Promise<string>;
|
|
|
|
/**
|
|
* Definition of a single pluggable hygiene check.
|
|
*/
|
|
export interface CheckDefinition {
|
|
/** Lowercase kebab command suffix → `/pygienium-<name>`. 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<string, CheckDefinition>();
|
|
|
|
/**
|
|
* 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();
|
|
}
|