pi-critique-model 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -41
- package/package.json +4 -6
- package/src/config.ts +43 -4
- package/src/index.ts +401 -68
- package/src/prompt-critique.ts +273 -0
- package/src/review.ts +16 -28
- package/src/work-step.ts +6 -6
package/README.md
CHANGED
|
@@ -1,28 +1,32 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|
|
|
5
5
|
</div>
|
|
6
6
|
|
|
7
|
-
# Critique — Adversarial
|
|
7
|
+
# Critique — Adversarial Review for pi
|
|
8
8
|
|
|
9
|
-
**Critique
|
|
9
|
+
**Critique questions the last work step with a separate model and can challenge user instructions before the model starts.** It is not limited to code: it can review implementation work, writing, plans, research, data analysis, ops, or any other project work. Optionally, Critique also runs a fast pre-flight check on sufficiently rich user prompts and shows a short `Critique` widget when the instruction deserves pushback. The feedback is **non-mandatory**: the user and the working model remain the final judges.
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
## Features
|
|
14
14
|
|
|
15
|
-
- **Independent reviewer** — a separate model
|
|
15
|
+
- **Independent reviewer** — a separate model questions the work step from serialized context, with no tools of its own (can't modify files or see hidden state)
|
|
16
16
|
- **Auto-detects the work step** — splits the session branch into *episodes* at user-message boundaries; the last episode containing tool calls or assistant output is the work step
|
|
17
17
|
- **Token-budgeted context** — the user prompt, assistant messages, tool calls and tool results are truncated to a self-contained block so the reviewer sees what it needs without overflow
|
|
18
|
-
- **Multi-step review** — review the last `N` episodes in one shot (`/critique 3`) to catch
|
|
19
|
-
- **Focus note** — `/critique check the
|
|
20
|
-
- **Structured output** — fixed `Verdict / Issues / Suggestions / Summary` schema
|
|
21
|
-
- **
|
|
18
|
+
- **Multi-step review** — review the last `N` episodes in one shot (`/critique 3`) to catch cross-step issues
|
|
19
|
+
- **Focus note** — `/critique check the assumptions` biases the review without limiting it; the reviewer still scans for everything
|
|
20
|
+
- **Structured output** — fixed `Verdict / Issues / Suggestions / Summary` schema for actionable, parseable feedback
|
|
21
|
+
- **Settings menu** — `/critique config` opens an editable menu so you can change one setting at a time without rerunning a full wizard
|
|
22
22
|
- **Advisory injection** — on by default; toggle off (or use `/critique view`) to keep the review as read-only
|
|
23
23
|
- **Cancelable loader** — in TUI mode, the review runs behind a loader that you can abort with `Esc`
|
|
24
24
|
- **No provider surprises** — empty reviews are flagged, provider errors are surfaced as errors instead of silently producing nothing
|
|
25
|
-
- **
|
|
25
|
+
- **Automatic prompt critique** — optional pre-flight challenge for user instructions before the model starts; trivial inputs (`ok`, tiny commands, quick corrections) are ignored
|
|
26
|
+
- **Three prompt-critique levels** — `Inconsistencies only` (low sensitivity), `Critical` (moderate), or `Corrosive` (high)
|
|
27
|
+
- **Prompt-critique model source** — use either the active working model or the configured critique model
|
|
28
|
+
- **Interactive Critique widget** — ultra-short one-sentence advice in the user's interaction language with `Accept`, `Discard`, or `Reply`; auto-discards after 30 seconds
|
|
29
|
+
- **Persistent config** — `~/.pi/agent/critique.json` stores model choice, auto-inject, and automatic prompt-critique settings across all projects
|
|
26
30
|
|
|
27
31
|
## Install
|
|
28
32
|
|
|
@@ -52,38 +56,38 @@ pi remove npm:pi-critique-model
|
|
|
52
56
|
|
|
53
57
|
> **Security:** pi packages run with full system access — extensions execute arbitrary code. Install only packages you trust and review the source.
|
|
54
58
|
|
|
55
|
-
**Requirements:** a working pi installation with at least
|
|
59
|
+
**Requirements:** a working pi installation with at least one configured model. For independent reviews, configure a second model as reviewer.
|
|
56
60
|
|
|
57
61
|
## Quick Start
|
|
58
62
|
|
|
59
63
|
```
|
|
60
|
-
/critique config # (optional)
|
|
64
|
+
/critique config # (optional) open the settings menu
|
|
61
65
|
... # let the main model do some work
|
|
62
66
|
/critique # review the last work step and feed the feedback back
|
|
63
67
|
```
|
|
64
68
|
|
|
65
|
-
The main model then sees the review appended to its next turn and decides what to apply. With auto-inject off (or `/critique view`), the review is only displayed to you
|
|
69
|
+
The main model then sees the review appended to its next turn and decides what to apply. With auto-inject off (or `/critique view`), the review is only displayed to you.
|
|
66
70
|
|
|
67
71
|
To focus the review:
|
|
68
72
|
|
|
69
73
|
```
|
|
70
|
-
/critique check the
|
|
74
|
+
/critique check the assumptions behind the plan
|
|
71
75
|
/critique 3 # review the last 3 work steps
|
|
72
|
-
/critique 2 look at the
|
|
76
|
+
/critique 2 look at the evidence gaps # combine count + focus
|
|
73
77
|
```
|
|
74
78
|
|
|
75
79
|
## Commands
|
|
76
80
|
|
|
77
81
|
| Command | Description |
|
|
78
82
|
|---------|-------------|
|
|
79
|
-
| `/critique` | Review the last work step and inject
|
|
83
|
+
| `/critique` | Review/question the last work step and inject feedback into the working model |
|
|
80
84
|
| `/critique <focus>` | Review the last work step with an additional focus note |
|
|
81
85
|
| `/critique N` | Review the last `N` work steps (max 5) |
|
|
82
86
|
| `/critique N <focus>` | Combine count and focus |
|
|
83
87
|
| `/critique view` | Show the review only, without injecting it |
|
|
84
88
|
| `/critique view <focus>` | View-only, with a focus note |
|
|
85
89
|
| `/critique view N` | View-only, last `N` steps |
|
|
86
|
-
| `/critique config` |
|
|
90
|
+
| `/critique config` | Open the editable settings menu for model, auto-inject, and automatic prompt critique |
|
|
87
91
|
|
|
88
92
|
**Argument parsing:**
|
|
89
93
|
|
|
@@ -95,7 +99,7 @@ To focus the review:
|
|
|
95
99
|
|
|
96
100
|
### 1. Extract the work step
|
|
97
101
|
|
|
98
|
-
The session branch is split into *episodes* at user-message boundaries. The last episode containing tool calls or assistant output is the work step: the user request that triggered it, every tool call (with arguments), and every tool result (diffs, command output, errors). Content is truncated to a token budget so the reviewer sees
|
|
102
|
+
The session branch is split into *episodes* at user-message boundaries. The last episode containing tool calls or assistant output is the work step: the user request that triggered it, every tool call (with arguments), and every tool result (diffs, command output, data, errors). Content is truncated to a token budget so the reviewer sees focused, self-contained context:
|
|
99
103
|
|
|
100
104
|
| Field | Max chars |
|
|
101
105
|
|-------|-----------|
|
|
@@ -109,11 +113,11 @@ Truncated content is marked with `… [truncated]` so the reviewer can tell what
|
|
|
109
113
|
|
|
110
114
|
### 2. Ask the reviewer
|
|
111
115
|
|
|
112
|
-
The critique model is called directly through `ctx.modelRegistry.complete()` with **no tools** — it only judges. The reviewer is told:
|
|
116
|
+
The critique model is called directly through `ctx.modelRegistry.complete()` with **no tools** — it only judges/questions. The reviewer is told:
|
|
113
117
|
|
|
114
118
|
- The work step inside `<work-step>` tags
|
|
115
119
|
- An optional `<focus-note>` if you passed one
|
|
116
|
-
- "Do not invent issues: if the work is
|
|
120
|
+
- "Do not invent issues: if the work is sound, say so and keep suggestions minimal"
|
|
117
121
|
|
|
118
122
|
It replies in a fixed Markdown structure:
|
|
119
123
|
|
|
@@ -131,29 +135,42 @@ APPROVED | APPROVED_WITH_SUGGESTIONS | CHANGES_RECOMMENDED
|
|
|
131
135
|
2-4 sentence overall assessment.
|
|
132
136
|
```
|
|
133
137
|
|
|
134
|
-
The reviewer is
|
|
138
|
+
The reviewer is told to base its judgment *only* on the provided work step, across any domain: code, writing, planning, research, data, ops, etc.
|
|
135
139
|
|
|
136
140
|
### 3. Inject the feedback
|
|
137
141
|
|
|
138
142
|
The review is sent back to the working model as a follow-up user message:
|
|
139
143
|
|
|
140
144
|
```
|
|
141
|
-
[Critique — advisory
|
|
145
|
+
[Critique — advisory]
|
|
142
146
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
disagree with any of them, briefly explain why and continue.
|
|
147
|
+
Reviewer: `provider/model`. Advice only: apply useful points; briefly reject bad ones.
|
|
148
|
+
|
|
149
|
+
---
|
|
147
150
|
|
|
148
|
-
--- Review ---
|
|
149
151
|
<the review>
|
|
150
152
|
```
|
|
151
153
|
|
|
152
154
|
The main model then has the freedom to apply, partially apply, or reject each point. If auto-inject is off, the review is only shown to you.
|
|
153
155
|
|
|
156
|
+
### 4. Optional automatic prompt critique
|
|
157
|
+
|
|
158
|
+
When enabled in `/critique config`, Critique listens to user input before prompt-template expansion and before the agent starts. A local heuristic skips acknowledgements, slash commands, tiny corrections, and short commands. For richer instructions, a tool-free model call decides whether there is anything worth challenging.
|
|
159
|
+
|
|
160
|
+
If critique is useful, pi shows an ultra-short `Critique` widget in the user's interaction language. The model always returns both an issue and a proposed fix:
|
|
161
|
+
|
|
162
|
+
- `Accept` includes the proposed fix as extra guidance for the model.
|
|
163
|
+
- `Discard` sends the original prompt unchanged.
|
|
164
|
+
- `Reply` lets the user answer the critique/fix before the model sees the prompt.
|
|
165
|
+
- No interaction within 30 seconds auto-discards the advice and sends the original prompt unchanged.
|
|
166
|
+
|
|
167
|
+
Automatic prompt critique can use either the active working model or the configured critique model. It does not require a separate model.
|
|
168
|
+
|
|
154
169
|
## Model Selection
|
|
155
170
|
|
|
156
|
-
`/critique config` opens
|
|
171
|
+
`/critique config` opens an editable settings menu. Select a setting to change only that value, toggle booleans directly, or choose `Done`/`Esc` to close. Changes are persisted as soon as each setting is edited.
|
|
172
|
+
|
|
173
|
+
The `Critique model` entry opens the critic model picker. It only offers models with configured auth, **only** from pi's native model registry — the same list you see in `/model`. The picker shows at most ten entries at a time and scrolls past that.
|
|
157
174
|
|
|
158
175
|
**Auto** (the default) prefers a *different* model than the working one, so the review is genuinely independent. If no second model is available, it falls back to the working model and warns you when it runs.
|
|
159
176
|
|
|
@@ -168,14 +185,20 @@ The config is persisted as JSON at `~/.pi/agent/critique.json`:
|
|
|
168
185
|
```json
|
|
169
186
|
{
|
|
170
187
|
"model": "anthropic/claude-sonnet-4",
|
|
171
|
-
"autoInject": true
|
|
188
|
+
"autoInject": true,
|
|
189
|
+
"autoPromptCritique": false,
|
|
190
|
+
"autoPromptCritiqueLevel": "inconsistencies",
|
|
191
|
+
"autoPromptCritiqueModel": "working"
|
|
172
192
|
}
|
|
173
193
|
```
|
|
174
194
|
|
|
175
195
|
- **`model`** — canonical `provider/modelId` of the reviewer. Empty string = Auto (different from working model).
|
|
176
196
|
- **`autoInject`** — when `true`, the review is injected back into the working model. When `false`, the review is only displayed.
|
|
197
|
+
- **`autoPromptCritique`** — when `true`, sufficiently rich user instructions are challenged before the model starts.
|
|
198
|
+
- **`autoPromptCritiqueLevel`** — `inconsistencies` only flags real misunderstanding risks; `critical` is moderate; `corrosive` is highly sensitive and skips only clearly logical/complete prompts.
|
|
199
|
+
- **`autoPromptCritiqueModel`** — `working` uses the active model; `critique` uses the configured critique model.
|
|
177
200
|
|
|
178
|
-
The
|
|
201
|
+
The settings menu offers any model with configured auth that's available in pi's registry; the config persists per-machine (in `getAgentDir()`), shared across all projects.
|
|
179
202
|
|
|
180
203
|
## Architecture
|
|
181
204
|
|
|
@@ -185,28 +208,29 @@ critique/
|
|
|
185
208
|
├── LICENSE # MIT
|
|
186
209
|
├── README.md
|
|
187
210
|
├── docs/
|
|
188
|
-
│ ├── banner.
|
|
189
|
-
│ └── preview.
|
|
190
|
-
├── screenshot.png # full-res master
|
|
211
|
+
│ ├── banner.jpeg # wide README header
|
|
212
|
+
│ └── preview.jpeg # npm pi.dev preview card
|
|
191
213
|
└── src/
|
|
192
|
-
├── index.ts
|
|
193
|
-
├── config.ts
|
|
194
|
-
├──
|
|
195
|
-
|
|
214
|
+
├── index.ts # /critique command surface, config UI, review UI, input hook
|
|
215
|
+
├── config.ts # persistence + model resolution: pinned, auto, fallback
|
|
216
|
+
├── prompt-critique.ts # automatic user-prompt critique prompt, gate, model call
|
|
217
|
+
├── work-step.ts # episode splitting + token-budgeted serialization
|
|
218
|
+
└── review.ts # reviewer prompt + model call + injected-message builder
|
|
196
219
|
```
|
|
197
220
|
|
|
198
|
-
|
|
221
|
+
Five-file extension with zero external dependencies (only pi's bundled `@earendil-works/*` + Node built-ins):
|
|
199
222
|
|
|
200
223
|
- **Episode splitter** — splits a session branch into user-message-bounded episodes, picks the last one with work
|
|
201
224
|
- **Token budgeter** — hard caps per field, marks truncations so the reviewer knows what it didn't see
|
|
202
|
-
- **Reviewer call** — tool-free `ctx.modelRegistry.complete()` with a structured
|
|
225
|
+
- **Reviewer call** — tool-free `ctx.modelRegistry.complete()` with a domain-general structured prompt
|
|
203
226
|
- **Advisory formatter** — wraps the review in a "non-mandatory" envelope before injecting as a follow-up user message
|
|
204
|
-
- **
|
|
227
|
+
- **Prompt critique** — optional input hook with local trivial-prompt gate, three challenge levels, and ultra-short JSON model output
|
|
228
|
+
- **UI** — lazy-loaded pickers/viewers/loaders + 30-second Critique widget
|
|
205
229
|
|
|
206
230
|
## Notes
|
|
207
231
|
|
|
208
|
-
- The critique model runs with **no tools** and never touches the filesystem. It judges purely from the serialized work step
|
|
209
|
-
- In TUI mode the review runs behind a cancelable loader (Esc aborts) and `/critique view` opens a scrollable Markdown viewer. In RPC mode reviews are surfaced through notifications; print mode logs
|
|
232
|
+
- The critique model runs with **no tools** and never touches the filesystem. It judges purely from the serialized work step, whatever the domain.
|
|
233
|
+
- In TUI mode the review runs behind a cancelable loader (Esc aborts) and `/critique view` opens a scrollable Markdown viewer. Automatic prompt critique appears as a compact `Critique` widget with a 30-second auto-discard timeout. In RPC mode reviews are surfaced through notifications/dialogs; print mode logs manual reviews to stdout and skips automatic prompt critique.
|
|
210
234
|
- Provider errors (bad keys, insufficient balance, rate limit) are surfaced as errors instead of silently producing empty reviews.
|
|
211
235
|
- Reviews are capped at 16,000 chars to keep the injected follow-up reasonable; longer reviews are truncated with `… [review truncated]`.
|
|
212
236
|
|
package/package.json
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-critique-model",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "pi critique: work review + prompt challenge.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
7
7
|
"review",
|
|
8
8
|
"critique",
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"second-opinion",
|
|
12
|
-
"advisory"
|
|
9
|
+
"adversarial-review",
|
|
10
|
+
"prompt-critique"
|
|
13
11
|
],
|
|
14
12
|
"author": "Javier Noguerol <https://github.com/noguerol>",
|
|
15
13
|
"license": "MIT",
|
package/src/config.ts
CHANGED
|
@@ -11,16 +11,30 @@ import type { Model } from "@earendil-works/pi-ai";
|
|
|
11
11
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
12
|
import { dirname, join } from "node:path";
|
|
13
13
|
|
|
14
|
+
import type {
|
|
15
|
+
AutoPromptCritiqueLevel,
|
|
16
|
+
AutoPromptCritiqueModelSource,
|
|
17
|
+
} from "./prompt-critique.ts";
|
|
18
|
+
|
|
14
19
|
export interface CritiqueConfig {
|
|
15
20
|
/** Canonical "provider/modelId" of the critique model. Empty string = auto. */
|
|
16
21
|
model: string;
|
|
17
22
|
/** Inject the review back into the working model automatically. */
|
|
18
23
|
autoInject: boolean;
|
|
24
|
+
/** Challenge sufficiently rich user instructions before the agent starts. */
|
|
25
|
+
autoPromptCritique: boolean;
|
|
26
|
+
/** How adversarial the automatic prompt critique should be. */
|
|
27
|
+
autoPromptCritiqueLevel: AutoPromptCritiqueLevel;
|
|
28
|
+
/** Which model is used for automatic prompt critique. */
|
|
29
|
+
autoPromptCritiqueModel: AutoPromptCritiqueModelSource;
|
|
19
30
|
}
|
|
20
31
|
|
|
21
32
|
export const DEFAULT_CONFIG: CritiqueConfig = {
|
|
22
33
|
model: "",
|
|
23
34
|
autoInject: true,
|
|
35
|
+
autoPromptCritique: false,
|
|
36
|
+
autoPromptCritiqueLevel: "inconsistencies",
|
|
37
|
+
autoPromptCritiqueModel: "working",
|
|
24
38
|
};
|
|
25
39
|
|
|
26
40
|
export function configFilePath(): string {
|
|
@@ -30,10 +44,24 @@ export function configFilePath(): string {
|
|
|
30
44
|
export function loadConfig(): CritiqueConfig {
|
|
31
45
|
try {
|
|
32
46
|
const raw = JSON.parse(readFileSync(configFilePath(), "utf8")) as Partial<CritiqueConfig>;
|
|
47
|
+
const level = raw.autoPromptCritiqueLevel;
|
|
48
|
+
const modelSource = raw.autoPromptCritiqueModel;
|
|
33
49
|
return {
|
|
34
50
|
model: typeof raw.model === "string" ? raw.model : DEFAULT_CONFIG.model,
|
|
35
51
|
autoInject:
|
|
36
52
|
typeof raw.autoInject === "boolean" ? raw.autoInject : DEFAULT_CONFIG.autoInject,
|
|
53
|
+
autoPromptCritique:
|
|
54
|
+
typeof raw.autoPromptCritique === "boolean"
|
|
55
|
+
? raw.autoPromptCritique
|
|
56
|
+
: DEFAULT_CONFIG.autoPromptCritique,
|
|
57
|
+
autoPromptCritiqueLevel:
|
|
58
|
+
level === "inconsistencies" || level === "critical" || level === "corrosive"
|
|
59
|
+
? level
|
|
60
|
+
: DEFAULT_CONFIG.autoPromptCritiqueLevel,
|
|
61
|
+
autoPromptCritiqueModel:
|
|
62
|
+
modelSource === "working" || modelSource === "critique"
|
|
63
|
+
? modelSource
|
|
64
|
+
: DEFAULT_CONFIG.autoPromptCritiqueModel,
|
|
37
65
|
};
|
|
38
66
|
} catch {
|
|
39
67
|
return { ...DEFAULT_CONFIG };
|
|
@@ -46,7 +74,7 @@ export function saveConfig(config: CritiqueConfig): void {
|
|
|
46
74
|
writeFileSync(path, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
47
75
|
}
|
|
48
76
|
|
|
49
|
-
export function modelLabel(model: Model): string {
|
|
77
|
+
export function modelLabel(model: Model<any>): string {
|
|
50
78
|
return `${model.provider}/${model.id}`;
|
|
51
79
|
}
|
|
52
80
|
|
|
@@ -55,7 +83,7 @@ export function modelLabel(model: Model): string {
|
|
|
55
83
|
* scoping is configured, otherwise the full available catalogue. Only models
|
|
56
84
|
* with configured auth are offered.
|
|
57
85
|
*/
|
|
58
|
-
export function pickableModels(ctx: ExtensionContext): Model[] {
|
|
86
|
+
export function pickableModels(ctx: ExtensionContext): Model<any>[] {
|
|
59
87
|
const scoped = (ctx.scopedModels ?? []).map((entry) => entry.model);
|
|
60
88
|
const candidates = scoped.length > 0 ? scoped : ctx.modelRegistry.getAvailable();
|
|
61
89
|
return candidates.filter((model) => ctx.modelRegistry.hasConfiguredAuth(model));
|
|
@@ -71,13 +99,13 @@ export function pickableModels(ctx: ExtensionContext): Model[] {
|
|
|
71
99
|
export function resolveCritiqueModel(
|
|
72
100
|
ctx: ExtensionContext,
|
|
73
101
|
config: CritiqueConfig,
|
|
74
|
-
): Model | undefined {
|
|
102
|
+
): Model<any> | undefined {
|
|
75
103
|
if (config.model) {
|
|
76
104
|
const slash = config.model.indexOf("/");
|
|
77
105
|
const provider = slash >= 0 ? config.model.slice(0, slash) : config.model;
|
|
78
106
|
const id = slash >= 0 ? config.model.slice(slash + 1) : config.model;
|
|
79
107
|
const model = ctx.modelRegistry.find(provider, id);
|
|
80
|
-
|
|
108
|
+
if (model && ctx.modelRegistry.hasConfiguredAuth(model)) return model;
|
|
81
109
|
}
|
|
82
110
|
|
|
83
111
|
const candidates = pickableModels(ctx);
|
|
@@ -87,3 +115,14 @@ export function resolveCritiqueModel(
|
|
|
87
115
|
);
|
|
88
116
|
return different ?? working ?? candidates[0];
|
|
89
117
|
}
|
|
118
|
+
|
|
119
|
+
/** Resolve the model used by automatic prompt critique. */
|
|
120
|
+
export function resolveAutoPromptCritiqueModel(
|
|
121
|
+
ctx: ExtensionContext,
|
|
122
|
+
config: CritiqueConfig,
|
|
123
|
+
): Model<any> | undefined {
|
|
124
|
+
if (config.autoPromptCritiqueModel === "working") {
|
|
125
|
+
return ctx.model ?? pickableModels(ctx)[0];
|
|
126
|
+
}
|
|
127
|
+
return resolveCritiqueModel(ctx, config) ?? ctx.model ?? pickableModels(ctx)[0];
|
|
128
|
+
}
|