pi-tool-supervisor
Pi tool supervisor extension with configurable lifecycle review for every tool and interactive configuration
Package details
Install pi-tool-supervisor from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-tool-supervisor- Package
pi-tool-supervisor- Version
0.9.0- Published
- Sep 21, 2026
- Downloads
- 1,058/mo · 528/wk
- Author
- maplezzk
- License
- MIT
- Types
- extension, skill
- Size
- 191.9 KB
- Dependencies
- 5 dependencies · 3 peers
Pi manifest JSON
{
"skills": [
"./SKILL.md"
],
"extensions": [
"./index.ts",
"../pi-extensions-i18n/index.ts",
"../pi-extensions-tool-display/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-tool-supervisor
pi-tool-supervisor is a configurable tool-lifecycle review extension for Pi. It can review selected tools before or after execution and uses the actual file change for edit and write audits.
What it solves
An edit tool can complete successfully while the resulting file still violates local conventions, architecture constraints, security rules, or task-specific instructions. Reviewing the actual before/after diff gives a model-based reviewer the information needed to catch those issues immediately, while keeping the review policy configurable per project.
How it works
- Each reviewer selects
tools, atrigger, and optionally a localconditionmodule: omitted fields default toedit/write+after, and"*"matches every built-in or custom tool. - Before reviewers inspect the proposed input and an explicit rejection blocks the native Pi tool call; model failures remain fail-open without injecting errors into the Agent's tool result, while the audit card keeps the failed status. Condition module failures block the before call.
- Captures the file state before
edit/writeand the actual file state after the tool result. - Sends the real-line-numbered post-edit file with the diff; files above
maxFileContextCharsuse bounded excerpts around the first and last changed lines. - Supports multiple reviewers running in parallel, each with its own model and one or more rule files.
- Reads optional front matter from rule files for
enabled,filePatterns,complexity, andconsumers. - Returns
passed,rejected,failed, orskippedstatus with summaries, findings, rule groups, and durations. - Treats a reviewer verdict as binding only when it is self-consistent:
passed: falsewithout any finding is downgraded to passed and annotated, so a self-contradicting reviewer cannot block an edit with nothing to fix. - Passes native tool results through unchanged; it does not truncate or write tool output to temporary files. Output control belongs to Pi or other extensions.
- Re-reads the configuration for every tool call, so configuration changes apply to the next matching operation.
- Shows an audit card through Pi's display middleware or a fallback renderer. The shared display protocol is provided by
pi-extensions-tool-display.
It observes Pi's native events and does not register a replacement edit or write tool.
Review engines
Each reviewer picks an engine with backend. Omitting it keeps the original behavior.
backend |
Who judges | Cost and latency | Result shape |
|---|---|---|---|
model (default) |
A Pi chat model reads every rule and the diff in one prompt | One chat completion per reviewer | Free-form JSON: passed, summary, findings |
typesafe |
TypeSafe System One answers one typed question per rule in a single batched request | Measured on a 3-rule reviewer: ~1-2 s, ~1,400 input tokens, about US$0.00006 per review | A calibrated noul probability per rule; code applies the thresholds |
TypeSafe backend
The typesafe backend turns every numbered clause of a rule file into one Noul question - "does code added or changed in diff violate this clause?" - answered as a probability. Every clause of one reviewer is asked in a single request, because TypeSafe answers all questions over the same state in parallel. Code owns the rest: threshold decides whether a clause counts as hit, and a second, smaller Choice request locates the offending line among the lines the diff actually added.
Rule files are engine-independent: switching backends changes one config line and not a single character of a rule file. There is no ## Criteria section to write, no severity level to declare, and no block heading to add.
The API key is read from typesafe.apiKey in config.json first and falls back to the TYPESAFE_API_KEY environment variable; typesafe.endpoint falls back to TYPESAFE_ENDPOINT and then to the official endpoint. A missing key, a failed request, or a rule file with no clause to judge produces a visible failed audit entry and does not block the tool, which matches how a failed chat-model review behaves.
Enabling it
- Make sure the installed version carries this backend (
pi update --extensions). - Add the TypeSafe connection settings at the top level of
config.json(endpointis optional and defaults to the official endpoint):
"typesafe": {
"apiKey": "<your-typesafe-api-key>"
}
- Switch the reviewer you want to the
typesafeengine (drop themodelfield):
{
"name": "code-taste",
"backend": "typesafe",
"typesafeModel": "jev-latest",
"rulesFiles": ["/absolute/path/to/javascript-typescript.md"],
"tools": ["edit", "write"],
"trigger": "after"
}
No rule file changes are needed. Restart the Pi session (or /reload) to apply it; start with one rule file for a few days and watch for false positives and misses before switching the rest.
How it reads your rule file
---
name: javascript-typescript
filePatterns: ["**/*.ts"]
---
# JavaScript / TypeScript rules
## Ownership (takes precedence)
1. `ruleGroup` may only use clause names or numbers that appear in this file. ← reporting, not judged
2. Do not report rules this file does not contain. ← reporting, not judged
## Requirements
1. **No magic values**: non-obvious numbers in business logic must be extracted into a semantic `const` or shared configuration.
2. **At most 3 parameters**: a declaration with 4 or more parameters must use a parameter object.
3. **No `any`**: prefer concrete types; use `unknown` and narrow it at the boundary.
Do not bypass type checking with `as any` or `any[]`.
This becomes 3 independent rules:
| Judgment id | Rule name (the ruleGroup in each finding) |
Criterion | noul |
Outcome |
|---|---|---|---|---|
rule_1 |
Requirements 1 No magic values |
clause text | 0.52 | below threshold |
rule_2 |
Requirements 2 At most 3 parameters |
clause text | 0.03 | below threshold |
rule_3 |
Requirements 3 No any |
clause text | 0.97 | blocks, located at line 3 |
A clause's text is both the criterion and the finding text. The three clauses are three independent Noul questions answered in the same request.
Splitting rules
| What you write | How it is treated |
|---|---|
A 1. text numbered clause |
One rule; indented continuation lines join the same clause |
Numbered items inside a section whose heading starts with 归属 ("Ownership") |
Not judged - those are reporting requirements, not code rules |
A legacy [error] / [warning] left on a clause |
Stripped from the criterion; there are no levels, so every hit blocks (old rule files keep working) |
A **bold** phrase in a clause |
Used as the rule name, combined as {section} {number} {title} - this is the ruleGroup |
threshold in front matter |
Hit threshold for every clause in that file; default 0.85 |
| Several rule files on one reviewer | Judgment ids gain a file prefix (f1_rule_1); the same number in different files is normal and never warns, only a duplicate number inside one file does |
Other prose, ## Output style sections |
Does not affect judging |
| A numbered list of exemptions ("only reads", "only runs a script") | Still one rule per item, but asked backwards - see below |
A section heading that starts with 归属 (English rule files often use ## Ownership and severity) is always skipped when judging, so requirements like "ruleGroup may only use clause names that appear in this file" never turn into code rules.
A numbered clause must say what code counts as a violation. A numbered list of exemptions does not disappear: every item becomes a rule and is asked as "does this code violate only reads?", which inverts its meaning, while the real prohibition - written as bullets or prose - is never asked at all. Extracting clauses is not the same as extracting the right clauses, so the no clause is an error check below cannot catch it: the file does yield clauses, just the wrong kind. Write clauses as prohibitions and put the exemptions inside the clause they exempt.
You can see which rule failed
Every hit clause produces its own finding, named the way your file names it:
Found 2 must-fix problems
- [Requirements 1 No magic values] line 5: non-obvious numbers in business logic must be extracted…
Offending line: const fallback = 30000;
- [Requirements 4 No any] line 3: prefer concrete types; use `unknown`…
Offending line: const raw = (config as any).timeout;
The reviewer summary also lists every noul, as in 2 rules hit: Requirements 1 No magic values=0.92, Requirements 4 No any=0.97.
One request asks N clauses, so adding clauses barely adds latency: measured 6 clauses at 2,438 input tokens, and 10 rules batched into one request at 1.1 s / 1,351 input tokens, against 4.3 s / 7,525 input tokens for 10 separate requests.
No clauses is an error, not a pass
When a rule file yields no numbered clause at all (say it is all prose, or the items are bullets), the review reports failed with the file path and is never treated as passing. Otherwise enabling typesafe would silently disable a rule file.
Limitations
Limitations worth knowing before treating a typesafe reviewer as a gate:
- The more specific the clause, the better the judgment. When a probability is off, fix how the sentence is written (spell out the exemptions) rather than changing the format. Measured: "non-obvious numbers must be extracted into a semantic constant" is too subjective about "semantic", so
const fallback = 30000;scored 0.52, while the sharp-edged "noany" scored 0.97. - The threshold is per file. Clause quality varies within one file, and there is no per-clause override; sharpen the clause instead.
- No severity levels anywhere. Both backends block on any hit; a legacy
[error]/[warning]marker in a rule file is stripped from the criterion and never changes the outcome. - Line localization needs the post-edit file. A
beforereview only has it forwrite, soeditbefore-reviews report rule-level issues without a line number. - When the diff adds more than 40 lines, localization is skipped and reported as a warning; the rule-level issues are still reported.
- The whole post-edit file is sent as context, bounded by
maxFileContextChars. It roughly doubles the input tokens compared with a diff-only request. - Every clause body is sent, so detailed clauses raise input tokens (measured about 2.4k input tokens for 6 clauses).
- A probability is a calibrated judgment, not a guarantee.
Install
pi install npm:pi-tool-supervisor
The package manifest also loads the shared pi-extensions-tool-display dependency as one extension entry; no separate host package is required.
Reload Pi after installation:
/reload
Use the interactive configuration command:
/config:tool-supervisor
Configuration
The default configuration path is:
~/.pi/agent/extensions/pi-tool-supervisor/config.json
Start from config.example.json:
{
"enabled": true,
"timeoutSeconds": 10,
"maxFileContextChars": 50000,
"maxRuleLines": 100,
"typesafe": {
"apiKey": "<your-typesafe-api-key>",
"endpoint": "https://api.typesafe.ai/v1/systemone"
},
"reviewers": [
{
"name": "project-rules",
"model": "provider/model",
"rulesFiles": [
"/absolute/path/to/rules.md"
],
"tools": ["edit", "write"],
"trigger": "after",
"condition": "/absolute/path/to/condition.ts"
},
{
"name": "code-taste",
"backend": "typesafe",
"typesafeModel": "jev-latest",
"rulesFiles": [
"/absolute/path/to/javascript-typescript.md"
],
"tools": ["edit", "write"],
"trigger": "after"
}
]
}
Each reviewer must have either a provider/model reference (model backend) or a typesafeModel (typesafe backend), plus either rulesFile or rulesFiles. Relative rule-file and condition-module paths are resolved from the current project working directory.
| Setting | Meaning |
|---|---|
enabled |
Enables or disables the review layer. |
timeoutSeconds |
Maximum time allowed for each reviewer model call. |
maxFileContextChars |
Maximum post-edit file context sent to reviewers. The default is 50,000 characters; oversized files use bounded, explicitly marked excerpts around changed lines. |
maxRuleLines |
Maximum rule-file size accepted for a single review rule. |
typesafe |
Connection settings for the typesafe backend: apiKey and endpoint, both optional and both falling back to the matching environment variable. |
backend |
model (default) or typesafe. Selects the review engine. |
typesafeModel |
TypeSafe model name used by the typesafe backend. Defaults to jev-latest. |
condition |
Optional local TypeScript/ESM module path. Its default export receives the native Pi tool event, ExtensionContext, and ToolConditionHelpers; returning false skips this reviewer without a model call. |
reviewers |
Reviewer name, model, rule files, tools, trigger, and optional condition module. Missing lifecycle fields keep the legacy edit/write + after behavior. |
Rule-file front matter can scope a rule to particular files or consumers:
---
name: TypeScript safety
enabled: true
filePatterns:
- "**/*.ts"
complexity: local
consumers:
- editor-review
---
filePatterns uses a simplified glob syntax: * does not cross /, ** does, and **/ at any position matches zero or more directory levels. Backslashes are normalized to /, and a leading ./ is ignored.
threshold is an optional front matter field used only by the typesafe backend, defaulting to 0.85; the model backend ignores it.
Condition modules
A reviewer may set condition to a local TypeScript or ESM module path. Relative paths resolve from the current project working directory, and ~ is expanded using the Pi home directory. The module must export a default synchronous or asynchronous function:
import type {
ExtensionContext,
ToolCallEvent,
} from "@earendil-works/pi-coding-agent";
import type { ToolConditionHelpers } from "pi-tool-supervisor";
export default function condition(
event: ToolCallEvent,
ctx: ExtensionContext,
helpers: ToolConditionHelpers,
): boolean {
if (event.toolName !== "bash") return false;
const command = event.input.command;
if (typeof command !== "string") return false;
// The native event and context are available for custom policy.
const ast = helpers.parseBash(command);
return ast.errors?.length === 0 && ast.commands.some((statement) =>
statement.command.type === "Command" && statement.command.name?.value === "mvn",
);
}
The first argument is the original tool_call event for before reviewers or the original tool_result event for after reviewers. The second argument is the native ExtensionContext, with the same capabilities available to other Pi extensions. A third helper argument provides parseBash(source) without requiring the condition module to resolve supervisor internals.
A condition returning false skips the reviewer without loading its rules or calling its model. A module load error, execution error, non-boolean result, or timeout is visible; before treats it as a failed gate and blocks the tool. Use trigger: "before" when a rejected review must prevent execution; after remains diagnostic only.
Review semantics
- A before reviewer rejection blocks the native tool call and sends the complete reason to the Agent; model failures/skips remain fail-open and stay only in the audit details, without appending an error to the tool result. Condition module load or execution failures are treated as a failed gate and block the before call.
- An after rejection is diagnostic only and sends the diagnostic to the Agent without rolling back a completed tool call; an after model failure stays only in the audit details and does not append an error diagnostic. A failed tool skips after review and preserves the original error.
- Review rejections and configuration warnings use Pi's
ctx.ui.notify; reviewer failures remain in the audit card and are not injected into the Agent's context. The extension does not callconsole.warnorconsole.errordirectly. - If the parent Agent request is interrupted, every in-flight reviewer model request is cancelled together; reviewers not yet started are skipped, and parent cancellation is reported as skipped rather than as a provider failure.
- A failed tool call or an unchanged file is skipped.
- The extension does not roll back edits, block the operating system, or replace Pi's permission and sandbox controls.
When upgrading from pi-file-edit-review, the extension reads the legacy configuration if the new configuration does not exist. Saving through /config:tool-supervisor writes the new configuration path. /pi-tool-supervisor remains available as a compatibility alias.
Requirements
- Node.js 22 or newer.
- A configured Pi model for each enabled
modelreviewer. - A TypeSafe API key for each enabled
typesafereviewer:typesafe.apiKeyinconfig.json, or theTYPESAFE_API_KEYenvironment variable. - Rule files that describe the project-specific checks the reviewer should apply.