# Pygienium Code hygiene for [pi](https://github.com/earendil-works/pi-coding-agent) — isolated sub-agent checks that scan a target, apply fixes, and emit a findings + changes report. Inspired by piolium's sub-agent loops. Pygienium runs **highly-structured hygiene passes** over a repo to clean up the common quality issues LLM-generated code accumulates: restating comments, shallow pass-through modules, dead exports/files, and redundant defensive guards. Each check is an isolated, resumable sub-agent run whose progress lands in a single inspectable run-state file. ## Install Pygienium is a local pi extension under `~/.pi/agent/extensions/pygienium/`. Pi loads it via `pi.extensions` in `package.json` (entry `./src/index.ts`). ```sh # from the extension root bun install # optional: only needed for typecheck/test dev deps bun run typecheck bun test ``` Pi auto-discovers the extension from this location via the `pi.extensions` entry in `package.json` — no settings.json config is needed. On load it emits a TUI notification `Pygienium loaded. Run /pygienium-help for available checks and flags.` (only when a dialog-capable UI is available). ## Commands Every command accepts a `[path]` target (default: the current directory) and is resumable — progress is persisted to `/.pygienium/run-state.json`. | Command | What it does | | --- | --- | | `/pygienium-help` | Print every command, shipped check, and flag. | | `/pygienium- [path] [--fix]` | Run one isolated sub-agent that scans a target, applies fixes with `--fix`, and emits a findings + changes report. | | `/pygienium-all [path] [--fix]` | Run every registered check in sequence under one resumable run-state. | | `/pygienium-status [path]` | Show per-check progress, artifact line counts, and errors for the latest run. | | `/pygienium-resume [path] [--fresh]` | Resume the latest in-progress/failed/partial run; complete/skipped checks skip unless `--fresh`. | | `/pygienium-export [path] [--check=] [--status=] [--out=md\|json]` | Bundle every check's `findings.md` + `changes.md` into `pygienium/export.{md\|json}`. | ## Flags | Flag | Scope | Description | | --- | --- | --- | | `[path]` | all check commands | Target file or directory to scan (default: current dir). | | `--fix` | ``, `all`, `resume` | Apply fixes (default: scan-only; emits findings only). | | `--fresh` | `resume` | Re-dispatch completed checks too — reset their run-state entries and re-run. | | `--check=` | `export` | Comma-separated check names to include in the bundle. | | `--status=` | `export` | Comma-separated statuses to include (e.g. `complete,failed,skipped`). | | `--out=` | `export` | Bundle format: `md` (default) or `json`. | ## Checks The five shipped checks live in [`src/checks/`](./src/checks/) and self-register on load. `/pygienium-help` lists whichever checks are currently registered, so this table and the live help always agree on the registered set. | Command | Check | Agent | What it fixes | | --- | --- | --- | --- | | `/pygienium-comments` | comments | `scanner` / `fixer` | Remove low-value/restating comments, tighten verbose ones, keep "why" comments. | | `/pygienium-deep-modules` | deep-modules | `deep-modules` | Detect shallow modules (pass-throughs, trivial wrappers, re-export barrels) and consolidate the safe ones. | | `/pygienium-dead-code` | dead-code | `scanner` / `fixer` | Find unreferenced exports, dead files, obsolete compat shims, migration helpers, and unused dependencies; remove clearly-dead items and flag dynamic ones. | | `/pygienium-defensive-guards` | defensive-guards | `defensive-guards` | Remove redundant defensive guarding (null checks on non-nullable types, swallowing try/catch, masking fallbacks) while keeping boundary guards (IO, parsing, untrusted input). | | `/pygienium-todos` | todos | `todos` / `fixer` | Inventory TODO/FIXME markers and stub implementations; with `--fix`, convert silent stubs (placeholder returns, empty bodies) into loud failures — never implementing TODOs or deleting markers. | ## Artifacts Every check writes its reports under `/.pygienium/checks//` (run state lives at `/.pygienium/run-state.json`): - `findings.md` — what the scan found (per-file line refs). - `changes.md` — what the fix phase changed + anything deferred for human review. `/pygienium-export` merges every check's artifacts into one `.pygienium/export.md` (or `export.json`). On first run in a git work tree, pygienium appends `.pygienium/` to the target repo's `.gitignore` so a run never stages its own state/artifacts into git (opt out with `--no-gitignore`). ## Adding a check One file + one `registerCheck()` call. **No `index.ts` command-wiring changes.** `index.ts` auto-discovers every `checks/*.ts` (except the registry barrel) at startup, so a new file self-registers and `/pygienium-` appears automatically. 1. Create `src/checks/.ts` from the template below. 2. Edit the `name`, `label`, `description`, the rubric in the scan/fix task builders, and the `gate` precondition. 3. Keep the trailing `registerCheck(Check)`. Done. ```ts import { registerCheck, type CheckScope } from "./registry.js"; export const myCheck = { name: "my-check", label: "My check", description: "What it fixes (shown in /pygienium-help).", agentName: "scanner", // reuse a shipped agent, or add agents/.md fixAgentName: "fixer", phaseId: "my-check", buildScanTask: (_cwd: string, scope: CheckScope) => `# Task: my-check scan\n…`, buildFixTask: (_cwd: string, scope: CheckScope, findings: string) => `# Task: my-check fix\n…`, gate: (cwd: string) => undefined, } as const; registerCheck(myCheck); ``` Reload pi (or `/reload`) and run `/pygienium-help` — `/pygienium-my-check` is listed and runnable. A `CheckDefinition` supplies the task builders and gate; the generic check-runner wires the phases together, so a new check never touches command plumbing. ## Architecture Each check is a "mode" running a fixed phase pipeline: ``` registerCheck(def) ← checks/*.ts self-register on load │ ▼ /pygienium- ─► runCheck(def) (src/modes/check-runner.ts) │ ├─ Q0 recon (shared, run once per run) (src/recon.ts) │ git state + source-file inventory → .pygienium/recon.json ├─ analysis sub-agent (buildScanTask) ← scanner/ agent │ writes pygienium/checks//findings.md ├─ fix sub-agent (buildFixTask) ← fixer, only with --fix │ writes pygienium/checks//changes.md ├─ verify gate (re-runs check.gate) └─ cleanup (drops transient scratch artifacts) ``` - **Sub-agents** are isolated in-memory `AgentSession`s scoped to the target `cwd` (see `src/agent-runner.ts`), with the agent definition's system prompt and tool allowlist applied. Agent definitions are plain editable markdown in [`agents/`](./agents/) (frontmatter `name` + `allowedTools`, body = system prompt) — tuning a sub-agent never needs TypeScript changes. - **Run-state** is a single JSON file at `/.pygienium/run-state.json` (`src/run-state.ts`): per-check phase progress, captured findings/changes text, and recon status. `/pygienium-status`, `/pygienium-resume`, and `/pygienium-export` are pure reads over it; `/pygienium-all` shares one `RunState` across every check so phases accumulate in one record. - **The registry** (`src/checks/registry.ts`) is the extensibility seam: a module-level `Map` of `CheckDefinition`s. `index.ts` iterates it and binds one `/pygienium-` command per entry, so adding a check is a file + one `registerCheck()` line. ## Layout ``` pygienium/ ├─ src/ │ ├─ index.ts ← entry: auto-discover checks, bind commands │ ├─ commands.ts ← slash-command handlers (thin binders) │ ├─ help.ts ← COMMANDS + CLI_FLAGS → /pygienium-help output │ ├─ agent-runner.ts ← isolated sub-agent sessions (injectable for tests) │ ├─ agents.ts ← markdown agent-definition loader │ ├─ recon.ts ← shared Q0 reconnaissance snapshot │ ├─ run-state.ts ← persistent, resumable run-state model │ ├─ status.ts ← /pygienium-status formatter (pure) │ ├─ export.ts ← /pygienium-export gatherer + md/json renderer │ ├─ phases.ts ← footer status-strip UI adapter │ ├─ modes/check-runner.ts ← the per-check phase pipeline │ └─ checks/ ← one file per check, self-registering │ ├─ registry.ts ← CheckDefinition + registerCheck │ ├─ comments.ts deep-modules.ts dead-code.ts │ ├─ defensive-guards.ts noop.ts └─ agents/ ← scanner.md fixer.md deep-modules.md defensive-guards.md ``` ## License MIT