Initial commit: pygenium as git submodule
This commit is contained in:
173
README.md
Normal file
173
README.md
Normal file
@@ -0,0 +1,173 @@
|
||||
# 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 `<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/`](./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.ts` → `src/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.
|
||||
|
||||
```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/<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 `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 `<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 `CheckDefinition`s. `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
|
||||
Reference in New Issue
Block a user