@tianhai/pi-workflow-kit 2.2.0 → 2.5.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 +14 -15
- package/agents/pwk-spec-reviewer.md +1 -1
- package/agents/pwk-tracing-reviewer.md +1 -1
- package/docs/developer-usage-guide.md +11 -17
- package/docs/oversight-model.md +12 -10
- package/docs/provider-delegation-contract.md +1 -1
- package/docs/workflow-phases.md +23 -14
- package/extensions/workflow-guard.ts +4 -10
- package/package.json +1 -1
- package/skills/pwk-brainstorming/SKILL.md +37 -79
- package/skills/pwk-brainstorming/references/design-format.md +83 -0
- package/skills/pwk-brainstorming/references/umbrella.md +26 -0
- package/skills/pwk-brainstorming/scripts/validate-design.py +194 -0
- package/skills/pwk-code-review/SKILL.md +25 -17
- package/skills/pwk-executing-tasks/SKILL.md +88 -126
- package/skills/pwk-executing-tasks/references/progress-format.md +81 -0
- package/skills/pwk-executing-tasks/references/resume.md +15 -0
- package/skills/pwk-executing-tasks/references/review.md +42 -0
- package/skills/pwk-executing-tasks/scripts/build-review-packet.py +318 -0
- package/skills/pwk-finalizing/SKILL.md +27 -29
- package/skills/pwk-status/SKILL.md +9 -12
- package/skills/pwk-walkthrough/SKILL.md +2 -2
- package/skills/pwk-diagnose/SKILL.md +0 -59
|
@@ -5,114 +5,52 @@ description: "Use this before any creative work — creating features, building
|
|
|
5
5
|
|
|
6
6
|
# Brainstorming
|
|
7
7
|
|
|
8
|
-
Read-only exploration of source code; every file you create or edit goes under `docs/plans/`. (
|
|
8
|
+
Read-only exploration of source code; every file you create or edit goes under `docs/plans/`. (An approved ADR goes to `docs/adr/`, which sits outside the writable boundary — the human writes it themselves or briefly runs `/pwk-guard off`; the design phase never unlocks it.) The design doc is the **single buildable artifact** — each requirement carries its own acceptance criteria and review tags, and the executor builds straight from it; nothing is re-derived into a second document later. Source-writing happens in `pwk-executing-tasks`.
|
|
9
9
|
|
|
10
10
|
## Proportionality: trivial vs non-trivial
|
|
11
11
|
|
|
12
12
|
Classify the change at the start.
|
|
13
13
|
|
|
14
|
-
- **Trivial** — typo or obvious bugfix with no open design questions, config/version bump, single-function change, or anything the human flags as trivial. Skip steps 3–
|
|
14
|
+
- **Trivial** — typo or obvious bugfix with no open design questions, config/version bump, single-function change, or anything the human flags as trivial. Skip steps 3–6; read the design-format reference and run the validator in step 7, writing a **minimal** design doc in one turn (an `In short:` one-liner — what + why + approach in plain words, plus a single `### R1:` requirement block with its criteria and tags, and an optional `## Production-risk areas` line — and deliberately **no `## Feature acceptance`**: a trivial slice's ship gate is the full existing suite, no E2E is manufactured for it), and hand off to `/skill:pwk-executing-tasks`. The guard still enforces read-only — trivial compresses the phase to one turn, it doesn't skip it.
|
|
15
15
|
- **Non-trivial** — open design questions, multiple approaches, cross-module impact, or new behavior. Run the full process below.
|
|
16
16
|
|
|
17
17
|
When unsure, ask: "This looks trivial — fast-path it, or full brainstorm?" Default to full.
|
|
18
18
|
|
|
19
|
-
##
|
|
19
|
+
## Scope and granularity
|
|
20
20
|
|
|
21
|
-
**One design doc = one PR; one requirement = one testable slice within it.** Most work is a single design doc.
|
|
21
|
+
**One design doc = one PR; one requirement = one testable slice within it.** Most work is a single design doc; several requirements serving one cohesive outcome do not force an umbrella.
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
- A requirement too big for one design doc but shipping as a single PR is an **umbrella** — multiple design docs decomposed under one overview, on one branch, finalized once. See [Umbrella](#umbrella) below.
|
|
23
|
+
Before drafting, assess whether a large request has distinct testable outcomes that need separate design/execute cycles. If so, propose an **umbrella** of vertical slices with one-line scopes and a build order, and get human approval before writing an overview or design. A layers-only split (database, API, UI) is not a vertical split: each slice delivers testable behavior. Design only the current slice; ground later designs in implemented code, not speculative sibling designs. The umbrella still ships as one PR on one branch, finalized once.
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
**One folder per topic:** `docs/plans/<date>-<topic>/`, containing `<leaf>-design.md`, `<leaf>-progress.md`, and `<leaf>-review-packet*.md`; `<leaf>` is `<topic>` for a single-part topic. More than one leaf adds `overview.md`. The whole folder is the creation/discovery/disposal unit.
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
**First brainstorm** (the requirement is too big for one design doc):
|
|
31
|
-
|
|
32
|
-
1. **Propose the split** — the parts, a one-line scope each, and build order. Get human approval before writing anything beyond discovery.
|
|
33
|
-
2. **Write the overview** — `docs/plans/<date>-<umbrella>/overview.md`, in the umbrella's own folder (every part doc lives beside it: `<part>-design.md`, `<part>-progress.md`, `*-review-packet.md`), a **status-free roster**:
|
|
34
|
-
|
|
35
|
-
```markdown
|
|
36
|
-
# Overview: <umbrella>
|
|
37
|
-
|
|
38
|
-
Goal: <one line — what the whole requirement delivers>
|
|
39
|
-
|
|
40
|
-
## Parts (build order)
|
|
41
|
-
1. <topic> — <one-line scope>
|
|
42
|
-
2. <topic> — <one-line scope>
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Goal, parts with one-line scopes, build order — **no status column**. No skill mutates the overview between this write and `pwk-finalizing` (which disposes it); part-completion is inferred from each part's own `*-progress.md`.
|
|
46
|
-
3. **Write the first part's** `<part>-design.md` in the same umbrella folder, then hand off to `/skill:pwk-executing-tasks`.
|
|
47
|
-
|
|
48
|
-
**Later parts** — re-run `/skill:pwk-brainstorming` for the next part. Read the overview for big-picture/roster context (which slice is yours, which siblings exist), then explore the codebase to design your slice **as brainstorm always does** — prior parts are just implemented code in the repo by then. There is no special "read your predecessors" step; cross-slice decisions that must persist go in an ADR, not the overview.
|
|
49
|
-
|
|
50
|
-
The whole umbrella is one branch and one PR: `pwk-executing-tasks` creates the branch in its pre-flight and reuses it for later parts, and suggests the next part (or finalize after the last); `pwk-finalizing` disposes the overview + every part's docs and ships one PR.
|
|
27
|
+
**Umbrella trigger:** before proposing or writing a new umbrella, or when discovery finds an existing `overview.md` beside this design, read [references/umbrella.md](references/umbrella.md). It defines the status-free roster, first/later-slice procedures, and header-only predecessor reporting. Build order is advisory, not an enforcement gate. An existing roster means the split is already decided; design the current slice against it.
|
|
51
28
|
|
|
52
29
|
## Process
|
|
53
30
|
|
|
54
31
|
1. **Check git state** — `git status` + `git log --oneline -5`. Uncommitted work? Ask the user what to do first.
|
|
55
|
-
2. **Discovery** *(skip in a brand-new repo with no `docs/plans/`)* — first verify the repo root: run `pwd` (or your shell's equivalent) and `git rev-parse --show-toplevel`; mismatch → report both paths and stop; never `cd` (a worktree root counts). Then list `docs/plans` recursively, excluding docs/plans/completed/, for `*-design.md
|
|
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 in **frontier rounds**: build a question tree seeded by the dimension checklist, then ask in rounds. The **frontier** is every question whose prerequisites are already settled — ask the whole frontier in one round; a question whose answer depends on another still-open question waits for a later round. Number each question (`Q1`, `Q2`, …) and attach your recommended answer (`➡️ <recommendation>`) — the recommendation is your assumption surfaced up front; the human confirms, strikes, or corrects each in one reply. Recompute the frontier after each round of answers. Seed the tree by walking every checklist dimension — *Goal & scope · Data & state · Behavior & edge cases · Errors & failure · Integration · Non-functional* — printing `— nothing to ask` for groups with no questions (never skip silently). **Facts vs. decisions**: anything answerable from the codebase, docs, or tools is looked up — recon scout or inline — never asked of the human; a pending lookup is an unsettled prerequisite that holds only its downstream questions, while the rest of the frontier is asked now. Only decisions are asked. Major approvals stay single-decision — one question each, never batched: approach selection (step 5), umbrella split, design approval, ADR
|
|
32
|
+
2. **Discovery** *(skip in a brand-new repo with no `docs/plans/`)* — first verify the repo root: run `pwd` (or your shell's equivalent) and `git rev-parse --show-toplevel`; mismatch → report both paths and stop; never `cd` (a worktree root counts). Then list `docs/plans` recursively, excluding docs/plans/completed/, for `*-design.md`, `*-progress.md`, and `overview.md` (topic docs live in `docs/plans/<date>-<topic>/` folders — archived work is not in flight). Use whatever recurses in your harness; one example: `find docs/plans -name '<suffix>' -not -path '*/completed/*'`; report in-flight topics and any active umbrella. For each in-flight topic with a progress file, report its `Feature phase:` line (matched header-only — `grep -m1 '^Feature phase:' <file>` — no body ingest); design-only topics report `design`. If the new idea continues an existing topic, ask whether to extend it or start fresh. Part of an umbrella? An existing `overview.md` **beside the design doc** means the split is already decided — read it for the roster and design this part's `<leaf>-design.md` against it (read [references/umbrella.md](references/umbrella.md) before proceeding).
|
|
33
|
+
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 in **frontier rounds**: build a question tree seeded by the dimension checklist, then ask in rounds. The **frontier** is every question whose prerequisites are already settled — ask the whole frontier in one round; a question whose answer depends on another still-open question waits for a later round. Number each question (`Q1`, `Q2`, …) and attach your recommended answer (`➡️ <recommendation>`) — the recommendation is your assumption surfaced up front; the human confirms, strikes, or corrects each in one reply. Recompute the frontier after each round of answers. Seed the tree by walking every checklist dimension — *Goal & scope · Data & state · Behavior & edge cases · Errors & failure · Integration · Non-functional* — printing `— nothing to ask` for groups with no questions (never skip silently). **Facts vs. decisions**: anything answerable from the codebase, docs, or tools is looked up — recon scout or inline — never asked of the human; a pending lookup is an unsettled prerequisite that holds only its downstream questions, while the rest of the frontier is asked now. Only decisions are asked. Major approvals stay single-decision — one question each, never batched: approach selection (step 5), umbrella split, design approval, ADR offer. The interview ends when the frontier is empty — every branch visited, nothing left silently assumed — not when you feel you understand. Then present a short summary and ask: "Should I proceed, or is there more?" The human decides when to move on.
|
|
57
34
|
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
35
|
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
36
|
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
37
|
|
|
61
38
|
**Assumptions to confirm first** — before presenting, sweep the drafted design: every assumption it would bake in unconfirmed (business rules, defaults, edge-case resolutions) re-opens as a numbered question carrying your recommended answer; the human confirms, strikes, or corrects each in one reply. An honest empty gate ("no unconfirmed assumptions") when the draft is clean — never invent items. Hard rule: no business behavior enters the design doc on the agent's assumption. Confirmed facts are woven into the doc's existing sections — no new template section.
|
|
62
39
|
|
|
63
|
-
Identified a significant architectural decision? Offer an ADR in `docs/adr/`. Only when all three hold: **hard to reverse**, **surprising without context**, **a real trade-off**. Format: title + 1–3 sentences of context/decision/why. ADRs are permanent institutional memory — they stay out of archive/rotation forever. (Guard note: `docs/adr/` is outside the writable `docs/plans/`;
|
|
64
|
-
7. **Write the design doc** — `docs/plans
|
|
65
|
-
|
|
66
|
-
**Open with `## At a glance`** — the human's two-minute digest, immediately before `## Requirements`. It contains (1) a 2–4 sentence plain-language summary: what is wrong or needed, what will be built, the key approach in plain words; (2) **Key decisions** — one line each: decision + why, with a `(rejected: <alternative> — <reason>)` clause only when the fork was real and load-bearing (never manufactured — no fork, no clause); (3) a table `| R# | Requirement in one line | Risk |` with exactly one row per requirement, where **R# = the requirement's block number in the `## Requirements` section below** — this ID is what every later digest keys on (progress rows, execution summary, reviewer coverage table). Plain language only: short sentences, no jargon, no Given/When/Then — those live in the requirement blocks for the executor. An umbrella overview gains no at-a-glance section; its roster already serves that role.
|
|
67
|
-
|
|
68
|
-
Then **`## Requirements`** — one block per requirement, each one testable behavior:
|
|
69
|
-
|
|
70
|
-
```markdown
|
|
71
|
-
## Requirements
|
|
40
|
+
Identified a significant architectural decision? Offer an ADR in `docs/adr/`. Only when all three hold: **hard to reverse**, **surprising without context**, **a real trade-off**. Format: title + 1–3 sentences of context/decision/why. ADRs are permanent institutional memory — they stay out of archive/rotation forever. (Guard note: `docs/adr/` is outside the writable `docs/plans/`; the human writes the approved ADR themselves or briefly runs `/pwk-guard off` — the agent never writes it while the design phase is gated.)
|
|
41
|
+
7. **Write the design doc** — validate its structure before handing off; before writing either a trivial or non-trivial doc, read [references/design-format.md](references/design-format.md) for the exact format, tag defaults, risk/Setup rules, and audit. Requirement blocks carry acceptance criteria plus `### Checkpoints` / `### Review` tags. Missing tags default to `none` / `skip`. Write `docs/plans/<date>-<topic>/<leaf>-design.md`, creating the topic folder when absent. It is descriptive, not a task list; the executor builds directly from it.
|
|
72
42
|
|
|
73
|
-
|
|
74
|
-
<one-line testable behavior — what the feature produces or changes, through its public interface>
|
|
43
|
+
**Completion gate:** Test observable behavior through the public interface. Audit every requirement's criteria, tags, and risk notes; confirm each appears exactly once. For non-trivial work, finish with end-to-end `## Feature acceptance` as the primary enforced spec. If writing a scenario step would require inventing behavior, that invention goes back through the assumption gate — an unwritable step means an unspecified requirement, not permission to guess. Risk notes never set the tag — the written `### Review` value stays `skip` unless the human sets it, and only the human tags a slice for review. Keep designing until the requirements compose into a coherent feature and the human approves the design, including feature acceptance. Trivial work retains the compressed one-turn path above, not another recurring approval stop.
|
|
75
44
|
|
|
76
|
-
**
|
|
77
|
-
- Given … When … Then …
|
|
78
|
-
- Given … When … Then … (edge case)
|
|
45
|
+
**Python prerequisite:** structural validation requires Python 3.9+ where commands execute, using only its standard library (no pip or project Node configuration). Confirm a compatible `python3` exists before invoking the helper. If Python is missing, unavailable, or too old, report the blocker and request a suitable runtime; stop at this gate. Do not auto-install Python, use a fallback implementation, or claim validation succeeded.
|
|
79
46
|
|
|
80
|
-
|
|
81
|
-
### Review: skip | parallel | inline
|
|
47
|
+
Resolve the helper from this installed skill's directory; pass explicit paths and quote paths with spaces. Relative input paths resolve from the caller's working directory, not the skill directory:
|
|
82
48
|
|
|
83
|
-
|
|
84
|
-
- <
|
|
85
|
-
|
|
86
|
-
### R2: <name>
|
|
87
|
-
…
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
Block rules:
|
|
91
|
-
|
|
92
|
-
- **No test-name lists.** The criteria are the test spec — the executor writes and names the actual tests red-green from them, so the doc contains no test-name lists and no R#-to-section mapping tables: the block structure is the map.
|
|
93
|
-
- **Tag every requirement** — `### Checkpoints` (how many human stops: `none` = no per-requirement stop, the default — the feature gate covers it; `full` = tests + complete stops) and `### Review` (per-requirement review: `skip` = none, the default; `parallel` = four delegated reviewers; `inline` = one `pwk-code-review` pass). Missing tags default to `none` / `skip`. Flag `full` only where complex logic or the main part of the feature makes a human look at the slice worth the stop.
|
|
94
|
-
- **Nothing is tagged silently here; only the human tags a slice for review.** A requirement that touches a production-risk area is flagged in the At-a-glance risk column and carries its `### Production-risk notes`, but its written `### Review` value stays `skip` unless the human sets it — propose the tag in prose, leave the field at `skip`. An explicit human tag always wins, in both directions.
|
|
95
|
-
- **No per-requirement spec stop, by design** — the acceptance criteria are approved right here, at design time; re-checking them mid-execution asks a question the human already answered. A legacy `spec` tag in an in-flight design doc resolves to `none`.
|
|
96
|
-
- **Production-risk notes** — a requirement touching a production-risk area carries its notes inside the block. Risks involving schema migrations, new dependencies, external APIs, or seed data also get a `## Setup` section (dependencies, migrations, seed data, and how to verify setup worked) between `## Requirements` and `## Feature acceptance`.
|
|
97
|
-
- **Ordering** — dependencies come earlier in the list; the executor runs blocks in listed order with no dependency graph. Aim for vertical slices that merge cleanly on their own.
|
|
98
|
-
|
|
99
|
-
Then the narrative sections the executor reads as context — problem, approaches considered, architecture, components, data flow, error handling. **Approaches considered records every real fork and why each loser lost** — the executor needs the reasoning, and the finalize learning sweep harvests it for ADRs.
|
|
100
|
-
|
|
101
|
-
Touches a production-risk area (DB schema/migrations, auth, external APIs, concurrency/batch, uploads/large data flows, Redis/caching/queues)? Add a brief `## Production-risk areas` **immediately after the last requirement block** — inside the `### R1 → ## Feature acceptance` span, so the review packet carries it to reviewers verbatim — and mirror the risks into each touched block's `### Production-risk notes`. `pwk-code-review` audits it per requirement, and finalize's learning sweep reads it.
|
|
102
|
-
|
|
103
|
-
**End with `## Feature acceptance`** — one or more end-to-end `Given/When/Then` scenarios proving the requirements *compose* into the feature. This is the feature's definition-of-done; the human approves it as what "the feature works" means. `pwk-executing-tasks` writes it as the E2E test first and runs it as the **primary enforced spec** (the test it gates on first). The section carries the feature-level `### Feature review: auto | parallel | inline` tag — the one whole-feature review; default `auto`, which resolves on the design's own production-risk content to `parallel` (all four reviewers) when the design has any — a non-empty `## Production-risk areas` section, or a non-empty `### Production-risk notes` on any requirement — and to `inline` (one pass) otherwise. An explicit `parallel` or `inline` is written as-is, never resolved, so the human always overrides `auto`. Treat "I can write this scenario" as the green light to finish designing — if you can't, keep designing because the requirements don't yet compose into a coherent feature. If writing a scenario step would require inventing behavior, that invention goes back through the assumption gate — it may never be silently written into the scenario; an unwritable step means an unspecified requirement, so keep designing.
|
|
104
|
-
|
|
105
|
-
```markdown
|
|
106
|
-
## Feature acceptance
|
|
107
|
-
|
|
108
|
-
- Given <starting state>, When <trigger>, Then <end-to-end outcome the feature promises>.
|
|
49
|
+
```sh
|
|
50
|
+
python3 "<brainstorming-skill-dir>/scripts/validate-design.py" "<design-path>"
|
|
109
51
|
```
|
|
110
52
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
**Audit before finishing:** every requirement block has acceptance criteria and both tags, and each requirement appears **exactly once** — none dropped, none duplicated. A `## Feature acceptance` section exists as the primary enforced spec. Production-risk areas are reflected in the requirement blocks that touch them.
|
|
114
|
-
|
|
115
|
-
**Splitting large issues:** if the requirement needs more than one design doc, propose an umbrella split (one `<topic>` per part) and get human approval first — see [Umbrella](#umbrella). On approval, write the status-free overview (`docs/plans/<date>-<umbrella>/overview.md`) and the **first part's** `-design.md` beside it, then hand off to `/skill:pwk-executing-tasks`. Later parts are brainstormed one by one (each re-reading the overview) — do not write every part's design doc up front.
|
|
53
|
+
Nonzero means validation did not succeed: report file-specific diagnostics, correct structural errors, and rerun. A missing local reference or helper is a blocker before its dependent action; do not silently skip it. Success verifies structure only, never semantic correctness, sound slicing, confirmed assumptions, or human approval.
|
|
116
54
|
|
|
117
55
|
The session stays read-only and uncommitted through brainstorm: branch creation and the design-doc commit happen at the start of `pwk-executing-tasks` (its pre-flight creates the feature branch).
|
|
118
56
|
|
|
@@ -127,3 +65,23 @@ The session stays read-only and uncommitted through brainstorm: branch creation
|
|
|
127
65
|
## After the design
|
|
128
66
|
|
|
129
67
|
Ask: "Ready to execute? Run `/skill:pwk-executing-tasks`"
|
|
68
|
+
|
|
69
|
+
## Contract index
|
|
70
|
+
|
|
71
|
+
The detailed design contract is in `references/design-format.md`; this compact index keeps
|
|
72
|
+
the pipeline markers visible to tools that inspect only the entrypoint:
|
|
73
|
+
`## At a glance` → **Key decisions** → `| R# | Requirement in one line | Risk |`;
|
|
74
|
+
`### R<n>: <name>` blocks contain Given/When/Then criteria, no test-name lists, and both
|
|
75
|
+
tags. The feature section ends with `### Feature review: auto | parallel | inline`.
|
|
76
|
+
Production-risk display is `⚠ production-risk` if and only if notes exist; alternatives
|
|
77
|
+
are never manufactured. The umbrella roster uses `## Parts (build order)` and
|
|
78
|
+
`N. <leaf> — <one-line scope>`.
|
|
79
|
+
The design format carries `Given/When/Then criteria`, `no test-name lists`, and tags;
|
|
80
|
+
the full contract also states `written \`### Review\` value stays \`skip\`` and records
|
|
81
|
+
architectural deviations `at deviation time`.
|
|
82
|
+
|
|
83
|
+
```markdown
|
|
84
|
+
## Feature acceptance
|
|
85
|
+
- Given <starting state>, When <trigger>, Then <outcome>.
|
|
86
|
+
### Feature review: auto | parallel | inline
|
|
87
|
+
```
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Design format
|
|
2
|
+
|
|
3
|
+
Read before writing any design doc. Keep the design descriptive, not a task list. It is the single buildable artifact; no separate implementation plan.
|
|
4
|
+
|
|
5
|
+
## Trivial format
|
|
6
|
+
|
|
7
|
+
A trivial design has an `In short:` one-liner (what + why + approach in plain words), a single `### R1:` block with acceptance criteria and tags, and optional `## Production-risk areas`. It has no `## At a glance`, `## Requirements`, or `## Feature acceptance`; its ship gate is the full existing suite, not a manufactured E2E. Use the block rules below for its criteria, tags, and risk notes.
|
|
8
|
+
|
|
9
|
+
```markdown
|
|
10
|
+
# Design: <topic>
|
|
11
|
+
|
|
12
|
+
In short: <what + why + approach>
|
|
13
|
+
|
|
14
|
+
### R1: <name>
|
|
15
|
+
<observable behavior>
|
|
16
|
+
|
|
17
|
+
**Acceptance criteria**
|
|
18
|
+
- Given <state>, When <action>, Then <observable outcome>.
|
|
19
|
+
|
|
20
|
+
### Checkpoints: none
|
|
21
|
+
### Review: skip
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Non-trivial format
|
|
25
|
+
|
|
26
|
+
**Open with `## At a glance`** — the human's two-minute digest, immediately before `## Requirements`. It contains (1) a 2–4 sentence plain-language summary: what is wrong or needed, what will be built, the key approach in plain words; (2) **Key decisions** — one line each: decision + why, with a `(rejected: <alternative> — <reason>)` clause only when the fork was real and load-bearing (never manufactured — no fork, no clause); (3) a table `| R# | Requirement in one line | Risk |` with exactly one row per requirement, where **R# = the requirement's block number in the `## Requirements` section below** — this ID is what every later digest keys on (progress rows, execution summary, reviewer coverage table). Plain language only: short sentences, no jargon, no Given/When/Then — those live in the requirement blocks for the executor. An umbrella overview gains no at-a-glance section; its roster already serves that role.
|
|
27
|
+
|
|
28
|
+
Then **`## Requirements`** — one `### R<n>: <name>` block per requirement, each one testable behavior:
|
|
29
|
+
|
|
30
|
+
```markdown
|
|
31
|
+
## Requirements
|
|
32
|
+
|
|
33
|
+
### R1: <name>
|
|
34
|
+
<one-line testable behavior — what the feature produces or changes, through its public interface>
|
|
35
|
+
|
|
36
|
+
**Acceptance criteria** — Given/When/Then criteria defining "done". Test observable behavior — what the feature produces or changes through its public interface; not implementation steps. Cover edge and error cases.
|
|
37
|
+
- Given … When … Then …
|
|
38
|
+
- Given … When … Then … (edge case)
|
|
39
|
+
|
|
40
|
+
### Checkpoints: none | full
|
|
41
|
+
### Review: skip | parallel | inline
|
|
42
|
+
|
|
43
|
+
### Production-risk notes
|
|
44
|
+
- <only when the requirement touches a risk area>
|
|
45
|
+
|
|
46
|
+
### R2: <name>
|
|
47
|
+
…
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Block rules:
|
|
51
|
+
|
|
52
|
+
- **No test-name lists.** The criteria are the test spec — the executor writes and names the actual tests red-green from them, so the doc contains no test-name lists and no R#-to-section mapping tables: the block structure is the map.
|
|
53
|
+
- **Tag every requirement** — `### Checkpoints` (how many human stops: `none` = no per-requirement stop, the default — the feature gate covers it; `full` = tests + complete stops) and `### Review` (per-requirement review: `skip` = none, the default; `parallel` = four delegated reviewers; `inline` = one `pwk-code-review` pass). Missing tags default to `none` / `skip`. Flag `full` only where complex logic or the main part of the feature makes a human look at the slice worth the stop.
|
|
54
|
+
- **Nothing is tagged silently here; only the human tags a slice for review.** A requirement that touches a production-risk area carries its `### Production-risk notes` in the block and is flagged `⚠ production-risk` in the At-a-glance Risk column (the column is derived, **display-only**: `⚠ production-risk` if and only if the block carries non-empty `### Production-risk notes`, else `—` — never normative, never an input; `auto` reads the underlying risk content — the `## Production-risk areas` section and the notes — never the display column), but its written `### Review` value stays `skip` unless the human sets it — propose the tag in prose, leave the field at `skip`. An explicit human tag always wins, in both directions.
|
|
55
|
+
- **No per-requirement spec stop, by design** — the acceptance criteria are approved right here, at design time; re-checking them mid-execution asks a question the human already answered.
|
|
56
|
+
- **Production-risk notes** — a requirement touching a production-risk area carries its notes inside the block. Risks involving schema migrations, new dependencies, external APIs, or seed data also get a `## Setup` section (dependencies, migrations, seed data, and how to verify setup worked) between `## Requirements` and `## Feature acceptance`.
|
|
57
|
+
- **Ordering** — dependencies come earlier in the list; the executor runs blocks in listed order with no dependency graph. Aim for vertical slices that merge cleanly on their own.
|
|
58
|
+
|
|
59
|
+
Then the narrative sections the executor reads as context — problem, approaches considered, architecture, components, data flow, error handling. Head the approaches section exactly `## Approaches considered` — finalizing's learning sweep reads it by that name. **`## Approaches considered` records every real fork and why each loser lost** — the executor needs the reasoning, and the finalize learning sweep harvests it for ADRs.
|
|
60
|
+
|
|
61
|
+
Touches a production-risk area (DB schema/migrations, auth, external APIs, concurrency/batch, uploads/large data flows, Redis/caching/queues)? Add a brief `## Production-risk areas` **immediately after the last requirement block** — inside the `### R1 → ## Feature acceptance` span, so the review packet carries it to reviewers verbatim — and mirror the risks into each touched block's `### Production-risk notes`. `pwk-code-review` audits it, per requirement when the slice is reviewed and at the feature review always; finalize's learning sweep reads it.
|
|
62
|
+
|
|
63
|
+
**End with `## Feature acceptance`** — one or more end-to-end `Given/When/Then` scenarios proving the requirements *compose* into the feature. This is the feature's definition-of-done; the human approves it as what "the feature works" means. `pwk-executing-tasks` writes it as the E2E test first and runs it as the **primary enforced spec** (the test it gates on first). The section carries the feature-level `### Feature review: auto | parallel | inline` tag — the one whole-feature review; default `auto`, which resolves on the design's own production-risk content to `parallel` (all four reviewers) when the design has any — a non-empty `## Production-risk areas` section, or a non-empty `### Production-risk notes` on any requirement — and to `inline` (one pass) otherwise. An explicit `parallel` or `inline` is written as-is, never resolved, so the human always overrides `auto`. Treat "I can write this scenario" as the green light to finish designing — if you can't, keep designing because the requirements don't yet compose into a coherent feature. If writing a scenario step would require inventing behavior, that invention goes back through the assumption gate — it may never be silently written into the scenario; an unwritable step means an unspecified requirement, so keep designing.
|
|
64
|
+
|
|
65
|
+
```markdown
|
|
66
|
+
## Feature acceptance
|
|
67
|
+
|
|
68
|
+
- Given <starting state>, When <trigger>, Then <end-to-end outcome the feature promises>.
|
|
69
|
+
|
|
70
|
+
### Feature review: auto | parallel | inline — the one whole-feature review; leave `auto` unless the human says otherwise.
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Example (rate limiting): "Given a new API consumer with no prior usage, When they exceed 100 requests/minute for 3 consecutive minutes, Then they're throttled, a `rate_limited` event is emitted, and further requests return 429."
|
|
74
|
+
|
|
75
|
+
**Audit before finishing:** every requirement block has acceptance criteria and both tags, and each requirement appears **exactly once** — none dropped, none duplicated. A `## Feature acceptance` section exists as the primary enforced spec — on every non-trivial doc; a trivial doc ends after its `### R1:` block and ships on the full suite instead. Production-risk areas are reflected in the requirement blocks that touch them.
|
|
76
|
+
|
|
77
|
+
## Structural validator contract
|
|
78
|
+
|
|
79
|
+
Run `scripts/validate-design.py` as directed in the main skill. It reads UTF-8 Markdown without modifying it. Headings, tags, and labels inside backtick or tilde fenced examples do not count; close every fence. Real requirements use unique sequential `### R1: <name>`, `### R2: <name>`, … headings with nonempty names. Each block needs a standalone `**Acceptance criteria**` label in its main body, before any tag or risk-note heading (an explanatory suffix after an em dash, dash, or colon is allowed) and nonempty criteria before the next heading or bold section label. A bare bullet or content borrowed from risk notes is not a criterion section.
|
|
80
|
+
|
|
81
|
+
Write each selected tag as one value, not the template's pipe-separated alternatives. Missing optional tags retain `Checkpoints: none`, `Review: skip`, and `Feature review: auto`. Non-trivial designs require nonempty `## Feature acceptance` scenarios before their feature-review tag. Only the minimal trivial shape above receives the no-Feature-acceptance exception.
|
|
82
|
+
|
|
83
|
+
The helper checks structure, not meaning: it does not prove Given/When/Then quality, testability, complete edge/error coverage, good slicing, risk classification, confirmed assumptions, or human approval. Those remain the agent's audit and the human's judgment. Structural success does not complete the design gate by itself.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Umbrella mechanics
|
|
2
|
+
|
|
3
|
+
An umbrella splits one large requirement into multiple design docs that ship together as **one PR**. It is a single-topic folder with more than one leaf: one branch, one `pwk-finalizing` at the end, which disposes the folder as one unit. Design only the current slice; ground later slices in implemented predecessor code. The split is intra-PR decomposition — a way to keep each design/execute cycle small and focused, not a multi-PR strategy.
|
|
4
|
+
|
|
5
|
+
**First brainstorm** (the requirement is too big for one design doc):
|
|
6
|
+
|
|
7
|
+
1. **Propose the split** — the parts, a one-line scope each, and build order. Get human approval before writing anything beyond discovery.
|
|
8
|
+
2. **Write the overview** — `docs/plans/<date>-<topic>/overview.md`, in the topic's own folder (every leaf lives beside it: `<leaf>-design.md`, `<leaf>-progress.md`, `<leaf>-review-packet*.md`), a **status-free roster**:
|
|
9
|
+
|
|
10
|
+
```markdown
|
|
11
|
+
# Overview: <topic>
|
|
12
|
+
|
|
13
|
+
Goal: <one line — what the whole requirement delivers>
|
|
14
|
+
|
|
15
|
+
## Parts (build order)
|
|
16
|
+
1. <leaf> — <one-line scope>
|
|
17
|
+
2. <leaf> — <one-line scope>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The `## Parts (build order)` heading and the `N. <leaf> — <one-line scope>` item shape together are the **roster contract**: `pwk-finalizing` and `pwk-status` both read them, and both are lint-pinned so a drifted heading fails loudly instead of reading an empty roster. Goal, parts with one-line scopes, build order — **no status column**. No skill mutates the overview between this write and `pwk-finalizing` (which disposes it); part-completion is inferred from each leaf's own `*-progress.md`.
|
|
21
|
+
3. **Write the first part's design doc** — `docs/plans/<date>-<topic>/<leaf>-design.md` in the same folder, then hand off to `/skill:pwk-executing-tasks`.
|
|
22
|
+
|
|
23
|
+
**Later parts** — re-run `/skill:pwk-brainstorming` for the next part. Read the overview for big-picture/roster context (which slice is yours, which siblings exist), then report each prior leaf's `Feature phase:` line (matched header-only — `grep -m1 '^Feature phase:' <leaf>-progress.md` beside the overview — no body ingest; a leaf with no progress file yet reports `not started`) before designing the slice, then explore the codebase to design your slice **as brainstorm always does** — prior parts are just implemented code in the repo by then. Predecessor phases are visibility, not enforcement: design the slice regardless; cross-slice decisions that must persist go in an ADR, not the overview.
|
|
24
|
+
|
|
25
|
+
The whole umbrella is one branch and one PR: `pwk-executing-tasks` creates the branch in its pre-flight and reuses it for later parts, and suggests the next part (or finalize after the last); `pwk-finalizing` disposes the whole folder — overview + every leaf — and ships one PR.
|
|
26
|
+
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Read-only validation of the workflow's Markdown design structure (Python 3.9+)."""
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import re
|
|
8
|
+
import sys
|
|
9
|
+
from typing import List, Optional, Tuple
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
TAG_VALUES = {
|
|
13
|
+
"Checkpoints": ("none", "full"),
|
|
14
|
+
"Review": ("skip", "parallel", "inline"),
|
|
15
|
+
"Feature review": ("auto", "parallel", "inline"),
|
|
16
|
+
}
|
|
17
|
+
HEADING = re.compile(r"^ {0,3}(#{1,6})[ \t]+(.+?)\s*#*\s*$")
|
|
18
|
+
REQUIREMENT = re.compile(r"^R([1-9][0-9]*):\s*(.*)$")
|
|
19
|
+
REQUIREMENT_LIKE = re.compile(r"^R(?:[0-9]|\s*:)")
|
|
20
|
+
CRITERIA = re.compile(r"^\*\*Acceptance criteria\*\*(?:\s*(?:—|–|-|:).*)?$")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class Heading:
|
|
25
|
+
level: int
|
|
26
|
+
title: str
|
|
27
|
+
index: int
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def visible_lines(text: str) -> Tuple[List[str], List[str]]:
|
|
31
|
+
"""Blank fenced examples, retaining indexes for diagnostics. Not a Markdown renderer."""
|
|
32
|
+
lines = []
|
|
33
|
+
errors = []
|
|
34
|
+
fence = ""
|
|
35
|
+
fence_line = 0
|
|
36
|
+
for number, line in enumerate(text.splitlines(), 1):
|
|
37
|
+
if fence:
|
|
38
|
+
if re.fullmatch(r" {0,3}" + re.escape(fence[0]) + "{" + str(len(fence)) + r",}[ \t]*", line):
|
|
39
|
+
fence = ""
|
|
40
|
+
lines.append("")
|
|
41
|
+
continue
|
|
42
|
+
opening = re.match(r"^ {0,3}(`{3,}|~{3,})(.*)$", line)
|
|
43
|
+
if opening and not (opening[1][0] == "`" and "`" in opening[2]):
|
|
44
|
+
fence = opening[1]
|
|
45
|
+
fence_line = number
|
|
46
|
+
lines.append("")
|
|
47
|
+
else:
|
|
48
|
+
lines.append(line)
|
|
49
|
+
if fence:
|
|
50
|
+
errors.append(f"line {fence_line}: unclosed code fence; close it before validating")
|
|
51
|
+
return lines, errors
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def has_content(lines: List[str]) -> bool:
|
|
55
|
+
# Check presence, not whether the assertion is true, testable, or sufficiently specific.
|
|
56
|
+
for line in lines:
|
|
57
|
+
text = re.sub(r"^\s*(?:[-+*]|[0-9]+[.)])\s*", "", line).strip()
|
|
58
|
+
if text and re.search(r"\w", text):
|
|
59
|
+
return True
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def criteria_body(lines: List[str], start: int, end: int) -> List[str]:
|
|
64
|
+
"""A heading or another bold section label terminates criteria, not just the next R ID."""
|
|
65
|
+
result = []
|
|
66
|
+
for line in lines[start:end]:
|
|
67
|
+
if HEADING.match(line) or re.match(r"^\s*\*\*[^*]+\*\*", line):
|
|
68
|
+
break
|
|
69
|
+
result.append(line)
|
|
70
|
+
return result
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def validate(text: str) -> Tuple[List[str], List[str]]:
|
|
74
|
+
lines, errors = visible_lines(text)
|
|
75
|
+
headings = []
|
|
76
|
+
for index, line in enumerate(lines):
|
|
77
|
+
match = HEADING.match(line)
|
|
78
|
+
if match:
|
|
79
|
+
headings.append(Heading(len(match[1]), match[2], index))
|
|
80
|
+
for heading in headings:
|
|
81
|
+
if REQUIREMENT_LIKE.match(heading.title) and heading.level != 3:
|
|
82
|
+
errors.append(f"line {heading.index + 1}: {heading.title} must use a ### requirement heading")
|
|
83
|
+
requirements = [h for h in headings if h.level == 3 and REQUIREMENT_LIKE.match(h.title)]
|
|
84
|
+
features = [h for h in headings if h.level == 2 and h.title == "Feature acceptance"]
|
|
85
|
+
summaries = []
|
|
86
|
+
|
|
87
|
+
if not requirements:
|
|
88
|
+
errors.append("required requirement block missing; add ### R1: <name> with Acceptance criteria")
|
|
89
|
+
seen = set()
|
|
90
|
+
for expected, heading in enumerate(requirements, 1):
|
|
91
|
+
match = REQUIREMENT.fullmatch(heading.title)
|
|
92
|
+
if not match:
|
|
93
|
+
errors.append(f"line {heading.index + 1}: malformed requirement '{heading.title}'; use ### R{expected}: <name>")
|
|
94
|
+
continue
|
|
95
|
+
number = int(match[1])
|
|
96
|
+
if number in seen:
|
|
97
|
+
errors.append(f"line {heading.index + 1}: duplicate requirement R{number}")
|
|
98
|
+
elif number != expected:
|
|
99
|
+
errors.append(f"line {heading.index + 1}: expected R{expected}, found R{number}; IDs must be sequential in document order")
|
|
100
|
+
seen.add(number)
|
|
101
|
+
if not match[2].strip():
|
|
102
|
+
errors.append(f"line {heading.index + 1}: R{number} requires a nonempty name")
|
|
103
|
+
|
|
104
|
+
intro_end = requirements[0].index if requirements else len(lines)
|
|
105
|
+
short = any(re.match(r"^In short:\s*\S.*$", line) for line in lines[:intro_end])
|
|
106
|
+
full_format = any(h.level == 2 and h.title in ("At a glance", "Requirements") for h in headings)
|
|
107
|
+
trivial = short and not full_format and len(requirements) == 1 and requirements[0].title.startswith("R1:") and not features
|
|
108
|
+
if not trivial and not features:
|
|
109
|
+
errors.append("## Feature acceptance is required for non-trivial designs; the trivial exception needs an In short: one-liner and a single R1 block without full-format sections")
|
|
110
|
+
if len(features) > 1:
|
|
111
|
+
errors.append("duplicate ## Feature acceptance section; keep exactly one")
|
|
112
|
+
|
|
113
|
+
# Record ownership so a valid-looking tag cannot silently land outside its section.
|
|
114
|
+
owned_tags = set()
|
|
115
|
+
|
|
116
|
+
def tags(start: int, end: int, names: Tuple[str, ...], owner: str) -> dict:
|
|
117
|
+
values = {name: TAG_VALUES[name][0] for name in names}
|
|
118
|
+
found = set()
|
|
119
|
+
for heading in headings:
|
|
120
|
+
if not start <= heading.index < end:
|
|
121
|
+
continue
|
|
122
|
+
for name in names:
|
|
123
|
+
if heading.level != 3 or not re.match(re.escape(name) + r"\b", heading.title):
|
|
124
|
+
continue
|
|
125
|
+
owned_tags.add(heading.index)
|
|
126
|
+
if name in found:
|
|
127
|
+
errors.append(f"line {heading.index + 1}: duplicate {name} tag in {owner}")
|
|
128
|
+
found.add(name)
|
|
129
|
+
value = heading.title[len(name):].strip()
|
|
130
|
+
value = value[1:].strip() if value.startswith(":") else ""
|
|
131
|
+
if value not in TAG_VALUES[name]:
|
|
132
|
+
allowed = " | ".join(TAG_VALUES[name])
|
|
133
|
+
errors.append(f"line {heading.index + 1}: invalid {name} tag in {owner}; expected {allowed}")
|
|
134
|
+
else:
|
|
135
|
+
values[name] = value
|
|
136
|
+
return values
|
|
137
|
+
|
|
138
|
+
for heading in requirements:
|
|
139
|
+
end = next((h.index for h in headings if h.index > heading.index and (h.level <= 2 or h in requirements)), len(lines))
|
|
140
|
+
criteria_end = next((h.index for h in headings if heading.index < h.index < end), end)
|
|
141
|
+
labels = [i for i in range(heading.index + 1, criteria_end) if CRITERIA.fullmatch(lines[i].strip())]
|
|
142
|
+
owner = heading.title.split(":", 1)[0]
|
|
143
|
+
if not labels:
|
|
144
|
+
errors.append(f"line {heading.index + 1}: {owner} needs a standalone **Acceptance criteria** section")
|
|
145
|
+
elif len(labels) > 1:
|
|
146
|
+
errors.append(f"line {labels[1] + 1}: {owner} has duplicate Acceptance criteria sections")
|
|
147
|
+
elif not has_content(criteria_body(lines, labels[0] + 1, end)):
|
|
148
|
+
errors.append(f"line {labels[0] + 1}: {owner} Acceptance criteria is empty; add a criterion before the next section")
|
|
149
|
+
values = tags(heading.index + 1, end, ("Checkpoints", "Review"), owner)
|
|
150
|
+
summaries.append(f"{owner}: Checkpoints={values['Checkpoints']}, Review={values['Review']}")
|
|
151
|
+
|
|
152
|
+
for heading in features:
|
|
153
|
+
end = next((h.index for h in headings if h.index > heading.index and h.level <= 2), len(lines))
|
|
154
|
+
if not has_content(criteria_body(lines, heading.index + 1, end)):
|
|
155
|
+
errors.append(f"line {heading.index + 1}: Feature acceptance is empty; add an end-to-end criterion before its review tag")
|
|
156
|
+
values = tags(heading.index + 1, end, ("Feature review",), "Feature acceptance")
|
|
157
|
+
summaries.append(f"Feature review={values['Feature review']}")
|
|
158
|
+
|
|
159
|
+
for heading in headings:
|
|
160
|
+
for name in TAG_VALUES:
|
|
161
|
+
if re.match(re.escape(name) + r"\b", heading.title) and heading.index not in owned_tags:
|
|
162
|
+
owner = "## Feature acceptance" if name == "Feature review" else "a ### R<n>: requirement block"
|
|
163
|
+
errors.append(f"line {heading.index + 1}: {name} must be a ### tag inside {owner}")
|
|
164
|
+
return errors, summaries
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def main(argv: Optional[List[str]] = None) -> int:
|
|
168
|
+
parser = argparse.ArgumentParser(
|
|
169
|
+
description="Read-only structural design validation. Requires Python 3.9+; standard library only. Semantic correctness is not verified.",
|
|
170
|
+
epilog="Pass an explicit design path. Absolute paths are used directly; relative paths resolve from the caller's working directory (cwd), not the installed skill directory.",
|
|
171
|
+
)
|
|
172
|
+
parser.add_argument("design", type=Path, help="UTF-8 Markdown design file (quote paths containing spaces)")
|
|
173
|
+
args = parser.parse_args(argv)
|
|
174
|
+
if sys.version_info < (3, 9):
|
|
175
|
+
print("Python 3.9+ is required; supply a compatible runtime before validation.", file=sys.stderr)
|
|
176
|
+
return 2
|
|
177
|
+
try:
|
|
178
|
+
text = args.design.read_text(encoding="utf-8")
|
|
179
|
+
except (OSError, UnicodeError, ValueError) as error:
|
|
180
|
+
print(f"{args.design}: cannot read UTF-8 design: {error}", file=sys.stderr)
|
|
181
|
+
return 2
|
|
182
|
+
errors, summaries = validate(text)
|
|
183
|
+
if errors:
|
|
184
|
+
for error in errors:
|
|
185
|
+
print(f"{args.design}: {error}", file=sys.stderr)
|
|
186
|
+
return 1
|
|
187
|
+
print(f"{args.design}: structural checks passed. Semantic correctness is not verified.")
|
|
188
|
+
for summary in summaries:
|
|
189
|
+
print(f" {summary}")
|
|
190
|
+
return 0
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
if __name__ == "__main__":
|
|
194
|
+
sys.exit(main())
|
|
@@ -5,42 +5,50 @@ description: "Review a completed requirement's code — trace the logic, check s
|
|
|
5
5
|
|
|
6
6
|
# Code Review
|
|
7
7
|
|
|
8
|
-
Review the code just implemented
|
|
8
|
+
Review the code just implemented — one tagged requirement, or the whole feature at the feature-level review. **Unlocked** — you may edit code to apply smell fixes; flag everything else as a follow-up.
|
|
9
9
|
|
|
10
10
|
## Process
|
|
11
11
|
|
|
12
|
-
1. **Identify the scope** — in the feature-gate flow (the default), you review the **whole feature diff** at the feature-level review (`git diff
|
|
12
|
+
1. **Identify the scope** — in the feature-gate flow (the default), you review the **whole feature diff** at the feature-level review (`git diff $FEATURE_BASE...HEAD` — the parent of the first Commit-column entry; see the executing skill's packet recipe); all acceptance criteria in the design doc and the `## Feature acceptance` E2E are in scope. When invoked per-requirement (`Review: inline` on a tagged requirement — or inline-completing a missing delegated role when the tag is `parallel`), scope is just that requirement — read its acceptance criteria from the design doc, run `git log --oneline -5` and `git diff` to see what changed for it. Invoked standalone — no invoking skill, no design doc, no packet — review the span the user names (the working-tree diff, or the branch's commits against its parent branch): tracing, smell fixes, and the hazard check run as usual, while spec alignment has no acceptance criteria to key on — say so and judge the diff against its stated intent instead of manufacturing a coverage table. When the invoking skill already assembled a review packet (the feature packet, or the requirement-scoped `<leaf>-review-packet-r<N>.md`), work from the packet — it is the scope of record; re-derive nothing the packet already states.
|
|
13
13
|
|
|
14
|
-
2. **🔍 Code tracing** — trace the new
|
|
14
|
+
2. **🔍 Code tracing** — trace the new or changed code paths end-to-end against the acceptance criteria and the feature E2E (a trivial design doc has no feature E2E — its acceptance criteria alone are the spec). 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.
|
|
15
15
|
|
|
16
16
|
3. **📐 Spec alignment** — 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.
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
**Open the report with a coverage table** — one row per requirement in scope, keyed by the design doc's `### R<n>:` headings (R# = n; the whole design doc at the feature-level review, just the tagged requirement when invoked per-requirement):
|
|
19
|
+
|
|
20
|
+
| R# | Verdict | Evidence |
|
|
21
|
+
|----|---------|----------|
|
|
22
|
+
| 1 | <verdict> | file:line (code), file:line (test) |
|
|
23
|
+
|
|
24
|
+
Verdict per requirement: `covered | gap | scope-creep` — `covered` = every criterion has covering code and a test; `gap` = a criterion lacks code or a test; `scope-creep` = the code does more than the criteria specify. Findings elaborate on every non-`covered` row; an all-`covered` table needs no elaboration.
|
|
25
|
+
|
|
26
|
+
4. **🧹 Code smells — fix these directly:** review the changed code and affected files against the requirement and feature scope, then fix what you find.
|
|
27
|
+
- Shallow modules (interface nearly as complex as implementation)
|
|
20
28
|
- Duplication
|
|
21
|
-
- Missing seams
|
|
29
|
+
- Missing seams or premature abstraction
|
|
22
30
|
- Poor naming, magic values, dead code
|
|
23
|
-
Apply the fix, re-run the
|
|
31
|
+
Apply the fix, re-run the full suite (must stay green), and commit. Only a smell that requires a refactor large enough to risk the requirement is **flagged** instead of applied.
|
|
24
32
|
|
|
25
33
|
5. **⚠️ Production hazard check** — audit the changed code against the high-risk hazards. For each, write `[SAFE]` (1-line justification) or `[TRIGGERED]` (concrete mitigation):
|
|
26
|
-
1. **Unbounded operations** — multi-key deletions/scans (`KEYS`, raw `SCAN` loops),
|
|
27
|
-
2. **Missing indexes** — hot queries on unindexed columns (table scans under load)
|
|
28
|
-
3. **Unbounded concurrency** — unthrottled fan-out (`Promise.all` without batch limits)
|
|
29
|
-
4. **Long-running transactions** — holding DB connections/locks across slow external calls
|
|
30
|
-
5. **Query/command interpolation** — raw variables merged into SQL or shell (injection)
|
|
31
|
-
6. **Unrestricted uploads / temp flooding** — uploads to local temp without limits or `finally` cleanup
|
|
32
|
-
7. **Silent swallowing loops** — background workers catching
|
|
34
|
+
1. **Unbounded operations** — multi-key deletions/scans (`KEYS`, raw `SCAN` loops), full-table loads filtered in memory
|
|
35
|
+
2. **Missing indexes** — hot queries on unindexed columns (table scans under load)
|
|
36
|
+
3. **Unbounded concurrency** — unthrottled fan-out (`Promise.all` without batch limits)
|
|
37
|
+
4. **Long-running transactions** — holding DB connections/locks across slow external calls
|
|
38
|
+
5. **Query/command interpolation** — raw variables merged into SQL or shell (injection)
|
|
39
|
+
6. **Unrestricted uploads / temp flooding** — uploads to local temp without limits or `finally` cleanup
|
|
40
|
+
7. **Silent swallowing loops** — background workers catching/suppressing exceptions without logging/back-off
|
|
33
41
|
Also check the design's `## Production-risk areas`, if any.
|
|
34
42
|
|
|
35
43
|
6. **Report** — summarize: tracing findings, spec gaps, smells fixed (with commits), hazards `[TRIGGERED]`. Non-trivial findings become follow-up items — the user decides whether to address now or defer.
|
|
36
|
-
7. **Mark done** —
|
|
44
|
+
7. **Mark done** — the review never edits the progress file. A clean pass hands back so the executing loop can set the requirement's Done cell `✅` (the row was left `🔄` while under review) and write its execution-summary row; findings leave the row `🔄` for another pass. (At the feature-level review every row is already terminal — verify none regressed and move on.) Already-terminal rows of other requirements never regress. Done means reviewed, not just committed.
|
|
37
45
|
|
|
38
46
|
## Principles
|
|
39
47
|
|
|
40
48
|
- **Tracing and spec alignment are the core** — they catch what tests miss: untested branches, missing criteria, scope creep.
|
|
41
49
|
- **Fix smells in place; flag everything else.** Keep the review scoped — the goal is polish on the requirement that just landed, not a redesign pass.
|
|
42
|
-
- **Be proportional** — a one-function change doesn't need the depth of a batch pipeline. When this skill runs as the inline path because the
|
|
50
|
+
- **Be proportional** — a one-function change doesn't need the depth of a batch pipeline. When this skill runs as the inline path because the design doc tagged the requirement `Review: inline`, that was a deliberate call at design approval — keep the review focused and don't second-guess the tag.
|
|
43
51
|
|
|
44
52
|
## After the review
|
|
45
53
|
|
|
46
|
-
Return to `/skill:pwk-executing-tasks`
|
|
54
|
+
Return to `/skill:pwk-executing-tasks` — the next requirement, or the ship checkpoint when every requirement is done. Finalizing is proposed from the ship checkpoint, never from here. (The human tagged this requirement at design approval when its `### Review` tag was set — keep the review focused on the tag's scope.)
|