Michael Freno 299f66c8f2
All checks were successful
port-to-omp / port (push) Successful in 9s
add AGENTS.md
2026-08-21 21:23:52 -04:00
2026-08-21 21:18:15 -04:00
2026-08-21 21:18:15 -04:00
2026-08-21 21:23:52 -04:00
2026-08-10 10:47:39 -04:00
2026-08-21 21:18:15 -04:00

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.

  1. Create src/checks/<name>.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(<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. A scanned project can ship its own agents/*.md at 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-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.
  • The footer (src/footer.ts) is the piolium-style pipeline-overview status strip: a single static line in the TUI footer (via ui.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-all they'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

Development

The omp port at ~/.omp/agent/extensions/pygienium is regenerated only by CI on push (.gitea/workflows/port-to-omp.yml) — never by hand.

The source repo's own tsconfig.json extends the host harness tsconfig, so the reliable typecheck target is the regenerated port (self-contained tsconfig + pinned @oh-my-pi SDK devDependency). A committed pre-commit hook runs exactly what CI runs — regenerate the port into a temp dir and tsc --noEmit it — and fails the commit on any error:

git config core.hooksPath .githooks

License

MIT

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