/** * help.ts — command + flag help builder (single source of truth for * `/pygienium-help`). * * `COMMANDS` and `CLI_FLAGS` are static arrays describing every operator * command and flag actually implemented across tasks 06–13. The per-check * command family (`/pygienium-`) is a single generic entry because the * concrete check commands come from the live registry — `buildPygieniumHelpLines` * appends one row per registered `CheckDefinition`, so a newly registered check * appears in `/pygienium-help` with zero edits here. This is what backs the * "add a check = one file + registerCheck, no index.ts changes" guarantee. * * @module pygienium/help */ import { getAllChecks } from "./checks/registry.js"; /** A flag row shown in the help output. */ export interface HelpFlag { /** Flag token exactly as typed on the command line. */ name: string; /** Which commands accept this flag. */ scope: string; /** What the flag does. */ description: string; } /** A command row shown in the help output. */ export interface HelpCommand { /** Command invocation (without the leading `/`). */ usage: string; /** One-line description of what it does. */ description: string; /** Concrete example call. */ example: string; } /** * Flags supported by `/pygienium-` and the operator commands. * Mirrors the arg parsing in `commands.ts` / `modes/all.ts` / `export.ts` * exactly. */ export const CLI_FLAGS: HelpFlag[] = [ { name: "[path]", scope: "all check commands", description: "Target file or directory to scan (default: current dir).", }, { name: "--fix", scope: ", all, resume", description: "Apply fixes (default: scan-only; emits findings only).", }, { name: "--fresh", scope: ", all, resume", description: "Re-dispatch completed checks too — reset their run-state entries and re-run.", }, { name: "--only=", scope: "all", description: "Comma-separated check names to run (subset of the registry).", }, { name: "--no-gitignore", scope: ", all, resume", description: "Don't add `.pygienium/` to the target repo's .gitignore (added by default so runs never stage their own output).", }, { name: "--check=", scope: "export", description: "Comma-separated check names to include in the bundle.", }, { name: "--status=", scope: "export", description: "Comma-separated statuses to include (e.g. complete,failed,skipped).", }, { name: "--out=", scope: "export", description: "Bundle format: `md` (default) or `json`.", }, ]; /** * The operator commands (the per-check `/pygienium-` family is rendered * dynamically from the registry below). Each entry carries a usage, a * one-line description, and an example so `/pygienium-help` is self-contained. */ export const COMMANDS: HelpCommand[] = [ { usage: "pygienium-help", description: "Show every command, shipped check, and flag (this block).", example: "/pygienium-help", }, { usage: "pygienium- [path] [--fix] [--fresh]", description: "Run one isolated sub-agent that scans a target, applies fixes with --fix, and emits a findings+changes report. Resume-aware: a completed/skipped check is skipped unless --fresh re-runs it.", example: "/pygienium-comments src --fix", }, { usage: "pygienium-all [path] [--fix] [--fresh] [--only=a,b]", description: "Run every registered check in sequence under one resumable run-state with a unified status strip; writes .pygienium/all-summary.md. --fresh re-runs completed checks; --only narrows to a check subset.", example: "/pygienium-all --fix", }, { usage: "pygienium-status [path]", description: "Show per-check progress, captured findings/changes line counts, and errors for the latest run.", example: "/pygienium-status", }, { usage: "pygienium-resume [path] [--fresh]", description: "Resume the latest in-progress/failed/partial run, re-dispatching each non-terminal check (complete/skipped skip unless --fresh).", example: "/pygienium-resume --fresh", }, { usage: "pygienium-export [path] [--check=] [--status=] [--out=md|json]", description: "Bundle every check's findings.md + changes.md into .pygienium/export.{md|json}.", example: "/pygienium-export --out=json", }, ]; /** Back-compat alias for the flag array. */ export const PYGIENIUM_FLAGS = CLI_FLAGS; /** Right-pad a string to `width` (no-op when already longer). */ function pad(s: string, width: number): string { return s.length >= width ? s : s + " ".repeat(width - s.length); } /** * Build the full `/pygienium-help` text. Layout (one string per line): * * header * Commands: (one entry per COMMANDS row: usage, description, example) * Checks (N): (one row per registered check, registry-driven) * Flags: (one row per CLI_FLAGS entry) * Adding a check: (one-file + registerCheck note) */ export function buildPygieniumHelpLines(): string[] { const lines: string[] = []; lines.push("Pygienium — code hygiene for pi", ""); lines.push("Commands:"); const usageWidth = Math.max(...COMMANDS.map((c) => c.usage.length)) + 2; for (const cmd of COMMANDS) { lines.push(` /${pad(cmd.usage, usageWidth)}${cmd.description}`); lines.push(` ${pad("", usageWidth)}e.g. ${cmd.example}`); } lines.push(""); const checks = getAllChecks(); lines.push(`Checks (${checks.length}):`); if (checks.length === 0) { lines.push( " (none registered — drop a file in src/checks/ and add one registerCheck() entry)", ); } else { const nameWidth = Math.max(...checks.map((c) => c.name.length)) + 2; for (const c of checks) { lines.push(` /pygienium-${pad(c.name, nameWidth)}${c.description}`); } } lines.push(""); lines.push("Flags:"); const flagNameWidth = Math.max(...CLI_FLAGS.map((f) => f.name.length)) + 2; const flagScopeWidth = Math.max(...CLI_FLAGS.map((f) => `[${f.scope}]`.length)) + 2; for (const f of CLI_FLAGS) { lines.push( ` ${pad(f.name, flagNameWidth)}${pad(`[${f.scope}]`, flagScopeWidth)}${f.description}`, ); } lines.push(""); lines.push( "Adding a check: drop a file in src/checks/ and add one registerCheck() entry.", ); lines.push("No index.ts command-wiring changes are required."); return lines; }