@tianhai/pi-workflow-kit 1.4.0 → 1.6.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
@@ -4,7 +4,7 @@
4
4
 
5
5
  AI coding agents tend to skip design and jump straight into implementation, producing over-engineered or misaligned code. **pi-workflow-kit** solves this by hard-blocking write operations during brainstorm and planning phases — the agent *literally cannot modify your source files* until you approve the design.
6
6
 
7
- [pi](https://github.com/badlogic/pi-mono) package. Zero configuration required.
7
+ [pi](https://github.com/badlogic/pi-mono) package. Skills are portable; the workflow guard and `/pwk-setup` command are Pi integrations.
8
8
 
9
9
  ## Install
10
10
 
@@ -12,7 +12,13 @@ AI coding agents tend to skip design and jump straight into implementation, prod
12
12
  pi install npm:@tianhai/pi-workflow-kit
13
13
  ```
14
14
 
15
- No setup needed skills and guards activate automatically after install.
15
+ For Pi delegation providers that discover project agents, install the canonical PWK roles before starting a gated workflow:
16
+
17
+ ```text
18
+ /pwk-setup
19
+ ```
20
+
21
+ This creates the five role definitions under `.agents/agents/`. It does not install or configure a provider. Existing customized files are preserved; use `/pwk-setup --force` only when you explicitly want to replace differing role files. Run setup before `/skill:pwk-brainstorming`; the command is refused during brainstorm and plan phases.
16
22
 
17
23
  **Want to try before committing?**
18
24
 
@@ -20,13 +26,19 @@ No setup needed — skills and guards activate automatically after install.
20
26
  pi -e npm:@tianhai/pi-workflow-kit
21
27
  ```
22
28
 
23
- **Optional — parallel code review.** The feature-level review can run four specialized reviewers in parallel over the whole feature diff via the `subagent` tool. Install [`pi-subagents`](https://pi.dev/packages/pi-subagents) to enable it:
29
+ **Optional — delegated recon and review.** The skills request logical capabilities rather than a specific agent tool. If the host has no safe compatible provider, they perform recon and review inline. In Pi, [`@tintinweb/pi-subagents`](https://github.com/tintinweb/pi-subagents) is one compatible provider:
24
30
 
25
31
  ```bash
26
- pi install npm:pi-subagents
32
+ pi install npm:@tintinweb/pi-subagents
27
33
  ```
28
34
 
29
- The four reviewers (`pwk-spec-reviewer`, `pwk-tracing-reviewer`, `pwk-smell-reviewer`, `pwk-hazard-reviewer`) ship with this kit as **package agents** — `pi-subagents` discovers them automatically, no extra setup. Without `pi-subagents`, `pwk-executing-tasks` falls back to inline `/skill:pwk-code-review`.
35
+ After `/pwk-setup`, Tintinweb can discover the five named roles from `.agents/agents/`; recon can also use its built-in read-only `Explore` agent. No running subagents need to be pre-created. Other Pi extensions require the documented capabilities or a separate adapter; arbitrary extensions are not automatically compatible.
36
+
37
+ ### Using the roles on other hosts
38
+
39
+ The five role files define portable logical roles, not a required provider API. Claude Code can map them to its native read-only task/subagent mechanism, but must enforce its own permissions or hooks because the Pi workflow guard does not transfer outside Pi. A different Pi extension can use the same roles when it provides the documented capabilities or an adapter; otherwise PWK performs recon and review inline.
40
+
41
+ See [`docs/provider-delegation-contract.md`](docs/provider-delegation-contract.md) for the normalized capability and outcome contract. The core kit does not install a provider, require `@tintinweb/pi-subagents`, or provide automatic compatibility with every Pi subagent extension.
30
42
 
31
43
  ## What You Get
32
44
 
@@ -58,10 +70,10 @@ A **design doc is one PR**; a **requirement is one testable slice within it**. A
58
70
 
59
71
  | Phase | Trigger | What Happens |
60
72
  |-------|---------|--------------|
61
- | **Brainstorm** | `/skill:pwk-brainstorming` | Explore approaches, produce a design doc with a `## Requirements` list. On non-trivial topics, dispatches the `pwk-recon-scout` agent (read-only) to map the codebase before design. |
73
+ | **Brainstorm** | `/skill:pwk-brainstorming` | Explore approaches, produce a design doc with a `## Requirements` list. On non-trivial topics, requests the logical `codebase-recon` capability; if unavailable or unsafe, performs the `pwk-recon-scout` role inline. |
62
74
  | **Plan** | `/skill:pwk-writing-plans` | Turn each requirement into **acceptance criteria + integration tests** — a behavioral spec (no implementation code) |
63
75
  | **Execute** | `/skill:pwk-executing-tasks` | Write the feature E2E (red) → **checkpoint: feature-spec** → implement requirements → **checkpoint: feature-complete** → feature review |
64
- | **Code review** | `/skill:pwk-code-review` | Feature-level (default) or per-requirement: code tracing, spec alignment, code smells (applies fixes), production hazard check |
76
+ | **Code review** | `/skill:pwk-code-review` | Feature-level (default) or per-requirement: code tracing, spec alignment, code smells (applies fixes), production hazard check. Delegated review uses four tiered logical roles (smell/hazard on a fast model via `/pwk-setup --fast-model`) over a script-assembled review packet when a safe provider is available; otherwise it runs inline. |
65
77
  | **Finalize** | `/skill:pwk-finalizing` | Delete consumed plan docs, update README/CHANGELOG, create PR |
66
78
  | **Diagnose** | `/skill:pwk-diagnose` | Debugging loop: reproduce → hypothesise → instrument → fix → cleanup. **Exits the gated phase** (debugging writes tests/instrumentation) |
67
79
  | **Status** | `/skill:pwk-status` | Read-only overview of all active design topics — phase + progress. Use when resuming or juggling several designs in parallel worktrees. Not a pipeline phase; **does not exit the gated phase**. |
@@ -177,11 +189,12 @@ pi-workflow-kit/
177
189
  │ ├── pwk-finalizing/SKILL.md
178
190
  │ ├── pwk-status/SKILL.md
179
191
  │ └── pwk-diagnose/SKILL.md
180
- ├── agents/ # package agents for parallel code-review + recon scout (discovered by pi-subagents)
192
+ ├── agents/ # canonical role contracts; /pwk-setup copies them to .agents/agents/
181
193
  ├── docs/
182
194
  │ ├── developer-usage-guide.md
183
195
  │ ├── workflow-phases.md
184
196
  │ ├── oversight-model.md
197
+ │ ├── provider-delegation-contract.md
185
198
  │ ├── lessons.md
186
199
  │ ├── adr/ # permanent architectural decisions (never archived)
187
200
  │ └── plans/ # active design/plan/progress docs (deleted after finalization)
@@ -3,13 +3,30 @@ name: pwk-hazard-reviewer
3
3
  description: Production-hazard reviewer — audits for unbounded ops, missing indexes, unbounded concurrency, long transactions, injection, silent swallowing loops. Read-only reporter.
4
4
  tools: read, grep, find, ls, bash
5
5
  systemPromptMode: replace
6
+ # model: <fast-tier> — set yours via /pwk-setup
7
+ thinking: low
8
+ max_turns: 20
6
9
  ---
7
10
 
8
- # PWK Hazard Reviewer
11
+ # PWK Reviewer
9
12
 
10
- You are a production-hazards reviewer. Execute the task instructions below faithfully using read-only tools (`read`, `grep`, `find`, `bash`). **Report findings only — do NOT modify files.** Flag hazards and non-trivial issues for the main agent / human to decide.
13
+ You are a read-only code reviewer. Execute the task instructions below faithfully using the host’s read-only tools. **Report findings only — do not modify files.**
11
14
 
12
- ## Checklist — audit each changed file
15
+ ## Authority boundary
16
+
17
+ The host must enforce read-only execution. Do not create, modify, delete, move, or copy files, and do not run commands that mutate system or repository state.
18
+
19
+ ## Reporting contract
20
+
21
+ Every finding cites evidence as file and line (file:line). For each finding, state the affected location, what you observed, and why it matters. If there are no findings, report `No findings` explicitly — an empty or missing report is not a valid outcome.
22
+
23
+ ## Working from the packet
24
+
25
+ The task provides a review packet: the diff under review plus the acceptance criteria, feature acceptance, production-risk notes, and a list of changed files. Work from the packet. Targeted reads of the files it lists are expected — read around the hunks you are judging. Reads beyond the packet are allowed only to verify a specific suspected finding; cite what sent you there. Do not re-derive scope: no re-running git log, no repo-wide sweeps. Your turn budget is a backstop, not a target. If you wrap up before completing your checklist — turn limit reached or otherwise — state explicitly what was not covered.
26
+
27
+ ## Your checklist
28
+
29
+ ### Production hazards — audit each changed file
13
30
 
14
31
  For each item below, write `[SAFE]` (1-line justification) or `[TRIGGERED]` (concrete mitigation):
15
32
 
@@ -21,4 +38,6 @@ For each item below, write `[SAFE]` (1-line justification) or `[TRIGGERED]` (con
21
38
  6. **Unrestricted uploads / temp flooding** — uploads to local temp without limits or `finally` cleanup
22
39
  7. **Silent swallowing loops** — background workers catching/suppressing exceptions without logging/back-off
23
40
 
24
- Also check the design doc's `## Production-risk areas`, if any.
41
+ Also check the design docs `## Production-risk areas`, if any.
42
+
43
+ Include file and line evidence for each finding. If there are no findings, report `No findings` explicitly.
@@ -7,21 +7,21 @@ systemPromptMode: replace
7
7
 
8
8
  # PWK Recon Scout
9
9
 
10
- You are a codebase recon scout dispatched by `pwk-brainstorming` before design work. Your job is to map how a repository handles a topic today so the main agent can design against prior art instead of loading the relevant files into its own context.
10
+ You are a codebase recon scout requested during brainstorming before design work. Your job is to map how a repository handles a topic today so the main agent can design against prior art instead of loading the relevant files into its own context.
11
11
 
12
12
  **You are observations only.** No design recommendations, no preferred-approach opinion, no code beyond one-line excerpts. Every claim must cite a `file:line` so the main agent can drill in if it needs to.
13
13
 
14
- ## Tools
14
+ ## Authority boundary
15
15
 
16
- You inherit the read-only set the workflow-guard already enforces on the brainstorm session: `read, grep, find, ls, bash`. Do not attempt writes or edits they will be blocked.
16
+ You are a read-only reporter. The host must enforce the requested read-only boundary; do not create, modify, delete, move, or copy files, and do not run commands that mutate system or repository state.
17
17
 
18
18
  ## Inputs
19
19
 
20
- The main agent dispatches you with three things in the task string:
20
+ The host provides three things:
21
21
 
22
22
  - a `<topic>` (one short phrase, the new feature or change)
23
23
  - a one-line `<intent>` (what the new thing does, in plain words)
24
- - the repo root
24
+ - the repository root
25
25
 
26
26
  If any of these is missing, ask for it before proceeding.
27
27
 
@@ -47,7 +47,9 @@ Where similar tests live, what harness they use (vitest, jest, go test, etc.), a
47
47
 
48
48
  ### Gotchas
49
49
 
50
- Anything that bit a previous change, in this layer of the code or in the topic area specifically. A migrations folder that must run in order, a feature flag that gates the new path, a known deadlock with another subsystem, an environment variable that has to be set, a CI hook that runs before tests. The point is to surface landmines before the main agent commits to a design.
50
+ Anything that bit a previous change, in this layer of the code or in the topic area specifically. A migrations folder that must run in order, a feature flag that gates the new path, a known deadlock with another subsystem, an environment variable that has to be set, a CI hook that runs before tests. The point is to surface landmines before the planning phase.
51
+
52
+ End with `Scout: complete` when the five sections are present. If the host cannot complete the report, return `Scout: unavailable` with the reason instead of inventing observations.
51
53
 
52
54
  ## Hard rules
53
55
 
@@ -59,10 +61,9 @@ Anything that bit a previous change, in this layer of the code or in the topic a
59
61
 
60
62
  ## When you finish
61
63
 
62
- Return the report as your final message. The main agent reads it into its context and uses it as the grounding for the next two brainstorm steps (Explore approaches, Present the design).
64
+ Return the report as your final message. The main agent reads it into its context and uses it as the grounding context for approach exploration and design presentation.
63
65
 
64
66
  ## Failure modes
65
67
 
66
- - Subagent tool unavailable: the main agent will fall back to inline recon and you will not be invoked. You do not need to handle this case.
67
- - You return empty: the main agent will treat it as greenfield and proceed with no-prior-art assumptions. Returning a short, honest report is better than padding it.
68
- - You return a wrong-shaped report: the main agent will downweight the findings but still proceed. Better to ship the 5-section shape than to improvise.
68
+ - If no compatible read-only delegation worker is available, the main agent performs this role inline.
69
+ - If the role cannot complete, return `Scout: unavailable` with the reason rather than returning an empty or invented report.
@@ -3,15 +3,36 @@ name: pwk-smell-reviewer
3
3
  description: Code-smell reviewer — flags shallow modules, duplication, missing seams, premature abstraction, poor naming, magic values, dead code. Read-only reporter.
4
4
  tools: read, grep, find, ls, bash
5
5
  systemPromptMode: replace
6
+ # model: <fast-tier> — set yours via /pwk-setup
7
+ thinking: low
8
+ max_turns: 20
6
9
  ---
7
10
 
8
- # PWK Smell Reviewer
11
+ # PWK Reviewer
9
12
 
10
- You are a code-smell reviewer. Execute the task instructions below faithfully using read-only tools (`read`, `grep`, `find`, `bash`). **Report findings only — do NOT modify files.** Flag only: smells that require large refactors risky to the requirement; everything else is for the main agent to fix.
13
+ You are a read-only code reviewer. Execute the task instructions below faithfully using the host’s read-only tools. **Report findings only — do not modify files.**
11
14
 
12
- ## Checklist — report what you find
15
+ ## Authority boundary
16
+
17
+ The host must enforce read-only execution. Do not create, modify, delete, move, or copy files, and do not run commands that mutate system or repository state.
18
+
19
+ ## Reporting contract
20
+
21
+ Every finding cites evidence as file and line (file:line). For each finding, state the affected location, what you observed, and why it matters. If there are no findings, report `No findings` explicitly — an empty or missing report is not a valid outcome.
22
+
23
+ ## Working from the packet
24
+
25
+ The task provides a review packet: the diff under review plus the acceptance criteria, feature acceptance, production-risk notes, and a list of changed files. Work from the packet. Targeted reads of the files it lists are expected — read around the hunks you are judging. Reads beyond the packet are allowed only to verify a specific suspected finding; cite what sent you there. Do not re-derive scope: no re-running git log, no repo-wide sweeps. Your turn budget is a backstop, not a target. If you wrap up before completing your checklist — turn limit reached or otherwise — state explicitly what was not covered.
26
+
27
+ ## Your checklist
28
+
29
+ ### Code smells
30
+
31
+ Review the changed code and affected files against the assigned requirement and feature scope. Flag only smells that require large refactors risky to the requirement; everything else is for the main agent to fix.
13
32
 
14
33
  - Shallow modules (interface nearly as complex as implementation)
15
34
  - Duplication
16
- - Missing seams / premature abstraction
17
- - Poor naming, magic values, dead code
35
+ - Missing seams or premature abstraction
36
+ - Poor naming, magic values, dead code
37
+
38
+ Include file and line evidence for each finding. If there are no findings, report `No findings` explicitly.
@@ -3,12 +3,27 @@ name: pwk-spec-reviewer
3
3
  description: Spec-alignment reviewer — checks each acceptance criterion has covering code and tests; flags gaps and scope creep. Read-only reporter.
4
4
  tools: read, grep, find, ls, bash
5
5
  systemPromptMode: replace
6
+ max_turns: 40
6
7
  ---
7
8
 
8
- # PWK Spec Reviewer
9
+ # PWK Reviewer
9
10
 
10
- You are a spec-alignment reviewer. Execute the task instructions below faithfully using read-only tools (`read`, `grep`, `find`, `bash`). **Report findings only — do NOT modify files.**
11
+ You are a read-only code reviewer. Execute the task instructions below faithfully using the host’s read-only tools. **Report findings only — do not modify files.**
11
12
 
12
- ## Checklist
13
+ ## Authority boundary
14
+
15
+ The host must enforce read-only execution. Do not create, modify, delete, move, or copy files, and do not run commands that mutate system or repository state.
16
+
17
+ ## Reporting contract
18
+
19
+ Every finding cites evidence as file and line (file:line). For each finding, state the affected location, what you observed, and why it matters. If there are no findings, report `No findings` explicitly — an empty or missing report is not a valid outcome.
20
+
21
+ ## Working from the packet
22
+
23
+ The task provides a review packet: the diff under review plus the acceptance criteria, feature acceptance, production-risk notes, and a list of changed files. Work from the packet. Targeted reads of the files it lists are expected — read around the hunks you are judging. Reads beyond the packet are allowed only to verify a specific suspected finding; cite what sent you there. Do not re-derive scope: no re-running git log, no repo-wide sweeps. Your turn budget is a backstop, not a target. If you wrap up before completing your checklist — turn limit reached or otherwise — state explicitly what was not covered.
24
+
25
+ ## Your checklist
26
+
27
+ ### Spec alignment
13
28
 
14
29
  For each acceptance criterion, point to the code and the test that satisfy it. A criterion with no covering code or no test is a **gap**. Code that does more than the criteria specify is **scope creep** — flag it.
@@ -3,12 +3,27 @@ name: pwk-tracing-reviewer
3
3
  description: Code-tracing reviewer — traces new/changed paths end-to-end against tests; flags untested branches, dead branches, and broken traces. Read-only reporter.
4
4
  tools: read, grep, find, ls, bash
5
5
  systemPromptMode: replace
6
+ max_turns: 40
6
7
  ---
7
8
 
8
- # PWK Trace Reviewer
9
+ # PWK Reviewer
9
10
 
10
- You are a code-tracing reviewer. Execute the task instructions below faithfully using read-only tools (`read`, `grep`, `find`, `bash`). **Report findings only — do NOT modify files.**
11
+ You are a read-only code reviewer. Execute the task instructions below faithfully using the host’s read-only tools. **Report findings only — do not modify files.**
11
12
 
12
- ## Checklist
13
+ ## Authority boundary
13
14
 
14
- Trace the new/changed code paths end-to-end against the integration tests. For each path: does data flow correctly from entry to the asserted outcome? Note any branch the tests don't exercise, any dead branch, any path where the trace breaks.
15
+ The host must enforce read-only execution. Do not create, modify, delete, move, or copy files, and do not run commands that mutate system or repository state.
16
+
17
+ ## Reporting contract
18
+
19
+ Every finding cites evidence as file and line (file:line). For each finding, state the affected location, what you observed, and why it matters. If there are no findings, report `No findings` explicitly — an empty or missing report is not a valid outcome.
20
+
21
+ ## Working from the packet
22
+
23
+ The task provides a review packet: the diff under review plus the acceptance criteria, feature acceptance, production-risk notes, and a list of changed files. Work from the packet. Targeted reads of the files it lists are expected — read around the hunks you are judging. Reads beyond the packet are allowed only to verify a specific suspected finding; cite what sent you there. Do not re-derive scope: no re-running git log, no repo-wide sweeps. Your turn budget is a backstop, not a target. If you wrap up before completing your checklist — turn limit reached or otherwise — state explicitly what was not covered.
24
+
25
+ ## Your checklist
26
+
27
+ ### Code tracing
28
+
29
+ Trace the new or changed code paths end-to-end against the integration tests. For each path, determine whether data flows correctly from entry to the asserted outcome. Note any branch the tests do not exercise, any dead branch, or any path where the trace breaks.
@@ -1,6 +1,6 @@
1
1
  # Developer Usage Guide
2
2
 
3
- How to install and use `pi-workflow-kit` with the Pi coding agent.
3
+ How to install and use `pi-workflow-kit` with Pi, and how its workflow roles map to other agent hosts.
4
4
 
5
5
  ## What you get
6
6
 
@@ -40,11 +40,19 @@ You control each phase by invoking the skill. A design doc is one PR; a requirem
40
40
 
41
41
  ### 1. Brainstorm
42
42
 
43
+ Before entering the gated phase in Pi, optionally install the canonical role definitions:
44
+
45
+ ```
46
+ /pwk-setup
47
+ ```
48
+
49
+ The command creates `.agents/agents/` and installs the five PWK roles. It preserves differing files unless `--force` is supplied and is refused during brainstorm and plan phases. It does not install a delegation provider.
50
+
43
51
  ```
44
52
  /skill:pwk-brainstorming
45
53
  ```
46
54
 
47
- Explore the idea through collaborative dialogue. The agent reads code, asks questions, proposes approaches, and presents the design for your review. On non-trivial topics with prior art, the agent dispatches the `pwk-recon-scout` package agent (read-only, fresh context) to map the codebase before design, so the main agent can design against prior art instead of loading files into its own context.
55
+ Explore the idea through collaborative dialogue. The agent reads code, asks questions, proposes approaches, and presents the design for your review. On non-trivial topics with prior art, the skill requests the logical `codebase-recon` capability using the `pwk-recon-scout` role. A compatible host may dispatch that role in a fresh, bounded, read-only worker; otherwise the skill reports `Scout: unavailable` and performs the same five-section recon inline.
48
56
 
49
57
  Outcome: `docs/plans/YYYY-MM-DD-<topic>-design.md` — descriptive, opening with a `## Requirements` list. For a too-big requirement, may start an **umbrella** (writes a status-free overview + the first part's design doc). ADRs go to `docs/adr/` (permanent).
50
58
 
@@ -68,13 +76,11 @@ Implement via the **feature-gate flow** with full autonomy: write the feature-ac
68
76
 
69
77
  ### 4. Code review (feature level)
70
78
 
71
- The `pwk-executing-tasks` skill invokes the `subagent` tool automatically at the feature-level review (programmatic, not user-driven). Four specialized reviewers launch in parallel over the whole feature diff — each from a different dimension (spec gaps & scope creep, end-to-end code tracing, code smells, production hazards). A per-requirement review runs the same way for a tagged requirement. The reviewers ship as **package agents** (`agents/pwk-*.md`, declared via the `pi-subagents.agents` manifest key) and are discovered natively by the optional **`pi-subagents`** packageno copy step. All report findings only; no agent edits files or produces commits. The main agent collects results, applies smell fixes itself, runs integration tests after each fix, then updates progress to `✅ done`.
79
+ The `pwk-executing-tasks` skill requests the `parallel-review` capability for four logical roles over the whole feature diff: spec alignment, code tracing, code smells, and production hazards. The scope is a script-assembled review packet (diff + acceptance criteria verbatim) handed to every role via a one-liner pointerthe packet never rides in spawn arguments. The roles are independent, fresh-context, read-only reporters; the main agent collects their results, applies smell fixes itself, runs the tests, and flags other findings for the human.
72
80
 
73
- *Fallback:* if `pi-subagents` is not installed (so the `subagent` tool is unavailable), the skill falls back to inline `/skill:pwk-code-review` as before. Install it to enable parallel review:
81
+ In Pi, `/pwk-setup` installs the canonical role definitions into `.agents/agents/`, where compatible providers such as `@tintinweb/pi-subagents` can discover them. `/pwk-setup --fast-model <model>` (or the interactive picker) sets the fast-tier model for the smell/hazard reviewers an advisory hint hosts may honor. Tintinweb may run the roles through its native `Agent` mechanism or map recon to its built-in read-only `Explore` type. The core kit does not require Tintinweb or any other provider.
74
82
 
75
- ```bash
76
- pi install npm:pi-subagents
77
- ```
83
+ *Fallback:* if no host/provider can guarantee the requested capabilities, the skill performs the missing recon or review work inline. Other Pi extensions are supported only when they expose the documented capabilities or have a separate adapter; arbitrary extensions are not automatically compatible. See `docs/provider-delegation-contract.md` for the integration contract.
78
84
 
79
85
  ### 5. Finalize
80
86
 
@@ -102,7 +108,7 @@ A read-only overview of all active design topics — which phase each is in and
102
108
 
103
109
  ## What the extension does
104
110
 
105
- The `workflow-guard` extension watches `write`/`edit` and `bash` tool calls:
111
+ The `workflow-guard` extension registers `/pwk-setup` and watches `write`/`edit` and `bash` tool calls:
106
112
 
107
113
  - **During brainstorm and writing-plans**: blocks writes outside `docs/plans/`, and blocks destructive bash via a simple common-blacklist (a command is allowed unless it matches a destructive pattern). A short phase reminder is shown once when the gated phase begins so the model self-restricts.
108
114
  - **During executing-tasks, code-review, finalizing, diagnose**: no restrictions.
@@ -6,10 +6,10 @@
6
6
 
7
7
  Skills teach the agent the workflow. There are 5 pipeline skills:
8
8
 
9
- - **pwk-brainstorming** — explore ideas, produce a descriptive design doc that opens with a `## Requirements` list. For a requirement too big for one design doc, may start an **umbrella** (multiple design docs under one status-free overview, shipping as one PR)
9
+ - **pwk-brainstorming** — explore ideas, produce a descriptive design doc that opens with a `## Requirements` list. For a requirement too big for one design doc, may start an **umbrella** (multiple design docs under one status-free overview, shipping as one PR). On non-trivial topics, requests the logical `codebase-recon` capability and falls back to the `pwk-recon-scout` role inline when unavailable or unsafe.
10
10
  - **pwk-writing-plans** — turn each requirement into acceptance criteria + integration-test cases (a behavioral spec, no implementation code)
11
11
  - **pwk-executing-tasks** — feature-gate flow: write the feature E2E first, implement the requirements, then one feature-level review; two mandatory checkpoints at the feature level, per-requirement ceremony opt-in
12
- - **pwk-code-review** — the inline reviewer (code tracing, spec alignment, code smells, production hazards). During `pwk-executing-tasks`, the **feature-level review** (the default) runs **four specialized reviewers in parallel** over the whole feature diff via the `subagent` tool, each from a fresh context (spec gaps & scope creep, tracing, smells, hazards); a per-requirement review runs the same way for a tagged requirement. These ship as package agents (`agents/pwk-*.md`) discovered natively by the optional **`pi-subagents`** package; all report findings only fixes are applied by the executing-tasks main agent. Falls back to inline `/skill:pwk-code-review` when `pi-subagents` is not installed.
12
+ - **pwk-code-review** — the inline reviewer (code tracing, spec alignment, code smells, production hazards). During `pwk-executing-tasks`, the feature-level review requests the `parallel-review` capability for four logical fresh-context, read-only roles; successful reports are retained and missing roles are retried or completed inline. It falls back to inline review when no safe compatible provider exists. The canonical provider contract is documented in `docs/provider-delegation-contract.md`.
13
13
  - **pwk-finalizing** — dispose consumed plan docs (archive or delete; for an umbrella, the overview + every part), curate lessons, update docs, create PR or merge
14
14
 
15
15
  Plus 2 on-demand skills:
@@ -21,7 +21,7 @@ They explain *what* to do and *when* to do it. Phase control is manual — you i
21
21
 
22
22
  ## Extension
23
23
 
24
- The `workflow-guard` extension enforces one rule:
24
+ The `workflow-guard` extension registers the Pi-only `/pwk-setup` command and enforces one workflow rule:
25
25
 
26
26
  > During brainstorm and plan phases, `write` and `edit` are **hard-blocked** outside `docs/plans/`.
27
27
 
@@ -29,7 +29,7 @@ The agent can still use `read` and `bash` for investigation. During those gated
29
29
 
30
30
  During executing-tasks, code-review, finalizing, **and diagnose**, nothing is restricted (diagnosis needs to write failing tests and debug instrumentation, so it exits the gate). `pwk-status` stays inside the gate.
31
31
 
32
- Reviewer-agent checklists live only in `agents/pwk-*-reviewer.md` (single source of truth); `pwk-executing-tasks` passes each reviewer just the requirement scope + diff and names the agent.
32
+ Canonical role contracts live in `agents/pwk-*.md` (single source of truth) and can be installed into `.agents/agents/` with `/pwk-setup`. `pwk-executing-tasks` requests logical review roles through the host’s delegation capabilities and passes each role a one-liner pointer to a script-assembled review packet — the packet defines the scope per review level (feature review: the whole feature diff; per-requirement: just that slice).
33
33
 
34
34
  Phases follow the skill you invoke — there is no message-keyword unlock. Invoking `/skill:pwk-executing-tasks`, `pwk-finalizing`, `pwk-code-review`, or `pwk-diagnose` exits the gated phase (those skills write source); `pwk-status` deliberately does **not** (read-only orientation). `/pwk-guard on|off|auto` manually overrides the guard.
35
35
 
@@ -0,0 +1,120 @@
1
+ # Provider Delegation Contract
2
+
3
+ This document defines the provider-neutral contract for running PWK roles outside the main agent. It is an integration contract for host adapters and extensions. The workflow skills describe the same behavior in portable language; they do not require this document’s TypeScript notation or any specific transport.
4
+
5
+ ## Logical operations
6
+
7
+ A provider may support either operation independently:
8
+
9
+ - `codebase-recon` — run one `pwk-recon-scout` role and return its five-section observation map.
10
+ - `feature-review` — run the requested review roles over one feature scope and return one outcome per role.
11
+
12
+ The logical role name is not the provider’s concrete agent type. A provider may map `pwk-recon-scout` to a safe built-in explorer, a custom agent definition, or a host-native read-only task.
13
+
14
+ ## Required capabilities
15
+
16
+ A provider advertises capabilities independently from its name:
17
+
18
+ | Capability | Meaning |
19
+ |---|---|
20
+ | `codebase-recon` | Can run one fresh, read-only recon worker and collect its report. |
21
+ | `named-role-dispatch` | Can map each requested logical role to an appropriate worker. |
22
+ | `parallel-review` | Can run independent review roles concurrently when requested. |
23
+ | `result-collection` | Returns one distinguishable outcome for every requested role. |
24
+ | `read-only-enforcement` | Prevents delegated workers from writing files or running destructive commands. |
25
+ | `fresh-context` | Starts each requested worker without reusing an unrelated prior conversation. |
26
+ | `bounded-execution` | Applies a timeout, turn limit, or equivalent resource bound. |
27
+
28
+ A provider must not claim `read-only-enforcement` when it only adds a prompt instruction. Providers may support `codebase-recon` without supporting `parallel-review`.
29
+
30
+ ## Role resource hints
31
+
32
+ Role definition frontmatter may declare optional resource hints: `model` (a host-resolvable model name), `thinking` (a reasoning-effort level), and `max_turns` (a turn budget — the per-role instance of `bounded-execution`). Hints are advisory:
33
+
34
+ - Hosts that support per-role resources SHOULD honor them; hosts that do not ignore them without failing the operation.
35
+ - If a `model` hint cannot be resolved to an available model, the host runs the role on its default model — an unresolvable hint MUST NOT fail the review.
36
+ - `max_turns` is a graceful backstop: the role wraps up and reports rather than running unbounded. A capped role still follows the normalized outcome rules — a non-empty report is required for completion.
37
+
38
+ ## Request shape
39
+
40
+ The following TypeScript is illustrative. Implementations may use Pi events, tool calls, CLI processes, native task APIs, or another transport. A reference implementation of the outcome normalization lives in `extensions/workflow-guard.ts` (`assessDelegationCoverage`), exported as a pure helper so future adapters and tests share one definition of complete coverage.
41
+
42
+ ```ts
43
+ type DelegationOperation = 'codebase-recon' | 'feature-review';
44
+ type DelegationRole =
45
+ | 'pwk-recon-scout'
46
+ | 'pwk-spec-reviewer'
47
+ | 'pwk-tracing-reviewer'
48
+ | 'pwk-smell-reviewer'
49
+ | 'pwk-hazard-reviewer';
50
+
51
+ type DelegationRequest = {
52
+ operation: DelegationOperation;
53
+ roles: DelegationRole[];
54
+ prompt: string;
55
+ cwd: string;
56
+ constraints: {
57
+ readOnly: true;
58
+ freshContext: true;
59
+ parallel: boolean;
60
+ bounded: true;
61
+ };
62
+ };
63
+ ```
64
+
65
+ The provider must preserve the logical roles and the repository root when translating a request. `parallel: true` requests concurrency; it does not permit unbounded concurrency. If the provider cannot satisfy a requested constraint, it must reject the delegated operation so the host can use inline fallback.
66
+
67
+ ## Normalized outcome shape
68
+
69
+ ```ts
70
+ type DelegationStatus = 'completed' | 'failed' | 'timed-out' | 'skipped';
71
+
72
+ type DelegationResult = {
73
+ role: DelegationRole;
74
+ status: DelegationStatus;
75
+ report?: string;
76
+ error?: string;
77
+ provider?: string;
78
+ runId?: string;
79
+ };
80
+ ```
81
+
82
+ Rules:
83
+
84
+ - `role` is required and must identify one requested logical role.
85
+ - `completed` requires a non-empty `report` that follows the role contract.
86
+ - `failed` and `timed-out` require an `error` or equivalent failure explanation.
87
+ - `skipped` is explicit and is not equivalent to completion.
88
+ - `provider` identifies the adapter or host that produced the outcome when known.
89
+ - `runId` is an opaque provider-local identifier when available; consumers must not interpret its format.
90
+ - A multi-role operation is complete only when every requested role has a completed result or an explicit fallback result approved by the workflow.
91
+
92
+ An empty report is not a successful result. A missing role is not silently discarded.
93
+
94
+ ## Fallback protocol
95
+
96
+ If no provider satisfies the requested capabilities, the host performs the role inline using the same logical contract. For recon, it reports:
97
+
98
+ ```text
99
+ Scout: unavailable — inline recon used.
100
+ ```
101
+
102
+ For feature review, successful delegated reports remain usable. A failed or timed-out role is retried or performed inline. The host does not report a complete review while a required role has neither a delegated result nor an inline result.
103
+
104
+ ## Provider discovery and selection
105
+
106
+ Provider discovery and transport are host-specific. A future Pi adapter may use a capability registry, an event-bus handshake, a shared extension RPC, or explicit configuration. The core PWK package does not assume that all Pi extensions are discoverable or that a package name identifies a compatible provider.
107
+
108
+ If multiple providers are available, selection must be deterministic. A host may use explicit configuration or a documented priority order. It must not select a provider based only on extension load order when that changes safety or result semantics.
109
+
110
+ ## Role setup and provider loading
111
+
112
+ The Pi-only `/pwk-setup` command copies the canonical role definitions into `.agents/agents/`. This makes named roles available to providers that discover the shared directory, including `@tintinweb/pi-subagents`. Setup creates role definitions; it does not install or configure a provider.
113
+
114
+ Providers may cache role definitions. Hosts should tell the user when `/reload` or a new session is needed. Setup must not reload a session implicitly.
115
+
116
+ ## Safety boundary
117
+
118
+ The provider is responsible for enforcing any capability it advertises. The role prompt is defense in depth, not a security boundary. The Pi workflow guard protects the main Pi session’s brainstorm and plan phases; it does not automatically protect Claude Code or another host, and it does not replace delegated-worker tool restrictions.
119
+
120
+ The `/pwk-setup` installer itself is hardened in depth: it opens the target with `O_NOFOLLOW` and does every subsequent check, read, and write through that one descriptor (`fstat` regular-file check, content comparison, and post-write verification), so nothing swapped in on the path afterward can affect what is read or written. On platforms where `O_NOFOLLOW` is unavailable, a symlink swapped in before the initial open remains a narrow advisory window; setup is a user-invoked development command, not a security boundary.
@@ -43,7 +43,7 @@ Write boundary: only `docs/plans/` is writable.
43
43
  /skill:pwk-executing-tasks
44
44
  ```
45
45
 
46
- - **Feature-gate flow:** write the feature-acceptance E2E test (red) → **⏸ checkpoint: feature-spec** (human confirms the E2E proves the feature) → implement the requirements back-to-back with full autonomy (the executor chooses structure/signatures/internals) → **⏸ checkpoint: feature-complete** (full suite + feature E2E green) → **feature review** (four parallel reviewers over the whole feature diff via the `subagent` tool; falls back to inline `/skill:pwk-code-review` when `pi-subagents` is absent — see [code-review](#code-review)).
46
+ - **Feature-gate flow:** write the feature-acceptance E2E test (red) → **⏸ checkpoint: feature-spec** (human confirms the E2E proves the feature) → implement the requirements back-to-back with full autonomy (the executor chooses structure/signatures/internals) → **⏸ checkpoint: feature-complete** (full suite + feature E2E green) → **feature review** (request the `parallel-review` capability for four logical read-only roles when the host supports it; otherwise run `/skill:pwk-code-review` inline — see [code-review](#code-review)).
47
47
  - Per-requirement checkpoints/reviews are **opt-in** — they fire only for requirements the plan tags (default off); see [Proportionality](#proportionality).
48
48
  - **Regression check after each commit** — run the full existing suite to catch cross-requirement regressions immediately. The feature E2E stays red until the last requirement and is gated only at `feature-complete` (the old integration gate folds into it).
49
49
  - Progress tracked in `docs/plans/*-progress.md` (feature phase + requirement checklist).
@@ -56,7 +56,7 @@ The **feature-gate flow** is the default: write the feature E2E first, implement
56
56
 
57
57
  - **Checkpoints** — `none` (no per-requirement stop, **default**) | `full` (both stops) | `spec` (tests stop only — cheap spec-correctness gate, implementation covered by review). Test-first is preserved either way: even `none` writes a meaningful test first (red) and implements to green; only the human *stops* are optional. `spec` requires at least `inline` review (never combine with `skip`).
58
58
  - **Review** — `skip` (no per-requirement review, **default**) | `parallel` (four fresh-context reviewers) | `inline` (single `pwk-code-review` pass).
59
- - **Feature review** — `parallel` (four reviewers over the whole feature diff, **default**) | `inline` (one pass, small features). Always on.
59
+ - **Feature review** — `parallel` (four reviewers over the whole feature diff, **default**) | `inline` (one pass, small features). Always on. The review scope is a script-assembled review packet (diff + criteria verbatim), so reviewers never re-derive scope; smell/hazard reviewers run on the fast tier set via `/pwk-setup --fast-model` (advisory hint).
60
60
 
61
61
  Flag a requirement for a checkpoint when it has complex logic or is the main part of the feature; for a review when it touches production-risk. A trivial fix can also skip the multi-turn brainstorm dialogue via the brainstorming trivial fast-path (compress to one turn, minimal design doc) — the guard still enforces read-only.
62
62
 
@@ -68,7 +68,7 @@ Flag a requirement for a checkpoint when it has complex logic or is the main par
68
68
 
69
69
  The **inline reviewer**: code tracing, spec alignment (vs acceptance criteria), code smells (applies fixes), production hazard check. Unlocked — may modify code to fix smells.
70
70
 
71
- **Not a phase you drive manually.** During `pwk-executing-tasks`, the **feature-level review** (the default) runs **four specialized reviewers in parallel** over the whole feature diff via the `subagent` tool (spec, tracing, smell, hazard — each fresh-context, read-only reporters); a per-requirement review runs the same way for a tagged requirement. This skill is the **fallback** when [`pi-subagents`](https://pi.dev/packages/pi-subagents) is not installed. You can also invoke `/skill:pwk-code-review` standalone for an ad-hoc review of any diff.
71
+ **Not a phase you drive manually.** During `pwk-executing-tasks`, the feature-level review requests four logical roles (`pwk-spec-reviewer`, `pwk-tracing-reviewer`, `pwk-smell-reviewer`, `pwk-hazard-reviewer`) through the host’s `parallel-review` capability. Roles are fresh-context, read-only reporters; successful reports are retained and failed roles are retried or completed inline. If no compatible provider is available, the whole review runs inline. In Pi, `/pwk-setup` installs the canonical role definitions into `.agents/agents/`; [`@tintinweb/pi-subagents`](https://github.com/tintinweb/pi-subagents) is one compatible provider. See `docs/provider-delegation-contract.md` for the provider contract. You can also invoke `/skill:pwk-code-review` standalone for an ad-hoc review of any diff.
72
72
 
73
73
  No write restrictions.
74
74
 
@@ -1,4 +1,17 @@
1
- import { resolve } from "node:path";
1
+ import {
2
+ closeSync,
3
+ constants,
4
+ fstatSync,
5
+ ftruncateSync,
6
+ lstatSync,
7
+ mkdirSync,
8
+ openSync,
9
+ readFileSync,
10
+ readSync,
11
+ writeSync,
12
+ } from "node:fs";
13
+ import { dirname, join, resolve } from "node:path";
14
+ import { fileURLToPath } from "node:url";
2
15
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
16
 
4
17
  /**
@@ -12,6 +25,331 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
12
25
 
13
26
  type Phase = "brainstorm" | "plan" | null;
14
27
 
28
+ type DelegationStatus = "completed" | "failed" | "timed-out" | "skipped";
29
+
30
+ export interface DelegationOutcome {
31
+ role: string;
32
+ status: DelegationStatus;
33
+ report?: string;
34
+ error?: string;
35
+ provider?: string;
36
+ runId?: string;
37
+ }
38
+
39
+ export interface DelegationCoverage {
40
+ complete: boolean;
41
+ missing: string[];
42
+ retainedReports: string[];
43
+ }
44
+
45
+ /** Summarize role outcomes without treating failed or empty outcomes as coverage. */
46
+ export function assessDelegationCoverage(
47
+ requiredRoles: readonly string[],
48
+ outcomes: readonly DelegationOutcome[],
49
+ ): DelegationCoverage {
50
+ const completedReports = new Map<string, string>();
51
+ for (const outcome of outcomes) {
52
+ if (outcome.status === "completed" && outcome.report) {
53
+ completedReports.set(outcome.role, outcome.report);
54
+ }
55
+ }
56
+
57
+ const missing = requiredRoles.filter((role) => !completedReports.has(role));
58
+ return {
59
+ complete: missing.length === 0,
60
+ missing,
61
+ retainedReports: [...completedReports.values()],
62
+ };
63
+ }
64
+
65
+ export const ROLE_NAMES = [
66
+ "pwk-recon-scout",
67
+ "pwk-spec-reviewer",
68
+ "pwk-tracing-reviewer",
69
+ "pwk-smell-reviewer",
70
+ "pwk-hazard-reviewer",
71
+ ] as const;
72
+
73
+ const REVIEWER_ROLES = ROLE_NAMES.filter((role) => role !== "pwk-recon-scout");
74
+ const FAST_TIER_ROLES = ["pwk-smell-reviewer", "pwk-hazard-reviewer"];
75
+
76
+ const FAST_MODEL_PLACEHOLDER = "# model: <fast-tier> — set yours via /pwk-setup";
77
+
78
+ /** Split a role file into its frontmatter block (with fences) and body. */
79
+ function splitFrontmatter(content: string): { frontmatter: string; rest: string } {
80
+ const match = content.match(/^(---\n[\s\S]*?\n---\n)([\s\S]*)$/);
81
+ return match ? { frontmatter: match[1], rest: match[2] } : { frontmatter: "", rest: content };
82
+ }
83
+
84
+ /** A model hint is a `model:` key line — one definition shared by every consumer
85
+ * (apply, detect, conflict-compare) so the three can never drift apart. Scoped to
86
+ * frontmatter: a body line that happens to start `model: ` is content, not config. */
87
+ const MODEL_HINT_LINE = /^model: /m;
88
+
89
+ /** Apply a fast-tier model hint to a role definition.
90
+ *
91
+ * Replaces an existing `model:` line; else swaps the commented placeholder for
92
+ * `model: <model>`; else inserts after `systemPromptMode:` when `insertIfAbsent`
93
+ * (the all-four path for judgment roles, which ship no placeholder). Pure: same
94
+ * input always yields the same output, so install comparisons stay byte-exact.
95
+ */
96
+ export function applyFastModelHint(content: string, model: string, opts?: { insertIfAbsent?: boolean }): string {
97
+ const trimmed = model.trim();
98
+ if (!trimmed || /\s/.test(trimmed)) throw new Error(`Invalid fast model name: ${JSON.stringify(model)}`);
99
+ const { frontmatter, rest } = splitFrontmatter(content);
100
+ if (!frontmatter) return content;
101
+ const hintedFrontmatter = MODEL_HINT_LINE.test(frontmatter)
102
+ ? frontmatter.replace(/^model: .*$/m, `model: ${trimmed}`)
103
+ : frontmatter.includes(FAST_MODEL_PLACEHOLDER)
104
+ ? frontmatter.replace(FAST_MODEL_PLACEHOLDER, `model: ${trimmed}`)
105
+ : opts?.insertIfAbsent
106
+ ? frontmatter.replace("systemPromptMode: replace\n", `systemPromptMode: replace\nmodel: ${trimmed}\n`)
107
+ : frontmatter;
108
+ return hintedFrontmatter + rest;
109
+ }
110
+
111
+ /** True when the only difference between two contents is the model hint line
112
+ * (an uncommented `model:` line or the commented placeholder). Such deltas are
113
+ * kit-managed config and auto-update without --force; anything else conflicts.
114
+ */
115
+ function differsOnlyByHint(a: string, b: string): boolean {
116
+ const strip = (content: string) => {
117
+ const { frontmatter, rest } = splitFrontmatter(content);
118
+ const stripped = frontmatter
119
+ .split("\n")
120
+ .filter((line) => !MODEL_HINT_LINE.test(line) && line !== FAST_MODEL_PLACEHOLDER)
121
+ .join("\n");
122
+ return stripped + rest;
123
+ };
124
+ return strip(a) === strip(b);
125
+ }
126
+
127
+ const CANONICAL_AGENTS_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..", "agents");
128
+
129
+ function setupUsageError(): Error {
130
+ return new Error("Usage: /pwk-setup [--force] [--fast-model <model>] [--all-roles]");
131
+ }
132
+
133
+ function parseSetupArgs(args: string): { force: boolean; fastModel?: string; allRoles: boolean } {
134
+ const tokens = args.trim().split(/\s+/).filter(Boolean);
135
+ let force = false;
136
+ let allRoles = false;
137
+ let fastModel: string | undefined;
138
+ for (let i = 0; i < tokens.length; i += 1) {
139
+ const token = tokens[i];
140
+ if (token === "--force") {
141
+ force = true;
142
+ } else if (token === "--all-roles") {
143
+ allRoles = true;
144
+ } else if (token.startsWith("--fast-model=")) {
145
+ fastModel = token.slice("--fast-model=".length);
146
+ } else if (token === "--fast-model") {
147
+ const next = tokens[i + 1];
148
+ if (next === undefined || next.startsWith("--")) throw setupUsageError();
149
+ fastModel = next;
150
+ i += 1;
151
+ } else {
152
+ throw setupUsageError();
153
+ }
154
+ }
155
+ if (fastModel !== undefined && !fastModel.trim()) throw setupUsageError();
156
+ if (allRoles && fastModel === undefined) throw setupUsageError(); // --all-roles pairs with --fast-model
157
+ return { force, fastModel, allRoles };
158
+ }
159
+
160
+ function ensureDirectory(path: string): void {
161
+ const stats = statNoFollow(path);
162
+ if (!stats) {
163
+ mkdirSync(path);
164
+ return;
165
+ }
166
+ if (stats.isSymbolicLink()) throw new Error(`Refusing symlink destination: ${path}`);
167
+ if (!stats.isDirectory()) throw new Error(`Destination is not a directory: ${path}`);
168
+ }
169
+
170
+ /** lstat without following symlinks; null when the path does not exist. */
171
+ function statNoFollow(path: string): ReturnType<typeof lstatSync> | null {
172
+ try {
173
+ return lstatSync(path);
174
+ } catch (error) {
175
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
176
+ return null;
177
+ }
178
+ }
179
+
180
+ /** Read exactly `size` bytes from `fd` starting at position 0, regardless of the fd's cursor. */
181
+ function readAllFromFd(fd: number, size: number): string {
182
+ const buffer = Buffer.alloc(size);
183
+ let offset = 0;
184
+ while (offset < size) {
185
+ const bytesRead = readSync(fd, buffer, offset, size - offset, offset);
186
+ if (bytesRead === 0) break;
187
+ offset += bytesRead;
188
+ }
189
+ return buffer.toString("utf8");
190
+ }
191
+
192
+ /** Write `content` to `fd` at position 0 (truncating first) and verify by reading the same fd back. */
193
+ function overwriteFd(fd: number, content: string, path: string): void {
194
+ ftruncateSync(fd, 0);
195
+ const buffer = Buffer.from(content, "utf8");
196
+ writeSync(fd, buffer, 0, buffer.length, 0);
197
+ if (readAllFromFd(fd, buffer.length) !== content) throw new Error(`Verification failed after writing: ${path}`);
198
+ }
199
+
200
+ function writeNewFile(path: string, content: string): void {
201
+ const noFollow = constants.O_NOFOLLOW ?? 0;
202
+ const flags = constants.O_RDWR | noFollow | constants.O_CREAT | constants.O_EXCL;
203
+ const fd = openSync(path, flags, 0o644);
204
+ try {
205
+ overwriteFd(fd, content, path);
206
+ } finally {
207
+ closeSync(fd);
208
+ }
209
+ }
210
+
211
+ function installRoleFiles(
212
+ cwd: string,
213
+ opts: { force: boolean; hint?: { model: string; allRoles: boolean } },
214
+ ): { installed: string[]; skipped: string[] } {
215
+ const projectAgentsDir = join(cwd, ".agents");
216
+ const targetDir = join(projectAgentsDir, "agents");
217
+ ensureDirectory(projectAgentsDir);
218
+ ensureDirectory(targetDir);
219
+
220
+ const installed: string[] = [];
221
+ const skipped: string[] = [];
222
+ const failures: string[] = [];
223
+ const noFollow = constants.O_NOFOLLOW ?? 0;
224
+
225
+ for (const roleName of ROLE_NAMES) {
226
+ const sourcePath = join(CANONICAL_AGENTS_DIR, `${roleName}.md`);
227
+ const targetPath = join(targetDir, `${roleName}.md`);
228
+
229
+ try {
230
+ // Read inside the try: one broken canonical source becomes a per-role failure
231
+ // instead of aborting the whole install and hiding other roles' results.
232
+ const canonical = readFileSync(sourcePath, "utf8");
233
+ const hintApplies =
234
+ opts.hint !== undefined && (opts.hint.allRoles ? REVIEWER_ROLES : FAST_TIER_ROLES).includes(roleName);
235
+ const content =
236
+ hintApplies && opts.hint ? applyFastModelHint(canonical, opts.hint.model, { insertIfAbsent: true }) : canonical;
237
+
238
+ // Open the existing target (if any) once and do every check/read/write through
239
+ // that single fd — the fd names one fixed inode, so nothing swapped in on the
240
+ // path between checks (TOCTOU) can affect what gets read or written.
241
+ let fd: number | null;
242
+ try {
243
+ fd = openSync(targetPath, constants.O_RDWR | noFollow);
244
+ } catch (error) {
245
+ const err = error as NodeJS.ErrnoException;
246
+ if (err.code === "ENOENT") {
247
+ fd = null;
248
+ } else if (err.code === "ELOOP") {
249
+ throw new Error(`Refusing symlink destination: ${targetPath}`);
250
+ } else {
251
+ throw error;
252
+ }
253
+ }
254
+
255
+ if (fd === null) {
256
+ writeNewFile(targetPath, content);
257
+ installed.push(roleName);
258
+ continue;
259
+ }
260
+
261
+ try {
262
+ const stats = fstatSync(fd);
263
+ if (!stats.isFile()) throw new Error(`Refusing non-regular destination: ${targetPath}`);
264
+
265
+ const existing = readAllFromFd(fd, stats.size);
266
+ if (existing === content) {
267
+ skipped.push(roleName);
268
+ continue;
269
+ }
270
+ if (opts.hint !== undefined && differsOnlyByHint(existing, content)) {
271
+ // Kit-managed only while a hint choice is active this run: bare runs treat
272
+ // any delta (including hand-added model lines) as content — conflict rules.
273
+ overwriteFd(fd, content, targetPath);
274
+ installed.push(roleName);
275
+ continue;
276
+ }
277
+ if (!opts.force) {
278
+ failures.push(`${targetPath}: conflict (use /pwk-setup --force to replace it)`);
279
+ continue;
280
+ }
281
+
282
+ overwriteFd(fd, content, targetPath);
283
+ installed.push(roleName);
284
+ } finally {
285
+ closeSync(fd);
286
+ }
287
+ } catch (error) {
288
+ failures.push(`${targetPath}: ${error instanceof Error ? error.message : String(error)}`);
289
+ }
290
+ }
291
+
292
+ if (failures.length > 0) {
293
+ const partial =
294
+ installed.length + skipped.length > 0
295
+ ? ` (${installed.length} installed, ${skipped.length} skipped before failure — installation is partial)`
296
+ : "";
297
+ throw new Error(`PWK setup incomplete${partial}:\n${failures.join("\n")}`);
298
+ }
299
+ return { installed, skipped };
300
+ }
301
+
302
+ /** Minimal structural view of the command context the fast-model prompt needs. */
303
+ interface FastModelPromptContext {
304
+ cwd: string;
305
+ hasUI?: boolean;
306
+ scopedModels?: { model?: string }[];
307
+ ui?: {
308
+ select?: (title: string, options: { value: string; label: string; description: string }[]) => Promise<string>;
309
+ confirm?: (title: string, message: string) => Promise<boolean>;
310
+ };
311
+ }
312
+
313
+ /** True when an installed fast-tier role already carries a model hint. */
314
+ function installedHint(cwd: string): { model: string; allRoles: boolean } | undefined {
315
+ const frontmatterOf = (role: string): string => {
316
+ try {
317
+ return splitFrontmatter(readFileSync(join(cwd, ".agents", "agents", `${role}.md`), "utf8")).frontmatter;
318
+ } catch {
319
+ return "";
320
+ }
321
+ };
322
+ const modelOf = (frontmatter: string): string | undefined =>
323
+ frontmatter.match(/^model: (\S.*)$/m)?.[1]?.trim() || undefined;
324
+ const model = FAST_TIER_ROLES.map(frontmatterOf).map(modelOf).find(Boolean);
325
+ if (!model) return undefined;
326
+ const allRoles = REVIEWER_ROLES.some((role) => modelOf(frontmatterOf(role)) !== undefined);
327
+ return { model, allRoles };
328
+ }
329
+
330
+ /** Ask for the fast-tier model once, only when a picker is available, no hint is
331
+ * installed, and no --fast-model argument was given. Headless hosts skip silently
332
+ * and reviewers stay on default models. */
333
+ async function promptFastModelChoice(
334
+ ctx: FastModelPromptContext,
335
+ ): Promise<{ model: string; allRoles: boolean } | undefined> {
336
+ const ui = ctx.ui;
337
+ if (typeof ui?.select !== "function" || ctx.hasUI === false) return undefined;
338
+ if (installedHint(ctx.cwd) !== undefined) return undefined;
339
+ const scoped = Array.isArray(ctx.scopedModels) ? ctx.scopedModels : [];
340
+ const models = scoped
341
+ .map((entry) => (typeof entry?.model === "string" ? entry.model : undefined))
342
+ .filter((model): model is string => model !== undefined && model.length > 0);
343
+ const options = [
344
+ ...models.map((model) => ({ value: model, label: model, description: "fast-tier reviewer model" })),
345
+ { value: "skip", label: "skip", description: "reviewers run on default models" },
346
+ ];
347
+ const choice = await ui.select("Fast-tier model for smell/hazard reviewers", options);
348
+ if (!choice || choice === "skip") return undefined;
349
+ const allRoles = (await ui.confirm?.("Apply to all four reviewers?", "No = smell+hazard only")) ?? false;
350
+ return { model: choice, allRoles };
351
+ }
352
+
15
353
  // Destructive commands blocked in brainstorm/plan phases (simple common blacklist)
16
354
  const DESTRUCTIVE_PATTERNS = [
17
355
  /\brm\b/i,
@@ -181,6 +519,16 @@ function enforceLabel(): string {
181
519
  return guardOverride === "on" ? "GUARD ON" : phase ? phase.toUpperCase() : "";
182
520
  }
183
521
 
522
+ /**
523
+ * Is `/pwk-setup` blocked right now? Deliberately NOT `enforceActive()`: setup must
524
+ * refuse during a gated phase even when the tool-call guard is manually disabled
525
+ * (`/pwk-guard off`), since its own banner promises writes stay confined to
526
+ * docs/plans/ for the whole gated phase, override or not.
527
+ */
528
+ function setupBlocked(): boolean {
529
+ return phase !== null || guardOverride === "on";
530
+ }
531
+
184
532
  export default function (pi: ExtensionAPI) {
185
533
  pi.on("session_start", () => {
186
534
  phase = null;
@@ -188,6 +536,51 @@ export default function (pi: ExtensionAPI) {
188
536
  guardOverride = null;
189
537
  });
190
538
 
539
+ // --- Project role setup -------------------------------------------------
540
+ // This command writes through Node rather than the write tool, so it enforces
541
+ // the gated-phase boundary itself instead of relying on tool_call interception.
542
+ pi.registerCommand("pwk-setup", {
543
+ description: "Install PWK role agents into .agents/agents/",
544
+ handler: async (args, ctx) => {
545
+ // Refuse whenever the session is read-only in fact: gated phase (even with the
546
+ // tool-call guard manually disabled — the design mandates that) or the manual
547
+ // read-only lock, whose banner promises "writes only under docs/plans/".
548
+ if (setupBlocked()) {
549
+ const scope =
550
+ guardOverride === "on"
551
+ ? "the manual read-only lock (/pwk-guard on)"
552
+ : `${(phase as string).toUpperCase()} phase`;
553
+ const message = `Cannot run /pwk-setup during ${scope}. Run it before entering the gated workflow or after leaving it (guard auto/off).`;
554
+ ctx.ui.notify(message, "warning");
555
+ throw new Error(message);
556
+ }
557
+
558
+ const { force, fastModel, allRoles } = parseSetupArgs(args ?? "");
559
+ try {
560
+ // Explicit arg wins; else the picker (only while no hint is installed);
561
+ // else re-apply the installed hint — the recorded choice — so bare runs
562
+ // are no-ops rather than stripping or conflicting on kit-managed lines.
563
+ const hint =
564
+ fastModel !== undefined
565
+ ? { model: fastModel, allRoles }
566
+ : ((await promptFastModelChoice(ctx)) ?? installedHint(ctx.cwd));
567
+ const result = installRoleFiles(ctx.cwd, { force, hint });
568
+ const parts = [`PWK setup complete: ${result.installed.length} installed`];
569
+ if (result.skipped.length > 0) parts.push(`${result.skipped.length} skipped`);
570
+ if (force) parts.push("forced conflicts replaced");
571
+ if (hint) {
572
+ const scope = hint.allRoles ? "all four reviewers" : "smell+hazard reviewers";
573
+ parts.push(`fast model ${hint.model} (${scope})`);
574
+ }
575
+ ctx.ui.notify(`${parts.join(", ")}. Providers may require /reload to discover updated roles.`, "info");
576
+ } catch (error) {
577
+ const message = error instanceof Error ? error.message : String(error);
578
+ ctx.ui.notify(message, "error");
579
+ throw error;
580
+ }
581
+ },
582
+ });
583
+
191
584
  // --- Manual override (escape hatch) -----------------------------------
192
585
  // Phases are driven by `/skill:` commands; `/pwk-guard` lets the user pin the
193
586
  // guard regardless of phase. `/pwk-guard auto` returns control to skill transitions.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tianhai/pi-workflow-kit",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "Enforce structured brainstorm→plan→execute→finalize workflow with TDD discipline in AI coding agents",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -32,6 +32,7 @@
32
32
  "docs/developer-usage-guide.md",
33
33
  "docs/workflow-phases.md",
34
34
  "docs/oversight-model.md",
35
+ "docs/provider-delegation-contract.md",
35
36
  "LICENSE",
36
37
  "README.md"
37
38
  ],
@@ -54,7 +54,7 @@ The whole umbrella is one branch and one PR: `pwk-writing-plans` creates the bra
54
54
  1. **Check git state** — `git status` + `git log --oneline -5`. Uncommitted work? Ask the user what to do first.
55
55
  2. **Discovery** *(skip in a brand-new repo with no `docs/plans/`)* — glob `docs/plans/*-design.md` and `*-overview.md`; report in-flight topics and any active umbrella. If the new idea continues an existing topic, ask whether to extend it or start fresh. Part of an umbrella? An existing `*-overview.md` means the split is already decided — read it for the roster and design this part's `-design.md` against it (see [Umbrella](#umbrella)).
56
56
  3. **Understand the idea** — read only enough code/docs/commits to ground the design. **Check `docs/lessons.md`** — known constraints may shape it. Ask questions one at a time, prefer multiple choice. Once you can articulate what/why/constraints, present a short summary and ask: "Should I proceed, or is there more?" The human decides when to move on.
57
- 4. **(skipped on trivial changes)** **Codebase recon** — for non-trivial topics with prior art, dispatch the `pwk-recon-scout` package agent (a fresh-context, read-only worker) with the topic, a one-line intent, and the repo root. Use the returned 5-section codebase map (Relevant files, Existing patterns, Call sites, Test layout, Gotchas) as the grounding context for the next two steps instead of reading those files inline. The scout is observations only — no design recommendations — and stays within the read-only tool set the guard already enforces. Skip this step on trivial changes (typo, version bump, single-function edit per the proportionality rule). If the `subagent` tool is unavailable (e.g. `pi-subagents` is not installed), skip the dispatch, do the recon inline as today, and append the literal line `Scout: unavailable (pi-subagents not installed) inline recon used.` to the design doc at step 7.
57
+ 4. **(skipped on trivial changes)** **Codebase recon** — for non-trivial topics with prior art, request the host’s `codebase-recon` capability using the logical `pwk-recon-scout` role. Require a fresh-context, read-only, bounded worker and pass the topic, one-line intent, and repo root. Use the returned 5-section codebase map (Relevant files, Existing patterns, Call sites, Test layout, Gotchas) as the grounding context for the next two steps instead of reading those files inline. The scout is observations only — no design recommendations. Skip this step on trivial changes (typo, version bump, single-function edit per the proportionality rule). If no compatible capability is available or the provider cannot enforce the requested constraints, report `Scout: unavailable` and do the same recon inline, preserving the five-section map and `file:line` citations; do not silently omit recon.
58
58
  5. **Explore approaches** — propose 2–3, leading with your recommendation. Sketch the concrete interface (types, signatures, example caller) for each so the comparison is grounded in code, not abstractions.
59
59
  6. **Present the design** in one pass, organized into sections (architecture, components, data flow, error handling, testing) — the human comments on any section; re-present only revised sections.
60
60
 
@@ -7,7 +7,7 @@ description: "Implement a plan via the feature-gate flow: write the feature-acce
7
7
 
8
8
  Implement the plan from `docs/plans/*-implementation.md` via the **feature-gate flow**. The plan is a behavioral spec (acceptance criteria + integration tests) — you choose structure, signatures, internals; the criteria define *what*, you decide *how*.
9
9
 
10
- The feature-acceptance E2E test is the primary enforced gate. The flow is always on: write the E2E first (red), implement the requirements back-to-back, then run one feature-level review over the whole diff. Per-requirement checkpoints and reviews are **opt-in** — they fire only for requirements the plan tags (default off); the feature gate covers everything else.
10
+ The feature-acceptance E2E test is the primary enforced gate and the primary enforced spec for the feature. The flow is always on: write the E2E first (red), implement the requirements back-to-back, then run one feature-level review over the whole diff. Per-requirement checkpoints and reviews are **opt-in** — they fire only for requirements the plan tags (default off); the feature gate covers everything else.
11
11
 
12
12
  ## Before you start
13
13
 
@@ -68,7 +68,7 @@ Set `Feature phase: implementing (0/N)` and work the requirements in listed orde
68
68
 
69
69
  ### Per-requirement review (opt-in)
70
70
 
71
- If the requirement's `### Review` tag is `parallel` or `inline` (default `skip`), review that slice now — same mechanics as the [feature review](#feature-review), scoped to the requirement's diff. With `skip`, no per-requirement review; the feature-level review covers it.
71
+ If the requirement's `### Review` tag is `parallel` or `inline` (default `skip`), review that slice now — same mechanics as the [feature review](#feature-review), with a requirement-scoped packet: the same recipe limited to the commits and criteria sections of that requirement, written to `docs/plans/<dated-stem>-review-packet.md`. With `skip`, no per-requirement review; the feature-level review covers it.
72
72
 
73
73
  `Checkpoints: spec` requires at least `inline` review — dropping the complete checkpoint is only safe when review covers implementation quality; never combine `spec` with `Review: skip` (use `Checkpoints: none` instead).
74
74
 
@@ -93,25 +93,37 @@ The old "integration gate" is gone — the feature E2E at `feature-complete` *is
93
93
 
94
94
  After `feature-complete` is approved, run **one** review over the **whole feature diff**, driven by the plan's feature-level `### Feature review` tag. This is the single thorough review — per-requirement reviews, if any, only saw slices in isolation.
95
95
 
96
- - **`parallel`** (default)four fresh-context reviewers via the `subagent` tool. Gather scope (the plan's acceptance criteria + Feature acceptance, `git log --oneline && git diff <merge-base>...HEAD`) and invoke:
97
-
98
- ```json
99
- {
100
- "tasks": [
101
- {"agent": "pwk-spec-reviewer", "task": "<scope + whole diff here>"},
102
- {"agent": "pwk-tracing-reviewer", "task": "<scope + whole diff here>"},
103
- {"agent": "pwk-smell-reviewer", "task": "<scope + whole diff here>"},
104
- {"agent": "pwk-hazard-reviewer", "task": "<scope + whole diff here>"}
105
- ],
106
- "agentScope": "both",
107
- "cwd": "<repo-root>"
108
- }
109
- ```
110
-
111
- The reviewer checklists live only in `agents/pwk-*-reviewer.md` — don't restate them in the task strings (duplication guarantees drift). Reviewers are read-only reporters; you apply smell fixes yourself (full suite + E2E must stay green, commit) and flag trace/spec/hazard findings as follow-ups for the human.
112
-
113
- - **`inline`** run `/skill:pwk-code-review` over the whole diff as a single pass.
114
- - **Fallback** — subagent tool unavailable or errors → run `/skill:pwk-code-review` inline instead.
96
+ **Assemble the review packet first** once, by script, so that no packet byte passes through model output (spawn arguments are model output; file reads are not). If commits land while the review is in flight, re-run the recipe before spawning any replacement role so the packet matches HEAD:
97
+
98
+ ```bash
99
+ PACKET="docs/plans/<dated-stem>-review-packet.md" # same dated stem as the plan docs
100
+ {
101
+ echo "# Review packet: <topic> feature review"
102
+ echo
103
+ echo "## Commits"
104
+ git log --oneline <merge-base>..HEAD
105
+ echo
106
+ echo "## Changed files"
107
+ git diff --stat <merge-base>...HEAD
108
+ echo
109
+ echo "## Acceptance criteria (verbatim from the plan)"
110
+ sed -n '/^## Requirement 1/,/^## Feature acceptance/p' docs/plans/<dated-stem>-implementation.md | sed '/^## Feature acceptance/,$d'
111
+ echo
112
+ echo "## Feature acceptance (verbatim)"
113
+ sed -n '/^## Feature acceptance/,/^### Feature review/p' docs/plans/<dated-stem>-implementation.md | sed '/^### Feature review/,$d'
114
+ echo
115
+ echo "## Production-risk notes (verbatim, if any)"
116
+ sed -n '/^### Production-risk notes/,/^## /p' docs/plans/<dated-stem>-implementation.md | sed '/^## /d'
117
+ echo
118
+ echo "## Diff"
119
+ git diff <merge-base>...HEAD
120
+ } > "$PACKET"
121
+ ```
122
+
123
+ - **`parallel`** (default) — request the host’s `parallel-review` capability for four fresh-context, read-only logical roles: `pwk-spec-reviewer`, `pwk-tracing-reviewer`, `pwk-smell-reviewer`, and `pwk-hazard-reviewer`. Spawn each role with a **one-liner** — a pointer to the packet file with the role framing appended last: the checklist name of the role (`spec alignment`, `code tracing`, `code smells`, or `production hazards`). For example: `Read docs/plans/<dated-stem>-review-packet.md. Your role: spec alignment.` The packet never appears in spawn arguments. Require independent execution and one collected outcome per role. The reviewer role contracts live in `agents/pwk-*-reviewer.md`; do not duplicate their checklists in the workflow instructions. Reviewers are read-only reporters; you apply smell fixes yourself (full suite + E2E must stay green, commit) and flag trace/spec/hazard findings as follow-ups for the human.
124
+
125
+ - **`inline`** — perform `/skill:pwk-code-review` over the whole diff as a single pass.
126
+ - **Fallback** — if the host has no compatible parallel-review capability, cannot prove the requested read-only/fresh-context/bounded constraints, or delegation fails, perform the missing review work inline. Retain successful delegated reports and do not mark the feature fully reviewed while a required role is missing.
115
127
 
116
128
  On success, set `Feature phase: done`.
117
129
 
@@ -27,7 +27,7 @@ Ship the completed work.
27
27
 
28
28
  ```bash
29
29
  # for each <topic> in the set:
30
- rm -f docs/plans/????-??-??-<topic>-design.md docs/plans/????-??-??-<topic>-implementation.md docs/plans/????-??-??-<topic>-progress.md
30
+ rm -f docs/plans/????-??-??-<topic>-design.md docs/plans/????-??-??-<topic>-implementation.md docs/plans/????-??-??-<topic>-progress.md docs/plans/????-??-??-<topic>-review-packet.md
31
31
  # umbrella only:
32
32
  rm -f docs/plans/????-??-??-<umbrella>-overview.md
33
33
  git add -A docs/plans/ && git commit -m "chore: delete planning docs for <topic-or-umbrella>"
@@ -41,6 +41,7 @@ Ship the completed work.
41
41
  mv docs/plans/????-??-??-<topic>-design.md docs/plans/completed/ 2>/dev/null || true
42
42
  mv docs/plans/????-??-??-<topic>-implementation.md docs/plans/completed/ 2>/dev/null || true
43
43
  mv docs/plans/????-??-??-<topic>-progress.md docs/plans/completed/ 2>/dev/null || true
44
+ mv docs/plans/????-??-??-<topic>-review-packet.md docs/plans/completed/ 2>/dev/null || true
44
45
  # umbrella only:
45
46
  mv docs/plans/????-??-??-<umbrella>-overview.md docs/plans/completed/ 2>/dev/null || true
46
47
  git add docs/plans/ && git commit -m "chore: archive planning docs for <topic-or-umbrella>"
@@ -23,7 +23,7 @@ Your writes go into `docs/plans/` and nowhere else. Source code and configuratio
23
23
  - **Integration tests** — test name + what each asserts. This is the spec the executor writes tests from.
24
24
  - **Meaningful tests** — write acceptance criteria and tests as observable behavior: (1) **Test observable behavior** — assert on what the feature produces or changes (a return value, persisted/updated data, an emitted event, an HTTP response) through its public interface; these assertions keep passing as the implementation changes. (2) **Write a per-slice test when the slice has its own observable behavior** — when a slice is pure config or a trivial extraction, the feature E2E covers it and a per-slice test is unnecessary.
25
25
  - **`### Checkpoints: none | full | spec`** — how many human stops. `none` = no per-requirement stop (default — the feature gate covers it); `full` = tests + complete stops; `spec` = tests stop only. Flag a requirement `full` or `spec` when it contains complex logic or is the main part of the feature — where a human look at the slice is worth the stop.
26
- - **`### Review: skip | parallel | inline`** — `skip` = no per-requirement review (default — the feature-level review covers it); `parallel` = four reviewers via subagent; `inline` = one `pwk-code-review` pass. The auto-tag bullet below is the single source of truth for risky-requirement tagging.
26
+ - **`### Review: skip | parallel | inline`** — `skip` = no per-requirement review (default — the feature-level review covers it); `parallel` = four reviewers via delegated parallel roles; `inline` = one `pwk-code-review` pass. The auto-tag bullet below is the single source of truth for risky-requirement tagging.
27
27
  - **`### Feature review: parallel | inline`** — one review over the **whole feature diff**, always present (the single thorough pass). `parallel` (default — thoroughness lives here, since it is the only review in the common case); `inline` for small features.
28
28
  - Tag every requirement — missing tags default to `none` / `skip`. **`spec` requires at least `inline` review** — dropping the complete checkpoint is only safe when review covers implementation quality; never combine `spec` with `Review: skip` (use `Checkpoints: none` instead).
29
29
  - **Production-risk notes** — carry forward the design's `## Production-risk areas`, if any.