Pygienium

Code hygiene for pi — 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).

# 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 <cwd>/.pygienium/run-state.json.

Command What it does
/pygienium-help Print every command, shipped check, and flag.
/pygienium-<check> [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 <check>, 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/ 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-noop noop scanner / fixer Reference/template check — performs no analysis, writes zero-issue artifacts. Clone it to start a new check.

Artifacts

Each check writes its report under <cwd>/pygienium/checks/<name>/:

  • 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). Some checks historically wrote under .pygienium/checks/; export walks both roots and merges by check name (pygienium/ wins).

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-<name> appears automatically.

  1. Clone src/checks/noop.tssrc/checks/<name>.ts.
  2. Edit the name, label, description, the rubric in the scan/fix task builders, and the gate precondition.
  3. Keep the trailing registerCheck(<name>Check). Done.
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/<name>.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-<check> ─► 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/<check> agent
        │     writes pygienium/checks/<name>/findings.md
        ├─ fix sub-agent       (buildFixTask)     ← fixer, only with --fix
        │     writes pygienium/checks/<name>/changes.md
        ├─ verify gate         (re-runs check.gate)
        └─ cleanup             (drops transient scratch artifacts)
  • Sub-agents are isolated in-memory AgentSessions 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/ (frontmatter name + allowedTools, body = system prompt) — tuning a sub-agent never needs TypeScript changes.
  • Run-state is a single JSON file at <cwd>/.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 CheckDefinitions. index.ts iterates it and binds one /pygienium-<name> 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

Description
No description provided
Readme MIT 667 KiB
Languages
TypeScript 94.4%
JavaScript 3.7%
Shell 1.9%