The todos pre-scan walked .output/ (Nitro) and .vercel/ (Vercel) build dirs, flagging 119 of 124 candidates inside minified bundles (single lines up to 162KB). buildTodosScanTask embedded full candidate lines in the task prompt, producing a 2.5MB prompt on freno-dev; the analysis agent settled with ok:true + empty text + no findings.md, verify failed, and resume re-ran the same oversized prompt and failed identically. - scope: exclude .output/.vercel/.netlify (shared by all checks) - todos: truncate candidate code at 160 chars in the prompt + fallback - agent-runner: a session settling with no text and no observed message/ tool events now fails the run loudly instead of reporting ok:true - agent prompts: add the three dirs to each skip list - tests: excluded-dir scan, prompt truncation, emptySessionError cases
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
pi install npm:@mikefreno/pygienium
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).
Configuration
Pygienium reads its chat-rendering style from pi's settings.json (~/.pi/agent/settings.json) under a pygienium key:
{
"pygienium": {
"chatStyle": "verbose"
}
}
| Setting | Default | Values | Description |
|---|---|---|---|
pygienium.chatStyle |
"verbose" |
"verbose" | "compact" |
Chat rendering for sub-agent tool calls. verbose (piolium-style) streams each tool event live as its own chat line ([Comments: Scanning] → bash ... / ← (ok)). compact (ralpi-style) suppresses the per-event stream and shows only the final completion message with its expandable phase tree. |
No entry means "verbose" (the default). An unreadable or missing settings.json also falls back to "verbose".
Environment variable (read at run time):
| Env var | Default | Description |
|---|---|---|
PYGIENIUM_AGENT_TIMEOUT_MS |
3600000 (60 min) |
Per-agent-phase settle deadline. A sub-agent session that never settles (stalled provider stream, hung retry/auto-compaction after its last tool call) is aborted and the phase fails with a visible error instead of hanging the run mid-transition with no state update and no completion message. Lower it (e.g. 1200000) to fail fast on flaky providers; raise it for very large repos. |
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-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 <cwd>/.pygienium/checks/<name>/ (run
state lives at <cwd>/.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-<name> appears
automatically.
- Create
src/checks/<name>.tsfrom the template below. - Edit the
name,label,description, the rubric in the scan/fix task builders, and thegateprecondition. - 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 targetcwd(seesrc/agent-runner.ts), with the agent definition's system prompt and tool allowlist applied. Agent definitions are plain editable markdown inagents/(frontmattername+allowedTools, body = system prompt) — tuning a sub-agent never needs TypeScript changes. A scanned project can ship its ownagents/*.mdat its root: those are loaded as overrides (repo agent wins on name collision), so teams can tune prompts or add project-specific agents without touching the extension. - 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-exportare pure reads over it;/pygienium-allshares oneRunStateacross every check so phases accumulate in one record. - The registry (
src/checks/registry.ts) is the extensibility seam: a module-levelMapofCheckDefinitions.index.tsiterates it and binds one/pygienium-<name>command per entry, so adding a check is a file + oneregisterCheck()line. - The footer (
src/footer.ts) is the piolium-style pipeline-overview status strip: a single static line in the TUI footer (viaui.setStatus(key, text)) listing the full ordered pipeline with the cursor on the current phase and what's to come. For a single check the items are the phases; for/pygienium-allthey're the checks (the full todo list), and the per-check footer is suppressed so two overviews never compete over the same status slot. The chat widget (phases.ts) remains the animated detail view (spinner + tool-call tree + completion tree); the footer is the overview — the two never overlap. In print/JSON mode the footer is a no-op.
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 ← live chat progress widget + completion-tree helpers
│ ├─ footer.ts ← pipeline-overview status strip (TUI footer)
│ ├─ 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
└─ agents/ ← scanner.md fixer.md deep-modules.md defensive-guards.md
License
MIT