feature-flow-cli 0.7.0__tar.gz → 0.8.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.8.0}/PKG-INFO +23 -6
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/README.md +22 -5
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/feature_flow/__init__.py +1 -1
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/feature_flow/cli.py +1 -1
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/feature_flow/command.py +6 -2
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/feature_flow/conductor.py +1 -1
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/feature_flow/floorguard.py +11 -2
- feature_flow_cli-0.8.0/feature_flow/report.py +89 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/feature_flow/status.py +53 -9
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0/feature_flow_cli.egg-info}/PKG-INFO +23 -6
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/feature_flow_cli.egg-info/SOURCES.txt +1 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/guides/plan-review.md +1 -1
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/guides/plan.md +2 -2
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/guides/templates/ticket.md +1 -1
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/LICENSE +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/MANIFEST.in +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/adapters/codex/feature-flow/SKILL.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/adapters/codex/feature-flow/agents/openai.yaml +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/agents/feature-planner.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/agents/plan-reviewer.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/agents/ticket-builder.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/agents/ticket-reviewer.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/feature_flow/__main__.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/feature_flow/checks.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/feature_flow/codehash.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/feature_flow/gate.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/feature_flow/git.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/feature_flow/install.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/feature_flow/proc.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/feature_flow/prompts.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/feature_flow/proof.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/feature_flow/released.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/feature_flow/state.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/feature_flow/suggest.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/feature_flow/tickets.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/feature_flow/uninstall.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/feature_flow/view.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/feature_flow_cli.egg-info/dependency_links.txt +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/feature_flow_cli.egg-info/entry_points.txt +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/feature_flow_cli.egg-info/top_level.txt +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/guides/brief.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/guides/build.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/guides/debug.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/guides/review-quality.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/guides/review-single.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/guides/review.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/guides/show.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/guides/templates/commands.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/guides/templates/learnings.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/guides/templates/map.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/guides/templates/spec.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/guides/templates/ui-mockup.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/install.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/install.sh +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/pyproject.toml +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/scripts/floor-guard.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/scripts/flow-status.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/scripts/flow-trust.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/scripts/flow-view.html +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/scripts/flow-view.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/scripts/flow.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/scripts/gate.py +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/setup.cfg +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/skills/architect-review/SKILL.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/skills/automation-design/SKILL.md +0 -0
- {feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/skills/feature-flow/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.8.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
|
|
|
@@ -203,14 +203,18 @@ prints OK
|
|
|
203
203
|
- **The Answers are the handoff between subagents.** The next builder reads the Answers of the tickets it depends on.
|
|
204
204
|
- `settle` tickets record a decision. `convert` tickets migrate something and must be safe to run twice.
|
|
205
205
|
- **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.
|
|
206
|
+
- `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
207
|
- 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
208
|
- The commands in `commands.md` run with nobody watching and must exit non zero on failure.
|
|
209
209
|
|
|
210
210
|
## See the graph
|
|
211
211
|
|
|
212
|
+

|
|
213
|
+
|
|
214
|
+
*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.*
|
|
215
|
+
|
|
212
216
|
```bash
|
|
213
|
-
python3 scripts/flow-view.py csv-export write the page and open it
|
|
217
|
+
python3 scripts/flow-view.py csv-export write the page and open it (or: feature-flow view csv-export)
|
|
214
218
|
python3 scripts/flow-view.py csv-export --watch keep it updating while tickets are built
|
|
215
219
|
bash examples/demo.sh --open try it on a made up project, no setup needed
|
|
216
220
|
```
|
|
@@ -251,6 +255,8 @@ python3 scripts/flow-status.py <feature> table of tickets and w
|
|
|
251
255
|
python3 scripts/flow-status.py <feature> --next path of the next ready ticket (exit 10 = done, 11 = stuck)
|
|
252
256
|
python3 scripts/flow-status.py <feature> --counts one line of counts
|
|
253
257
|
python3 scripts/flow-status.py <feature> --check cycles, missing blockers, missing Done when, unordered mentions
|
|
258
|
+
python3 scripts/flow-status.py <feature> --lint --check plus: a check block (or `Check: none <why>`) on every ticket, a Test command
|
|
259
|
+
feature-flow report <feature> what a run did, from its log: builds, reviews, failed checks, stops
|
|
254
260
|
python3 scripts/flow-status.py <feature> --mermaid the ticket graph, coloured by status (add "plain" for none)
|
|
255
261
|
python3 scripts/flow-status.py <feature> --json every ticket with status, blockers and readiness
|
|
256
262
|
python3 scripts/flow-view.py <feature> [--watch] the animated graph page, opened in your browser
|
|
@@ -314,6 +320,17 @@ A `package` job builds the wheel and the sdist and runs `tests/package_smoke.py`
|
|
|
314
320
|
|
|
315
321
|
`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
322
|
|
|
323
|
+
## Limits
|
|
324
|
+
|
|
325
|
+
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.
|
|
326
|
+
|
|
327
|
+
- **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.
|
|
328
|
+
- **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`.
|
|
329
|
+
- **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.
|
|
330
|
+
- **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).
|
|
331
|
+
- **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.
|
|
332
|
+
- **One ticket at a time**, with a handoff to a new session every few tickets.
|
|
333
|
+
|
|
317
334
|
## Upgrading
|
|
318
335
|
|
|
319
336
|
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
|
|
|
@@ -184,14 +184,18 @@ prints OK
|
|
|
184
184
|
- **The Answers are the handoff between subagents.** The next builder reads the Answers of the tickets it depends on.
|
|
185
185
|
- `settle` tickets record a decision. `convert` tickets migrate something and must be safe to run twice.
|
|
186
186
|
- **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.
|
|
187
|
+
- `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
188
|
- 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
189
|
- The commands in `commands.md` run with nobody watching and must exit non zero on failure.
|
|
190
190
|
|
|
191
191
|
## See the graph
|
|
192
192
|
|
|
193
|
+

|
|
194
|
+
|
|
195
|
+
*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.*
|
|
196
|
+
|
|
193
197
|
```bash
|
|
194
|
-
python3 scripts/flow-view.py csv-export write the page and open it
|
|
198
|
+
python3 scripts/flow-view.py csv-export write the page and open it (or: feature-flow view csv-export)
|
|
195
199
|
python3 scripts/flow-view.py csv-export --watch keep it updating while tickets are built
|
|
196
200
|
bash examples/demo.sh --open try it on a made up project, no setup needed
|
|
197
201
|
```
|
|
@@ -232,6 +236,8 @@ python3 scripts/flow-status.py <feature> table of tickets and w
|
|
|
232
236
|
python3 scripts/flow-status.py <feature> --next path of the next ready ticket (exit 10 = done, 11 = stuck)
|
|
233
237
|
python3 scripts/flow-status.py <feature> --counts one line of counts
|
|
234
238
|
python3 scripts/flow-status.py <feature> --check cycles, missing blockers, missing Done when, unordered mentions
|
|
239
|
+
python3 scripts/flow-status.py <feature> --lint --check plus: a check block (or `Check: none <why>`) on every ticket, a Test command
|
|
240
|
+
feature-flow report <feature> what a run did, from its log: builds, reviews, failed checks, stops
|
|
235
241
|
python3 scripts/flow-status.py <feature> --mermaid the ticket graph, coloured by status (add "plain" for none)
|
|
236
242
|
python3 scripts/flow-status.py <feature> --json every ticket with status, blockers and readiness
|
|
237
243
|
python3 scripts/flow-view.py <feature> [--watch] the animated graph page, opened in your browser
|
|
@@ -295,6 +301,17 @@ A `package` job builds the wheel and the sdist and runs `tests/package_smoke.py`
|
|
|
295
301
|
|
|
296
302
|
`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
303
|
|
|
304
|
+
## Limits
|
|
305
|
+
|
|
306
|
+
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.
|
|
307
|
+
|
|
308
|
+
- **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.
|
|
309
|
+
- **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`.
|
|
310
|
+
- **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.
|
|
311
|
+
- **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).
|
|
312
|
+
- **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.
|
|
313
|
+
- **One ticket at a time**, with a handoff to a new session every few tickets.
|
|
314
|
+
|
|
298
315
|
## Upgrading
|
|
299
316
|
|
|
300
317
|
Earlier versions had seven skills and an unattended loop. The five flow skills are gone, and `/feature-flow` replaces them:
|
|
@@ -107,7 +107,7 @@ def run(args, scripts, trusted):
|
|
|
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)
|
|
@@ -573,7 +573,7 @@ class Conductor:
|
|
|
573
573
|
self.no_plan_yet()
|
|
574
574
|
if not self.draft.is_dir():
|
|
575
575
|
raise Stop("no draft plan in %s. run the planner first" % self.draft_text())
|
|
576
|
-
check = checks.flow_status(self.scripts, self.feature, "--
|
|
576
|
+
check = checks.flow_status(self.scripts, self.feature, "--lint", root=self.draft_root)
|
|
577
577
|
if not check.ok:
|
|
578
578
|
raise Stop("the draft fails the plan check, fix it first: %s"
|
|
579
579
|
% " ".join(check.out.split())[:400])
|
|
@@ -7,7 +7,7 @@ lowered thresholds, edited lint or test config.
|
|
|
7
7
|
it also protects the plan from the worker: only the ticket's own Status line and Answer,
|
|
8
8
|
appended lines in map.md and learnings.md may change, and nothing else under the plans folder.
|
|
9
9
|
a ticket can allow categories with a line "Floor: allow skip, suppress" (skip, suppress,
|
|
10
|
-
empty-catch, test-delete, threshold, config, ticket-edit, commands-edit). the line is
|
|
10
|
+
empty-catch, test-delete, threshold, trivial-assert, config, ticket-edit, commands-edit). the line is
|
|
11
11
|
read from the ticket as it was at base-commit, so a worker cannot excuse itself.
|
|
12
12
|
run it from the repo root. exit 0 clean, 1 findings, 2 usage error.
|
|
13
13
|
|
|
@@ -29,6 +29,13 @@ FOCUSED = re.compile(r"\.only\(|(?<!def )(?<![A-Za-z0-9_.])(fit|fdescribe)\(")
|
|
|
29
29
|
SUPPRESS = re.compile(r"# noqa|# type: ignore|# pylint: disable|# pragma: no cover|# fmt: off|# ruff: noqa"
|
|
30
30
|
r"|eslint-disable|@ts-ignore|@ts-expect-error|// nolint|#\[allow\(|--no-verify")
|
|
31
31
|
EMPTY_CATCH = re.compile(r"except[^:]*:[ \t]*pass[ \t]*$|catch[ \t]*(\([^)]*\))?[ \t]*\{[ \t]*\}")
|
|
32
|
+
# an assertion that cannot fail, added to a test file: assert True, assertEqual(x, x), expect(true), expect(x).toBe(x)
|
|
33
|
+
TRIVIAL = (re.compile(r"(^|[^A-Za-z_.])assert\s+(True|1\s*==\s*1|not\s+False)\s*(#.*)?$"),
|
|
34
|
+
re.compile(r"assertTrue\(\s*True\s*[,)]"),
|
|
35
|
+
re.compile(r"assertEqual\(\s*([A-Za-z_][\w.]*|\d+)\s*,\s*\1\s*[,)]"),
|
|
36
|
+
re.compile(r"(^|[^A-Za-z_.])assert\s+([A-Za-z_][\w.]*)\s*==\s*\2\s*(#.*)?$"),
|
|
37
|
+
re.compile(r"expect\(\s*true\s*\)\.(toBe\(\s*true\s*\)|toBeTruthy\(\))"),
|
|
38
|
+
re.compile(r"expect\(\s*([A-Za-z_][\w.]*)\s*\)\.toBe\(\s*\1\s*\)"))
|
|
32
39
|
THRESHOLD = re.compile(r"fail_under|fail-under|coverageThreshold|cov-fail-under")
|
|
33
40
|
DELETED_TEST = re.compile(r"(^|/)(test_[^/]*\.py|[^/]*_test\.[a-z]+|[^/]*\.(test|spec)\.[a-z]+|tests?/|__tests__/)")
|
|
34
41
|
# Makefile is judged by content, not name: a Makefile change only counts when the plan's commands.md has a
|
|
@@ -77,7 +84,7 @@ def allow_line(source):
|
|
|
77
84
|
|
|
78
85
|
|
|
79
86
|
def diff_findings(diff, allow):
|
|
80
|
-
"""The pass over added lines: skip, suppress, empty-catch and
|
|
87
|
+
"""The pass over added lines: skip, suppress, empty-catch, threshold and trivial-assert."""
|
|
81
88
|
allow = " " + allow + " "
|
|
82
89
|
out = []
|
|
83
90
|
file = ""
|
|
@@ -105,6 +112,8 @@ def diff_findings(diff, allow):
|
|
|
105
112
|
report("empty-catch", line)
|
|
106
113
|
if THRESHOLD.search(line):
|
|
107
114
|
report("threshold", line)
|
|
115
|
+
if DELETED_TEST.search(file) and any(r.search(line) for r in TRIVIAL):
|
|
116
|
+
report("trivial-assert", line)
|
|
108
117
|
return "".join(out)
|
|
109
118
|
|
|
110
119
|
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""feature-flow report <feature>: what a run did, read from its log.
|
|
2
|
+
|
|
3
|
+
usage: feature-flow report <feature>
|
|
4
|
+
Reads .feature-flow/state/flow-<feature>.log (one `time,ticket,EVENT` line per step) and prints, per
|
|
5
|
+
ticket, how many times it was built and reviewed and which checks failed, then the run's STOPs, resets
|
|
6
|
+
and handoffs. Read-only. It cannot see a trust-runner STOP (the runner stops before the conductor
|
|
7
|
+
logs anything): the session's own output shows those, so count them by hand.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from feature_flow import git, state, suggest
|
|
14
|
+
|
|
15
|
+
USAGE = "usage: feature-flow report <feature>"
|
|
16
|
+
FAILS = ("GATE-FAIL", "GUARD-FAIL", "DONEWHEN-FAIL", "TESTFIRST-FAIL", "VERDICT-FAIL")
|
|
17
|
+
RUN_EVENTS = ("START", "TAKEOVER", "RESET", "HANDOFF", "STOP")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def parse(text):
|
|
21
|
+
"""[(ticket, event, detail)] for each well formed line; a bad line is skipped."""
|
|
22
|
+
rows = []
|
|
23
|
+
for line in text.splitlines():
|
|
24
|
+
parts = line.split(",", 2)
|
|
25
|
+
if len(parts) != 3 or not parts[2].strip():
|
|
26
|
+
continue
|
|
27
|
+
event, _, detail = parts[2].strip().partition(" ")
|
|
28
|
+
rows.append((parts[1].strip(), event, detail.strip()))
|
|
29
|
+
return rows
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def summarize(rows):
|
|
33
|
+
"""(per-ticket dict in first-seen order, run-level counts, STOP reasons)."""
|
|
34
|
+
tickets = {}
|
|
35
|
+
run = {name: 0 for name in RUN_EVENTS}
|
|
36
|
+
stops = []
|
|
37
|
+
for num, event, detail in rows:
|
|
38
|
+
if event in RUN_EVENTS:
|
|
39
|
+
run[event] += 1
|
|
40
|
+
if event == "STOP":
|
|
41
|
+
stops.append((num, detail or "(no reason logged)"))
|
|
42
|
+
if num in ("", "-") or event in RUN_EVENTS:
|
|
43
|
+
continue
|
|
44
|
+
row = tickets.setdefault(num, {})
|
|
45
|
+
row[event] = row.get(event, 0) + 1
|
|
46
|
+
return tickets, run, stops
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def render(feature, rows):
|
|
50
|
+
tickets, run, stops = summarize(rows)
|
|
51
|
+
out = ["report for %s: %d log line(s)" % (feature, len(rows)), ""]
|
|
52
|
+
if tickets:
|
|
53
|
+
out.append("ticket builds reviews small quick-gate full-gate failures")
|
|
54
|
+
for num, row in tickets.items():
|
|
55
|
+
fails = ", ".join("%s x%d" % (name, row[name]) for name in FAILS if row.get(name))
|
|
56
|
+
out.append("%-6s %6d %7d %5d %10d %9d %s"
|
|
57
|
+
% (num, row.get("BUILD", 0), row.get("REVIEW", 0), row.get("REVIEW-SMALL", 0),
|
|
58
|
+
row.get("GATE-QUICK", 0), row.get("GATE-FULL", 0), fails or "none"))
|
|
59
|
+
reworked = [n for n, row in tickets.items() if row.get("BUILD", 0) > 1]
|
|
60
|
+
out += ["", "tickets built more than once (reworked): %s" % (", ".join(reworked) or "none")]
|
|
61
|
+
else:
|
|
62
|
+
out.append("no ticket events yet")
|
|
63
|
+
out += ["", "starts %d, takeovers %d, resets %d, handoffs %d, stops %d"
|
|
64
|
+
% (run["START"], run["TAKEOVER"], run["RESET"], run["HANDOFF"], run["STOP"])]
|
|
65
|
+
for num, reason in stops:
|
|
66
|
+
out.append(" STOP at ticket %s: %s" % (num, reason))
|
|
67
|
+
out += ["", "trust-runner STOPs are not in this log: count them from the session by hand"]
|
|
68
|
+
return "\n".join(out) + "\n"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def main(argv, out=None, err=None):
|
|
72
|
+
out = out or sys.stdout
|
|
73
|
+
err = err or sys.stderr
|
|
74
|
+
if len(argv) != 1 or argv[0].startswith("-"):
|
|
75
|
+
err.write(USAGE + "\n")
|
|
76
|
+
return 2
|
|
77
|
+
feature = argv[0]
|
|
78
|
+
top = git.toplevel()
|
|
79
|
+
if top is None:
|
|
80
|
+
err.write("not inside a git repo\n")
|
|
81
|
+
return 2
|
|
82
|
+
path = state.log_path(Path(top) / state.STATE_DIR, feature)
|
|
83
|
+
text = state.read_text(path)
|
|
84
|
+
if text is None:
|
|
85
|
+
err.write("no run log for %s (%s)\n" % (feature, path))
|
|
86
|
+
err.write(suggest.hint(feature, [p.name[5:-4] for p in path.parent.glob("flow-*.log")]))
|
|
87
|
+
return 2
|
|
88
|
+
out.write(render(feature, parse(text)))
|
|
89
|
+
return 0
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
"""The ticket graph reader, ported from the bash script of the same name.
|
|
2
2
|
|
|
3
|
-
usage: python3 scripts/flow-status.py <feature> [--next | --counts | --check | --mermaid [plain] | --json]
|
|
3
|
+
usage: python3 scripts/flow-status.py <feature> [--next | --counts | --check | --lint | --mermaid [plain] | --json]
|
|
4
4
|
Exit codes for --next: 0 a ready ticket path is printed, 10 all done, 11 unfinished but nothing ready.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import os
|
|
8
8
|
import re
|
|
9
|
+
import shlex
|
|
10
|
+
import shutil
|
|
9
11
|
import sys
|
|
10
12
|
|
|
11
13
|
from feature_flow import suggest, tickets
|
|
12
14
|
|
|
13
|
-
USAGE = "usage: python3 scripts/flow-status.py <feature> [--next | --counts | --check | --mermaid [plain] | --json]"
|
|
15
|
+
USAGE = "usage: python3 scripts/flow-status.py <feature> [--next | --counts | --check | --lint | --mermaid [plain] | --json]"
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
def _env(name, default):
|
|
@@ -55,7 +57,7 @@ def run(argv, out, err):
|
|
|
55
57
|
err.write("no tickets in %s\n" % folder)
|
|
56
58
|
return 2
|
|
57
59
|
|
|
58
|
-
if mode
|
|
60
|
+
if mode in ("--check", "--lint"):
|
|
59
61
|
learnings = "%s/%s/learnings.md" % (root, feature)
|
|
60
62
|
if os.path.isfile(learnings):
|
|
61
63
|
lines = sum(1 for line in tickets.records(learnings) if line.strip(" \t\n\v\f\r") != "")
|
|
@@ -64,10 +66,10 @@ def run(argv, out, err):
|
|
|
64
66
|
% lines)
|
|
65
67
|
|
|
66
68
|
g = tickets.Graph(paths)
|
|
67
|
-
return _report(g, mode, opt, feature, out, err)
|
|
69
|
+
return _report(g, mode, opt, feature, out, err, root)
|
|
68
70
|
|
|
69
71
|
|
|
70
|
-
def _report(g, mode, opt, feature, out, err):
|
|
72
|
+
def _report(g, mode, opt, feature, out, err, root="plans"):
|
|
71
73
|
n = len(g.order)
|
|
72
74
|
count = {"resolved": 0, "open": 0, "claimed": 0, "waiting": 0, "parked": 0, "unknown": 0}
|
|
73
75
|
ready = {}
|
|
@@ -138,8 +140,9 @@ def _report(g, mode, opt, feature, out, err):
|
|
|
138
140
|
" classDef parked fill:#eeeeee,stroke:#bbbbbb,stroke-dasharray:4 3\n")
|
|
139
141
|
return 0
|
|
140
142
|
|
|
141
|
-
if mode
|
|
142
|
-
|
|
143
|
+
if mode in ("--check", "--lint"):
|
|
144
|
+
extra = lint(g, "%s/%s" % (root, feature)) if mode == "--lint" else ([], [])
|
|
145
|
+
return _check(g, ready, nready, unfinished, out, extra)
|
|
143
146
|
|
|
144
147
|
for t in g.order:
|
|
145
148
|
bl = ",".join("%02d" % d for d in g.blk[t])
|
|
@@ -148,8 +151,47 @@ def _report(g, mode, opt, feature, out, err):
|
|
|
148
151
|
return 0
|
|
149
152
|
|
|
150
153
|
|
|
151
|
-
def
|
|
152
|
-
errors
|
|
154
|
+
def lint(g, plan_dir):
|
|
155
|
+
"""(errors, warnings) the stricter plan lint adds to --check: every ticket carries a check block or
|
|
156
|
+
says `Check: none <why>`, and commands.md names a Test command that can be found."""
|
|
157
|
+
errors, warnings = [], []
|
|
158
|
+
for t in g.order:
|
|
159
|
+
lab = g.lab[t]
|
|
160
|
+
if g.typ.get(t) == "settle":
|
|
161
|
+
continue
|
|
162
|
+
lines = tickets.records(g.path[t])
|
|
163
|
+
has_block = any(line.startswith("```check") for line in lines)
|
|
164
|
+
none = next((line for line in lines[:20] if line.startswith("Check:")), None)
|
|
165
|
+
if none is not None:
|
|
166
|
+
reason = none[len("Check:"):].strip()
|
|
167
|
+
if not reason.lower().startswith("none") or not reason[4:].strip(" \t:-"):
|
|
168
|
+
errors.append(lab + ": `Check:` must read `Check: none <why>`")
|
|
169
|
+
elif not has_block:
|
|
170
|
+
errors.append(lab + ": no check block. add one under Done when, or `Check: none <why>` in the header")
|
|
171
|
+
for line in lines:
|
|
172
|
+
if line.strip().startswith("##"):
|
|
173
|
+
continue
|
|
174
|
+
for ref in re.findall(r"`([A-Za-z0-9_./-]+/[A-Za-z0-9_.-]+\.[A-Za-z0-9]+)`", line):
|
|
175
|
+
if "new" not in line.lower() and "plans/" not in ref and not os.path.exists(ref):
|
|
176
|
+
warnings.append("%s: names `%s`, which does not exist here (say `new` if it will be created)" % (lab, ref))
|
|
177
|
+
commands = "%s/commands.md" % plan_dir
|
|
178
|
+
test = tickets.commands_value(commands, "Test")
|
|
179
|
+
if not os.path.isfile(commands):
|
|
180
|
+
errors.append("commands.md is missing")
|
|
181
|
+
elif not test:
|
|
182
|
+
errors.append("commands.md has no Test command (a placeholder in angle brackets does not count)")
|
|
183
|
+
else:
|
|
184
|
+
try:
|
|
185
|
+
first = shlex.split(test, posix=(os.name != "nt"))[0].strip("\"'")
|
|
186
|
+
except (ValueError, IndexError):
|
|
187
|
+
first = test.split()[0]
|
|
188
|
+
if "=" not in first and shutil.which(first) is None and first not in ("cd", "set", "export"):
|
|
189
|
+
warnings.append("commands.md Test starts with `%s`, which is not on PATH here" % first)
|
|
190
|
+
return errors, list(dict.fromkeys(warnings))
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _check(g, ready, nready, unfinished, out, extra=([], [])):
|
|
194
|
+
errors = list(extra[0])
|
|
153
195
|
for t in g.order:
|
|
154
196
|
lab = g.lab[t]
|
|
155
197
|
if t in g.dup:
|
|
@@ -209,6 +251,8 @@ def _check(g, ready, nready, unfinished, out):
|
|
|
209
251
|
continue
|
|
210
252
|
out.write("warning: %s: mentions ticket %s but is not ordered against it. add it to Blocked by, or reword\n"
|
|
211
253
|
% (g.lab[t], g.lab[m]))
|
|
254
|
+
for w in extra[1]:
|
|
255
|
+
out.write("warning: " + w + "\n")
|
|
212
256
|
if errors:
|
|
213
257
|
for e in errors:
|
|
214
258
|
out.write(e + "\n")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: feature-flow-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.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
|
|
|
@@ -203,14 +203,18 @@ prints OK
|
|
|
203
203
|
- **The Answers are the handoff between subagents.** The next builder reads the Answers of the tickets it depends on.
|
|
204
204
|
- `settle` tickets record a decision. `convert` tickets migrate something and must be safe to run twice.
|
|
205
205
|
- **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.
|
|
206
|
+
- `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
207
|
- 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
208
|
- The commands in `commands.md` run with nobody watching and must exit non zero on failure.
|
|
209
209
|
|
|
210
210
|
## See the graph
|
|
211
211
|
|
|
212
|
+

|
|
213
|
+
|
|
214
|
+
*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.*
|
|
215
|
+
|
|
212
216
|
```bash
|
|
213
|
-
python3 scripts/flow-view.py csv-export write the page and open it
|
|
217
|
+
python3 scripts/flow-view.py csv-export write the page and open it (or: feature-flow view csv-export)
|
|
214
218
|
python3 scripts/flow-view.py csv-export --watch keep it updating while tickets are built
|
|
215
219
|
bash examples/demo.sh --open try it on a made up project, no setup needed
|
|
216
220
|
```
|
|
@@ -251,6 +255,8 @@ python3 scripts/flow-status.py <feature> table of tickets and w
|
|
|
251
255
|
python3 scripts/flow-status.py <feature> --next path of the next ready ticket (exit 10 = done, 11 = stuck)
|
|
252
256
|
python3 scripts/flow-status.py <feature> --counts one line of counts
|
|
253
257
|
python3 scripts/flow-status.py <feature> --check cycles, missing blockers, missing Done when, unordered mentions
|
|
258
|
+
python3 scripts/flow-status.py <feature> --lint --check plus: a check block (or `Check: none <why>`) on every ticket, a Test command
|
|
259
|
+
feature-flow report <feature> what a run did, from its log: builds, reviews, failed checks, stops
|
|
254
260
|
python3 scripts/flow-status.py <feature> --mermaid the ticket graph, coloured by status (add "plain" for none)
|
|
255
261
|
python3 scripts/flow-status.py <feature> --json every ticket with status, blockers and readiness
|
|
256
262
|
python3 scripts/flow-view.py <feature> [--watch] the animated graph page, opened in your browser
|
|
@@ -314,6 +320,17 @@ A `package` job builds the wheel and the sdist and runs `tests/package_smoke.py`
|
|
|
314
320
|
|
|
315
321
|
`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
322
|
|
|
323
|
+
## Limits
|
|
324
|
+
|
|
325
|
+
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.
|
|
326
|
+
|
|
327
|
+
- **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.
|
|
328
|
+
- **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`.
|
|
329
|
+
- **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.
|
|
330
|
+
- **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).
|
|
331
|
+
- **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.
|
|
332
|
+
- **One ticket at a time**, with a handoff to a new session every few tickets.
|
|
333
|
+
|
|
317
334
|
## Upgrading
|
|
318
335
|
|
|
319
336
|
Earlier versions had seven skills and an unattended loop. The five flow skills are gone, and `/feature-flow` replaces them:
|
|
@@ -12,7 +12,7 @@ You cannot edit or write anything. You may read, search, run the plan check, and
|
|
|
12
12
|
4. **The commands are real.** Each command in `commands.md` exists in the project's own config, and Smoke runs and exits 0 now. Run it. If `commands.md` has `Quick:`, run it too and report that it exits 0, that it is part of what `Test` runs, and whether it covers the areas the tickets touch.
|
|
13
13
|
5. **Done when is checkable by a stranger.** Each bullet names a command and the result it should print, or an outcome someone who has not read the plan can observe.
|
|
14
14
|
6. **Forks belong to the right person.** A choice that changes what the user gets is a `settle` ticket or an open question, not a decision the planner made quietly.
|
|
15
|
-
7. **The graph is sound.** Run `FLOW_DIR=.feature-flow/state/draft python3 scripts/flow-status.py <feature> --
|
|
15
|
+
7. **The graph is sound.** Run `FLOW_DIR=.feature-flow/state/draft python3 scripts/flow-status.py <feature> --lint` and report any problem or warning. Tickets are small (about 5 Done when bullets at most), and `Blocked by` lists only real dependencies. No ticket is so small it should have been folded into another: name it and the ticket it belongs with.
|
|
16
16
|
8. **Outside facts have sources.** Each fact about a library or outside API that a ticket relies on has a source line in `map.md`.
|
|
17
17
|
|
|
18
18
|
Do not check style, wording or ticket order beyond what the list says.
|
|
@@ -75,13 +75,13 @@ prints OK
|
|
|
75
75
|
9. **The last ticket is the acceptance ticket**: it runs the map's bar end to end and is blocked by every ticket that leaves the bar unproven.
|
|
76
76
|
10. **Number from 01 and never renumber.** Tickets added later get the next number.
|
|
77
77
|
11. **More than about 12 tickets?** Plan in rounds: `plans/<feature>/` first, then `plans/<feature>-round-two/` with its own map that links back.
|
|
78
|
-
12. **`Floor: allow <categories>` only when the brief says so.** The floor guard fails a ticket whose diff skips or deletes tests, silences checks, adds empty catches, lowers thresholds, or edits lint, test or CI config. It also fails a worker that edits anything in the plan except its own Status and Answer, appended lines in `map.md` and `learnings.md`, and it fails any change to `commands.md`. If a ticket legitimately has to (for example "migrate the test runner", which changes `commands.md`), add a line such as `Floor: allow config, test-delete, commands-edit` to that ticket, and say so in your reply. Categories: `skip`, `suppress`, `empty-catch`, `test-delete`, `threshold`, `config`, `ticket-edit`, `commands-edit`. The line is read from the ticket as it was before the work started, so a worker cannot add it for itself.
|
|
78
|
+
12. **`Floor: allow <categories>` only when the brief says so.** The floor guard fails a ticket whose diff skips or deletes tests, silences checks, adds empty catches, lowers thresholds, or edits lint, test or CI config. It also fails a worker that edits anything in the plan except its own Status and Answer, appended lines in `map.md` and `learnings.md`, and it fails any change to `commands.md`. If a ticket legitimately has to (for example "migrate the test runner", which changes `commands.md`), add a line such as `Floor: allow config, test-delete, commands-edit` to that ticket, and say so in your reply. Categories: `skip`, `suppress`, `empty-catch`, `test-delete`, `threshold`, `trivial-assert`, `config`, `ticket-edit`, `commands-edit`. The line is read from the ticket as it was before the work started, so a worker cannot add it for itself.
|
|
79
79
|
13. **Name a ticket in prose only if it is ordered against this one.** If a ticket's text says "uses the output of ticket 02", list 02 under `Blocked by`. `--check` warns about mentions that are not ordered.
|
|
80
80
|
|
|
81
81
|
|
|
82
82
|
## Step 5: Check
|
|
83
83
|
|
|
84
|
-
Run `FLOW_DIR=.feature-flow/state/draft python3 scripts/flow-status.py <feature> --check`. It fails on a missing blocker, a cycle, a missing Done when, a duplicate number, a bad `Test first` value, a bad `Review` value, or no ready ticket, and it warns about tickets that name another ticket in their text without being ordered against it. If the script is not installed, check those by hand. Fix every problem and every warning before you reply.
|
|
84
|
+
Run `FLOW_DIR=.feature-flow/state/draft python3 scripts/flow-status.py <feature> --check`. It fails on a missing blocker, a cycle, a missing Done when, a duplicate number, a bad `Test first` value, a bad `Review` value, or no ready ticket, and it warns about tickets that name another ticket in their text without being ordered against it. If the script is not installed, check those by hand. Then run it again with `--lint` instead of `--check`: the same checks, and it also fails a ticket that has no `check` block (unless it is `Type: settle`, or its header says `Check: none <why>` with a real reason) and a `commands.md` with no Test command, and it warns about a Test command that is not on the PATH and about files a ticket names that do not exist (say `new` on that line when the ticket will create it). `plan-accept` runs the `--lint` rules too. Fix every problem and every warning before you reply.
|
|
85
85
|
|
|
86
86
|
## Step 6: Reply
|
|
87
87
|
|
|
@@ -20,7 +20,7 @@ is being converted and what the safe default is for anything that does not match
|
|
|
20
20
|
- <every bullet is checkable by someone who has not read the rest of the plan>
|
|
21
21
|
|
|
22
22
|
```check
|
|
23
|
-
# The conductor runs this block before review. One `$` line per bullet that names a command.
|
|
23
|
+
# The conductor runs this block before review. No command can prove the ticket? Drop the block and put `Check: none <why>` in the header instead. One `$` line per bullet that names a command.
|
|
24
24
|
$ <the command from a bullet, read-only, run from the repo's top folder>
|
|
25
25
|
prints <text the output must contain>
|
|
26
26
|
```
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/adapters/codex/feature-flow/agents/openai.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/feature_flow_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{feature_flow_cli-0.7.0 → feature_flow_cli-0.8.0}/feature_flow_cli.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|