feature-flow-cli 0.2.0__tar.gz → 0.5.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.2.0/feature_flow_cli.egg-info → feature_flow_cli-0.5.0}/PKG-INFO +45 -14
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/README.md +44 -13
- feature_flow_cli-0.5.0/adapters/codex/feature-flow/SKILL.md +80 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/feature_flow/__init__.py +1 -1
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/feature_flow/cli.py +56 -3
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/feature_flow/codehash.py +10 -2
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/feature_flow/command.py +7 -3
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/feature_flow/conductor.py +113 -19
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/feature_flow/git.py +8 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/feature_flow/install.py +72 -45
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/feature_flow/prompts.py +54 -13
- feature_flow_cli-0.5.0/feature_flow/proof.py +294 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/feature_flow/state.py +48 -6
- feature_flow_cli-0.5.0/feature_flow/uninstall.py +235 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0/feature_flow_cli.egg-info}/PKG-INFO +45 -14
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/feature_flow_cli.egg-info/SOURCES.txt +5 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/guides/build.md +5 -3
- feature_flow_cli-0.5.0/guides/debug.md +32 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/guides/plan.md +9 -2
- feature_flow_cli-0.5.0/guides/review-quality.md +74 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/guides/review.md +1 -15
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/guides/templates/ticket.md +6 -0
- feature_flow_cli-0.5.0/scripts/floor-guard.py +29 -0
- feature_flow_cli-0.5.0/scripts/flow-status.py +29 -0
- feature_flow_cli-0.5.0/scripts/flow-trust.py +367 -0
- feature_flow_cli-0.5.0/scripts/flow-view.py +29 -0
- feature_flow_cli-0.5.0/scripts/flow.py +29 -0
- feature_flow_cli-0.5.0/scripts/gate.py +29 -0
- feature_flow_cli-0.5.0/skills/feature-flow/SKILL.md +80 -0
- feature_flow_cli-0.2.0/adapters/codex/feature-flow/SKILL.md +0 -68
- feature_flow_cli-0.2.0/scripts/floor-guard.py +0 -14
- feature_flow_cli-0.2.0/scripts/flow-status.py +0 -14
- feature_flow_cli-0.2.0/scripts/flow-view.py +0 -14
- feature_flow_cli-0.2.0/scripts/flow.py +0 -14
- feature_flow_cli-0.2.0/scripts/gate.py +0 -14
- feature_flow_cli-0.2.0/skills/feature-flow/SKILL.md +0 -68
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/LICENSE +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/MANIFEST.in +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/adapters/codex/feature-flow/agents/openai.yaml +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/agents/feature-planner.md +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/agents/plan-reviewer.md +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/agents/ticket-builder.md +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/agents/ticket-reviewer.md +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/feature_flow/__main__.py +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/feature_flow/checks.py +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/feature_flow/floorguard.py +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/feature_flow/gate.py +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/feature_flow/proc.py +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/feature_flow/released.py +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/feature_flow/status.py +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/feature_flow/suggest.py +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/feature_flow/tickets.py +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/feature_flow/view.py +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/feature_flow_cli.egg-info/dependency_links.txt +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/feature_flow_cli.egg-info/entry_points.txt +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/feature_flow_cli.egg-info/top_level.txt +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/guides/brief.md +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/guides/plan-review.md +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/guides/show.md +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/guides/templates/commands.md +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/guides/templates/learnings.md +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/guides/templates/map.md +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/guides/templates/spec.md +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/guides/templates/ui-mockup.md +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/install.py +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/install.sh +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/pyproject.toml +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/scripts/flow-view.html +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/setup.cfg +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.0}/skills/architect-review/SKILL.md +0 -0
- {feature_flow_cli-0.2.0 → feature_flow_cli-0.5.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.5.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
|
|
@@ -57,6 +57,8 @@ The installer lists what it wrote in `.feature-flow/installed.txt`, with each fi
|
|
|
57
57
|
|
|
58
58
|
To keep the install out of git, so only your plans and tickets get committed, install with `--private`. It lists the installed files in `.git/info/exclude`, which git reads like `.gitignore` but never commits, and later installs keep that list up to date. If you already committed the install, it prints the `git rm --cached` command that untracks it and keeps the files. Every clone then needs its own install.
|
|
59
59
|
|
|
60
|
+
To remove it, run `feature-flow uninstall` (or `uvx feature-flow-cli uninstall`) in the repo. It removes exactly the files listed in `.feature-flow/installed.txt`, and only while they still hold the bytes the installer wrote: a file you edited is kept and named, and `--force` removes it too. It also drops the `--private` block from `.git/info/exclude`. Your `plans/`, your tickets and the run logs in `.feature-flow/state/` are never touched. `--dry-run` shows what would go. If you installed with `--user`, `feature-flow uninstall --user` removes the personal copy in `~/.claude` and `~/.agents`, and `~/.feature-flow`, the same way (the repo's own install is separate and needs its own `uninstall`).
|
|
61
|
+
|
|
60
62
|
Then, in your repo, in the agent:
|
|
61
63
|
|
|
62
64
|
| | Claude Code | Codex |
|
|
@@ -73,6 +75,11 @@ The same command plans when `plans/csv-export/` does not exist yet and builds wh
|
|
|
73
75
|
|
|
74
76
|
Try the graph first, with no setup and no agent: `bash examples/demo.sh --open`.
|
|
75
77
|
|
|
78
|
+
### Two ways to install
|
|
79
|
+
|
|
80
|
+
- `feature-flow install --user`: once, for every repo. Skills and roles go to `~/.claude` / `~/.agents`, the conductor, guides and package to `~/.feature-flow/`. `FEATURE_FLOW_HOME` moves that folder. No repo files are written.
|
|
81
|
+
- `feature-flow install <repo>`: one repo. When a repo has its own copy, that copy wins over the user install.
|
|
82
|
+
|
|
76
83
|
### What the installer does
|
|
77
84
|
|
|
78
85
|
| | Claude Code (`--agent claude`, the default) | Codex (`--agent codex`) |
|
|
@@ -82,7 +89,7 @@ Try the graph first, with no setup and no agent: `bash examples/demo.sh --open`.
|
|
|
82
89
|
| Scripts go to | `<repo>/scripts/` | `<repo>/scripts/` |
|
|
83
90
|
| Guides, roles and the conductor go to | `<repo>/.feature-flow/` | `<repo>/.feature-flow/` |
|
|
84
91
|
|
|
85
|
-
Next to the `feature-flow` skill it installs `architect-review` and `automation-design`. The installer never deletes anything. A file that already exists and differs is kept and reported; `--force` replaces it. `--dry-run` shows what would change and writes nothing. `CLAUDE_HOME` and `AGENTS_HOME` move the user folders. With `--user` the skill
|
|
92
|
+
Next to the `feature-flow` skill it installs `architect-review` and `automation-design`. The installer never deletes anything. A file that already exists and differs is kept and reported; `--force` replaces it. `--dry-run` shows what would change and writes nothing. `CLAUDE_HOME` and `AGENTS_HOME` move the user folders. With `--user` the skill and roles go to the user folders, and the scripts, guides, conductor and package go to `~/.feature-flow/` (`FEATURE_FLOW_HOME` moves it); nothing is written into a repo.
|
|
86
93
|
|
|
87
94
|
The Claude Code skill is `skills/feature-flow/`. The Codex skill is written by hand in `adapters/codex/feature-flow/`, with an `agents/openai.yaml` that sets `allow_implicit_invocation: false`, so it runs only when you name it. The steps are the same; only the way a subagent is started differs. Your own `AGENTS.md` and `CLAUDE.md` are never touched.
|
|
88
95
|
|
|
@@ -104,16 +111,26 @@ The Claude Code skill is `skills/feature-flow/`. The Codex skill is written by h
|
|
|
104
111
|
plan touched beyond own Status + Answer + appended lines? │
|
|
105
112
|
│ clean │
|
|
106
113
|
▼ │
|
|
107
|
-
|
|
108
|
-
|
|
114
|
+
Done when checks (script): runs every ```check block fail ─┤
|
|
115
|
+
test-first check (script): Test first: yes needs a test-only │
|
|
116
|
+
commit that failed Test before the code fail ─┤
|
|
117
|
+
│ pass │
|
|
118
|
+
▼ │
|
|
119
|
+
spec review: fresh reviewer subagent, sees only the ticket │
|
|
120
|
+
+ diff, re-runs every Done when FAIL ─┤
|
|
121
|
+
│ PASS │
|
|
122
|
+
▼ │
|
|
123
|
+
quality review: a second fresh reviewer subagent FAIL ─┤
|
|
109
124
|
│ PASS ▼
|
|
110
125
|
▼ findings written into the ticket,
|
|
111
|
-
next ticket reopened, built again (3 rounds max)
|
|
126
|
+
next ticket reopened, built again (3 rounds max);
|
|
127
|
+
the builder's prompt then carries
|
|
128
|
+
a root-cause debugging guide
|
|
112
129
|
```
|
|
113
130
|
|
|
114
131
|
`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
132
|
|
|
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 checks that the reviewer changed no tracked file and made no commit. The review
|
|
133
|
+
**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.
|
|
117
134
|
|
|
118
135
|
## The skill and the roles
|
|
119
136
|
|
|
@@ -153,7 +170,7 @@ plans/
|
|
|
153
170
|
|
|
154
171
|
One markdown file per ticket in `plans/<feature>/tasks/NN-slug.md`:
|
|
155
172
|
|
|
156
|
-
|
|
173
|
+
````
|
|
157
174
|
# Add the retry policy to the job runner
|
|
158
175
|
|
|
159
176
|
Type: task task | settle | convert
|
|
@@ -166,9 +183,14 @@ Floor: allow config optional, only when a human decides the guard may let
|
|
|
166
183
|
|
|
167
184
|
## Not in this ticket
|
|
168
185
|
## Done when commands and the results they print
|
|
186
|
+
```check
|
|
187
|
+
$ python3 -m unittest discover -s tests/py
|
|
188
|
+
exit 0
|
|
189
|
+
prints OK
|
|
190
|
+
```
|
|
169
191
|
## Reference
|
|
170
192
|
## Answer Built, Proof, Decisions, Shortcuts taken, Review fixes, For later tickets
|
|
171
|
-
|
|
193
|
+
````
|
|
172
194
|
|
|
173
195
|
- **Order comes from `Blocked by`**, not from numbers. A ticket is *ready* when it is open and every ticket it is blocked by is resolved. Lowest number wins.
|
|
174
196
|
- **Status lives only in the ticket files.** `map.md` never repeats it.
|
|
@@ -204,7 +226,7 @@ One self contained HTML file: no server, no libraries, no network, light and dar
|
|
|
204
226
|
|
|
205
227
|
## Long features and handoff
|
|
206
228
|
|
|
207
|
-
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
|
|
229
|
+
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.
|
|
208
230
|
|
|
209
231
|
- **One owner at a time.** `start` gives the session a token, and every later call must carry it, so two sessions can never drive the same feature. `HANDOFF` and `DONE` release it.
|
|
210
232
|
- **Resuming after a closed session.** If a session ended without `HANDOFF` (you closed it, it crashed, it ran out of budget), the feature is still owned by it, and a new session stops and says so. Once you are sure the old session is gone, answer yes when the new session asks, or start it with `FLOW_TAKEOVER=1`. With `auto` the skill never takes over on its own.
|
|
@@ -217,7 +239,8 @@ The state lives in `.feature-flow/state/` (the state, a log of every step, and t
|
|
|
217
239
|
### Commands and settings
|
|
218
240
|
|
|
219
241
|
```bash
|
|
220
|
-
python3 scripts/flow.py <feature> start|
|
|
242
|
+
python3 scripts/flow.py <feature> start|reset [NN] the conductor (the skill runs it; also plan-prompt, plan-review-prompt, plan-accept)
|
|
243
|
+
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)
|
|
221
244
|
python3 scripts/flow-status.py <feature> table of tickets and which are READY
|
|
222
245
|
python3 scripts/flow-status.py <feature> --next path of the next ready ticket (exit 10 = done, 11 = stuck)
|
|
223
246
|
python3 scripts/flow-status.py <feature> --counts one line of counts
|
|
@@ -229,6 +252,8 @@ python3 scripts/gate.py <feature> run Build, Test and Li
|
|
|
229
252
|
python3 scripts/floor-guard.py <feature> <NN> [base] check a ticket's diff, run from the repo root
|
|
230
253
|
```
|
|
231
254
|
|
|
255
|
+
`next`, `prompt` and `verdict` run directly stop with `STOP run the conductor through scripts/flow-trust.py, as the skill says`. Pass `FLOW_TRUST=new` on a first call, then the digest from the last `TRUST` line. Run by hand like this, nothing checks `flow-trust.py` itself, so it is for trying things out, not a protected run. The skills never run it this way: they start it through a loader line that first checks the file against the sha256 the skill pins, and stops with `flow-trust.py does not match this skill` if it differs (see `## Calling the conductor` in the skill). For a `--user` install the scripts are in `~/.feature-flow/scripts/` (or `$FEATURE_FLOW_HOME/scripts/`).
|
|
256
|
+
|
|
232
257
|
The commands in `commands.md` run through `bash -c` on Linux and macOS and through the system shell (`cmd.exe`) on Windows, so write them for the platform your team uses, or call `python` as in the examples.
|
|
233
258
|
|
|
234
259
|
`flow-status.py` also understands the `.scratch/<feature>/issues/` layout and the statuses `done`, `ready-for-agent`, `ready-for-human` and `closed`: `FLOW_DIR=.scratch FLOW_TICKETS=issues`.
|
|
@@ -240,14 +265,14 @@ The commands in `commands.md` run through `bash -c` on Linux and macOS and throu
|
|
|
240
265
|
| `FLOW_TAKEOVER` | unset | `1` lets `start` take a feature over from a session that is gone |
|
|
241
266
|
| `FLOW_MAX_RETRIES` | `2` | builds or reviews per step before the run stops |
|
|
242
267
|
| `FLOW_MAX_REVIEW_ROUNDS` | `3` | times a ticket may be sent back before the run stops |
|
|
243
|
-
| `FLOW_GATE` | `on` | `off` skips Build, Test and Lint after each ticket |
|
|
268
|
+
| `FLOW_GATE` | `on` | `off` skips Build, Test and Lint after each ticket, and also the Done when and test-first checks |
|
|
244
269
|
| `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` |
|
|
245
270
|
| `FLOW_SMOKE` | the `Smoke:` line of `commands.md` | command run before every ticket; the run stops if it fails |
|
|
246
271
|
| `FLOW_DIR`, `FLOW_TICKETS` | `plans`, `tasks` | where the plans and the ticket folder live |
|
|
247
272
|
| `FLOW_NO_OPEN` | unset | `1` makes `flow-view.py` never open a browser |
|
|
248
273
|
| `FLOW_WATCH_SECONDS` | `3` | how often `flow-view.py --watch` rewrites the page |
|
|
249
274
|
|
|
250
|
-
The run also stops on its own after a number of phases (`BUILD` and `REVIEW` hand-outs) that grows with the ticket count, recomputed at every pick, so a loop of failures cannot go on forever.
|
|
275
|
+
The run also stops on its own after a number of phases (`BUILD` and `REVIEW` hand-outs, the spec review and the quality review each counting as one) that grows with the ticket count, recomputed at every pick, so a loop of failures cannot go on forever.
|
|
251
276
|
|
|
252
277
|
## What is tested
|
|
253
278
|
|
|
@@ -274,7 +299,7 @@ bash tests/run.sh
|
|
|
274
299
|
python3 -m unittest discover -s tests/py
|
|
275
300
|
```
|
|
276
301
|
|
|
277
|
-
Offline and free: no model is called. The suite covers the conductor's every answer and how a run stops, sessions, handoff and takeover, a `.git` the agent cannot write, the gate, the floor guard, plan protection, the prompts and guides, both skills, the installer for both agents, the graph page and its data, and the acceptance harness. The page's layout and replay logic are also tested under Node (`tests/viewer-logic.test.js`, skipped when Node is absent). `.github/workflows/tests.yml` runs it on every push to `main` and every pull request, on Ubuntu and on macOS. A fourth job runs the Python unit tests on Windows, including a fixture drive that takes a two-ticket plan through `python scripts/flow.py f start` and `next` to `BUILD` and then `REVIEW
|
|
302
|
+
Offline and free: no model is called. The suite covers the conductor's every answer and how a run stops, sessions, handoff and takeover, a `.git` the agent cannot write, the gate, the floor guard, plan protection, the prompts and guides, both skills, the installer for both agents, the graph page and its data, the Done when checks, the test-first check, the two reviews and the debugging guide on a send-back, and the acceptance harness. `tests/run.sh` also drives a prepared demo with no model through both checks and both reviews. The page's layout and replay logic are also tested under Node (`tests/viewer-logic.test.js`, skipped when Node is absent). `.github/workflows/tests.yml` runs it on every push to `main` and every pull request, on Ubuntu and on macOS. A fourth job runs the Python unit tests on Windows, including a fixture drive that takes a two-ticket plan through `python scripts/flow.py f start` and `next` to `BUILD` and then `REVIEW`, with `FLOW_TRUSTED=1` set as the runner sets it. `tests/run.sh` itself is a bash harness and does not run on Windows. Windows is tested this way only: no real agent run has been done there.
|
|
278
303
|
|
|
279
304
|
A `package` job builds the wheel and the sdist and runs `tests/package_smoke.py` on Ubuntu (Python 3.9 and the latest), macOS and Windows: it installs the wheel in a fresh virtual environment, runs `feature-flow install`, and checks that the repo gets the same files as from `install.py` in a clone. Run it locally with `python3 -m pip install build && python3 tests/package_smoke.py`.
|
|
280
305
|
|
|
@@ -305,7 +330,13 @@ git tag v0.1.1 && git push origin v0.1.1
|
|
|
305
330
|
|
|
306
331
|
## Caution
|
|
307
332
|
|
|
308
|
-
The builder subagent has edit and shell access and commits after each ticket. Each ticket costs at least two subagents. Build on a branch you can throw away. Ignore build output in `.gitignore`, because the conductor stops if the tree is dirty after a ticket. The gate and the smoke test stop after 30 minutes (`FLOW_GATE_TIMEOUT`). The conductor
|
|
333
|
+
The builder subagent has edit and shell access and commits after each ticket. Each ticket costs at least two subagents. Build on a branch you can throw away. Ignore build output in `.gitignore`, because the conductor stops if the tree is dirty after a ticket. The gate and the smoke test stop after 30 minutes (`FLOW_GATE_TIMEOUT`). The session runs every conductor call through `scripts/flow-trust.py`, which sits beside the conductor it runs (`scripts/` in the repo, or `~/.feature-flow/scripts/` for a `--user` install). The skill pins the runner's sha256 and starts it with a one-line loader that checks and runs those same bytes, so a subagent cannot change the check. The runner prints `TRUST <code>.<state>` before the conductor's output, and the session passes that digest to the next call as `FLOW_TRUST`.
|
|
334
|
+
|
|
335
|
+
**Protected:** edits by the builder, reviewer or planner subagents during a session to feature-flow's own files (its `feature_flow` package, its files in `scripts/` (`flow.py`, `flow-status.py`, `gate.py`, `floor-guard.py`, `flow-view.py`, `flow-trust.py`), the guides, the roles and the skill folders, in the repo and in the install folders) and to the run state in `.feature-flow/state/`. That includes edits made by code the conductor runs during a step, such as tests, `check` commands and git hooks. Any of these makes the runner print a `STOP` instead of `TRUST`, naming the changed files where it can, and the session stops. It also holds between sessions when the `HANDOFF` line carries its digest (`HANDOFF /feature-flow <feature> <digest>`). `Floor: allow flow-edit` on a ticket allows its build to change the flow's code (never the run state, and never `scripts/flow-trust.py`, whose hash the skill pins: change the runner outside the flow, or build it with a conductor from another install, then update the pinned sha in both skills); the runner then prints `FLOW-EDIT <files>`, and a session run without `auto` tells you which flow files changed.
|
|
336
|
+
|
|
337
|
+
**Not protected:** a subagent that changes `python3` on PATH or the system Python, the agent itself, or anything outside the repo and the install folders. The session's very first conductor call: whatever is on disk then is trusted. A background process left running by a step's own commands: it can swap a flow file in the instant between the runner's check and the conductor loading it, then put it back. A session that stops passing its digest, or a resume from a `HANDOFF` line without one (the session warns). The conductor's own code check (`STOP flow code changed while building <ticket>: <files>`) stays as a second line that catches accidental edits; on its own it cannot stop deliberate tampering.
|
|
338
|
+
|
|
339
|
+
The floor guard is pattern matching: it can miss things and it can raise false alarms, and `Floor: allow` is the release valve. The run stops on its own when a ticket stays unresolved, when the smoke test or the gate keeps failing, when a ticket keeps failing review, or when it reaches its phase limit.
|
|
309
340
|
|
|
310
341
|
## License
|
|
311
342
|
|
|
@@ -38,6 +38,8 @@ The installer lists what it wrote in `.feature-flow/installed.txt`, with each fi
|
|
|
38
38
|
|
|
39
39
|
To keep the install out of git, so only your plans and tickets get committed, install with `--private`. It lists the installed files in `.git/info/exclude`, which git reads like `.gitignore` but never commits, and later installs keep that list up to date. If you already committed the install, it prints the `git rm --cached` command that untracks it and keeps the files. Every clone then needs its own install.
|
|
40
40
|
|
|
41
|
+
To remove it, run `feature-flow uninstall` (or `uvx feature-flow-cli uninstall`) in the repo. It removes exactly the files listed in `.feature-flow/installed.txt`, and only while they still hold the bytes the installer wrote: a file you edited is kept and named, and `--force` removes it too. It also drops the `--private` block from `.git/info/exclude`. Your `plans/`, your tickets and the run logs in `.feature-flow/state/` are never touched. `--dry-run` shows what would go. If you installed with `--user`, `feature-flow uninstall --user` removes the personal copy in `~/.claude` and `~/.agents`, and `~/.feature-flow`, the same way (the repo's own install is separate and needs its own `uninstall`).
|
|
42
|
+
|
|
41
43
|
Then, in your repo, in the agent:
|
|
42
44
|
|
|
43
45
|
| | Claude Code | Codex |
|
|
@@ -54,6 +56,11 @@ The same command plans when `plans/csv-export/` does not exist yet and builds wh
|
|
|
54
56
|
|
|
55
57
|
Try the graph first, with no setup and no agent: `bash examples/demo.sh --open`.
|
|
56
58
|
|
|
59
|
+
### Two ways to install
|
|
60
|
+
|
|
61
|
+
- `feature-flow install --user`: once, for every repo. Skills and roles go to `~/.claude` / `~/.agents`, the conductor, guides and package to `~/.feature-flow/`. `FEATURE_FLOW_HOME` moves that folder. No repo files are written.
|
|
62
|
+
- `feature-flow install <repo>`: one repo. When a repo has its own copy, that copy wins over the user install.
|
|
63
|
+
|
|
57
64
|
### What the installer does
|
|
58
65
|
|
|
59
66
|
| | Claude Code (`--agent claude`, the default) | Codex (`--agent codex`) |
|
|
@@ -63,7 +70,7 @@ Try the graph first, with no setup and no agent: `bash examples/demo.sh --open`.
|
|
|
63
70
|
| Scripts go to | `<repo>/scripts/` | `<repo>/scripts/` |
|
|
64
71
|
| Guides, roles and the conductor go to | `<repo>/.feature-flow/` | `<repo>/.feature-flow/` |
|
|
65
72
|
|
|
66
|
-
Next to the `feature-flow` skill it installs `architect-review` and `automation-design`. The installer never deletes anything. A file that already exists and differs is kept and reported; `--force` replaces it. `--dry-run` shows what would change and writes nothing. `CLAUDE_HOME` and `AGENTS_HOME` move the user folders. With `--user` the skill
|
|
73
|
+
Next to the `feature-flow` skill it installs `architect-review` and `automation-design`. The installer never deletes anything. A file that already exists and differs is kept and reported; `--force` replaces it. `--dry-run` shows what would change and writes nothing. `CLAUDE_HOME` and `AGENTS_HOME` move the user folders. With `--user` the skill and roles go to the user folders, and the scripts, guides, conductor and package go to `~/.feature-flow/` (`FEATURE_FLOW_HOME` moves it); nothing is written into a repo.
|
|
67
74
|
|
|
68
75
|
The Claude Code skill is `skills/feature-flow/`. The Codex skill is written by hand in `adapters/codex/feature-flow/`, with an `agents/openai.yaml` that sets `allow_implicit_invocation: false`, so it runs only when you name it. The steps are the same; only the way a subagent is started differs. Your own `AGENTS.md` and `CLAUDE.md` are never touched.
|
|
69
76
|
|
|
@@ -85,16 +92,26 @@ The Claude Code skill is `skills/feature-flow/`. The Codex skill is written by h
|
|
|
85
92
|
plan touched beyond own Status + Answer + appended lines? │
|
|
86
93
|
│ clean │
|
|
87
94
|
▼ │
|
|
88
|
-
|
|
89
|
-
|
|
95
|
+
Done when checks (script): runs every ```check block fail ─┤
|
|
96
|
+
test-first check (script): Test first: yes needs a test-only │
|
|
97
|
+
commit that failed Test before the code fail ─┤
|
|
98
|
+
│ pass │
|
|
99
|
+
▼ │
|
|
100
|
+
spec review: fresh reviewer subagent, sees only the ticket │
|
|
101
|
+
+ diff, re-runs every Done when FAIL ─┤
|
|
102
|
+
│ PASS │
|
|
103
|
+
▼ │
|
|
104
|
+
quality review: a second fresh reviewer subagent FAIL ─┤
|
|
90
105
|
│ PASS ▼
|
|
91
106
|
▼ findings written into the ticket,
|
|
92
|
-
next ticket reopened, built again (3 rounds max)
|
|
107
|
+
next ticket reopened, built again (3 rounds max);
|
|
108
|
+
the builder's prompt then carries
|
|
109
|
+
a root-cause debugging guide
|
|
93
110
|
```
|
|
94
111
|
|
|
95
112
|
`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.
|
|
96
113
|
|
|
97
|
-
**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 checks that the reviewer changed no tracked file and made no commit. The review
|
|
114
|
+
**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.
|
|
98
115
|
|
|
99
116
|
## The skill and the roles
|
|
100
117
|
|
|
@@ -134,7 +151,7 @@ plans/
|
|
|
134
151
|
|
|
135
152
|
One markdown file per ticket in `plans/<feature>/tasks/NN-slug.md`:
|
|
136
153
|
|
|
137
|
-
|
|
154
|
+
````
|
|
138
155
|
# Add the retry policy to the job runner
|
|
139
156
|
|
|
140
157
|
Type: task task | settle | convert
|
|
@@ -147,9 +164,14 @@ Floor: allow config optional, only when a human decides the guard may let
|
|
|
147
164
|
|
|
148
165
|
## Not in this ticket
|
|
149
166
|
## Done when commands and the results they print
|
|
167
|
+
```check
|
|
168
|
+
$ python3 -m unittest discover -s tests/py
|
|
169
|
+
exit 0
|
|
170
|
+
prints OK
|
|
171
|
+
```
|
|
150
172
|
## Reference
|
|
151
173
|
## Answer Built, Proof, Decisions, Shortcuts taken, Review fixes, For later tickets
|
|
152
|
-
|
|
174
|
+
````
|
|
153
175
|
|
|
154
176
|
- **Order comes from `Blocked by`**, not from numbers. A ticket is *ready* when it is open and every ticket it is blocked by is resolved. Lowest number wins.
|
|
155
177
|
- **Status lives only in the ticket files.** `map.md` never repeats it.
|
|
@@ -185,7 +207,7 @@ One self contained HTML file: no server, no libraries, no network, light and dar
|
|
|
185
207
|
|
|
186
208
|
## Long features and handoff
|
|
187
209
|
|
|
188
|
-
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
|
|
210
|
+
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.
|
|
189
211
|
|
|
190
212
|
- **One owner at a time.** `start` gives the session a token, and every later call must carry it, so two sessions can never drive the same feature. `HANDOFF` and `DONE` release it.
|
|
191
213
|
- **Resuming after a closed session.** If a session ended without `HANDOFF` (you closed it, it crashed, it ran out of budget), the feature is still owned by it, and a new session stops and says so. Once you are sure the old session is gone, answer yes when the new session asks, or start it with `FLOW_TAKEOVER=1`. With `auto` the skill never takes over on its own.
|
|
@@ -198,7 +220,8 @@ The state lives in `.feature-flow/state/` (the state, a log of every step, and t
|
|
|
198
220
|
### Commands and settings
|
|
199
221
|
|
|
200
222
|
```bash
|
|
201
|
-
python3 scripts/flow.py <feature> start|
|
|
223
|
+
python3 scripts/flow.py <feature> start|reset [NN] the conductor (the skill runs it; also plan-prompt, plan-review-prompt, plan-accept)
|
|
224
|
+
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)
|
|
202
225
|
python3 scripts/flow-status.py <feature> table of tickets and which are READY
|
|
203
226
|
python3 scripts/flow-status.py <feature> --next path of the next ready ticket (exit 10 = done, 11 = stuck)
|
|
204
227
|
python3 scripts/flow-status.py <feature> --counts one line of counts
|
|
@@ -210,6 +233,8 @@ python3 scripts/gate.py <feature> run Build, Test and Li
|
|
|
210
233
|
python3 scripts/floor-guard.py <feature> <NN> [base] check a ticket's diff, run from the repo root
|
|
211
234
|
```
|
|
212
235
|
|
|
236
|
+
`next`, `prompt` and `verdict` run directly stop with `STOP run the conductor through scripts/flow-trust.py, as the skill says`. Pass `FLOW_TRUST=new` on a first call, then the digest from the last `TRUST` line. Run by hand like this, nothing checks `flow-trust.py` itself, so it is for trying things out, not a protected run. The skills never run it this way: they start it through a loader line that first checks the file against the sha256 the skill pins, and stops with `flow-trust.py does not match this skill` if it differs (see `## Calling the conductor` in the skill). For a `--user` install the scripts are in `~/.feature-flow/scripts/` (or `$FEATURE_FLOW_HOME/scripts/`).
|
|
237
|
+
|
|
213
238
|
The commands in `commands.md` run through `bash -c` on Linux and macOS and through the system shell (`cmd.exe`) on Windows, so write them for the platform your team uses, or call `python` as in the examples.
|
|
214
239
|
|
|
215
240
|
`flow-status.py` also understands the `.scratch/<feature>/issues/` layout and the statuses `done`, `ready-for-agent`, `ready-for-human` and `closed`: `FLOW_DIR=.scratch FLOW_TICKETS=issues`.
|
|
@@ -221,14 +246,14 @@ The commands in `commands.md` run through `bash -c` on Linux and macOS and throu
|
|
|
221
246
|
| `FLOW_TAKEOVER` | unset | `1` lets `start` take a feature over from a session that is gone |
|
|
222
247
|
| `FLOW_MAX_RETRIES` | `2` | builds or reviews per step before the run stops |
|
|
223
248
|
| `FLOW_MAX_REVIEW_ROUNDS` | `3` | times a ticket may be sent back before the run stops |
|
|
224
|
-
| `FLOW_GATE` | `on` | `off` skips Build, Test and Lint after each ticket |
|
|
249
|
+
| `FLOW_GATE` | `on` | `off` skips Build, Test and Lint after each ticket, and also the Done when and test-first checks |
|
|
225
250
|
| `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` |
|
|
226
251
|
| `FLOW_SMOKE` | the `Smoke:` line of `commands.md` | command run before every ticket; the run stops if it fails |
|
|
227
252
|
| `FLOW_DIR`, `FLOW_TICKETS` | `plans`, `tasks` | where the plans and the ticket folder live |
|
|
228
253
|
| `FLOW_NO_OPEN` | unset | `1` makes `flow-view.py` never open a browser |
|
|
229
254
|
| `FLOW_WATCH_SECONDS` | `3` | how often `flow-view.py --watch` rewrites the page |
|
|
230
255
|
|
|
231
|
-
The run also stops on its own after a number of phases (`BUILD` and `REVIEW` hand-outs) that grows with the ticket count, recomputed at every pick, so a loop of failures cannot go on forever.
|
|
256
|
+
The run also stops on its own after a number of phases (`BUILD` and `REVIEW` hand-outs, the spec review and the quality review each counting as one) that grows with the ticket count, recomputed at every pick, so a loop of failures cannot go on forever.
|
|
232
257
|
|
|
233
258
|
## What is tested
|
|
234
259
|
|
|
@@ -255,7 +280,7 @@ bash tests/run.sh
|
|
|
255
280
|
python3 -m unittest discover -s tests/py
|
|
256
281
|
```
|
|
257
282
|
|
|
258
|
-
Offline and free: no model is called. The suite covers the conductor's every answer and how a run stops, sessions, handoff and takeover, a `.git` the agent cannot write, the gate, the floor guard, plan protection, the prompts and guides, both skills, the installer for both agents, the graph page and its data, and the acceptance harness. The page's layout and replay logic are also tested under Node (`tests/viewer-logic.test.js`, skipped when Node is absent). `.github/workflows/tests.yml` runs it on every push to `main` and every pull request, on Ubuntu and on macOS. A fourth job runs the Python unit tests on Windows, including a fixture drive that takes a two-ticket plan through `python scripts/flow.py f start` and `next` to `BUILD` and then `REVIEW
|
|
283
|
+
Offline and free: no model is called. The suite covers the conductor's every answer and how a run stops, sessions, handoff and takeover, a `.git` the agent cannot write, the gate, the floor guard, plan protection, the prompts and guides, both skills, the installer for both agents, the graph page and its data, the Done when checks, the test-first check, the two reviews and the debugging guide on a send-back, and the acceptance harness. `tests/run.sh` also drives a prepared demo with no model through both checks and both reviews. The page's layout and replay logic are also tested under Node (`tests/viewer-logic.test.js`, skipped when Node is absent). `.github/workflows/tests.yml` runs it on every push to `main` and every pull request, on Ubuntu and on macOS. A fourth job runs the Python unit tests on Windows, including a fixture drive that takes a two-ticket plan through `python scripts/flow.py f start` and `next` to `BUILD` and then `REVIEW`, with `FLOW_TRUSTED=1` set as the runner sets it. `tests/run.sh` itself is a bash harness and does not run on Windows. Windows is tested this way only: no real agent run has been done there.
|
|
259
284
|
|
|
260
285
|
A `package` job builds the wheel and the sdist and runs `tests/package_smoke.py` on Ubuntu (Python 3.9 and the latest), macOS and Windows: it installs the wheel in a fresh virtual environment, runs `feature-flow install`, and checks that the repo gets the same files as from `install.py` in a clone. Run it locally with `python3 -m pip install build && python3 tests/package_smoke.py`.
|
|
261
286
|
|
|
@@ -286,7 +311,13 @@ git tag v0.1.1 && git push origin v0.1.1
|
|
|
286
311
|
|
|
287
312
|
## Caution
|
|
288
313
|
|
|
289
|
-
The builder subagent has edit and shell access and commits after each ticket. Each ticket costs at least two subagents. Build on a branch you can throw away. Ignore build output in `.gitignore`, because the conductor stops if the tree is dirty after a ticket. The gate and the smoke test stop after 30 minutes (`FLOW_GATE_TIMEOUT`). The conductor
|
|
314
|
+
The builder subagent has edit and shell access and commits after each ticket. Each ticket costs at least two subagents. Build on a branch you can throw away. Ignore build output in `.gitignore`, because the conductor stops if the tree is dirty after a ticket. The gate and the smoke test stop after 30 minutes (`FLOW_GATE_TIMEOUT`). The session runs every conductor call through `scripts/flow-trust.py`, which sits beside the conductor it runs (`scripts/` in the repo, or `~/.feature-flow/scripts/` for a `--user` install). The skill pins the runner's sha256 and starts it with a one-line loader that checks and runs those same bytes, so a subagent cannot change the check. The runner prints `TRUST <code>.<state>` before the conductor's output, and the session passes that digest to the next call as `FLOW_TRUST`.
|
|
315
|
+
|
|
316
|
+
**Protected:** edits by the builder, reviewer or planner subagents during a session to feature-flow's own files (its `feature_flow` package, its files in `scripts/` (`flow.py`, `flow-status.py`, `gate.py`, `floor-guard.py`, `flow-view.py`, `flow-trust.py`), the guides, the roles and the skill folders, in the repo and in the install folders) and to the run state in `.feature-flow/state/`. That includes edits made by code the conductor runs during a step, such as tests, `check` commands and git hooks. Any of these makes the runner print a `STOP` instead of `TRUST`, naming the changed files where it can, and the session stops. It also holds between sessions when the `HANDOFF` line carries its digest (`HANDOFF /feature-flow <feature> <digest>`). `Floor: allow flow-edit` on a ticket allows its build to change the flow's code (never the run state, and never `scripts/flow-trust.py`, whose hash the skill pins: change the runner outside the flow, or build it with a conductor from another install, then update the pinned sha in both skills); the runner then prints `FLOW-EDIT <files>`, and a session run without `auto` tells you which flow files changed.
|
|
317
|
+
|
|
318
|
+
**Not protected:** a subagent that changes `python3` on PATH or the system Python, the agent itself, or anything outside the repo and the install folders. The session's very first conductor call: whatever is on disk then is trusted. A background process left running by a step's own commands: it can swap a flow file in the instant between the runner's check and the conductor loading it, then put it back. A session that stops passing its digest, or a resume from a `HANDOFF` line without one (the session warns). The conductor's own code check (`STOP flow code changed while building <ticket>: <files>`) stays as a second line that catches accidental edits; on its own it cannot stop deliberate tampering.
|
|
319
|
+
|
|
320
|
+
The floor guard is pattern matching: it can miss things and it can raise false alarms, and `Floor: allow` is the release valve. The run stops on its own when a ticket stays unresolved, when the smoke test or the gate keeps failing, when a ticket keeps failing review, or when it reaches its phase limit.
|
|
290
321
|
|
|
291
322
|
## License
|
|
292
323
|
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: feature-flow
|
|
3
|
+
description: "Use to plan a feature (from the conversation so far, with a fresh planner and plan reviewer subagent) or build its tickets, one fresh builder and one fresh reviewer subagent per ticket, with scripts/flow.py deciding every step. Also draws the ticket graph with show."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`<arguments>` below stands for the text the user typed after `$feature-flow`.
|
|
7
|
+
|
|
8
|
+
Plan or build the feature in `<arguments>`.
|
|
9
|
+
|
|
10
|
+
The first word is the **feature**. `show` means draw the graph. `auto` means ask nothing and go. If no feature was given and the conversation so far describes work to build, plan it (below) and propose the feature's name in the brief. If no feature was given otherwise, list `plans/*/` and ask which one.
|
|
11
|
+
|
|
12
|
+
The conductor is `python3 scripts/flow.py <feature> <command>`. If `scripts/flow.py` exists in the repo and the feature-flow package is beside it (`.feature-flow/feature_flow/`, or `feature_flow/` in a feature-flow checkout), use it (the repo's own install wins; an unrelated `scripts/flow.py` does not count); otherwise use `python3 "${FEATURE_FLOW_HOME:-$HOME/.feature-flow}/scripts/flow.py"` (`~/.feature-flow/scripts/flow.py`). Wherever this skill or a message names `scripts/flow.py`, `scripts/flow-status.py` or `scripts/flow-view.py`, it means the same folder, and the guides are then in that folder's sibling `guides/` (`~/.feature-flow/guides/`, else `.feature-flow/guides/` in the repo). Run the conductor with the working directory in the repo: state and plans stay there. It decides the order, runs the gate and the floor guard, and keeps its state in `.feature-flow/state/`, a folder git ignores. You ask it, and you do what it says. The guides it uses are in `.feature-flow/guides/` (in a feature-flow checkout, `guides/`). Read the project's `AGENTS.md` for its conventions.
|
|
13
|
+
|
|
14
|
+
## Calling the conductor
|
|
15
|
+
|
|
16
|
+
Every conductor command (`start`, `next`, `prompt`, `verdict`, `reset`, `plan-prompt`, `plan-review-prompt`, `plan-accept`) runs through `flow-trust.py`, never `flow.py` itself. `<S>` below is the folder of the `flow.py` picked above: `scripts` for the repo's own install, else `"${FEATURE_FLOW_HOME:-$HOME/.feature-flow}/scripts"`. `flow-trust.py` is started by this loader line, which checks the file against the sha256 pinned here and runs those same bytes. Copy it exactly, replacing only `<S>`:
|
|
17
|
+
|
|
18
|
+
python3 -I -c "import hashlib,sys;b=open(sys.argv[1],'rb').read().replace(b'\r\n',b'\n');sys.exit(exec(compile(b,'flow-trust','exec')) if hashlib.sha256(b).hexdigest()==sys.argv[2] else 'flow-trust.py does not match this skill')" <S>/flow-trust.py ec4e2430b3ed4dae4fa5d9190c3e1726294a0f854bf92584c0c756a07b9029d9
|
|
19
|
+
|
|
20
|
+
A full conductor command, here `next` (any other command and its arguments go where `next` is):
|
|
21
|
+
|
|
22
|
+
FLOW_TRUST=<digest> FLOW_SESSION=<token> FLOW_INVOKE='$feature-flow' python3 -I -c "import hashlib,sys;b=open(sys.argv[1],'rb').read().replace(b'\r\n',b'\n');sys.exit(exec(compile(b,'flow-trust','exec')) if hashlib.sha256(b).hexdigest()==sys.argv[2] else 'flow-trust.py does not match this skill')" <S>/flow-trust.py ec4e2430b3ed4dae4fa5d9190c3e1726294a0f854bf92584c0c756a07b9029d9 <feature> next
|
|
23
|
+
|
|
24
|
+
- The first line of the output is `TRUST <digest>`. Keep that digest and pass it as `FLOW_TRUST` on the next conductor call. Treat the rest as the conductor's output and act on it as below (for `prompt`, `plan-prompt` and `plan-review-prompt`, the child agent's prompt is everything after the `TRUST` line). Use `FLOW_TRUST=new` only on this session's first conductor call, or when resuming from an old-style `HANDOFF` line (below).
|
|
25
|
+
- If you no longer have the last `TRUST` digest, stop and say so. Never guess it, and never use `new` in its place.
|
|
26
|
+
- On the `next` right after a `BUILD <ticket> <NN> <sha>`, add `--after-build <ticket> <sha>` before the feature: `... <S>/flow-trust.py ec4e2430b3ed4dae4fa5d9190c3e1726294a0f854bf92584c0c756a07b9029d9 --after-build <ticket> <sha> <feature> next`. If the output has a `FLOW-EDIT <paths>` line after `TRUST`, the build changed feature-flow's own files and its ticket allows that: carry on; without `auto`, first tell the user which flow files changed.
|
|
27
|
+
- If the first line is `STOP` (from `flow-trust.py`), or the loader prints `flow-trust.py does not match this skill` (say to reinstall feature-flow: the skill and `flow-trust.py` do not match), report it, show the `python3 <S>/flow-status.py <feature>` table, and stop. A `STOP` first line may be followed by the conductor's own output: never act on that. Never fix it and carry on.
|
|
28
|
+
- A `HANDOFF` line from `flow-trust.py` has a third field after the invocation and the feature: the digest. When the feature is followed by a `<16 hex>.<16 hex>` word, the user is resuming from a `HANDOFF` line and that word is the digest: pass it as `FLOW_TRUST` on `start`. When a resumed session gets no digest (an old-style `HANDOFF` line), use `FLOW_TRUST=new` and warn the user that the flow's files could not be checked since the last session.
|
|
29
|
+
|
|
30
|
+
## Show
|
|
31
|
+
|
|
32
|
+
With `show`, follow `.feature-flow/guides/show.md` for the feature and stop.
|
|
33
|
+
|
|
34
|
+
## Plan
|
|
35
|
+
|
|
36
|
+
When `plans/<feature>/` does not exist, or you are planning from the conversation, follow `.feature-flow/guides/brief.md` with the user. You write the brief and ask for both yeses (unless `auto`); a child agent plans, another reviews, and you never draft the plan yourself. Once the brief names the feature, run `start` through `flow-trust.py` (see Calling the conductor) with `FLOW_INVOKE='$feature-flow'`, and check that it prints `PLAN` after the `TRUST` line, and check that `feature-planner.md` and `plan-reviewer.md` exist in `.agents/flow-roles/` or in `${FEATURE_FLOW_HOME:-$HOME/.feature-flow}/agents/` (see below).
|
|
37
|
+
|
|
38
|
+
- `plan-prompt <brief>`: start the planner with `spawn_agent(task_name="feature_planner", fork_turns="none", message=...)`. The message is "Work only in <repo>." followed by the whole output. Get its final reply with `wait_agent`. A child cannot be limited to the draft folder, so it works from its instructions, and `plan-accept` is the only way its draft reaches `plans/`. If web search is off in this Codex, the planner plans from the codebase and says so.
|
|
39
|
+
- `plan-review-prompt`: start a new reviewer the same way, with `task_name="plan_reviewer"` and `fork_turns="none"`, never with the planner's reply. Get its final reply with `wait_agent` and save it with the shell into `.feature-flow/state/plan-review-<feature>.txt`.
|
|
40
|
+
- `plan-accept`: writes `plans/<feature>/` from the draft, only after the second yes.
|
|
41
|
+
|
|
42
|
+
Then run `python3 scripts/flow-status.py <feature> --check` and list the files `plan-accept` wrote. The second yes also approves committing the plan, so commit the folder `plan-accept` printed (`OK <folder>`, `plans/<feature>` unless `FLOW_DIR` is set) and nothing else: `git add -- <folder> && git commit -m "docs(<feature>): plan" -- <folder>`. If the commit fails, report why and stop. Then go straight on to building it below, unless the user asked to stop after planning.
|
|
43
|
+
|
|
44
|
+
To add tickets to a plan that exists, edit it by hand following the ticket rules in `.feature-flow/guides/plan.md`.
|
|
45
|
+
|
|
46
|
+
## Before building
|
|
47
|
+
|
|
48
|
+
With a plan present, check these before `start`, and stop at the first that fails:
|
|
49
|
+
|
|
50
|
+
- `git status --porcelain` is empty, apart from untracked Python bytecode (`__pycache__/`, `*.pyc`) and files listed in `.feature-flow/installed.txt` and that list itself: those are feature-flow's own install or upgrade, so never stop for them (`next` still stops if one of them was edited). Suggest committing them (`git add --pathspec-from-file=.feature-flow/installed.txt && git commit -m "chore: install feature-flow"`) and carry on.
|
|
51
|
+
- `python3 scripts/flow-status.py <feature> --check` prints `OK`.
|
|
52
|
+
- `ticket-builder.md` and `ticket-reviewer.md` are in `.agents/flow-roles/` in the repo or in `${FEATURE_FLOW_HOME:-$HOME/.feature-flow}/agents/` (`~/.feature-flow/agents/` by default). If not, say to run `uvx feature-flow-cli install . --agent codex` in this repo, or `uvx feature-flow-cli install --user --agent codex` (once for every repo) (or `python3 install.py <repo> --agent codex` from a feature-flow clone).
|
|
53
|
+
|
|
54
|
+
Then run `start` through `flow-trust.py` with `FLOW_INVOKE='$feature-flow'`. After the `TRUST` line it prints `OK <token>`. Keep the token and put `FLOW_SESSION=<token>` in front of **every** later conductor command, with `FLOW_INVOKE='$feature-flow'` and the last `FLOW_TRUST`. The conductor keeps its state in `.feature-flow/state/`, a git-ignored folder in the repo, so it never needs to write `.git`. If it prints `STOP cannot write the flow state`, tell the user this session must be allowed to write that folder.
|
|
55
|
+
|
|
56
|
+
If `start` prints `STOP` naming another owner, another session may still be working this feature. Ask the user whether that session is closed. Only on a clear yes, run `start` once more with `FLOW_TAKEOVER=1`. In `auto` mode, never take over: report and stop.
|
|
57
|
+
|
|
58
|
+
If the user says the flow is stuck on a ticket or asks for a reset, run `reset` (or `reset <NN>` to redo one ticket): it reopens a half-built ticket, commits that, and clears the run, then run `start` again. It needs `FLOW_TAKEOVER=1` while a session owns the feature, on the same clear yes. Never reset on your own.
|
|
59
|
+
|
|
60
|
+
Say what will happen: for each ticket, a builder subagent and then a reviewer subagent. After every few tickets (`FLOW_TICKETS_PER_SESSION`, default 4) you hand off to a new session. Ask for a yes, unless `auto` was given or you came straight from planning: the plan's yes already covered the build.
|
|
61
|
+
|
|
62
|
+
## The loop
|
|
63
|
+
|
|
64
|
+
Run `next`, act on its one line after `TRUST` (and any `FLOW-EDIT` line), and repeat:
|
|
65
|
+
|
|
66
|
+
- `BUILD <ticket> <NN> <sha>`: run `prompt`. Start the builder with `spawn_agent(task_name="ticket_builder_<NN>_<k>", fork_turns="none", message=...)`. The message is "Work only in <repo>." followed by the whole `prompt` output, which already starts with the builder role. Use a new `task_name` for every spawn: keep a count per ticket in this session, and `<k>` is the number of builders already started for this ticket, plus 1 (1 for the first, then up each time `next` sends the ticket back to a builder). Get its final reply with `wait_agent`, then run `next` with `--after-build <ticket> <sha>` from that `BUILD` line.
|
|
67
|
+
- `REVIEW <ticket> <NN> <sha>`: run `prompt`. Start a new reviewer the same way, with `fork_turns="none"` so it never sees the builder's context, and a unique `task_name` per spawn: `ticket_reviewer_<NN>_<pass>_<k>`, where `<pass>` is `spec` or `quality` by the pass the prompt names (`(spec pass)` or `(quality pass)`; `review` if it names none) and `<k>` is the number of reviews already started for this ticket in this session, plus 1 (1 for the first). Count every review, also one that ended in `RETRY` or a failed review followed by a rebuild, so a repeated pass never reuses a name. Get its final reply with `wait_agent`. A child cannot be made read-only, so the reviewer works from its instructions; the conductor catches any edit it makes. Save the whole reply with the shell: `cat > .feature-flow/state/flow-review-<feature>.txt <<'EOF'` ... `EOF`. Then run `verdict .feature-flow/state/flow-review-<feature>.txt` and `next`. If `verdict` prints `RETRY`, just run `next`.
|
|
68
|
+
- `DONE <summary>`: report it and suggest `$feature-flow <feature> show`.
|
|
69
|
+
- `STOP <reason>`: report the reason, run `python3 scripts/flow-status.py <feature>`, show the table, and stop.
|
|
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
|
+
|
|
72
|
+
## Rules while building
|
|
73
|
+
|
|
74
|
+
These apply from `start` on, once a plan exists.
|
|
75
|
+
|
|
76
|
+
- Never run the gate, the floor guard or a review yourself. The conductor runs the checks, and the reviewer subagent reviews.
|
|
77
|
+
- Never edit a ticket, the plan or the code, and never commit, apart from the approved plan above. The builder does that.
|
|
78
|
+
- Never skip a step, reorder steps, or decide the next step yourself. Only `next` decides.
|
|
79
|
+
- Never read a ticket's `## Answer` into the reviewer's prompt. `prompt` already holds everything it needs.
|
|
80
|
+
- Tell the user one short line per phase (`01 built`, `01 review: PASS`), not the subagents' reports.
|
|
@@ -1,19 +1,69 @@
|
|
|
1
1
|
"""python3 scripts/flow.py <feature> <command>: the conductor's command line."""
|
|
2
2
|
|
|
3
|
+
import os
|
|
3
4
|
import sys
|
|
5
|
+
import traceback
|
|
4
6
|
from pathlib import Path
|
|
5
7
|
|
|
6
|
-
from feature_flow import suggest
|
|
8
|
+
from feature_flow import git, state, suggest
|
|
7
9
|
from feature_flow.conductor import Conductor, NoPhase, Stop
|
|
8
10
|
|
|
9
11
|
USAGE = ("usage: python3 scripts/flow.py <feature> start | next | prompt | verdict <file> | reset [NN]\n"
|
|
10
12
|
" python3 scripts/flow.py <feature> plan-prompt <brief> [findings] | plan-review-prompt | plan-accept")
|
|
11
13
|
COMMANDS = ("start", "next", "prompt", "verdict", "plan-prompt", "plan-review-prompt", "plan-accept", "reset")
|
|
12
14
|
ARGS = {"verdict": (3,), "plan-prompt": (3, 4), "reset": (2, 3)}
|
|
15
|
+
RUNNER_ONLY = ("next", "prompt", "verdict")
|
|
13
16
|
|
|
14
17
|
|
|
15
18
|
def main(argv=None, scripts=None):
|
|
16
|
-
|
|
19
|
+
"""With FLOW_TRUSTED=1 (set by scripts/flow-trust.py) the last stderr line is always
|
|
20
|
+
`flow-state <state sha256 or none> <findings sha256 or none>`, for the bytes the conductor left."""
|
|
21
|
+
trusted = os.environ.get("FLOW_TRUSTED") == "1"
|
|
22
|
+
state.SEEN.clear()
|
|
23
|
+
try:
|
|
24
|
+
args = list(sys.argv[1:] if argv is None else argv)
|
|
25
|
+
if trusted:
|
|
26
|
+
peek(args)
|
|
27
|
+
return run(args, scripts, trusted)
|
|
28
|
+
except SystemExit as err: # say what the interpreter would say, but before the report
|
|
29
|
+
if not trusted:
|
|
30
|
+
raise
|
|
31
|
+
if err.code is None or isinstance(err.code, int):
|
|
32
|
+
return err.code or 0
|
|
33
|
+
print(err.code, file=sys.stderr)
|
|
34
|
+
return 1
|
|
35
|
+
except BaseException: # the traceback goes before the report, which stays the last stderr line
|
|
36
|
+
if not trusted:
|
|
37
|
+
raise
|
|
38
|
+
traceback.print_exc()
|
|
39
|
+
return 1
|
|
40
|
+
finally:
|
|
41
|
+
if trusted:
|
|
42
|
+
sys.stdout.flush()
|
|
43
|
+
sys.stderr.write("flow-state %s\n" % state.reported())
|
|
44
|
+
sys.stderr.flush()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def peek(args):
|
|
48
|
+
"""Read the feature's state and findings files before anything can return early, so the report
|
|
49
|
+
names the files that are there even after a usage error. Creates nothing."""
|
|
50
|
+
if not args or not plain(args[0]):
|
|
51
|
+
return
|
|
52
|
+
feature = args[0]
|
|
53
|
+
top = git.toplevel()
|
|
54
|
+
if top is not None:
|
|
55
|
+
folder = top / state.STATE_DIR
|
|
56
|
+
state.read_bytes(state.state_path(folder, feature), "state")
|
|
57
|
+
state.read_bytes(state.file_path(folder, feature, "findings"), "findings")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def plain(feature):
|
|
61
|
+
"""True when feature is a plain folder name: not empty, no leading -, no path parts."""
|
|
62
|
+
return (bool(feature) and not feature.startswith("-") and feature not in (".", "..")
|
|
63
|
+
and not any(c in feature for c in "/\\:"))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def run(args, scripts, trusted):
|
|
17
67
|
if len(args) < 2 or args[1] not in COMMANDS or not args[0] or args[0].startswith("-"):
|
|
18
68
|
print(USAGE, file=sys.stderr)
|
|
19
69
|
if len(args) >= 2 and args[0] in COMMANDS:
|
|
@@ -22,12 +72,15 @@ def main(argv=None, scripts=None):
|
|
|
22
72
|
sys.stderr.write(suggest.hint(args[1], COMMANDS))
|
|
23
73
|
return 2
|
|
24
74
|
feature, command = args[0], args[1]
|
|
25
|
-
if
|
|
75
|
+
if not plain(feature):
|
|
26
76
|
print("the feature must be a plain folder name, not %s" % feature, file=sys.stderr)
|
|
27
77
|
return 2
|
|
28
78
|
if len(args) not in ARGS.get(command, (2,)):
|
|
29
79
|
print(USAGE, file=sys.stderr)
|
|
30
80
|
return 2
|
|
81
|
+
if command in RUNNER_ONLY and not trusted:
|
|
82
|
+
print("STOP run the conductor through scripts/flow-trust.py, as the skill says")
|
|
83
|
+
return 1
|
|
31
84
|
scripts = Path(scripts) if scripts else Path.cwd() / "scripts"
|
|
32
85
|
conductor = None
|
|
33
86
|
try:
|
|
@@ -7,17 +7,25 @@ from pathlib import Path
|
|
|
7
7
|
|
|
8
8
|
from feature_flow import git, prompts
|
|
9
9
|
|
|
10
|
+
# feature-flow's own scripts; any other .py file in the same folder is the repo's own code
|
|
11
|
+
SCRIPTS = ("flow.py", "flow-status.py", "gate.py", "floor-guard.py", "flow-view.py", "flow-trust.py")
|
|
12
|
+
|
|
10
13
|
|
|
11
14
|
def _files(scripts):
|
|
12
15
|
package = Path(__file__).resolve().parent
|
|
13
16
|
found = [p for p in package.rglob("*.py") if "__pycache__" not in p.parts]
|
|
14
|
-
|
|
15
|
-
for
|
|
17
|
+
folder = Path(scripts).resolve()
|
|
18
|
+
found += [folder / name for name in SCRIPTS if (folder / name).is_file()]
|
|
19
|
+
for phase in ("build", "review", "review-quality"):
|
|
16
20
|
for folder, names in (("agents", prompts.ROLES), ("guides", prompts.GUIDES)):
|
|
17
21
|
try:
|
|
18
22
|
found.append(prompts.find_file(scripts, folder, names[phase]).resolve())
|
|
19
23
|
except FileNotFoundError:
|
|
20
24
|
pass
|
|
25
|
+
try:
|
|
26
|
+
found.append(prompts.find_file(scripts, "guides", "debug.md").resolve())
|
|
27
|
+
except FileNotFoundError:
|
|
28
|
+
pass
|
|
21
29
|
return sorted(set(found))
|
|
22
30
|
|
|
23
31
|
|
|
@@ -9,21 +9,23 @@ import sys
|
|
|
9
9
|
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
|
-
from feature_flow import __version__, cli, install, status, suggest, view
|
|
12
|
+
from feature_flow import __version__, cli, install, 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]
|
|
16
|
+
feature-flow uninstall [target-repo] [--user] [--force] [--dry-run]
|
|
16
17
|
feature-flow status <feature> [--next | --counts | --check | --mermaid [plain] | --json]
|
|
17
18
|
feature-flow view <feature> [--watch] [--no-open] [--out FILE]
|
|
18
19
|
feature-flow reset <feature> [NN]
|
|
19
20
|
feature-flow --version
|
|
20
|
-
install copies the skill, its roles, scripts and guides into a repo; feature-flow install --help says more.
|
|
21
|
+
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
|
+
uninstall removes exactly what install wrote (files you edited are kept); --user removes the personal install in ~/.claude, ~/.agents and ~/.feature-flow.
|
|
21
23
|
status and view read plans/<feature>/ in the current directory, like scripts/flow-status.py and scripts/flow-view.py.
|
|
22
24
|
reset reopens a half-built ticket (or ticket NN), commits that, and clears the run, like scripts/flow.py <feature> reset.
|
|
23
25
|
"""
|
|
24
26
|
|
|
25
27
|
|
|
26
|
-
COMMANDS = ("install", "status", "view", "reset", "--version", "--help")
|
|
28
|
+
COMMANDS = ("install", "uninstall", "status", "view", "reset", "--version", "--help")
|
|
27
29
|
|
|
28
30
|
|
|
29
31
|
def main(argv=None):
|
|
@@ -38,6 +40,8 @@ def main(argv=None):
|
|
|
38
40
|
return 0
|
|
39
41
|
if command == "install":
|
|
40
42
|
return install.main(install.source_root(), rest)
|
|
43
|
+
if command == "uninstall":
|
|
44
|
+
return uninstall.main(rest)
|
|
41
45
|
if command == "status":
|
|
42
46
|
return status.main(rest)
|
|
43
47
|
if command == "view":
|