# Features

Source: https://flow-next.dev/skills/features/

Seed and maintain the committed user-POV feature map at .flow/features/ - how a user reaches each feature, how an agent drives it, and which traps waste a run.

`/flow-next:features` builds and keeps a **committed user-POV drive map** at `.flow/features/`, beside `.flow/memory/`. Each file records what a user-facing feature is, how a user reaches it, how an agent drives it, and which traps waste a verification run. [`/flow-next:qa`](https://flow-next.dev/skills/qa/), [Drive](https://flow-next.dev/skills/flow-next-drive/) and [every other stage that drives the running app](https://flow-next.dev/skills/features/#which-routes-read-the-map) read it when it exists, so navigation knowledge survives the run that discovered it.

One split keeps every existing contract intact: **the map is how a user gets there, the spec is what to prove this time, and the live drive is the proof.**

`.flow/features/` is team knowledge, like memory, so it lives in git and travels with the repo. That is deliberately the reverse of [`/flow-next:map`](https://flow-next.dev/skills/map/), whose `.clawpatch/` code index is local-per-developer and git-ignored by default. The two are different layers - user-POV drive knowledge versus a code-POV semantic index - and both stay.

Flow-Next runs fully without a feature map. Consumers gate on directory existence only: no registration, and an absent directory is exactly today’s behavior at zero added cost. Setup and prime always recommend seeding it, and flow recommends a maintain pass when the map is due, but the run is yours - by hand, or under a host loop you start. `flow --auto` and land never dispatch it; any autonomy marker in the environment ends the run `REFUSED`.

## When to use it

Reach for it when a repo has a real user surface that people keep re-deriving. Every QA run that starts by working out “how do I even get to this screen” is paying the same navigation tax again, and a seeded map turns that into a read. Good moments to seed:

* The app has a stable set of user-facing features somebody drives regularly.
* QA passes keep rediscovering the same login flow, seed data, and empty-state traps.
* A second person (or a cold agent session) needs to drive the app without a walkthrough.

[`/flow-next:setup`](https://flow-next.dev/skills/setup/) and [`/flow-next:prime`](https://flow-next.dev/skills/prime/) recommend seeding on every repository, whatever the live-QA setting. In Flow-Next’s feature-map evaluation, runs that read a map finished locate-heavy reports (“where is this screen”, “what does this button do”) in fewer turns than runs that rediscovered the route.

## Which routes read the map

Every flow stage that drives your running app reads the map before it drives:

* the [performance route](https://flow-next.dev/choosing-your-route/#performance)’s baseline and its post-change measurement;
* the [bug route](https://flow-next.dev/choosing-your-route/#bug-or-defect)’s live proof on base and head;
* [QA](https://flow-next.dev/skills/qa/);
* any live check a pull request reports.

Each reads the index and the one feature file that matches its target, and never the whole map. It reads the file even when the target names its page, because the file’s driving notes warn about controls that misbehave and state the accessibility tree reports wrongly. No match falls back to live discovery; several plausible matches are tried most specific first. Without a map, each stage pays one existence check and runs as before.

Routes with no running app (questions, refactors, capture, plan, refine) never read the map. [Bug intake](https://flow-next.dev/skills/flow/#bug-intake) keeps its own rule: it reads the map only when the report does not say where the problem is.

## Two modes, resolved by state

One command, and the state of the repo answers which mode is meant.

| Mode         | When                                                                                                    | What it does                                                                                                |
| ------------ | ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| **seed**     | `.flow/features/` is absent, or you pass explicit init intent (`--init`, `init`, `--seed`, `mode:seed`) | Interview the repo, prove each route with one live drive, write the index plus one file per proven feature. |
| **maintain** | `.flow/features/` is present and no init intent was given                                               | The audit-shaped pass: index hygiene, source readers, reconcile, one live pass, triage, then ship or stop.  |

### Seed

Seed interviews the **repo**, not you. It reads the checkout for five facts - surface, run command, drive mechanism, observable evidence, isolation - and asks only for what no file answers. Then it names the top handful of user-facing features, and proves each route by driving it once against a live instance before anything lands.

Nothing undriven enters the map. A partial seed lands the proven features and names the failures, so one broken route never discards the run. A repo with no drivable user surface (a pure library), no usable driver on this host, or a checkout that will not build ends `REFUSED` with the reason, rather than manufacturing a map that teaches wrong steps.

### Maintain

Maintain is the audit-shaped pass over the map that already exists:

1. **Index hygiene** - reconcile the index against the files on disk, pull in the open drift notes readers filed under the `feature-map-drift` memory tag, and record each file’s last-proven age from `flowctl features status`.
2. **Source wave** - one read-only reader per feature file, dispatched concurrently. Readers never drive and never edit; each returns a summary, source entry points, one concrete drift claim or `none`, and a live recipe.
3. **Reconcile** - merge those recipes into as few app states as practical, spot-check only the drift that was actually cited, and sweep recent churn for unmapped user-facing surfaces. A concrete source path is required before calling one missing.
4. **Live pass** - exercise every feature once against an instance this run started, required even when source looks clean.
5. **Triage** - a wrong user-POV description is doc drift, so fix the map; working behavior the harness cannot drive is a harness gap, so fix the owned harness and re-drive before shipping; broken app behavior is a product bug, so report it and keep it out of the PR. Every file proven this pass gets a fresh `**Last proven:**` line, and drift notes whose route proved are marked stale.
6. **Ship or stop** - one outcome, no resume state. The branch and commit follow your repo’s naming rules, the PR opens through the same create command make-pr uses, and a ship step that fails after the proofs leaves the proven edits in place. See [shipping a maintain pass](https://flow-next.dev/skills/features/#shipping-a-maintain-pass).

Maintain’s edit scope is `.flow/features/**` plus the harness scripts the map already owns as launch, seed, or drive helpers, plus the status of the drift notes it retires. Product code is never edited, and a `changed` pass opens one chore PR and leaves it open for you or [land](https://flow-next.dev/autonomy/land/).

## Keeping it current between passes

Maintain is the occasional clean-up, not the only upkeep. [`/flow-next:work`](https://flow-next.dev/skills/work/) updates the feature files whose route its own change altered, proving each new route live and refreshing its `**Last proven:**` line; when it cannot prove the route it files a drift note instead. Every other stage that drives from the map (QA, drive, bug intake) only reads it and files a drift note when a route no longer matches. `flowctl features status` reports the map **due** when an open drift note exists or a feature’s last proof is at least `features.staleAfterCommits` (default 50) product-touching default-branch commits old, and flow’s no-argument reading then prints an `Also recommended: /flow-next:features` line. The full picture, including running maintain on a schedule: [Keep the feature map current](https://flow-next.dev/guides/keep-feature-map-current/).

## Doctor: is this instance worth driving

Doctor is one read-only check that runs **before the first drive, on every fresh session, and again after any failed drive**. It observes; it never clicks, types, or mutates.

| Check                  | Passes when                                                                                                     |
| ---------------------- | --------------------------------------------------------------------------------------------------------------- |
| Right build / version  | The process this run started is the checkout under test.                                                        |
| Port owned by this run | The listening port, socket, or CLI pid is the one this run bound, recorded in the run notes.                    |
| Auth valid             | The disposable profile is signed in as the documented user, or the scenario is the public unauthenticated path. |

All three must pass. One failure is a stop, not a hope. Ready means usable: launch has to reach a working app from an empty data directory, migrations and seed data included, so an open port or a passing database health check is not enough on its own. Two ownership rules ride along: never drive an instance this run did not start, and never kill by process name - kill what this run started, by recorded pid, container id, or compose project. An orphaned port from a crashed prior run ends the invocation `BLOCKED` with the reclaim instruction, even when the wreckage is ours.

Doctor cannot see a wedged UI on a healthy process, so a stuck spinner or an undismissable modal is reset or relaunched rather than hoped through, then re-Doctored before the next drive.

## The file shape

The index at `.flow/features/README.md` carries the operating rules - baseline preconditions, driving conventions, proof standards, a pointer at the feature-entry contract - plus the inventory grouped by surface. A cold agent reads it first and can drive from the map alone.

Every feature file opens with an H1 title, one paragraph of user-visible behavior, and a required one-line `**Surface:** <id>` identifier (`web`, `cli`, …). The index groups entries by that identifier, and consumers select a feature deterministically by surface plus sub-feature IDs. Directly under it sits an optional provenance line, `**Last proven:** <YYYY-MM-DD> at <short commit>`, naming the day and commit of the live drive that last proved the route. Seed, maintain, and work’s update step write it; a file without it, or with a malformed one, reads as never proven. Then exactly four H2s, in this order:

| Section                          | Owns                                                                                                                        |
| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `## Sub-features`                | Short stable IDs, one line each (`notes.list` - see owned notes, newest first).                                             |
| `## How to get to it (user POV)` | Every user entry point - fresh session and already-signed-in, URLs, sidebar labels, the CLI a user would type.              |
| `## Driving it`                  | Opens with `Preconditions:`, then labeled bullets pairing each user action with an exact command and its observable result. |
| `## Gotchas`                     | Traps that waste or invalidate a run, each with its recovery.                                                               |

Commands are literal, handles are stable (roles, accessible names, prompt strings, never pixel coordinates), and implementation details stay out. Proof standards apply to everything that lands: capture the user action **and** the resulting state, verify side effects beside what is visible, exercise the real user path rather than a test-only endpoint, and report an unreachable route with the attempted route plus the unmet precondition. A journey whose seed data lacks something it needs fails with that reason rather than being shortened until it passes. A dry-run or test mode is checked for what it actually skips. A proof run in a stand-in, such as a container in place of a fresh VM, lists the setup steps the real target will not get.

## Outcomes

Every run ends with one terminal line, last, nothing after it:

```text
FEATURES_VERDICT=<SEEDED|CLEAN|CHANGED|BLOCKED|REFUSED> features=<n> reason="<one line>"
```

| Verdict   | When                                                                                                                                                                            |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SEEDED`  | Seed landed at least one proven feature. A partial seed names its failed routes in `reason`.                                                                                    |
| `CLEAN`   | Maintain covered every feature and found nothing to correct. No branch, no PR.                                                                                                  |
| `CHANGED` | Maintain shipped one chore PR of proven map and owned-harness corrections, refreshed last-proven lines, or retired drift notes. Never a merge.                                  |
| `BLOCKED` | A named blocker stopped the pass - orphaned port, isolation failure, source-reader collapse, a PR that did not open. Terminal for the invocation; the next run re-enters fresh. |
| `REFUSED` | An autonomy marker, no drivable surface, no usable driver on this host, or a checkout that will not build.                                                                      |

`features=<n>` is the count that landed or remains covered, `0` when nothing did. The line is grammar a host loop can read, which is what makes a cadence like `/loop 1d /flow-next:features` legible.

## Entry gates for a maintain pass

Maintain checks these before it inspects anything, because they are what make its proofs mean something and what let it ship them:

* **On the default base.** The checkout must match `origin/<default>` for product code, for the map itself, and for untracked product files. A pass proves routes against the code its PR will ship on, so proof gathered on a diverged feature branch is proof of the wrong base. Diverged ends `BLOCKED` with the instruction to re-run from the default branch; branches are never switched over your working state.
* **Owned paths clean.** `.flow/features/` (plus any harness paths the map owns) must have no uncommitted edits. Pre-existing edits could not be told apart from the run’s own later, and the `BLOCKED` restore would discard them. Dirty ends `BLOCKED` asking you to commit or stash first.
* **Ship names.** Maintain reads your repo’s branch and commit naming rules (project instructions, contributing docs, commit hooks) and decides now how it will name the branch and commit. When those rules need a value only you have, such as a ticket key, it asks for it here, before any proof work. When nobody can answer, as in a host loop, the pass ends `BLOCKED` at entry, naming the missing value.

Explicit re-init over an existing map applies the same clean-tree requirement, then re-proves or removes every pre-existing feature file - no unproven leftovers survive a re-seed.

## Shipping a maintain pass

A `CHANGED` pass cuts a fresh branch from the default base, commits only the map and owned-harness edits, pushes, and opens one chore PR that it leaves open for you or [land](https://flow-next.dev/autonomy/land/).

* **Names follow your repo.** The branch and commit message follow the naming rules maintain read at entry, so a repo whose hooks require a Jira key gets one. With no rules stated, the defaults are `chore/features-maintain-<date>-<run-id>` and `chore(features): maintain pass`.
* **Any host.** The PR opens through `FLOW_PR_CREATE_CMD`, the same create command [make-pr](https://flow-next.dev/skills/make-pr/) uses, so a Bitbucket or GitLab repo can plug in its own. When no create command reaches the host, the pass stops after the push and ends `CHANGED`, naming the pushed branch so you can open the PR yourself.
* **Less, when you ask.** Ask at invocation for a commit only, or for the edits left uncommitted, and the pass stops there and ends `CHANGED`, naming the local branch or the files to stage.
* **A failed ship keeps the proofs.** When the commit, push or PR create fails after the proofs, the pass ends `BLOCKED` with the proven edits left on the local branch or in the working tree and named in the reason. Commit, ship or stash them before the next pass. A block before or during the proofs, or a base that moved during the pass, still restores the map to its committed state, because those proofs no longer describe the code.

## Where it stops

* **Never a pipeline stage.** `flow --auto` and land do not dispatch it, and no post-merge hook triggers it. Setup, prime, and flow recommend it; cadence belongs to you or a host loop you start.
* **Two writers only.** This skill and work’s scoped update of the routes its own change altered. Every other stage reads the map and files drift notes.
* **Never the intent source.** Specs and acceptance criteria stay the contract of what to prove; the map is how-to-drive only.
* **Never a second QA pass.** Maintain proves the map is honest; it does not replace [`/flow-next:qa`](https://flow-next.dev/skills/qa/) or the human review that follows it.
* **Never a substitute for live evidence.** Consumers read navigation from the map, and a SHIP verdict still rests on evidence captured from the running app.
* **Not the code index.** [`/flow-next:map`](https://flow-next.dev/skills/map/)’s `.clawpatch/` slices are a code-POV layer for scouts and stay local-only.
* **No frozen scenarios.** This-run test scenarios do not belong in the map; they rot and miss the change under test.

## Worked example

```plaintext
/flow-next:features
```

```text
Mode: seed (.flow/features/ absent)
Interviewed the repo: web surface, `npm run dev`, agent-browser, disposable profile per run
Doctor: build matches HEAD, port 8787 owned by this run, dev user signed in
Proved 3 of 4 routes live (settings/export failed: seed data missing, named in the index)
Wrote .flow/features/README.md + notes-list.md, note-editor.md, sign-in.md


FEATURES_VERDICT=SEEDED features=3 reason="settings/export unproven this pass; retry next maintain"
```

A later cadence tick over the same map:

```plaintext
/flow-next:features
```

```text
Mode: maintain (.flow/features/ present)
Entry gate: on origin/main, owned paths clean
Source wave: 3 readers, 1 cited drift (sign-in route moved to /login)
Live pass: 3 features driven, settings/export retried and proved
Triage: 1 doc drift fixed, 0 harness gaps, 1 product bug filed to memory (kept out of the PR)
PR: chore(features): maintain pass


FEATURES_VERDICT=CHANGED features=4 reason="sign-in route corrected; settings/export now proven"
```

## Dynamic usage

Recipes that compose with features in the [cookbook](https://flow-next.dev/guides/cookbook/):

* [Evidence-first](https://flow-next.dev/guides/cookbook/#evidence-first) - seed the map once, then let QA read navigation instead of re-deriving it.
* [Autonomy dial](https://flow-next.dev/guides/cookbook/#autonomy-dial) - a host loop can drive the maintain cadence; the pipeline still never dispatches it.
* [Keep the feature map current](https://flow-next.dev/guides/keep-feature-map-current/) - work updates, drift notes, the due trigger, and running maintain on a schedule.

## Next step

Seed the map, then run a live pass that consumes it:

```bash
/flow-next:features        # seed, then maintain on later invocations
/flow-next:qa <spec-id>    # QA reads navigation from the map when it exists
```

* [Keep the feature map current](https://flow-next.dev/guides/keep-feature-map-current/) - how the map stays true between maintain passes, and when to run one.
* [How it compounds](https://flow-next.dev/understand/how-it-compounds/) - where the feature map sits among the self-improving surfaces.
* [Live-app QA & driving UIs](https://flow-next.dev/guides/live-qa/) - the consumer, its driver ladder, and what the map does not change about the verdict.
* [Glossary: Feature map (user-POV drive map)](https://flow-next.dev/reference/glossary/#feature-map-user-pov-drive-map) and [Doctor](https://flow-next.dev/reference/glossary/#doctor).
