feature-flow-cli 0.7.0__tar.gz → 0.10.0__tar.gz
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.
- {feature_flow_cli-0.7.0/feature_flow_cli.egg-info → feature_flow_cli-0.10.0}/PKG-INFO +63 -14
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/README.md +62 -13
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/adapters/codex/feature-flow/SKILL.md +9 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/feature_flow/__init__.py +1 -1
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/feature_flow/cli.py +6 -6
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/feature_flow/command.py +6 -2
- feature_flow_cli-0.10.0/feature_flow/conductor.py +1112 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/feature_flow/floorguard.py +11 -2
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/feature_flow/git.py +6 -0
- feature_flow_cli-0.10.0/feature_flow/lanes.py +227 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/feature_flow/prompts.py +13 -2
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/feature_flow/proof.py +11 -0
- feature_flow_cli-0.10.0/feature_flow/report.py +94 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/feature_flow/status.py +63 -11
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/feature_flow/tickets.py +47 -2
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0/feature_flow_cli.egg-info}/PKG-INFO +63 -14
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/feature_flow_cli.egg-info/SOURCES.txt +2 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/guides/build.md +3 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/guides/plan-review.md +4 -2
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/guides/plan.md +9 -3
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/guides/review-quality.md +4 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/guides/review-single.md +5 -1
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/guides/review.md +4 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/guides/templates/commands.md +7 -1
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/guides/templates/ticket.md +3 -2
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/skills/feature-flow/SKILL.md +9 -0
- feature_flow_cli-0.7.0/feature_flow/conductor.py +0 -587
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/LICENSE +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/MANIFEST.in +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/adapters/codex/feature-flow/agents/openai.yaml +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/agents/feature-planner.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/agents/plan-reviewer.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/agents/ticket-builder.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/agents/ticket-reviewer.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/feature_flow/__main__.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/feature_flow/checks.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/feature_flow/codehash.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/feature_flow/gate.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/feature_flow/install.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/feature_flow/proc.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/feature_flow/released.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/feature_flow/state.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/feature_flow/suggest.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/feature_flow/uninstall.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/feature_flow/view.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/feature_flow_cli.egg-info/dependency_links.txt +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/feature_flow_cli.egg-info/entry_points.txt +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/feature_flow_cli.egg-info/top_level.txt +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/guides/brief.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/guides/debug.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/guides/show.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/guides/templates/learnings.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/guides/templates/map.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/guides/templates/spec.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/guides/templates/ui-mockup.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/install.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/install.sh +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/pyproject.toml +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/scripts/floor-guard.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/scripts/flow-status.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/scripts/flow-trust.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/scripts/flow-view.html +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/scripts/flow-view.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/scripts/flow.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/scripts/gate.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/setup.cfg +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/skills/architect-review/SKILL.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.10.0}/skills/automation-design/SKILL.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: feature-flow-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.0
|
|
4
4
|
Summary: A ticket-graph workflow for coding agents: installs the feature-flow skill into a repo
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/hamilton-sky/feature-flow
|
|
@@ -25,10 +25,10 @@ Dynamic: license-file
|
|
|
25
25
|
|
|
26
26
|
Plan a feature as a graph of small tickets, then build them in the session you are already in. One command, `/feature-flow`, does both. Every ticket is built by one fresh subagent and checked by another that never saw the builder's reasoning. A script, not the agent, decides the order of the steps, runs the build and tests, and watches for work that weakens the checks, and the whole run has limits.
|
|
27
27
|
|
|
28
|
-
One skill,
|
|
28
|
+
One skill, 4 agent roles, a Python conductor, 6 Python scripts (`flow`, `flow-trust`, `flow-status`, `flow-view`, `gate`, `floor-guard`), an installer for both agents, a demo and a test suite. All of it is Python (standard library only), so it runs on Linux, macOS and Windows. MIT licensed.
|
|
29
29
|
|
|
30
30
|
- [Quick start](#quick-start) · [How a ticket flows](#how-a-ticket-flows) · [The skill and the roles](#the-skill-and-the-roles) · [Plans and tickets](#plans-and-tickets)
|
|
31
|
-
- [See the graph](#see-the-graph) · [Long features and handoff](#long-features-and-handoff) · [What is tested](#what-is-tested) · [Upgrading](#upgrading) · [Caution](#caution)
|
|
31
|
+
- [See the graph](#see-the-graph) · [Long features and handoff](#long-features-and-handoff) · [What is tested](#what-is-tested) · [Limits](#limits) · [Upgrading](#upgrading) · [Caution](#caution)
|
|
32
32
|
|
|
33
33
|
## Quick start
|
|
34
34
|
|
|
@@ -51,7 +51,7 @@ Without a clone, the same installer runs from [PyPI](https://pypi.org/project/fe
|
|
|
51
51
|
uvx feature-flow-cli install /path/to/your/repo --agent all # or: pipx run feature-flow-cli install ...
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
The package puts a `feature-flow` command on your PATH with `install`, `status <feature>` and `
|
|
54
|
+
The package puts a `feature-flow` command on your PATH with `install`, `uninstall`, `status <feature>`, `view <feature>`, `reset <feature>` and `report <feature>`. It installs the same files, byte for byte, as `install.py` from a clone.
|
|
55
55
|
|
|
56
56
|
The installer lists what it wrote in `.feature-flow/installed.txt`, with each file's hash in `.feature-flow/installed.sha256`. Commit those files (`git add --pathspec-from-file=.feature-flow/installed.txt`); until you do, the flow does not count them as uncommitted changes. Installing a newer version over an older one updates every file nobody edited since it was installed, and keeps (and names) the ones you changed; `--force` replaces those too.
|
|
57
57
|
|
|
@@ -71,7 +71,7 @@ Then, in your repo, in the agent:
|
|
|
71
71
|
|
|
72
72
|
The same command plans when `plans/csv-export/` does not exist yet and builds when it does. After your second yes it commits the new plan folder, and only that folder, then goes straight on to building it; say so if you want it to stop after planning.
|
|
73
73
|
|
|
74
|
-
**Planning asks you twice.** First the session shows a short feature brief, written from your conversation (what, why, scope, the bar, and anything it had to assume), and waits for yes, edit or cancel. Then a fresh `feature-planner` subagent, given only that brief, reads the code, looks up outside docs where the code cannot answer, and writes a draft plan into the git-ignored `.feature-flow/state/draft/`. The planner folds changes too small to stand alone into the ticket they belong to, and
|
|
74
|
+
**Planning asks you twice.** First the session shows a short feature brief, written from your conversation (what, why, scope, the bar, and anything it had to assume), and waits for yes, edit or cancel. Then a fresh `feature-planner` subagent, given only that brief, reads the code, looks up outside docs where the code cannot answer, and writes a draft plan into the git-ignored `.feature-flow/state/draft/`. The planner folds changes too small to stand alone into the ticket they belong to, and gives every ticket a level (1 light, 2 standard, 3 boundary) that decides how much review it gets. A fresh `plan-reviewer` checks the draft against the brief. The session shows you the goal, the commands, the ticket graph, what was dropped and the review result, and waits for a second yes. Only then does `flow.py plan-accept` copy the draft into `plans/<feature>/`, and the session commits it and starts the build.
|
|
75
75
|
|
|
76
76
|
Try the graph first, with no setup and no agent: `bash examples/demo.sh --open`.
|
|
77
77
|
|
|
@@ -121,22 +121,34 @@ The Claude Code skill is `skills/feature-flow/`. The Codex skill is written by h
|
|
|
121
121
|
│ PASS │
|
|
122
122
|
▼ │
|
|
123
123
|
quality review: a second fresh reviewer subagent FAIL ─┤
|
|
124
|
-
(a
|
|
125
|
-
|
|
124
|
+
(a level 1 or 2 ticket gets one combined review instead
|
|
125
|
+
of these two; see "Ticket levels" below)
|
|
126
126
|
│ PASS ▼
|
|
127
127
|
▼ findings written into the ticket,
|
|
128
|
-
next ticket reopened, built again (
|
|
128
|
+
next ticket reopened, built again (rounds by level);
|
|
129
129
|
the builder's prompt then carries
|
|
130
130
|
a root-cause debugging guide
|
|
131
131
|
```
|
|
132
132
|
|
|
133
133
|
`scripts/flow.py` is the conductor. The session asks it `next`, and it answers with one line: `BUILD`, `REVIEW`, `DONE`, `STOP` or `HANDOFF`. It prints the full prompt for each subagent from runtime-neutral guides, so the session never writes one itself.
|
|
134
134
|
|
|
135
|
-
**What the script checks and what it trusts.** The conductor verifies the build from the repo: the ticket file must say `resolved`, the tree must be clean and committed, and it runs the gate and the floor guard itself. It also runs the `check` block under each ticket's Done when and compares the exit code and printed text, and for a `Test first: yes` ticket it proves the red test commit itself: it checks out the test-only commit, runs Test, and requires it to fail before the code. A failure of either goes back to the builder before any reviewer is started. It also checks that the reviewer changed no tracked file and made no commit. The review verdicts are different: each reviewer's reply (spec review, then quality review) is relayed by the session, which saves it to a file and hands it to `flow.py verdict`. The script reads `REVIEW: PASS` or `REVIEW: FAIL` from that reply; it cannot prove the session passed it on unedited. A ticket moves on only after two PASS verdicts, or one for a ticket the conductor judged small. The size is measured from the repo (`git diff` against the commit the ticket started from), never taken from the builder.
|
|
135
|
+
**What the script checks and what it trusts.** The conductor verifies the build from the repo: the ticket file must say `resolved`, the tree must be clean and committed, and it runs the gate and the floor guard itself. It also runs the `check` block under each ticket's Done when and compares the exit code and printed text, and for a `Test first: yes` ticket it proves the red test commit itself: it checks out the test-only commit, runs Test, and requires it to fail before the code. A failure of either goes back to the builder before any reviewer is started. It also checks that the reviewer changed no tracked file and made no commit. The review verdicts are different: each reviewer's reply (spec review, then quality review) is relayed by the session, which saves it to a file and hands it to `flow.py verdict`. The script reads `REVIEW: PASS` or `REVIEW: FAIL` from that reply; it cannot prove the session passed it on unedited. A ticket moves on only after two PASS verdicts, or one for a level 1 or 2 ticket (or, with no level, a ticket the conductor judged small). The size is measured from the repo (`git diff` against the commit the ticket started from), never taken from the builder.
|
|
136
136
|
|
|
137
137
|
**The gate: full or quick.** The gate runs Build, Test and Lint after every ticket. With an optional `Quick:` line in `commands.md` (a faster subset of `Test`), it runs Build, Quick and Lint after the tickets in the middle of a plan, and the full Build, Test and Lint after the last ticket, before a handoff, and under `FLOW_GATE_FULL=1`. A plan without `Quick:` runs the full gate every time. The test-first proof still runs the full `Test` command.
|
|
138
138
|
|
|
139
|
-
**
|
|
139
|
+
**Ticket levels.** The planner writes `Level: 1`, `2` or `3` in each ticket, the plan reviewer checks it against written rules, and you approve it with the plan.
|
|
140
|
+
|
|
141
|
+
| | Level 1 (light) | Level 2 (standard) | Level 3 (boundary) |
|
|
142
|
+
|---|---|---|---|
|
|
143
|
+
| For | tests, docs, prompt text, spikes, the acceptance ticket | ordinary feature code | auth, tokens, permissions, money and run limits, data loss, the flow's own code, anything a hostile caller reaches |
|
|
144
|
+
| Reviews | one combined review | one combined review, spec checklist first | spec review, then a quality review that probes every input as a hostile caller |
|
|
145
|
+
| Rework rounds | 1, then the run stops for you | 2; from round 3 only a `blocker` sends it back, majors go to `plans/<feature>/followups.md` and the ticket passes | `FLOW_MAX_REVIEW_ROUNDS` (3), then the run stops for you |
|
|
146
|
+
|
|
147
|
+
The gate does not depend on the level: it still runs Quick mid-plan and the full Test on the last ticket and before a handoff. Nobody but you can lower a level. The conductor reads `Level:` from the ticket as it was at the base commit, so a builder's edit does nothing (and the floor guard fails it). It raises the level from what it measures, never lowers it: `Review: full`, `Type: convert` and `Floor: allow flow-edit` make a ticket level 3, so does a diff that touches a path on the optional `Sensitive:` line of `commands.md` (frozen by the floor guard), and a level 1 diff over the small limits becomes level 2. A reviewer can raise it with a line `LEVEL: 3` above the verdict; a raise to 3 during a combined review adds a quality review. The log records `LEVEL-1`, `LEVEL-2` or `LEVEL-3` for each review round, and `feature-flow report` shows it.
|
|
148
|
+
|
|
149
|
+
**A rebuild reruns only the review that failed.** When a level 3 ticket fails its quality review, the rebuilt ticket gets the quality review only; the spec review it already passed is not repeated. The conductor's own checks (gate, floor guard, Done when, test first) still run on every rebuild.
|
|
150
|
+
|
|
151
|
+
**One review for a small ticket (no level).** A ticket with no `Level:` line, from a plan written before 0.9.0, keeps the 0.8.0 rules. A ticket whose built diff is 3 files and 60 changed lines at most (the plan folder left out), and whose `check` block ran and passed, gets one combined review instead of the spec and quality reviews. The conductor measures the diff itself. `Review: full` in the ticket (as it was at the start), `Type: convert`, no `check` block, or `FLOW_GATE=off` keep both reviews. Every other conductor check still runs on every ticket.
|
|
140
152
|
|
|
141
153
|
## The skill and the roles
|
|
142
154
|
|
|
@@ -183,6 +195,8 @@ Type: task task | settle | convert
|
|
|
183
195
|
Status: open open | claimed | resolved | parked
|
|
184
196
|
Blocked by: 01, 02 or —
|
|
185
197
|
Test first: yes yes | no
|
|
198
|
+
Level: 2 1 light | 2 standard | 3 boundary
|
|
199
|
+
Touches: src/export/ the paths it changes; a build outside them goes back
|
|
186
200
|
Floor: allow config optional, only when a human decides the guard may let it through
|
|
187
201
|
|
|
188
202
|
...what to do and why...
|
|
@@ -203,14 +217,18 @@ prints OK
|
|
|
203
217
|
- **The Answers are the handoff between subagents.** The next builder reads the Answers of the tickets it depends on.
|
|
204
218
|
- `settle` tickets record a decision. `convert` tickets migrate something and must be safe to run twice.
|
|
205
219
|
- **A worker may change only** its own Status line and Answer, lines appended to `map.md` and `learnings.md`, and the code the ticket calls for. Rewriting its own Done when, editing another ticket, or touching `commands.md` fails the guard.
|
|
206
|
-
- `Floor:` categories: `skip`, `suppress`, `empty-catch`, `test-delete`, `threshold`, `config`, `ticket-edit`, `commands-edit`, and `flow-edit` (allows the conductor's code check below, for the build only). `skip` covers skipped, expected-fail and focused tests (`.only(`, `fit(`, `fdescribe(` on test paths); `config` covers more lint and test config files, the lint and test sections of `pyproject.toml` and `setup.cfg`, scripts and test keys in `package.json`, new skip or xfail words in `conftest.py`, and a `Makefile` only when `commands.md` uses `make`. A test file that loses assertions is shown to the reviewer as a warning, not a finding. The line is read from the ticket as it was before the work started, so a worker cannot excuse itself.
|
|
220
|
+
- `Floor:` categories: `skip`, `suppress`, `empty-catch`, `test-delete`, `threshold`, `trivial-assert` (an assertion that cannot fail, added to a test file: `assert True`, `assertEqual(x, x)`, `expect(v).toBe(v)`), `config`, `ticket-edit`, `commands-edit`, and `flow-edit` (allows the conductor's code check below, for the build only). `skip` covers skipped, expected-fail and focused tests (`.only(`, `fit(`, `fdescribe(` on test paths); `config` covers more lint and test config files, the lint and test sections of `pyproject.toml` and `setup.cfg`, scripts and test keys in `package.json`, new skip or xfail words in `conftest.py`, and a `Makefile` only when `commands.md` uses `make`. A test file that loses assertions is shown to the reviewer as a warning, not a finding. The line is read from the ticket as it was before the work started, so a worker cannot excuse itself.
|
|
207
221
|
- The guard needs the plan to be tracked by git. If you keep tickets in an ignored folder, it cannot see changes to them.
|
|
208
222
|
- The commands in `commands.md` run with nobody watching and must exit non zero on failure.
|
|
209
223
|
|
|
210
224
|
## See the graph
|
|
211
225
|
|
|
226
|
+

|
|
227
|
+
|
|
228
|
+
*The page `flow-view` writes, shown for the demo plan from `examples/demo.sh`: resolved tickets in green, ready ones in amber, the one being worked in blue, the rest waiting. Press Replay to watch the run back from git history.*
|
|
229
|
+
|
|
212
230
|
```bash
|
|
213
|
-
python3 scripts/flow-view.py csv-export write the page and open it
|
|
231
|
+
python3 scripts/flow-view.py csv-export write the page and open it (or: feature-flow view csv-export)
|
|
214
232
|
python3 scripts/flow-view.py csv-export --watch keep it updating while tickets are built
|
|
215
233
|
bash examples/demo.sh --open try it on a made up project, no setup needed
|
|
216
234
|
```
|
|
@@ -230,6 +248,21 @@ One self contained HTML file: no server, no libraries, no network, light and dar
|
|
|
230
248
|
- **Safe by construction:** ticket text comes from files an agent wrote, so it is escaped when embedded and only ever inserted as text.
|
|
231
249
|
- `flow-status.py <feature> --json` prints the same data for other tools. Mermaid (`--mermaid`) remains the choice for pull requests, because GitHub renders it.
|
|
232
250
|
|
|
251
|
+
## Tickets side by side (lanes)
|
|
252
|
+
|
|
253
|
+
With `FLOW_LANES=2` (up to 4) on `start`, tickets that do not touch the same files are built at the same time, each with its own builder and reviewers in its own git worktree. Only the start and the end of a ticket change: the build, the gate, the floor guard and the reviews are the same as with one lane.
|
|
254
|
+
|
|
255
|
+
- **Plan.** Each ticket lists the paths it changes on a `Touches:` line (a folder ends with `/`, a `*` glob crosses folders). `commands.md` may name `Shared:` paths that many tickets change, and a `Setup:` command a fresh checkout needs. The plan reviewer checks the footprints; you approve them with the plan.
|
|
256
|
+
- **Schedule.** The conductor leases a ready ticket to a free lane only when its `Touches:` (read from the feature branch, never from a builder) does not overlap any running ticket's, it touches nothing on `Shared:`, and, when either ticket is level 3, the two keep to different top-level folders. A ticket with no `Touches:` line runs alone.
|
|
257
|
+
- **Lanes.** The lanes are worktrees in `.feature-flow/lanes/<feature>/` (git ignores the folder), created on first use, with `Setup:` run once in each. Each ticket gets a branch `ff/<feature>/<NN>` from the feature head.
|
|
258
|
+
- **Footprint check.** After every build, with or without lanes, a change to a path outside the ticket's `Touches:` sends the ticket back (`FOOTPRINT-FAIL` in the log).
|
|
259
|
+
- **Merge.** A ticket that passes its review is merged by the conductor, one at a time, in `.feature-flow/lanes/<feature>/integrate` with `git merge --no-ff`, so the reviewed commits land unchanged. The gate runs on the merge (Quick mid-plan, the full Test for the last ticket). Only a green merge fast-forwards the feature branch; then the ticket's branch is deleted with `git branch -d`. When two lanes both appended to `map.md`, `learnings.md` or `followups.md`, the conductor keeps both sides' lines; no agent ever resolves a conflict.
|
|
260
|
+
- **Rebuild.** Any other conflict, or a gate that fails on the merge, sends the ticket back to be built again from the feature branch as it is now, with what went wrong and its earlier commit in the ticket. A second collision stops the run for you: the plan's `Touches:` are likely wrong.
|
|
261
|
+
- **Level 3.** When another ticket merged after a level 3 ticket started, it is merged with the new feature head in its lane, gated, and given its quality review again before it merges.
|
|
262
|
+
- **The end.** After the last merge the lanes are removed. `reset` removes them too, with the ticket branches.
|
|
263
|
+
|
|
264
|
+
The session runs `prompt`, `next` and `verdict` with the lane from the `BUILD` or `REVIEW` line, and `next` answers `WAIT <lanes>` when every lane that can work is busy (see `## Lanes` in the skill).
|
|
265
|
+
|
|
233
266
|
## Long features and handoff
|
|
234
267
|
|
|
235
268
|
A session's context fills up, so the flow moves to a fresh session every few tickets. After `FLOW_TICKETS_PER_SESSION` tickets (default 4) pass review, `next` prints a `HANDOFF` line instead of the next ticket, for example `HANDOFF /feature-flow csv-export 3f9c0a1b2c3d4e5f.0a1b2c3d4e5f6a7b` (the last word is the runner's digest, which lets the new session check that the flow's files did not change in between). The session stops and tells you to open a new session and type that line. The new session picks up exactly where the last one stopped. Set `FLOW_TICKETS_PER_SESSION=0` to never hand off.
|
|
@@ -249,8 +282,11 @@ python3 scripts/flow.py <feature> start|reset [NN] the conductor (the s
|
|
|
249
282
|
FLOW_TRUST=<digest|new> FLOW_SESSION=<token> python3 -I scripts/flow-trust.py <feature> next|prompt|verdict <file> loop commands by hand, through the runner: prints TRUST <digest> first (no hash check, see below)
|
|
250
283
|
python3 scripts/flow-status.py <feature> table of tickets and which are READY
|
|
251
284
|
python3 scripts/flow-status.py <feature> --next path of the next ready ticket (exit 10 = done, 11 = stuck)
|
|
285
|
+
python3 scripts/flow-status.py <feature> --ready every ready ticket, one per line (the same exit codes)
|
|
252
286
|
python3 scripts/flow-status.py <feature> --counts one line of counts
|
|
253
287
|
python3 scripts/flow-status.py <feature> --check cycles, missing blockers, missing Done when, unordered mentions
|
|
288
|
+
python3 scripts/flow-status.py <feature> --lint --check plus: a check block (or `Check: none <why>`) on every ticket, a Test command
|
|
289
|
+
feature-flow report <feature> what a run did, from its log: builds, reviews, failed checks, stops
|
|
254
290
|
python3 scripts/flow-status.py <feature> --mermaid the ticket graph, coloured by status (add "plain" for none)
|
|
255
291
|
python3 scripts/flow-status.py <feature> --json every ticket with status, blockers and readiness
|
|
256
292
|
python3 scripts/flow-view.py <feature> [--watch] the animated graph page, opened in your browser
|
|
@@ -268,12 +304,13 @@ The commands in `commands.md` run through `bash -c` on Linux and macOS and throu
|
|
|
268
304
|
|---|---|---|
|
|
269
305
|
| `FLOW_TICKETS_PER_SESSION` | `4` | tickets per session before `HANDOFF`; `0` never hands off |
|
|
270
306
|
| `FLOW_RELAY` | `0` | `1` hands off after every build and every review |
|
|
307
|
+
| `FLOW_LANES` | `1` | read by `start`: `2` to `4` builds tickets with disjoint `Touches:` side by side in worktrees (not with `FLOW_RELAY`) |
|
|
271
308
|
| `FLOW_TAKEOVER` | unset | `1` lets `start` take a feature over from a session that is gone |
|
|
272
309
|
| `FLOW_MAX_RETRIES` | `2` | builds or reviews per step before the run stops |
|
|
273
|
-
| `FLOW_MAX_REVIEW_ROUNDS` | `3` | times a ticket may be sent back before the run stops |
|
|
310
|
+
| `FLOW_MAX_REVIEW_ROUNDS` | `3` | times a ticket may be sent back before the run stops (a level 1 ticket: once) |
|
|
274
311
|
| `FLOW_GATE` | `on` | `off` skips Build, Test and Lint after each ticket, and also the Done when and test-first checks |
|
|
275
312
|
| `FLOW_GATE_FULL` | unset | `1` runs the full Build, Test and Lint after every ticket, even when `commands.md` has a `Quick:` line |
|
|
276
|
-
| `FLOW_SMALL_FILES` | `3` | most files a ticket may change and still get the single review |
|
|
313
|
+
| `FLOW_SMALL_FILES` | `3` | most files a ticket may change and still get the single review (no level), or stay level 1 |
|
|
277
314
|
| `FLOW_SMALL_LINES` | `60` | most changed lines for the single review; `0` turns the single review off |
|
|
278
315
|
| `FLOW_GATE_TIMEOUT` | `30` | minutes each gate command and the smoke test may run; `0` means no limit. A failed smoke test writes `.feature-flow/state/<feature>.smoke.log` |
|
|
279
316
|
| `FLOW_SMOKE` | the `Smoke:` line of `commands.md` | command run before every ticket; the run stops if it fails |
|
|
@@ -314,6 +351,18 @@ A `package` job builds the wheel and the sdist and runs `tests/package_smoke.py`
|
|
|
314
351
|
|
|
315
352
|
`RUN_REAL=1 bash tests/smoke-real.sh --interactive` is the acceptance run with Claude Code. It runs real sessions on the demo project and spends money, which is why it asks for `RUN_REAL=1`.
|
|
316
353
|
|
|
354
|
+
## Limits
|
|
355
|
+
|
|
356
|
+
feature-flow's layers are tripwires, not a security boundary. They catch accidents and lazy shortcuts by an agent; a determined agent that controls the session could still get around them.
|
|
357
|
+
|
|
358
|
+
- **The verdict relay is trusted.** The session saves a reviewer's reply and hands it to `flow.py verdict`. The conductor reads `REVIEW: PASS` or `REVIEW: FAIL` from that file and cannot prove the session passed it on unedited.
|
|
359
|
+
- **The floor guard reads diffs with patterns.** It does not see a test made trivially true by other means (a mock that returns the expected answer, an `assert` on something that is always set, a test that no longer exercises the code), or an assertion weakened without being deleted. It does catch assertions that cannot fail (`trivial-assert`) and warns the reviewer when a test file loses assertions. A new test with no assertion at all is not caught. The red-first proof only covers tickets with `Test first: yes`.
|
|
360
|
+
- **A plan can be wrong and still pass.** The plan lint checks that every ticket has a way to be proven and that `commands.md` has a Test command. It cannot tell whether the plan builds the right thing; the second yes is yours.
|
|
361
|
+
- **The trust runner stops accidents, not the session.** It catches a builder editing the conductor mid-run. The session that runs it is the thing it cannot check; a hand-run `flow-trust.py` is unprotected (see above).
|
|
362
|
+
- **Little real-world use so far.** Almost everything has been built by feature-flow on its own repo. The Codex skill and the Codex planner have not been run by hand, and the Quick gate has only been run by tests. See [docs/field-run.md](docs/field-run.md) for how to try it on your own repo and what to send back.
|
|
363
|
+
- **One ticket at a time**, with a handoff to a new session every few tickets, unless you turn on lanes.
|
|
364
|
+
- **Lanes share one disk.** A builder in one lane can still write into another lane's folder or the main worktree. The conductor sees that as a dirty tree and stops, but only afterwards. Overlap of `Touches:` is judged from the patterns, so two tickets that could never meet may still run one after the other.
|
|
365
|
+
|
|
317
366
|
## Upgrading
|
|
318
367
|
|
|
319
368
|
Earlier versions had seven skills and an unattended loop. The five flow skills are gone, and `/feature-flow` replaces them:
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
Plan a feature as a graph of small tickets, then build them in the session you are already in. One command, `/feature-flow`, does both. Every ticket is built by one fresh subagent and checked by another that never saw the builder's reasoning. A script, not the agent, decides the order of the steps, runs the build and tests, and watches for work that weakens the checks, and the whole run has limits.
|
|
8
8
|
|
|
9
|
-
One skill,
|
|
9
|
+
One skill, 4 agent roles, a Python conductor, 6 Python scripts (`flow`, `flow-trust`, `flow-status`, `flow-view`, `gate`, `floor-guard`), an installer for both agents, a demo and a test suite. All of it is Python (standard library only), so it runs on Linux, macOS and Windows. MIT licensed.
|
|
10
10
|
|
|
11
11
|
- [Quick start](#quick-start) · [How a ticket flows](#how-a-ticket-flows) · [The skill and the roles](#the-skill-and-the-roles) · [Plans and tickets](#plans-and-tickets)
|
|
12
|
-
- [See the graph](#see-the-graph) · [Long features and handoff](#long-features-and-handoff) · [What is tested](#what-is-tested) · [Upgrading](#upgrading) · [Caution](#caution)
|
|
12
|
+
- [See the graph](#see-the-graph) · [Long features and handoff](#long-features-and-handoff) · [What is tested](#what-is-tested) · [Limits](#limits) · [Upgrading](#upgrading) · [Caution](#caution)
|
|
13
13
|
|
|
14
14
|
## Quick start
|
|
15
15
|
|
|
@@ -32,7 +32,7 @@ Without a clone, the same installer runs from [PyPI](https://pypi.org/project/fe
|
|
|
32
32
|
uvx feature-flow-cli install /path/to/your/repo --agent all # or: pipx run feature-flow-cli install ...
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
The package puts a `feature-flow` command on your PATH with `install`, `status <feature>` and `
|
|
35
|
+
The package puts a `feature-flow` command on your PATH with `install`, `uninstall`, `status <feature>`, `view <feature>`, `reset <feature>` and `report <feature>`. It installs the same files, byte for byte, as `install.py` from a clone.
|
|
36
36
|
|
|
37
37
|
The installer lists what it wrote in `.feature-flow/installed.txt`, with each file's hash in `.feature-flow/installed.sha256`. Commit those files (`git add --pathspec-from-file=.feature-flow/installed.txt`); until you do, the flow does not count them as uncommitted changes. Installing a newer version over an older one updates every file nobody edited since it was installed, and keeps (and names) the ones you changed; `--force` replaces those too.
|
|
38
38
|
|
|
@@ -52,7 +52,7 @@ Then, in your repo, in the agent:
|
|
|
52
52
|
|
|
53
53
|
The same command plans when `plans/csv-export/` does not exist yet and builds when it does. After your second yes it commits the new plan folder, and only that folder, then goes straight on to building it; say so if you want it to stop after planning.
|
|
54
54
|
|
|
55
|
-
**Planning asks you twice.** First the session shows a short feature brief, written from your conversation (what, why, scope, the bar, and anything it had to assume), and waits for yes, edit or cancel. Then a fresh `feature-planner` subagent, given only that brief, reads the code, looks up outside docs where the code cannot answer, and writes a draft plan into the git-ignored `.feature-flow/state/draft/`. The planner folds changes too small to stand alone into the ticket they belong to, and
|
|
55
|
+
**Planning asks you twice.** First the session shows a short feature brief, written from your conversation (what, why, scope, the bar, and anything it had to assume), and waits for yes, edit or cancel. Then a fresh `feature-planner` subagent, given only that brief, reads the code, looks up outside docs where the code cannot answer, and writes a draft plan into the git-ignored `.feature-flow/state/draft/`. The planner folds changes too small to stand alone into the ticket they belong to, and gives every ticket a level (1 light, 2 standard, 3 boundary) that decides how much review it gets. A fresh `plan-reviewer` checks the draft against the brief. The session shows you the goal, the commands, the ticket graph, what was dropped and the review result, and waits for a second yes. Only then does `flow.py plan-accept` copy the draft into `plans/<feature>/`, and the session commits it and starts the build.
|
|
56
56
|
|
|
57
57
|
Try the graph first, with no setup and no agent: `bash examples/demo.sh --open`.
|
|
58
58
|
|
|
@@ -102,22 +102,34 @@ The Claude Code skill is `skills/feature-flow/`. The Codex skill is written by h
|
|
|
102
102
|
│ PASS │
|
|
103
103
|
▼ │
|
|
104
104
|
quality review: a second fresh reviewer subagent FAIL ─┤
|
|
105
|
-
(a
|
|
106
|
-
|
|
105
|
+
(a level 1 or 2 ticket gets one combined review instead
|
|
106
|
+
of these two; see "Ticket levels" below)
|
|
107
107
|
│ PASS ▼
|
|
108
108
|
▼ findings written into the ticket,
|
|
109
|
-
next ticket reopened, built again (
|
|
109
|
+
next ticket reopened, built again (rounds by level);
|
|
110
110
|
the builder's prompt then carries
|
|
111
111
|
a root-cause debugging guide
|
|
112
112
|
```
|
|
113
113
|
|
|
114
114
|
`scripts/flow.py` is the conductor. The session asks it `next`, and it answers with one line: `BUILD`, `REVIEW`, `DONE`, `STOP` or `HANDOFF`. It prints the full prompt for each subagent from runtime-neutral guides, so the session never writes one itself.
|
|
115
115
|
|
|
116
|
-
**What the script checks and what it trusts.** The conductor verifies the build from the repo: the ticket file must say `resolved`, the tree must be clean and committed, and it runs the gate and the floor guard itself. It also runs the `check` block under each ticket's Done when and compares the exit code and printed text, and for a `Test first: yes` ticket it proves the red test commit itself: it checks out the test-only commit, runs Test, and requires it to fail before the code. A failure of either goes back to the builder before any reviewer is started. It also checks that the reviewer changed no tracked file and made no commit. The review verdicts are different: each reviewer's reply (spec review, then quality review) is relayed by the session, which saves it to a file and hands it to `flow.py verdict`. The script reads `REVIEW: PASS` or `REVIEW: FAIL` from that reply; it cannot prove the session passed it on unedited. A ticket moves on only after two PASS verdicts, or one for a ticket the conductor judged small. The size is measured from the repo (`git diff` against the commit the ticket started from), never taken from the builder.
|
|
116
|
+
**What the script checks and what it trusts.** The conductor verifies the build from the repo: the ticket file must say `resolved`, the tree must be clean and committed, and it runs the gate and the floor guard itself. It also runs the `check` block under each ticket's Done when and compares the exit code and printed text, and for a `Test first: yes` ticket it proves the red test commit itself: it checks out the test-only commit, runs Test, and requires it to fail before the code. A failure of either goes back to the builder before any reviewer is started. It also checks that the reviewer changed no tracked file and made no commit. The review verdicts are different: each reviewer's reply (spec review, then quality review) is relayed by the session, which saves it to a file and hands it to `flow.py verdict`. The script reads `REVIEW: PASS` or `REVIEW: FAIL` from that reply; it cannot prove the session passed it on unedited. A ticket moves on only after two PASS verdicts, or one for a level 1 or 2 ticket (or, with no level, a ticket the conductor judged small). The size is measured from the repo (`git diff` against the commit the ticket started from), never taken from the builder.
|
|
117
117
|
|
|
118
118
|
**The gate: full or quick.** The gate runs Build, Test and Lint after every ticket. With an optional `Quick:` line in `commands.md` (a faster subset of `Test`), it runs Build, Quick and Lint after the tickets in the middle of a plan, and the full Build, Test and Lint after the last ticket, before a handoff, and under `FLOW_GATE_FULL=1`. A plan without `Quick:` runs the full gate every time. The test-first proof still runs the full `Test` command.
|
|
119
119
|
|
|
120
|
-
**
|
|
120
|
+
**Ticket levels.** The planner writes `Level: 1`, `2` or `3` in each ticket, the plan reviewer checks it against written rules, and you approve it with the plan.
|
|
121
|
+
|
|
122
|
+
| | Level 1 (light) | Level 2 (standard) | Level 3 (boundary) |
|
|
123
|
+
|---|---|---|---|
|
|
124
|
+
| For | tests, docs, prompt text, spikes, the acceptance ticket | ordinary feature code | auth, tokens, permissions, money and run limits, data loss, the flow's own code, anything a hostile caller reaches |
|
|
125
|
+
| Reviews | one combined review | one combined review, spec checklist first | spec review, then a quality review that probes every input as a hostile caller |
|
|
126
|
+
| Rework rounds | 1, then the run stops for you | 2; from round 3 only a `blocker` sends it back, majors go to `plans/<feature>/followups.md` and the ticket passes | `FLOW_MAX_REVIEW_ROUNDS` (3), then the run stops for you |
|
|
127
|
+
|
|
128
|
+
The gate does not depend on the level: it still runs Quick mid-plan and the full Test on the last ticket and before a handoff. Nobody but you can lower a level. The conductor reads `Level:` from the ticket as it was at the base commit, so a builder's edit does nothing (and the floor guard fails it). It raises the level from what it measures, never lowers it: `Review: full`, `Type: convert` and `Floor: allow flow-edit` make a ticket level 3, so does a diff that touches a path on the optional `Sensitive:` line of `commands.md` (frozen by the floor guard), and a level 1 diff over the small limits becomes level 2. A reviewer can raise it with a line `LEVEL: 3` above the verdict; a raise to 3 during a combined review adds a quality review. The log records `LEVEL-1`, `LEVEL-2` or `LEVEL-3` for each review round, and `feature-flow report` shows it.
|
|
129
|
+
|
|
130
|
+
**A rebuild reruns only the review that failed.** When a level 3 ticket fails its quality review, the rebuilt ticket gets the quality review only; the spec review it already passed is not repeated. The conductor's own checks (gate, floor guard, Done when, test first) still run on every rebuild.
|
|
131
|
+
|
|
132
|
+
**One review for a small ticket (no level).** A ticket with no `Level:` line, from a plan written before 0.9.0, keeps the 0.8.0 rules. A ticket whose built diff is 3 files and 60 changed lines at most (the plan folder left out), and whose `check` block ran and passed, gets one combined review instead of the spec and quality reviews. The conductor measures the diff itself. `Review: full` in the ticket (as it was at the start), `Type: convert`, no `check` block, or `FLOW_GATE=off` keep both reviews. Every other conductor check still runs on every ticket.
|
|
121
133
|
|
|
122
134
|
## The skill and the roles
|
|
123
135
|
|
|
@@ -164,6 +176,8 @@ Type: task task | settle | convert
|
|
|
164
176
|
Status: open open | claimed | resolved | parked
|
|
165
177
|
Blocked by: 01, 02 or —
|
|
166
178
|
Test first: yes yes | no
|
|
179
|
+
Level: 2 1 light | 2 standard | 3 boundary
|
|
180
|
+
Touches: src/export/ the paths it changes; a build outside them goes back
|
|
167
181
|
Floor: allow config optional, only when a human decides the guard may let it through
|
|
168
182
|
|
|
169
183
|
...what to do and why...
|
|
@@ -184,14 +198,18 @@ prints OK
|
|
|
184
198
|
- **The Answers are the handoff between subagents.** The next builder reads the Answers of the tickets it depends on.
|
|
185
199
|
- `settle` tickets record a decision. `convert` tickets migrate something and must be safe to run twice.
|
|
186
200
|
- **A worker may change only** its own Status line and Answer, lines appended to `map.md` and `learnings.md`, and the code the ticket calls for. Rewriting its own Done when, editing another ticket, or touching `commands.md` fails the guard.
|
|
187
|
-
- `Floor:` categories: `skip`, `suppress`, `empty-catch`, `test-delete`, `threshold`, `config`, `ticket-edit`, `commands-edit`, and `flow-edit` (allows the conductor's code check below, for the build only). `skip` covers skipped, expected-fail and focused tests (`.only(`, `fit(`, `fdescribe(` on test paths); `config` covers more lint and test config files, the lint and test sections of `pyproject.toml` and `setup.cfg`, scripts and test keys in `package.json`, new skip or xfail words in `conftest.py`, and a `Makefile` only when `commands.md` uses `make`. A test file that loses assertions is shown to the reviewer as a warning, not a finding. The line is read from the ticket as it was before the work started, so a worker cannot excuse itself.
|
|
201
|
+
- `Floor:` categories: `skip`, `suppress`, `empty-catch`, `test-delete`, `threshold`, `trivial-assert` (an assertion that cannot fail, added to a test file: `assert True`, `assertEqual(x, x)`, `expect(v).toBe(v)`), `config`, `ticket-edit`, `commands-edit`, and `flow-edit` (allows the conductor's code check below, for the build only). `skip` covers skipped, expected-fail and focused tests (`.only(`, `fit(`, `fdescribe(` on test paths); `config` covers more lint and test config files, the lint and test sections of `pyproject.toml` and `setup.cfg`, scripts and test keys in `package.json`, new skip or xfail words in `conftest.py`, and a `Makefile` only when `commands.md` uses `make`. A test file that loses assertions is shown to the reviewer as a warning, not a finding. The line is read from the ticket as it was before the work started, so a worker cannot excuse itself.
|
|
188
202
|
- The guard needs the plan to be tracked by git. If you keep tickets in an ignored folder, it cannot see changes to them.
|
|
189
203
|
- The commands in `commands.md` run with nobody watching and must exit non zero on failure.
|
|
190
204
|
|
|
191
205
|
## See the graph
|
|
192
206
|
|
|
207
|
+

|
|
208
|
+
|
|
209
|
+
*The page `flow-view` writes, shown for the demo plan from `examples/demo.sh`: resolved tickets in green, ready ones in amber, the one being worked in blue, the rest waiting. Press Replay to watch the run back from git history.*
|
|
210
|
+
|
|
193
211
|
```bash
|
|
194
|
-
python3 scripts/flow-view.py csv-export write the page and open it
|
|
212
|
+
python3 scripts/flow-view.py csv-export write the page and open it (or: feature-flow view csv-export)
|
|
195
213
|
python3 scripts/flow-view.py csv-export --watch keep it updating while tickets are built
|
|
196
214
|
bash examples/demo.sh --open try it on a made up project, no setup needed
|
|
197
215
|
```
|
|
@@ -211,6 +229,21 @@ One self contained HTML file: no server, no libraries, no network, light and dar
|
|
|
211
229
|
- **Safe by construction:** ticket text comes from files an agent wrote, so it is escaped when embedded and only ever inserted as text.
|
|
212
230
|
- `flow-status.py <feature> --json` prints the same data for other tools. Mermaid (`--mermaid`) remains the choice for pull requests, because GitHub renders it.
|
|
213
231
|
|
|
232
|
+
## Tickets side by side (lanes)
|
|
233
|
+
|
|
234
|
+
With `FLOW_LANES=2` (up to 4) on `start`, tickets that do not touch the same files are built at the same time, each with its own builder and reviewers in its own git worktree. Only the start and the end of a ticket change: the build, the gate, the floor guard and the reviews are the same as with one lane.
|
|
235
|
+
|
|
236
|
+
- **Plan.** Each ticket lists the paths it changes on a `Touches:` line (a folder ends with `/`, a `*` glob crosses folders). `commands.md` may name `Shared:` paths that many tickets change, and a `Setup:` command a fresh checkout needs. The plan reviewer checks the footprints; you approve them with the plan.
|
|
237
|
+
- **Schedule.** The conductor leases a ready ticket to a free lane only when its `Touches:` (read from the feature branch, never from a builder) does not overlap any running ticket's, it touches nothing on `Shared:`, and, when either ticket is level 3, the two keep to different top-level folders. A ticket with no `Touches:` line runs alone.
|
|
238
|
+
- **Lanes.** The lanes are worktrees in `.feature-flow/lanes/<feature>/` (git ignores the folder), created on first use, with `Setup:` run once in each. Each ticket gets a branch `ff/<feature>/<NN>` from the feature head.
|
|
239
|
+
- **Footprint check.** After every build, with or without lanes, a change to a path outside the ticket's `Touches:` sends the ticket back (`FOOTPRINT-FAIL` in the log).
|
|
240
|
+
- **Merge.** A ticket that passes its review is merged by the conductor, one at a time, in `.feature-flow/lanes/<feature>/integrate` with `git merge --no-ff`, so the reviewed commits land unchanged. The gate runs on the merge (Quick mid-plan, the full Test for the last ticket). Only a green merge fast-forwards the feature branch; then the ticket's branch is deleted with `git branch -d`. When two lanes both appended to `map.md`, `learnings.md` or `followups.md`, the conductor keeps both sides' lines; no agent ever resolves a conflict.
|
|
241
|
+
- **Rebuild.** Any other conflict, or a gate that fails on the merge, sends the ticket back to be built again from the feature branch as it is now, with what went wrong and its earlier commit in the ticket. A second collision stops the run for you: the plan's `Touches:` are likely wrong.
|
|
242
|
+
- **Level 3.** When another ticket merged after a level 3 ticket started, it is merged with the new feature head in its lane, gated, and given its quality review again before it merges.
|
|
243
|
+
- **The end.** After the last merge the lanes are removed. `reset` removes them too, with the ticket branches.
|
|
244
|
+
|
|
245
|
+
The session runs `prompt`, `next` and `verdict` with the lane from the `BUILD` or `REVIEW` line, and `next` answers `WAIT <lanes>` when every lane that can work is busy (see `## Lanes` in the skill).
|
|
246
|
+
|
|
214
247
|
## Long features and handoff
|
|
215
248
|
|
|
216
249
|
A session's context fills up, so the flow moves to a fresh session every few tickets. After `FLOW_TICKETS_PER_SESSION` tickets (default 4) pass review, `next` prints a `HANDOFF` line instead of the next ticket, for example `HANDOFF /feature-flow csv-export 3f9c0a1b2c3d4e5f.0a1b2c3d4e5f6a7b` (the last word is the runner's digest, which lets the new session check that the flow's files did not change in between). The session stops and tells you to open a new session and type that line. The new session picks up exactly where the last one stopped. Set `FLOW_TICKETS_PER_SESSION=0` to never hand off.
|
|
@@ -230,8 +263,11 @@ python3 scripts/flow.py <feature> start|reset [NN] the conductor (the s
|
|
|
230
263
|
FLOW_TRUST=<digest|new> FLOW_SESSION=<token> python3 -I scripts/flow-trust.py <feature> next|prompt|verdict <file> loop commands by hand, through the runner: prints TRUST <digest> first (no hash check, see below)
|
|
231
264
|
python3 scripts/flow-status.py <feature> table of tickets and which are READY
|
|
232
265
|
python3 scripts/flow-status.py <feature> --next path of the next ready ticket (exit 10 = done, 11 = stuck)
|
|
266
|
+
python3 scripts/flow-status.py <feature> --ready every ready ticket, one per line (the same exit codes)
|
|
233
267
|
python3 scripts/flow-status.py <feature> --counts one line of counts
|
|
234
268
|
python3 scripts/flow-status.py <feature> --check cycles, missing blockers, missing Done when, unordered mentions
|
|
269
|
+
python3 scripts/flow-status.py <feature> --lint --check plus: a check block (or `Check: none <why>`) on every ticket, a Test command
|
|
270
|
+
feature-flow report <feature> what a run did, from its log: builds, reviews, failed checks, stops
|
|
235
271
|
python3 scripts/flow-status.py <feature> --mermaid the ticket graph, coloured by status (add "plain" for none)
|
|
236
272
|
python3 scripts/flow-status.py <feature> --json every ticket with status, blockers and readiness
|
|
237
273
|
python3 scripts/flow-view.py <feature> [--watch] the animated graph page, opened in your browser
|
|
@@ -249,12 +285,13 @@ The commands in `commands.md` run through `bash -c` on Linux and macOS and throu
|
|
|
249
285
|
|---|---|---|
|
|
250
286
|
| `FLOW_TICKETS_PER_SESSION` | `4` | tickets per session before `HANDOFF`; `0` never hands off |
|
|
251
287
|
| `FLOW_RELAY` | `0` | `1` hands off after every build and every review |
|
|
288
|
+
| `FLOW_LANES` | `1` | read by `start`: `2` to `4` builds tickets with disjoint `Touches:` side by side in worktrees (not with `FLOW_RELAY`) |
|
|
252
289
|
| `FLOW_TAKEOVER` | unset | `1` lets `start` take a feature over from a session that is gone |
|
|
253
290
|
| `FLOW_MAX_RETRIES` | `2` | builds or reviews per step before the run stops |
|
|
254
|
-
| `FLOW_MAX_REVIEW_ROUNDS` | `3` | times a ticket may be sent back before the run stops |
|
|
291
|
+
| `FLOW_MAX_REVIEW_ROUNDS` | `3` | times a ticket may be sent back before the run stops (a level 1 ticket: once) |
|
|
255
292
|
| `FLOW_GATE` | `on` | `off` skips Build, Test and Lint after each ticket, and also the Done when and test-first checks |
|
|
256
293
|
| `FLOW_GATE_FULL` | unset | `1` runs the full Build, Test and Lint after every ticket, even when `commands.md` has a `Quick:` line |
|
|
257
|
-
| `FLOW_SMALL_FILES` | `3` | most files a ticket may change and still get the single review |
|
|
294
|
+
| `FLOW_SMALL_FILES` | `3` | most files a ticket may change and still get the single review (no level), or stay level 1 |
|
|
258
295
|
| `FLOW_SMALL_LINES` | `60` | most changed lines for the single review; `0` turns the single review off |
|
|
259
296
|
| `FLOW_GATE_TIMEOUT` | `30` | minutes each gate command and the smoke test may run; `0` means no limit. A failed smoke test writes `.feature-flow/state/<feature>.smoke.log` |
|
|
260
297
|
| `FLOW_SMOKE` | the `Smoke:` line of `commands.md` | command run before every ticket; the run stops if it fails |
|
|
@@ -295,6 +332,18 @@ A `package` job builds the wheel and the sdist and runs `tests/package_smoke.py`
|
|
|
295
332
|
|
|
296
333
|
`RUN_REAL=1 bash tests/smoke-real.sh --interactive` is the acceptance run with Claude Code. It runs real sessions on the demo project and spends money, which is why it asks for `RUN_REAL=1`.
|
|
297
334
|
|
|
335
|
+
## Limits
|
|
336
|
+
|
|
337
|
+
feature-flow's layers are tripwires, not a security boundary. They catch accidents and lazy shortcuts by an agent; a determined agent that controls the session could still get around them.
|
|
338
|
+
|
|
339
|
+
- **The verdict relay is trusted.** The session saves a reviewer's reply and hands it to `flow.py verdict`. The conductor reads `REVIEW: PASS` or `REVIEW: FAIL` from that file and cannot prove the session passed it on unedited.
|
|
340
|
+
- **The floor guard reads diffs with patterns.** It does not see a test made trivially true by other means (a mock that returns the expected answer, an `assert` on something that is always set, a test that no longer exercises the code), or an assertion weakened without being deleted. It does catch assertions that cannot fail (`trivial-assert`) and warns the reviewer when a test file loses assertions. A new test with no assertion at all is not caught. The red-first proof only covers tickets with `Test first: yes`.
|
|
341
|
+
- **A plan can be wrong and still pass.** The plan lint checks that every ticket has a way to be proven and that `commands.md` has a Test command. It cannot tell whether the plan builds the right thing; the second yes is yours.
|
|
342
|
+
- **The trust runner stops accidents, not the session.** It catches a builder editing the conductor mid-run. The session that runs it is the thing it cannot check; a hand-run `flow-trust.py` is unprotected (see above).
|
|
343
|
+
- **Little real-world use so far.** Almost everything has been built by feature-flow on its own repo. The Codex skill and the Codex planner have not been run by hand, and the Quick gate has only been run by tests. See [docs/field-run.md](docs/field-run.md) for how to try it on your own repo and what to send back.
|
|
344
|
+
- **One ticket at a time**, with a handoff to a new session every few tickets, unless you turn on lanes.
|
|
345
|
+
- **Lanes share one disk.** A builder in one lane can still write into another lane's folder or the main worktree. The conductor sees that as a dirty tree and stops, but only afterwards. Overlap of `Touches:` is judged from the patterns, so two tickets that could never meet may still run one after the other.
|
|
346
|
+
|
|
298
347
|
## Upgrading
|
|
299
348
|
|
|
300
349
|
Earlier versions had seven skills and an unattended loop. The five flow skills are gone, and `/feature-flow` replaces them:
|
|
@@ -69,6 +69,15 @@ Run `next`, act on its one line after `TRUST` (and any `FLOW-EDIT` line), and re
|
|
|
69
69
|
- `STOP <reason>`: report the reason, run `python3 scripts/flow-status.py <feature>`, show the table, and stop.
|
|
70
70
|
- `HANDOFF <line>`: stop here. Tell the user to open a new session and type exactly `<line>`, the whole line with its digest at the end. The new session resumes where this one stopped.
|
|
71
71
|
|
|
72
|
+
## Lanes
|
|
73
|
+
|
|
74
|
+
With `FLOW_LANES=2` (up to 4) on `start`, the conductor builds tickets whose `Touches:` lines do not overlap at the same time, each in its own git worktree (a lane) on its own branch, and merges each one onto the feature branch itself once it passes. Use it only when the user asks for tickets side by side. Then every `BUILD` and `REVIEW` line carries a fifth word, the lane, and the loop is:
|
|
75
|
+
|
|
76
|
+
- `BUILD <ticket> <NN> <sha> <lane>` or `REVIEW <ticket> <NN> <sha> <lane>`: run `prompt <lane>` and start the builder or reviewer with `spawn_agent` as above, with that output (it names the lane's folder, and the child works only there). Do not wait for it: run `next` with no lane straight away, to fill the other lanes. `WAIT <lanes>` means every lane that can work is busy: use `wait_agent` for whichever running child finishes first.
|
|
77
|
+
- A builder for lane `<k>` finished: run `next <k>`, with `--after-build <ticket> <sha>` from its `BUILD` line. A reviewer for lane `<k>` finished: save its whole reply into `.feature-flow/state/flow-review-<feature>-<k>.txt` as the loop says, then run `verdict <that file> <k>` and `next <k>`, with no other conductor call between them (on `RETRY`, just `next <k>`).
|
|
78
|
+
- Act on whatever line comes back, for whichever lane it names. The conductor merges a passed ticket before it answers; a merge that conflicts or fails the gate comes back as a new `BUILD` of that ticket, built once more from the feature branch.
|
|
79
|
+
- Run one conductor command at a time, never two at once, and keep passing the latest `TRUST` digest. `DONE`, `STOP` and `HANDOFF` mean what the loop says; after a `STOP`, run no other conductor command while children still run. `reset` removes the lanes and their branches.
|
|
80
|
+
|
|
72
81
|
## Rules while building
|
|
73
82
|
|
|
74
83
|
These apply from `start` on, once a plan exists.
|
|
@@ -8,10 +8,10 @@ from pathlib import Path
|
|
|
8
8
|
from feature_flow import git, state, suggest
|
|
9
9
|
from feature_flow.conductor import Conductor, NoPhase, Stop
|
|
10
10
|
|
|
11
|
-
USAGE = ("usage: python3 scripts/flow.py <feature> start | next | prompt | verdict <file> | reset [NN]\n"
|
|
11
|
+
USAGE = ("usage: python3 scripts/flow.py <feature> start | next [lane] | prompt [lane] | verdict <file> [lane] | reset [NN]\n"
|
|
12
12
|
" python3 scripts/flow.py <feature> plan-prompt <brief> [findings] | plan-review-prompt | plan-accept")
|
|
13
13
|
COMMANDS = ("start", "next", "prompt", "verdict", "plan-prompt", "plan-review-prompt", "plan-accept", "reset")
|
|
14
|
-
ARGS = {"verdict": (3,), "plan-prompt": (3, 4), "reset": (2, 3)}
|
|
14
|
+
ARGS = {"verdict": (3, 4), "plan-prompt": (3, 4), "reset": (2, 3), "next": (2, 3), "prompt": (2, 3)}
|
|
15
15
|
RUNNER_ONLY = ("next", "prompt", "verdict")
|
|
16
16
|
|
|
17
17
|
|
|
@@ -86,7 +86,7 @@ def run(args, scripts, trusted):
|
|
|
86
86
|
try:
|
|
87
87
|
conductor = Conductor(feature, scripts)
|
|
88
88
|
if command == "verdict":
|
|
89
|
-
line = conductor.verdict(args[2])
|
|
89
|
+
line = conductor.verdict(*args[2:])
|
|
90
90
|
elif command == "plan-prompt":
|
|
91
91
|
line = conductor.plan_prompt(args[2], args[3] if len(args) == 4 else None)
|
|
92
92
|
elif command == "plan-review-prompt":
|
|
@@ -94,20 +94,20 @@ def run(args, scripts, trusted):
|
|
|
94
94
|
elif command == "plan-accept":
|
|
95
95
|
line = conductor.plan_accept()
|
|
96
96
|
elif command == "prompt":
|
|
97
|
-
line = conductor.prompt()
|
|
97
|
+
line = conductor.prompt(*args[2:])
|
|
98
98
|
elif command == "reset":
|
|
99
99
|
line = conductor.reset(args[2] if len(args) == 3 else None)
|
|
100
100
|
elif command == "start":
|
|
101
101
|
line = conductor.start()
|
|
102
102
|
else:
|
|
103
|
-
line = conductor.next()
|
|
103
|
+
line = conductor.next(*args[2:])
|
|
104
104
|
except NoPhase as err:
|
|
105
105
|
print(err, file=sys.stderr)
|
|
106
106
|
return 2
|
|
107
107
|
except Stop as stop:
|
|
108
108
|
line = "STOP %s" % stop
|
|
109
109
|
if conductor is not None:
|
|
110
|
-
conductor.log("STOP")
|
|
110
|
+
conductor.log("STOP " + " ".join(str(stop).split())[:200])
|
|
111
111
|
print(line)
|
|
112
112
|
return 1
|
|
113
113
|
out = sys.stdout
|
|
@@ -9,7 +9,7 @@ import sys
|
|
|
9
9
|
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
|
-
from feature_flow import __version__, cli, install, status, suggest, uninstall, view
|
|
12
|
+
from feature_flow import __version__, cli, install, report, status, suggest, uninstall, view
|
|
13
13
|
|
|
14
14
|
USAGE = """\
|
|
15
15
|
usage: feature-flow install [target-repo] [--agent claude|codex|all] [--user] [--private] [--force] [--dry-run]
|
|
@@ -17,15 +17,17 @@ usage: feature-flow install [target-repo] [--agent claude|codex|all] [--user] [-
|
|
|
17
17
|
feature-flow status <feature> [--next | --counts | --check | --mermaid [plain] | --json]
|
|
18
18
|
feature-flow view <feature> [--watch] [--no-open] [--out FILE]
|
|
19
19
|
feature-flow reset <feature> [NN]
|
|
20
|
+
feature-flow report <feature>
|
|
20
21
|
feature-flow --version
|
|
21
22
|
install copies the skill, its roles, scripts and guides into a repo; --user installs them once for every repo, in ~/.claude, ~/.agents and ~/.feature-flow, and writes nothing into a repo. feature-flow install --help says more.
|
|
22
23
|
uninstall removes exactly what install wrote (files you edited are kept); --user removes the personal install in ~/.claude, ~/.agents and ~/.feature-flow.
|
|
23
24
|
status and view read plans/<feature>/ in the current directory, like scripts/flow-status.py and scripts/flow-view.py.
|
|
24
25
|
reset reopens a half-built ticket (or ticket NN), commits that, and clears the run, like scripts/flow.py <feature> reset.
|
|
26
|
+
report prints what a run did (builds, reviews, failed checks, stops) from its log in .feature-flow/state/. Read-only.
|
|
25
27
|
"""
|
|
26
28
|
|
|
27
29
|
|
|
28
|
-
COMMANDS = ("install", "uninstall", "status", "view", "reset", "--version", "--help")
|
|
30
|
+
COMMANDS = ("install", "uninstall", "status", "view", "reset", "report", "--version", "--help")
|
|
29
31
|
|
|
30
32
|
|
|
31
33
|
def main(argv=None):
|
|
@@ -46,6 +48,8 @@ def main(argv=None):
|
|
|
46
48
|
return status.main(rest)
|
|
47
49
|
if command == "view":
|
|
48
50
|
return view.main(rest, here=os.path.join(install.source_root(), "scripts"))
|
|
51
|
+
if command == "report":
|
|
52
|
+
return report.main(rest)
|
|
49
53
|
if command == "reset":
|
|
50
54
|
if not rest or len(rest) > 2:
|
|
51
55
|
sys.stderr.write(USAGE)
|