@dieulc/workflow 0.2.0 → 0.3.0

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 CHANGED
@@ -1,10 +1,11 @@
1
1
  # @dieulc/workflow
2
2
 
3
- Plan↔Build mode, questionnaire, subagent explore, todos and shadow-checkpoint rewind for Pi.
3
+ Plan↔Build mode with an independent **Review Mode** reviewer, questionnaire, subagent explore, todos and shadow-checkpoint rewind for Pi.
4
4
 
5
5
  ## Features
6
6
 
7
7
  - **Plan / Build duality** — `Plan` tool gating (`edit`/`write` blocked in plan), `Build` restores full tools; tab + `CtrlAlt+p` shortcuts, `--workflow-plan` flag
8
+ - **Review Mode** — an independent reviewer agent that runs **after** Plan Mode finishes (sequential — never in parallel), in its own in-process session. It audits the plan, rewrites it, streams its work into the main transcript, and hands the result to Plannotator. Off by default; see [Review Mode](#review-mode) below.
8
9
  - **Questionnaire** — 1–4 questions with headers/options, dedup freeform aliases, inline `↳ Your answer:` editor
9
10
  - **Explore (subagents)** — parallel `pi --mode json` subagents (`max 8`, concurrency 4), `session_shutdown` kill, `activeSubagents`
10
11
  - **Todos** — plan-faithful todo list (global step refs + statuses + phase groups) that persists across Plan/Build/Default modes. `workflow_todo` (`list/update/done/pending/add/toggle/sync/clear`) with industry-style wholesale `update` (send the COMPLETE list; omitted items are removed) plus idempotent `done`/`pending`, lenient `[DONE:<ref>]` markers, throttled Claude-Code-style reminders, a tool-result footer, and a stale ⚠ notice — but **no forced turns and no heuristic auto-completion** (reference: Claude Code / OpenCode / Codex practice).
@@ -32,15 +33,196 @@ Manage it with `pi list`, `pi update --extensions`, `pi remove npm:@dieulc/workf
32
33
  | `/rewind` | Pick checkpoint to restore |
33
34
  | `/todos` | Show todo list |
34
35
  | `/btw` | Queue note for next turn |
35
- | `/role` | Config screen: one model + thinking per role (planner/explorer/builder tabs, custom roles included). CLI: `list`, `set <role> <provider/model> [thinking]`, `use <role\|auto>`, `add/remove/reset` |
36
+ | `/role` | Config screen: one model + thinking per role (planner/explorer/builder/reviewer tabs, custom roles included). CLI: `list`, `set <role> <provider/model> [thinking]`, `use <role\|auto>`, `add/remove/reset` |
37
+ | `/review-mode [on\|off] [global\|project]` | Enable/disable Review Mode (default `off`). `status` prints the effective config, naming the settings file that supplied the timeout |
38
+ | `/review-mode timeout <seconds\|unlimited> [global\|project]` | Set or show the per-pass time cap (`unlimited`/`0` = no cap). Writes to global settings by default |
39
+ | `/review-prompt [global]` | Edit the reviewer's guidance (defaults to "align with the existing framework and industry best practice") |
40
+ | `/review-status` | Show review phase, model, plan hash, findings and any error |
41
+ | `/review` | Run one review round against the current plan file now |
42
+
43
+ ## Review Mode
44
+
45
+ An independent second agent supervises the plan before you see it. The flow is strictly **sequential**: Plan Mode finishes, then the reviewer runs, then the reviewed plan goes to Plannotator.
46
+
47
+ ```text
48
+ Plan Mode (main session) ──plan file──▶ Review Mode (child session, sequential)
49
+ │ rewrites the plan
50
+ ▼
51
+ Plannotator webview ◀── reviewed plan + `## Review changes`
52
+ │ approve ──▶ Build Mode
53
+ └─ feedback ──▶ Plan Mode revises ──▶ Review re-reviews
54
+ ```
55
+
56
+ - **Approve** in Plannotator → Build mode; both Plan and Review shut down.
57
+ - **Feedback** → Plan revises, Review re-reviews the revision, Plannotator reopens.
58
+ - **Any failure** (missing model, unauth'd provider, abort, error) → the author's plan is handed over unchanged with a warning. Review Mode never blocks the plan. A pass whose run ends without a `review_submit_plan` submission fails *immediately* (the settle race), so a silent reviewer cannot hang the handoff even with no time cap. `timeoutMs` is **off by default (`0` = unlimited)**; set a positive value to re-enable the outer safety net, and a pass that outlives it is aborted — though a plan submitted before the deadline is always kept.
59
+
60
+ ### Enabling
61
+
62
+ ```text
63
+ /review-mode on # global (~/.pi/agent/settings.json)
64
+ /review-mode on project # this repo only (<cwd>/.pi/settings.json)
65
+ /review-mode status
66
+ /review-mode timeout unlimited # remove the per-pass cap (global)
67
+ /review-mode timeout 600 project # 10-minute cap for this repo only
68
+ ```
69
+
70
+ Turning the toggle off stops the reviewer and restores the single-agent plan/build flow exactly as it was. Nothing in Review Mode runs while it is disabled.
71
+
72
+ `/review-mode status` reflects what the current process loaded, so `/role` changes and extension edits are picked up on the next `/reload` (or a new session). After `/reload`, the reviewer model, the effective timeout and its settings file (and the absence of the old `flow` row) all reflect the current files on disk.
73
+
74
+ ### The reviewer's model
75
+
76
+ Review Mode runs in its own session with its own model, taken from the **`reviewer` role** (`/role`). The default seed is deliberately a *different* model from the planner so the review brings an independent perspective — a reviewer that shares the planner's model and thinking tends to share its blind spots.
77
+
78
+ ```text
79
+ /role set reviewer opencode-go/muse-spark-1.3-contributor xhigh
80
+ ```
81
+
82
+ If the reviewer role has no model, Review Mode falls back to the planner model and warns rather than silently doing nothing.
83
+
84
+ Roles load from `~/.pi/agent/roles.json` (with a `<cwd>/.pi/roles.json` project override) **at session start and again whenever either file changes on disk**, so a `/role` change made in another window is picked up without `/reload`. A missing, unreadable, or legacy `roles.json` is never overwritten: the built-in defaults are used in memory and a warning naming the file is shown, leaving it intact. `/review-mode status`, the mode band and the reviewer run all read through the same loader.
85
+
86
+ ### Reviewer guidance
87
+
88
+ `.pi/review-prompt.md` (project) wins over `~/.pi/agent/review-prompt.md` (global), which wins over the built-in default. Edit it with `/review-prompt`.
89
+
90
+ The built-in default asks the reviewer to, in priority order: align with the **existing project framework**, follow **industry best practice**, verify every requirement maps to a plan step, check the plan is executable without guessing, and prefer simplicity. Repository context files (`AGENTS.md`, `CLAUDE.md`, …) also load into the reviewer's system prompt, which is where most framework alignment actually comes from.
91
+
92
+ This guidance lives **only in the reviewer's session**. Plan Mode receives a single neutral line ("a reviewer will audit this plan") and can never see the reviewer's prompt, findings, or transcript.
93
+
94
+ ### Permissions
95
+
96
+ Plan Mode and Review Mode share one role-based command policy (`permissions.ts`). Review Mode has Plan Mode's permissions: read-only inspection plus test/lint/typecheck runs, and a single plan-write path.
97
+
98
+ | Tool | Purpose |
99
+ | --- | --- |
100
+ | `read`, `grep`, `find`, `ls` | Inspect the repository |
101
+ | `review_bash` | Shell access on the same role policy Plan Mode uses: read-only inspection plus **test/lint/typecheck** runners (`npm test`, `npm run lint/typecheck`, `eslint`, `tsc --noEmit`, `node --test`, `pytest`, `cargo clippy`, …). No file redirects (`2>/dev/null` and `2>&1` are allowed), no `sed`/`awk`, no `rm`/`mv`/`cp`/`mkdir`/`touch`, no `sudo`, no package installs, no builds, no `git add/commit/push` |
102
+ | `review_explore` | Read-only reconnaissance subagents on the **explorer** role's model, capped by `exploreBudget` per round |
103
+ | `review_submit_plan` | The **only** write path: validates and saves the revised plan to Plan Mode's own `.pi/plans/<UTC-date>-<slug>.md` |
104
+ | `review_pass_done` | Ends a pass with a verdict (refused if nothing was submitted) |
105
+
106
+ The policy classifies commands into three classes and maps them to roles:
107
+
108
+ | Role | Command class | Write tools | Used by |
109
+ | --- | --- | --- | --- |
110
+ | planner | read-only + verify | `.pi/plans/` only | Plan Mode |
111
+ | reviewer | read-only + verify | `review_submit_plan` only | Review Mode |
112
+ | explorer | read-only | none | `explore` / `review_explore` subagents |
113
+ | builder | full (unrestricted) | all | Build Mode / normal sessions |
114
+
115
+ - **read-only** — inspection only (`ls`, `cat`, `rg`, `git log/blame/ls-files/show-ref`, `npm run` / `yarn run` with no script argument (lists them), `docker logs`, `journalctl`, `base64`, `ss`, `netstat`, `lsof`, …). `sed`/`awk` are deliberately **not** read-only — their programs are code — so they are Builder-only.
116
+ - **verify** — the above plus `npm test`, `npm run lint|typecheck|check`, `eslint`, `tsc --noEmit`, `node --test`, `npm run`/`node --run` with an allowlisted script, `pytest`, `mypy`, `go vet`, `cargo clippy`, … Runners that rewrite (`--fix`, `--write`, `-u`/`-w`/`--update`, `--test-update-snapshots`, `--watch`) — including through a wrapper such as `npm test -- -u` — and arbitrary output-path flags (`eslint -o`, `jest --outputFile`) stay blocked, as do build commands (`npm run build`).
117
+ - **full** — no gate (Builder and normal sessions only).
118
+
119
+ Compound commands are checked segment-by-segment with quote awareness, so `git status && npm test` and `npm test 2>/dev/null | tail -50` work while `curl … | sh`, `$(…)`, backticks, heredocs, `sed`/`awk` and file redirects to anything other than `/dev/null` are refused. Refusals name the rule that failed (`explainCommandRefusal`), so a blocked command explains what to change. Explorer subagents run as separate `pi` processes and carry their role in `PI_WORKFLOW_ROLE`, so the same policy gates their `bash` too. The `powershell` tool is refused for every non-builder role, since its syntax cannot be classified by the POSIX allowlist.
120
+
121
+ The built-in `bash`, `edit` and `write` tools are never given to the reviewer at all.
122
+
123
+ The child session is created with an explicit tool allowlist: the read-only built-ins **plus every reviewer tool above**. That union is load-bearing — Pi's `createAgentSession({ tools })` option is a *global* allowlist that filters custom tools too, so a built-ins-only list silently removes `review_submit_plan` and makes every review end in a fallback. The runtime therefore reads `getActiveToolNames()` straight after creation and refuses the session (naming the missing tools) rather than starting a review that cannot succeed; the active tool set is also logged once per session.
124
+
125
+ ### Two passes
126
+
127
+ 1. **Review + rewrite** — audits the plan against the repository and the guidance, records findings, submits the revised plan.
128
+ 2. **Self-verify** — re-reads the original and the rewrite, confirms every accepted finding was actually addressed, and reports anything that was silently dropped. This pass exists because a rewrite can lose a requirement the author cared about.
129
+
130
+ Pass 2 is skipped when `verify` is false or when pass 1 found nothing at severity ≥ 5.
131
+
132
+ ### Every change is visible
133
+
134
+ A `## Review changes` appendix is appended to the plan automatically (the reviewer is told not to write it), grouped by tier with file:line, severity, confidence and rationale:
135
+
136
+ ```markdown
137
+ ## Review changes
138
+
139
+ reviewer `opencode-go/muse-spark-1.3-contributor` · verdict **revise**
140
+
141
+ **Findings:** 2 addressed, 1 rejected, 1 deferred
142
+
143
+ ### Addressed
144
+
145
+ **Critical**
146
+
147
+ - 🔴 `src/export/csv.ts:1` — Plan ignored the Result<T,E> convention _(severity 9, confidence 85%, framework-alignment)_
148
+ - Every sibling exporter returns Result; the plan returned a bare string.
149
+
150
+ ### Deferred
151
+
152
+ - ⏸ No rollback path _(severity 7, confidence 80%)_
153
+ ```
154
+
155
+ ### Review in the main transcript
156
+
157
+ Because the two agents run sequentially — Plan Mode finishes before the reviewer starts — their transcripts can never interleave, and there is no overlay or second pane to navigate. Everything shows up in Pi's normal transcript.
158
+
159
+ Mode changes are reported by Pi's **footer status** (`⏸ plan` / `▶ build`, plus todo progress): switching with **Tab** or `Ctrl+Alt+P` is silent and adds nothing to the transcript, while `/plan`, `/build` and `/default` print a single confirmation line. Only the review phase adds bands, and the reviewer's stream is rendered in the review color:
160
+
161
+ | Entry | Color | Meaning |
162
+ | --- | --- | --- |
163
+ | `▌ REVIEW MODE` | accent | A review round is running (round + model shown) |
164
+ | action headers | muted | One line per reviewer tool call with its key argument (`▸ read src/app.ts`, `▸ review_bash: git log -5`). Assistant prose and thinking are not streamed, so the transcript stays close to Plan Mode's; status lines are muted |
165
+ | `▌ REVIEW DONE` | success / warning | Round finished, with the verdict or the fallback reason |
166
+ | `▌ REVIEW SUMMARY` | success / warning | The submitted plan's verdict, plan path and findings (severity-colored) |
167
+
168
+ These are **display-only custom entries** (`pi.appendEntry` + `pi.registerEntryRenderer`): they are durable in the transcript and survive `/reload`, but they never enter the LLM context, so Plan Mode and Build Mode cannot see the reviewer's work. Consecutive identical bands are suppressed (the gate is seeded from the newest band on reload), so cycling modes or re-running a review cannot grow the transcript. Long reviews are coalesced (flushed roughly every 400 ms) and capped, with a one-line notice pointing at `/review-status` when output is truncated.
169
+
170
+ #### Where your next message goes
171
+
172
+ Input follows the **active mode**, decided automatically — there is nothing to focus or toggle:
173
+
174
+ - **Plan Mode** → the message goes to the plan agent.
175
+ - **Review Mode** (a review round is in flight) → the message is delivered to the reviewer's session (steered if it is mid-pass) and echoed in the transcript as `you → reviewer`. The plan agent never sees it.
176
+ - **Neither** (including while the plan sits in Plannotator) → the message goes to the plan agent, so you can still refine the plan.
177
+
178
+ Delivery to the reviewer is best-effort: if it fails, the message falls through to the plan agent rather than being swallowed.
179
+
180
+ **Status line.** While a review is running, the status widget above the editor shows the live phase (`🔍 review · pass 2/2 · 3 findings · ● verifying`).
181
+
182
+ ### Isolation
183
+
184
+ The reviewer is an in-process child session created with `createAgentSession`, and it is isolated on purpose:
185
+
186
+ - `DefaultResourceLoader({ noExtensions: true })` — the child cannot load this (or any other) extension, so Review Mode cannot recurse.
187
+ - `SessionManager.inMemory(...)` — no session file is written; the reviewer never touches the parent's history.
188
+ - Its own `ModelRuntime` — no contention with the parent's model state.
189
+ - Plan Mode's conversation is passed as **pruned** prompt context (oversized tool results elided, workflow-injected markers dropped, nothing after the plan write), not by sharing the session.
190
+
191
+ An environment marker (`PI_WORKFLOW_REVIEW_CHILD`) is set only for the duration of session creation and restored immediately — the child runs in the same process, so leaking it would affect a later `/reload`.
192
+
193
+ ### Configuration
194
+
195
+ Settings live under `workflow.reviewMode` in `~/.pi/agent/settings.json` (global) or `<cwd>/.pi/settings.json` (project). Project values win field-by-field over global. Writing them preserves every other key in the file.
36
196
 
37
197
  ## Settings
38
198
 
39
- Role config lives in `~/.pi/agent/roles.json` v2 (user) with optional per-project override at `<cwd>/.pi/roles.json` (per-role replace). Each role holds exactly one `{provider, id, thinking}`. v1 `modelPool` files are discarded and reseeded on load. Seeded with defaults on first run. No extra `settings.json` keys — respects `theme`, `modelRegistry`, and `plannotator` phases from `agent/plannotator.json`.
199
+ Role config lives in `~/.pi/agent/roles.json` v2 (user) with optional per-project override at `<cwd>/.pi/roles.json` (per-role replace). Each role holds exactly one `{provider, id, thinking}`. v1 `modelPool` files are discarded and reseeded on load. Seeded with defaults on first run. Respects `theme`, `modelRegistry`, and `plannotator` phases from `agent/plannotator.json`.
200
+
201
+ Review Mode settings live under `workflow.reviewMode`:
202
+
203
+ | Key | Default | Meaning |
204
+ | --- | --- | --- |
205
+ | `enabled` | `false` | Master toggle. When false, every Review Mode code path is a no-op |
206
+ | `rounds` | `1` | Max Plan↔Review rounds per user-feedback cycle. Reached the cap → the plan is handed over with an `## Unresolved findings` appendix |
207
+ | `passes` | `2` | `1` = review+rewrite only; `2` = review, then self-verify |
208
+ | `verify` | `true` | Run the self-verification pass |
209
+ | `fallbackOnError` | `"skip"` | `skip` hands over the author's plan on any failure (`block` is reserved) |
210
+ | `exploreBudget` | `3` | Max `review_explore` subagents per round (cost ceiling) |
211
+ | `timeoutMs` | `0` | Optional wall-clock cap for a whole pass, run included. `0` = unlimited (default); a pass then ends on `review_pass_done`, run settle, abort or error. A silent reviewer is still caught immediately by the settle race. `/review-mode status` prints the value and the settings file that set it, and `/review-mode timeout <seconds\|unlimited>` changes it without editing JSON |
212
+
213
+ ```json
214
+ {
215
+ "workflow": {
216
+ "reviewMode": { "enabled": true, "rounds": 1, "passes": 2, "exploreBudget": 3 }
217
+ }
218
+ }
219
+ ```
220
+
221
+ `"reviewMode": true` is accepted as shorthand for `{ "enabled": true }`.
40
222
 
41
223
  ## How it works
42
224
 
43
- See `index.ts`, `checkpoint.ts` (shadow bare-repo), `utils.ts` (safe-command gating, todo/plan extraction), `roles.ts` (role registry, one model per role). Hot-reload via `/reload` when placed in `~/.pi/agent/extensions/workflow/` or loaded via `pi.extensions` manifest.
225
+ See `index.ts`, `checkpoint.ts` (shadow bare-repo), `utils.ts` (safe-command gating, todo/plan extraction, plan hashing), `roles.ts` (role registry, one model per role), and the Review Mode modules: `review.ts` (config, prompt, state, transcript, child session — no runtime imports, so it is unit-testable), `review-tools.ts` (the reviewer's tool set), `review-runtime.ts` (round orchestration), `review-ui.ts` (the main-transcript entries: line buffer, formatters, renderers). Hot-reload via `/reload` when placed in `~/.pi/agent/extensions/workflow/` or loaded via `pi.extensions` manifest.
44
226
 
45
227
  ### Todo protocol (industry-aligned)
46
228
 
@@ -55,8 +237,8 @@ See `index.ts`, `checkpoint.ts` (shadow bare-repo), `utils.ts` (safe-command gat
55
237
  ```bash
56
238
  cd agent/extensions/workflow
57
239
  npm install
58
- npm run typecheck # tsc --noEmit
59
- npm test # node --test utils.todo.test.ts
240
+ npm run typecheck # tsc --noEmit (covers every source file, including the review modules)
241
+ npm test # node --test utils.todo.test.ts review.test.ts review-ui.test.ts
60
242
  ```
61
243
 
62
244
  Plain TypeScript, no build step — Pi loads `index.ts` directly (via jiti). From the repo root, `npm run verify` checks this package's manifest, tarball contents, tests and load path before a release. Live reload: edit the `.ts` file and run `/reload` in Pi.