Initial commit: pygenium as git submodule
This commit is contained in:
139
src/checks/registry.ts
Normal file
139
src/checks/registry.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
Reference in New Issue
Block a user