| name | plan-consolidation |
| description | Refine the active session's spec.json by merging reviewer findings.json into it. Backs up the pre-refinement spec to a .pre-consolidation sidecar. Triggers on "consolidate plan", "finalize plan". |
| allowed-tools | ["Read","Write","Edit","Grep","Glob","Bash","AskUserQuestion"] |
Plan Consolidation Skill
Merge review findings into the active session's spec.json. Pre-refinement spec is preserved as a .pre-consolidation sidecar so the refinement is auditable (D7). The refined spec.json retains the same top-level shape as the pre-refinement spec โ only the content changes. Do NOT add top-level fields like origin, risks, or notes; the schema rejects additional properties. Namespace: plugin uses .flywheel/plugin/sessions/.
Input
No arguments. Reads the active session from .flywheel/plugin/active.json.
Phase 0: Load Active Session
- Read
.flywheel/plugin/active.json to resolve session_id
- Compute session directory:
.flywheel/plugin/sessions/<session_id>/
- Read two inputs:
spec.json (the pre-refinement spec)
review.findings.json (written by plan-review)
Errors:
active.json missing โ ask the user to run plan-creation first
review.findings.json missing โ ask the user to run plan-review first, or abort
spec.json missing โ the session is broken; ask the user to delete and restart
Phase 1: Back Up to Sidecar (D7)
cp .flywheel/plugin/sessions/<id>/spec.json \
.flywheel/plugin/sessions/<id>/spec.json.pre-consolidation
Cleaned on ship.
Phase 2: No-Op Check
If review.findings.json has zero findings and zero open questions:
- Print: "No refinements needed โ spec is already work-ready."
- Skip to Phase 6 (next-steps prompt).
Phase 3: Surface Open Questions
Questions to surface:
review.findings.json.open_questions (entries the synthesizer could not resolve)
- Inter-reviewer conflicts โ findings where two or more reviewers described the same issue but assigned different severities. Surface the divergence; the user decides which severity is right rather than defaulting to the more severe.
BLOCKING: Each AskUserQuestion call MUST contain exactly ONE question. Never pass multiple questions to a single AskUserQuestion call (no questions: [...] arrays of length > 1). Make a separate call per question, await the response, then make the next call. Bundled multi-question prompts produce a confusing wizard-style "Review your answers / Submit" review flow that breaks both UX and test automation.
For each question:
Question: "[Topic]: [The question]"
Context: [Brief explanation of why this matters]
My recommendation: [Preferred option and why]
Options:
1. [Option A] (Recommended) - [Brief description]
2. [Option B] - [Brief description]
3. "You pick what's best" - Let me decide
Record: user picks option โ decision logged; "You decide" โ apply recommendation, note delegated; custom answer โ record exactly. BLOCKING: Never proceed with unresolved questions.
Phase 4: Propagate Decisions Into the Spec
BLOCKING: Each resolved decision MUST be reflected in the spec's content, not just remembered in the conversation. The implementer dispatches against the refined spec.json; if a decision isn't IN the spec, it won't be honored โ that's a consolidation failure, not an implementer failure.
For each answered question:
- Identify affected tasks/phases โ the question's topic points to one or more
phases[].tasks[]. Read the task descriptions and find the ones that would behave differently under each option.
- Rewrite task descriptions to bake the decision in as a constraint. Examples:
- "Vitest or Jest for tests?" โ "Vitest" โ rewrite test task descriptions to mandate
vitest; remove any jest.fn / jest.mock references; convert describe.each calls to vitest's API.
- "Single- or multi-tenant for the MVP?" โ "single-tenant" โ strip
tenant_id columns from the schema task; remove tenant-scoping middleware; document deferred multi-tenancy in context.constraints[].
- Update
verification commands if the decision changes them. Example: webpack build โ vite build after a switch from webpack to vite.
- Append the decision to
context.constraints[] as a one-line note that survives into the implementer dispatch. Format: "Decision: <topic> โ <answer>. <one-sentence why>.". Example: "Decision: Tailwind v4 over styled-components โ matches the design-system standard the team adopted in Q1. Verification command runs 'bun run check:css'.".
The bar: a fresh implementer who reads only spec.json (no conversation history) must reach the same outcome the user's answer prescribed. If they could plausibly do something different, the decision wasn't propagated thoroughly enough.
Phase 5: Integrate Findings by Severity
Surface failures into context. For every P1/P2 finding integrated into spec.json, append a one-line summary of the failure to spec.context.constraints[] so the implementer sees the reasoning during dispatch, not just the patch.
Structural failures replace, don't patch. Match the leading word(s) of each finding's Failure paragraph against the catalog below โ string comparison, not judgment. Match โ reshape the affected phase. No match โ fold the fix into the task description.
Catalog of names that route to redesign (from flywheel/skills/flywheel-conventions/references/elegance.md):
- Universal Principles: Single Source of Truth, Working with the Grain, Depth over Indirection, Narrow Interfaces, One-Direction Data Flow, Dead Code Is Debt
- Structural: God Class, Shallow Wrapper, Forwarding Chain, Parallel State, Speculative Code, Config Soup, Stubborn Duplication
- Data Flow: Manual Sync, Bidirectional Coupling, Cascade Mutation, Leaky Event
- Abstraction: Premature Abstraction, Leaky Interface, Comments-as-Apology, Indirection Tax, Concrete Dependency
- Plan-Specific: Test Desert, Test Afterthought, Reinvented Wheel, Shotgun Surgery
- Performance/data-integrity canonicals: N+1 Query, Race Condition, Layering Violation, Convention Drift
When a finding matches, the patch is the wrong response. Folding "use a JOIN instead" into a task that says "build the in-memory join with N+1 queries" leaves both shapes in the spec. Delete the inelegant task; replace it with one that prescribes the cleaner shape from the start.
Example โ finding leads Forwarding Chain. ...:
- Spec before: phase-2 task: "Add
UserManager.authenticate() that calls AuthService.verify() that calls TokenService.check()."
- Spec after: phase-2 task: "Add a route handler that calls
TokenService.check() directly." Original task deleted. constraints[] records the Failure paragraph one-liner.
The "Maximize elegance over minimizing churn" rule applies: pick the cleaner shape even when reshaping deletes tasks the original spec prescribed.
Consistency check (run before integrating any finding)
The user's verbatim feature description lives in context.constraints[0], marked (authoritative). The user's exact words are immutable. Reviewers don't see the original prompt and may suggest alternatives in good faith โ but the user's authority overrides reviewer suggestions every time.
For each finding, before applying its fix:
-
Does the finding propose changing or replacing something the user named explicitly? If the user wrote "use Stripe for payments" and a finding suggests Braintree, defer it. Rationale: Deferred: <finding-title> contradicts user's verbatim description ('<user words>').
-
Does the finding's fix add capabilities the user did not request? Concurrency, caching, body-size limits, pagination, an extra endpoint โ if the user's verbatim description doesn't mention them, defer with rationale: Deferred: <finding-title> adds <feature> not requested by user.
-
Does the finding contradict an existing success_criteria entry derived from the user's words? Defer.
Do not reinterpret the user's words to accommodate the finding. "No Docker" means no Docker for any environment โ dev, test, CI, production โ even if a reviewer thinks containerization is the obvious choice. The user said what they said. Honor it literally; do not charitably re-scope it. Do NOT rewrite the existing success_criteria or summary to make room for the finding โ that's how drift sneaks into the spec. If the existing content conflicts with the finding, the existing content wins.
Only integrate findings whose fix is consistent with the user's verbatim description AND the existing spec content. The result is an internally-coherent spec that honors the user's exact words, not one that says "X" in success_criteria and "not-X" in verification commands.
For each remaining finding in review.findings.json.findings:
P1 โ Integrate (after consistency check)
- Fold
fix language into the affected task's description
- Add test scenarios that cover the failure described in
failure
- If the finding does not map to an existing task: add a new task to the relevant phase
P2 โ Integrate by default; defer only when the finding adds a new what
Users describe what to build and the hows they care about (specific tools, libraries, frameworks, ports, file layouts). They generally underspecify the rest. Reviewers fill in the underspecified hows โ robustness, correctness, polish โ and that's their job. Adding "how" to underspecified areas is good scope growth. Integrate these like a P1.
Defer only when a finding adds a new what โ a capability, endpoint, or feature the user didn't describe at all:
| Finding shape | Verdict |
|---|
Adds prepared statements to a Postgres INSERT to prevent injection | Integrate โ how |
| Adds a 30-second timeout to the outbound HTTP client | Integrate โ how |
| Adds CSRF token verification to a form POST handler | Integrate โ how |
Adds Content-Encoding: gzip to a download endpoint already streaming bytes | Integrate โ how |
| Adds TypeScript types to existing untyped JS functions | Integrate โ how |
| Adds rate limiting to a webhook receiver (user described a single-source webhook) | Defer โ new what |
| Adds a metrics dashboard (user described a CLI tool) | Defer โ new what |
| Adds a new CLI subcommand the user did not list | Defer โ new what |
| Adds Redis caching (user described an in-memory MVP) | Defer โ new what |
If you defer, record Deferred: <finding-title> โ adds new <feature> not described by user as a one-line task note.
The consistency check above is the strict gate (catches contradictions with explicit user wording). This P2 check is the secondary gate (catches new-what additions). Default is integrate โ most P2 findings are reviewers filling in how, which is what they're for.
P3 โ Default Include
P3 findings are nice-to-have, low-impact suggestions. Integrate them by default โ fold the fix into the affected task's description and append the failure summary to constraints[], just like P2.
Apply the same consistency check as above: if a P3 contradicts an existing summary, success_criteria, or constraints entry, defer it with a one-line rationale. Otherwise, integrate.
This is the path of least resistance for the common case โ most users approve most P3s, and removing the triage prompt also unblocks autonomous orchestrators (/yolo). If the user wants to drop a P3 after seeing it integrated, they can edit the spec or remove it before /work.
Phase 6: Write Refined Spec & Hand Off
- Validate the refined spec against
flywheel/schemas/task-list.schema.json.
- Atomic write
.flywheel/plugin/sessions/<id>/spec.json (.tmp โ mv).
- Delete
review.findings.json โ it has been consumed. This is the signal to /work that no unhandled review remains.
rm .flywheel/plugin/sessions/<id>/review.findings.json
- Print summary:
Spec refined โ <id>
Integrated: N P1, N P2, N P3
Deferred: N
- AskUserQuestion: "Spec consolidated and ready. What next?"
- Start
/work (Recommended)
- Done for now
Error Handling
- Active session missing: Prompt user to run plan-creation first
- Findings missing: Prompt user to run plan-review first, or abort
- Schema validation failure on write: Restore from
spec.json.pre-consolidation; report which field failed; do not leave a half-merged spec on disk
- User rejects every option on an open question: Abort consolidation; spec stays in pre-refinement state (sidecar was created but the main spec.json was not overwritten)
Anti-Patterns
- Skip open-question resolution โ Don't refine with unresolved questions
- Multiple questions at once โ One at a time
- BLOCKING: Auto-drop a P1 โ P1s integrate; only the user may downgrade to follow-up
- Resolve a question without rewriting the spec โ Decisions must propagate into task descriptions, verification commands, and
context.constraints[]. A decision that lives only in the conversation history is invisible to the implementer (Phase 4).
- Fold structural failures into existing tasks โ Replace the affected phase or task with the simpler shape, don't patch the original. Surface the
failure into context.constraints[].