feature-workflow · git:20260918.abebcec · 2026-09-18 · sha256 b21421998dd17f0e

feature-workflow git:20260918.abebcecB

Immutable. This exact content is served forever at /api/v1/blob/b21421998dd17f0e.

---
name: feature-workflow
description: End-to-end feature development — grill the user into an approved spec, fan out research into an implementation plan, then implement in parallel dependency waves with review and integration
---

# Feature workflow

Three phases with a hard human gate before implementation. All artifacts live
in `docs/features/<name>.md` (`<name>` is kebab-case) inside the project, so
they survive context trimming and can be committed. The two workflow scripts
ship in this skill directory; run each with `workflow_run({"skill":
"feature-workflow", "path": "<script>.js", "input": "<JSON>"})` and the host
reads the file. Never pass a script's text as `source` — a retyped copy
differs from the file — and never spawn agents to execute one.

## Phase 1 — Brainstorm and grill (interactive, you and the user)

This phase is a conversation, not a form. One topic per message; follow up on
vague answers instead of moving on. Cover, in roughly this order:

- **Problem**: what hurts today, who feels it, what breaks if we do nothing.
- **Scope**: goals and explicit non-goals. Push for non-goals — "everything
  else" is not an answer.
- **Design**: proposed shape, alternatives considered and rejected (and why),
  compatibility with existing behavior, new configuration surface.
- **Edge cases and failure modes**: empty inputs, errors, concurrency,
  platform-specific variants, security or sandboxing implications.
- **Verification**: how the user will know it works; which tests must exist.

Grilling means challenging: ask "why not the existing X?", "what's the
cheapest version of this?", "what breaks if we skip it?". Don't accept the
first answer to scope questions.

When converged, write `docs/features/<name>.md` with sections: Problem,
Goals, Non-goals, Design, Edge cases, Acceptance criteria, Open questions.
Get explicit user approval of the spec before phase 2.

## Phase 2 — Research fan-out (workflow, read-only)

Run `{baseDir}/feature-research.js` with input `{"name": "<name>", "spec":
"<full spec text>", "source": "<project root>"}`. It captures the project
once, so every agent reads the same code even if files change meanwhile, fans
out read-only researchers (codebase, conventions, verification — build,
checks, and testing — external prior art via curl, docs/config) and
synthesizes a wave-ordered implementation plan, including the `checks`
commands implementation must pass. Researchers inherit the default sandbox
preset (`workspace+net+git`), so curl works.

It returns `plan`, `commit` (the capture the plan was verified against;
absent outside Git), `research` (per lens: the task id, a summary, and its
unknowns), `gaps`, and `repairs`. The full reports are not in the output:
read one with `swarm_read({view:"tasks", id:"<task>", section:"result"})`
when the plan leaves a question its summary does not answer. A lens in `gaps`
failed and the plan was made without it — tell the user which, and why. Its
researcher is paused with the investigation intact (the gap names its
`session` and `task`), and only the user can resume it. A
plan the workflow could not execute as written (duplicate ids, unknown or
cyclic dependencies, concurrent tasks sharing a path) was sent back to the
synthesizer up to twice; `repairs` counts that. Every check was also run
once on the captured commit: `preflight` lists each with its exit code and
the number of tests already failing there. A check that failed naming no
test, whose packages could not set up or build, or that left files behind
went back too; one still like that after the repairs is in `checkProblems`
beside the plan — present each at the gate, because implementation would
block every wave on it or verify nothing with it. A failed run carries the
same `research` and `gaps` in its error result, plus the last `plan` and its
`problems` when validation was what failed, so nothing needs re-running to
see what was learned.

When it returns:

1. Append the plan to `docs/features/<name>.md` as a `## Plan` section
   (summary, the `commit` it was verified against, checks, final checks,
   tasks with ids/briefs/paths/dependencies/acceptance, docs updates, risks,
   open questions). Copy each task's brief whole: it is written to stand on
   its own, and an editor sees nothing else.
2. Present the user a short summary plus every open question, risk, gap,
   and check problem.
3. **GATE: stop.** Wait for explicit approval. The user may edit the plan
   file directly; re-read it after they do. Only then phase 3.

## Phase 3 — Implementation (workflow, editing)

Run `{baseDir}/feature-implement.js` with input `{"name": "<name>", "spec":
"<spec text>", "plan": <plan object>, "source": "<project root>"}`. Read the
plan back from the file rather than trusting conversation memory. The
workflow runs editing workers in dependency waves (parallel within a wave),
then per wave: merge → single reviewer + the plan's `checks` → bounded repair
→ integrate. The next wave starts only after integration. Pass `"checks":
[...]` in the input to override the plan's commands; with no checks at all,
validation is reviewer-only — confirm that with the user first. A failing
check is re-run on the commit the wave merged onto, and one whose named
failures all failed there too does not block: a check list that a worker's
environment cannot satisfy costs the wave nothing, so prefer the project's
real commands over a list narrowed to what you expect to pass. A failure
whose output names nothing (an unfamiliar runner, a formatter's list) always
blocks, because nothing ties it to the baseline, and so does a compile error
or panic in a package that was fine on that commit. A package that failed to
set up or build there too does not block, but none of its tests ran on
either commit: the result returns that check in `unverified`.

When it returns:

- `status: "applied"`: update `docs/features/<name>.md` with a status line,
  run the returned `finalChecks` (the plan's suites too slow or too
  environment-bound for every wave, which no wave ran) and the project's own
  verification commands yourself, and summarize what landed per wave: each
  entry in `waves` holds the editors' reports and the review and checks the
  wave passed (a check that failed on both commits shows its failure count),
  and a task's full record is readable with `swarm_read`. Run
  every `unverified` check too (`{wave, command, packages}`): its packages
  failed to set up or build in the workers' environment, so fix the
  environment the command needs and run it where it can reach them. All of
  these run in the user's own tree: afterwards `git status` must show only
  the feature's changes, so delete anything a command left behind.
- `status: "incomplete"`: earlier waves are applied and a later one stopped.
  `waves` is what landed and `unverified` names its checks to run yourself,
  as above; `stopped` says which wave failed and why,
  `remaining` lists the task ids still to do, and `plan` is the plan to
  relaunch with: the remaining tasks, with their dependencies on applied
  tasks already removed (a relaunch refuses a dependency it cannot see).
  Verify what landed, then relaunch with that `plan` — do not re-run the
  whole plan, and do not rebuild the task list by hand.
- `status: "recovery_required"`: earlier waves are applied and the stopped
  wave's integrate began without a confirmed outcome, so the parent files
  may hold part of it. Do not relaunch its tasks. Reconcile the `candidate`
  first (`workflow_run` with `polly.integration.reconcile(id)`), read the
  receipt, and only then decide between an explicit retry and repair.
- `status: "blocked"`: the first wave failed, so nothing was applied. Report
  the failure evidence (validations, candidate, retained contexts) and stop.
  Do not retry blindly — the blocker needs a human decision or a follow-up
  repair assignment.

## Gotchas

- Workflows are non-interactive once launched. All user interaction happens
  in phase 1 and the gate; never promise the workflow will "ask" anything.
- A failed editor fails the whole wave (`throw_after_all`); the failure
  details include the sibling editors' completed work, which can be
  integrated manually or continued with `followup_task`. It stops the run,
  but never discards a wave already integrated — that is `incomplete`, not
  `blocked`.
- Keep `docs/features/<name>.md` current after every phase transition — it
  is the durable memory of the feature.