feature-flow-cli 0.25.0__tar.gz → 0.28.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.25.0 → feature_flow_cli-0.28.0}/MANIFEST.in +2 -1
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/PKG-INFO +38 -3
- feature_flow_cli-0.25.0/feature_flow_cli.egg-info/PKG-INFO → feature_flow_cli-0.28.0/README.md +35 -21
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/adapters/codex/feature-flow/SKILL.md +3 -3
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/__init__.py +1 -1
- feature_flow_cli-0.28.0/feature_flow/brainstorm_page.py +190 -0
- feature_flow_cli-0.28.0/feature_flow/codeintel.py +127 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/conductor.py +2 -2
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/evaluate.py +68 -42
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/install.py +8 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/prompts.py +4 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/scouts.py +7 -2
- feature_flow_cli-0.28.0/feature_flow/studio.py +47 -0
- feature_flow_cli-0.25.0/README.md → feature_flow_cli-0.28.0/feature_flow_cli.egg-info/PKG-INFO +56 -2
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow_cli.egg-info/SOURCES.txt +20 -3
- feature_flow_cli-0.28.0/feature_flow_cli.egg-info/requires.txt +3 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/brainstorm.md +14 -1
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/templates/brainstorm.md +7 -1
- feature_flow_cli-0.28.0/packages/forkboard/forkboard/__init__.py +8 -0
- feature_flow_cli-0.28.0/packages/forkboard/forkboard/__main__.py +3 -0
- feature_flow_cli-0.28.0/packages/forkboard/forkboard/assets/base.css +116 -0
- feature_flow_cli-0.28.0/packages/forkboard/forkboard/assets/editor.js +533 -0
- feature_flow_cli-0.28.0/packages/forkboard/forkboard/assets/live.css +28 -0
- feature_flow_cli-0.25.0/feature_flow/brainstorm_live.py → feature_flow_cli-0.28.0/packages/forkboard/forkboard/assets/live.js +0 -187
- feature_flow_cli-0.28.0/packages/forkboard/forkboard/cli.py +127 -0
- feature_flow_cli-0.28.0/packages/forkboard/forkboard/doc.py +35 -0
- feature_flow_cli-0.28.0/packages/forkboard/forkboard/graph.py +136 -0
- feature_flow_cli-0.28.0/packages/forkboard/forkboard/live.py +85 -0
- feature_flow_cli-0.28.0/packages/forkboard/forkboard/page.py +304 -0
- {feature_flow_cli-0.25.0/guides/styles → feature_flow_cli-0.28.0/packages/forkboard/forkboard/themes}/blueprint.css +1 -0
- feature_flow_cli-0.28.0/packages/forkboard/forkboard/themes/bright.css +29 -0
- feature_flow_cli-0.28.0/packages/forkboard/forkboard/themes/paper.css +35 -0
- {feature_flow_cli-0.25.0/guides/styles → feature_flow_cli-0.28.0/packages/forkboard/forkboard/themes}/route.css +1 -0
- feature_flow_cli-0.28.0/packages/forkboard/forkboard/themes/sketch.css +30 -0
- feature_flow_cli-0.28.0/packages/forkboard/forkboard/themes/terminal.css +30 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/pyproject.toml +7 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/scripts/flow-trust.py +1 -2
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/skills/feature-flow/SKILL.md +3 -3
- feature_flow_cli-0.25.0/feature_flow/brainstorm_page.py +0 -1066
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/LICENSE +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/adapters/codex/feature-flow/agents/openai.yaml +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/agents/feature-planner.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/agents/flow-architect.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/agents/flow-designer.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/agents/flow-lens.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/agents/flow-po.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/agents/flow-scout.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/agents/flow-skeptic.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/agents/flow-tester.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/agents/flow-web.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/agents/plan-reviewer.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/agents/ticket-builder.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/agents/ticket-reviewer.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/__main__.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/checks.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/cli.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/codehash.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/command.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/floorguard.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/gate.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/git.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/importer.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/lanes.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/proc.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/profiles.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/proof.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/released.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/report.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/state.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/status.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/suggest.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/tickets.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/uninstall.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/view.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow_cli.egg-info/dependency_links.txt +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow_cli.egg-info/entry_points.txt +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow_cli.egg-info/top_level.txt +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/brief.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/build.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/debug.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/lenses/artistic.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/lenses/open-minded.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/lenses/positive.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/lenses/researcher.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/lenses/security.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/lenses/ux.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/plan-review.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/plan.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/replan.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/review-feature.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/review-quality.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/review-single.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/review.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/show.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/templates/commands.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/templates/learnings.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/templates/map.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/templates/spec.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/templates/ticket.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/templates/ui-mockup.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/test.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/install.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/install.sh +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/scripts/floor-guard.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/scripts/flow-status.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/scripts/flow-view.html +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/scripts/flow-view.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/scripts/flow.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/scripts/gate.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/setup.cfg +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/skills/architect-review/SKILL.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.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.28.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
|
|
@@ -15,6 +15,8 @@ Classifier: Topic :: Software Development
|
|
|
15
15
|
Requires-Python: >=3.9
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
License-File: LICENSE
|
|
18
|
+
Provides-Extra: codeintel
|
|
19
|
+
Requires-Dist: codecortex>=0.29; extra == "codeintel"
|
|
18
20
|
Dynamic: license-file
|
|
19
21
|
|
|
20
22
|
# Feature Flow
|
|
@@ -42,7 +44,7 @@ One skill, 12 agent roles, a Python conductor, 6 Python scripts (`flow`, `flow-t
|
|
|
42
44
|
[docs/releases.md](docs/releases.md) lists what each release added.
|
|
43
45
|
|
|
44
46
|
- [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) · [See the graph](#see-the-graph)
|
|
45
|
-
- [Lanes](#tickets-side-by-side-lanes) · [Scouts](#scouts) · [Questions](#questions-for-the-architect-designer-or-product-owner) · [Acceptance tests](#acceptance-tests-the-tester) · [Fast path](#the-fast-path) · [Time budget](#a-time-budget) · [Eval](#recording-a-run-eval) · [Profiles](#profiles) · [Brainstorm](#brainstorm-before-planning) · [Import](#starting-from-spec-kit-or-openspec)
|
|
47
|
+
- [Lanes](#tickets-side-by-side-lanes) · [Scouts](#scouts) · [codeintel](#codeintel-optional) · [Questions](#questions-for-the-architect-designer-or-product-owner) · [Acceptance tests](#acceptance-tests-the-tester) · [Fast path](#the-fast-path) · [Time budget](#a-time-budget) · [Eval](#recording-a-run-eval) · [Profiles](#profiles) · [Brainstorm](#brainstorm-before-planning) · [Import](#starting-from-spec-kit-or-openspec)
|
|
46
48
|
- [Long features and handoff](#long-features-and-handoff) · [Commands and settings](#commands-and-settings) · [What is tested](#what-is-tested) · [Limits](#limits) · [Upgrading](#upgrading) · [Caution](#caution)
|
|
47
49
|
|
|
48
50
|
## Quick start
|
|
@@ -332,6 +334,17 @@ Before a planner, builder or reviewer starts, the session can spawn cheap helper
|
|
|
332
334
|
|
|
333
335
|
`scouts [lane]` lists `SCOUT <k> <agent> <reply file>` lines (or `NONE`) and clears the last ones' replies; `scout-prompt <k> [lane]` prints one scout's prompt. The session saves each reply into its file, and the phase's `prompt` adds them under `## Scout findings (unverified)`, cut to 60 lines each. A reply saved for one phase never reaches another. Retries and later rounds get no scouts. `FLOW_SCOUTS=off` turns them off.
|
|
334
336
|
|
|
337
|
+
## codeintel (optional)
|
|
338
|
+
|
|
339
|
+
[codeintel](https://github.com/hamilton-sky/codeintel) is a separate code-intelligence tool: one command answers who calls a function, what a change would break, or where the code that does something lives, from a call graph, a language server and semantic search. feature-flow never needs it. When it is installed and has the repository indexed, two roles get a short `## codeintel` section in their prompt:
|
|
340
|
+
|
|
341
|
+
- **Scouts** (`flow-scout`, not `flow-web`) are told to ask it `callers`, `impact` or `search` where grep would take several tries.
|
|
342
|
+
- **Reviewers** (every ticket review and the feature review) are told to run `codeintel query --op changed --target <base>`, which lists each function the change removed, re-signed or rewrote with the callers it did not update.
|
|
343
|
+
|
|
344
|
+
Each of them ends its reply with one line per question, `codeintel: <op> <target>: helped | no answer | wrong: <why>`, and is told to check an answer before relying on it and to fall back to grep. `eval` counts the queries and those lines, and the verdict form asks whether codeintel helped, made no difference or misled the run, with a line for an answer it got wrong: material for a codeintel issue.
|
|
345
|
+
|
|
346
|
+
To set it up: `pip install "feature-flow-cli[codeintel]"` (or `pip install codecortex`), then `codeintel setup --all .` in the repository. feature-flow asks `codeintel doctor --json` which engines are ready and keeps the answer for an hour in `.feature-flow/state/codeintel.json`; with nothing ready, or no `codeintel` on PATH, the prompts are as before. `FLOW_CODEINTEL=off` leaves it out. In a lane, the agents run it in the lane's worktree, where codeintel may not have an index, and then fall back to grep. The two tools are released on their own and neither imports the other.
|
|
347
|
+
|
|
335
348
|
## Questions for the architect, designer or product owner
|
|
336
349
|
|
|
337
350
|
A builder, reviewer or planner that hits a question it should not decide alone asks it instead of guessing, and the flow pauses that job until the right role answers.
|
|
@@ -377,6 +390,7 @@ Every finished run can leave a record of what it cost and what it caught, so a t
|
|
|
377
390
|
- **`plans/<feature>/eval.json`**: tickets, builds, reviews and rework; tickets settled when the plan was approved and never built; every catch by the check that made it (gate, floor guard, Done when, test first, footprint, reviewer, feature review); the follow-ups in `followups.md` and the deferred checks; who drove the run (`driver`: `person` or `agent`); where the run needed its driver (stops, questions, handoffs, resets); active minutes; and tokens.
|
|
378
391
|
- **`plans/<feature>/followups.md`**: what the run left for later, committed as it goes: the `minor` findings of every passing review, the findings a level 2 ticket passed with after its full rounds, and each `needs` check (below) that could not pass on this host.
|
|
379
392
|
- **Tokens** come from Claude Code's own session transcripts (`~/.claude/projects/`, or `CLAUDE_CONFIG_DIR`), never from the agent: every message of the sessions and subagents that worked in this repo during the run, each counted once, by model and, without lanes, split per ticket by time. Codex sessions are not read yet.
|
|
393
|
+
- **codeintel**: when an agent asked [codeintel](#codeintel-optional), `codeintel` in `eval.json` counts the queries by op and what the agents said of the answers (helped, no answer, wrong, and why), and the form below asks whether it helped.
|
|
380
394
|
- **`plans/<feature>/eval-verdict.md`**: a form for you. Was the plan right, how many fixes you made by hand, would you use it again, and for each catch whether it was real or a false alarm. Run `--eval` again and your answers land in `eval.json`. The false-alarm count is the only way to tell whether the checks are worth their cost.
|
|
381
395
|
- **Later:** `feature-flow eval <feature> --later`, a week or two on, lists the commits since the run whose subject says fix, revert, bug or regression on the paths each ticket's `Touches:` names: the defects the run let through.
|
|
382
396
|
|
|
@@ -402,10 +416,24 @@ When an idea is still fuzzy, the session can brainstorm it with you before it wr
|
|
|
402
416
|
|
|
403
417
|
Read only helpers work for the session during a brainstorm: a code scout, a web scout, the architect, designer and product owner, `flow-skeptic`, who attacks each finished option so the session that wrote the options is not the one that judges them, and `flow-lens`, who looks at the options through one lens the session picks for the idea: `positive`, `researcher`, `open-minded`, `artistic`, `ux` or `security` ([guides/lenses/](guides/lenses/)). Each gets its prompt from `brainstorm-prompt <helper> <question file>`, which counts them: `normal` allows 2 scouts, 1 web, 2 consultants, 1 skeptic and 2 lenses per brainstorm, `careful` 3, 2, 3, 1 and 3, and `FLOW_SCOUTS=off` no scouts. Helpers never write the brainstorm and never ask you anything; the session does both.
|
|
404
418
|
|
|
405
|
-
Before you pick, the session shows the brainstorm as a review page: `feature-flow brainstorm-page <feature>` (or `flow-status.py <feature> --brainstorm-page`) writes it beside the brainstorm as one HTML file. It has a sidebar that routes between the sections and shows how far you read, a system, light or dark switch, flowchart diagrams you can edit (drag, add boxes of seven kinds, colour them from 12 colours, rename and delete them, connect them with arrows, undo and redo, with the keyboard too), reset, and copy back as mermaid (the edited diagram once you change it; edits are kept only in that browser), and an arrow animation that stays off until you turn it on. Its look is a style
|
|
419
|
+
Before you pick, the session shows the brainstorm as a review page: `feature-flow brainstorm-page <feature>` (or `flow-status.py <feature> --brainstorm-page`) writes it beside the brainstorm as one HTML file. It has a sidebar that routes between the sections and shows how far you read, a system, light or dark switch, flowchart diagrams you can edit (arrange them left to right, top to bottom, radial or as a grid, drag, add boxes of seven kinds, colour them from 12 colours, rename and delete them, connect them with arrows, undo and redo, with the keyboard too), reset, and copy back as mermaid (the edited diagram once you change it; edits are kept only in that browser), and an arrow animation that stays off until you turn it on. Its look is a style: `route` (the default), `blueprint`, `sketch`, `paper`, `terminal` or `bright`, picked with `--style`, the brainstorm's `Style:` line or `FLOW_STYLE`; the session picks the one that fits the subject unless you name one, and colours boxes by role. A style is one CSS file of colour, font and box tokens in Forkboard's [themes/](packages/forkboard/forkboard/themes/), so adding one is adding a file. `--fragment` leaves out the html and body tags, for publishing the page as an artifact.
|
|
420
|
+
|
|
421
|
+

|
|
422
|
+
|
|
423
|
+
What the page is good at:
|
|
424
|
+
|
|
425
|
+
- **You review the design, not a wall of text.** Each option is a diagram you can see at once, with its pros, cons, size and risky areas right under it, and the sidebar jumps between options.
|
|
426
|
+
- **You answer by editing the diagram itself.** Drag, add, rename, connect or delete boxes, then Copy as mermaid and paste it into the chat. Your change reaches the session as a diagram, not a paraphrase.
|
|
427
|
+
- **More than one person can review it.** On the live page each reviewer keeps their own version of a diagram, compare with original marks what they added, removed or renamed, and their questions wake the session, which answers on the page.
|
|
428
|
+
- **One file, nothing to install or host.** The plain page is a single HTML file that needs no server and no account: open it from disk, or publish it as an artifact. It sends nothing anywhere (it only loads its fonts, and mermaid for a diagram that is not a flowchart), and the brainstorm's text is escaped, so a label cannot run a script.
|
|
429
|
+
- **Its own package, tested in a browser.** The page's editor, its phone layout and the live page's saves are driven in Chromium in CI, apart from the harness.
|
|
430
|
+
|
|
431
|
+
The page itself is **Forkboard** ([packages/forkboard/](packages/forkboard/)), a separate package in this repository with its own version and tests. feature-flow turns the brainstorm into a review document and Forkboard makes the page from it; Forkboard never reads or writes the brainstorm file. Every install of feature-flow carries its copy of Forkboard (as `feature_flow/_forkboard/`), so nothing extra needs installing.
|
|
406
432
|
|
|
407
433
|
**The live page (Claude only).** When the session can publish artifacts, it publishes the page live instead (`--live <folder>` writes `index.html` and a `state.json` beside it). Everyone you share it with (a claude.ai sign-in and edit access, no Claude Code needed) can save their own version of each diagram, pick between the original and anyone's version, turn on **Compare with original** to see added, removed and renamed boxes and arrows marked, ask Claude in the page to change a diagram (it uses that person's own Claude), and send the session questions from **Talk to Claude**. A save wakes the session: it reads the page's `state.json`, lists the open asks (`--asks`), answers each one (`--answer`), puts a version into the brainstorm when asked (`--apply <folder> <figure> <version>`), and publishes the next round to the same link. `/feature-flow <feature> brainstorm sketch` starts from your own drawing: the page opens with a small sketch of the idea, you draw yours and send it, and the options build on it. Without an Artifact tool (or on Codex) you get the plain page.
|
|
408
434
|
|
|
435
|
+
[docs/brainstorm.md](docs/brainstorm.md) walks through a brainstorm step by step, how to use the page, and Forkboard's own commands.
|
|
436
|
+
|
|
409
437
|
The result is `brainstorm.md` ([template](guides/templates/brainstorm.md)), committed with the plan. The planner follows its chosen option and copies its diagram into `spec.md`, the plan reviewer fails a plan that builds a rejected option or leaves a risky area below level 3, and a re-plan keeps the choice. It is skipped in `fast`, for an import, and with `auto`; with `agent`, the driving agent picks, the file says `Picked by: agent`, and the eval records it.
|
|
410
438
|
|
|
411
439
|
## Starting from Spec Kit or OpenSpec
|
|
@@ -457,6 +485,7 @@ feature-flow eval <feature> [--later] record the run in plan
|
|
|
457
485
|
python3 scripts/flow-status.py <feature> --mermaid the ticket graph, coloured by status (add "plain" for none)
|
|
458
486
|
python3 scripts/flow-status.py <feature> --json every ticket with status, blockers and readiness
|
|
459
487
|
python3 scripts/flow-view.py <feature> [--watch] the animated graph page, opened in your browser
|
|
488
|
+
feature-flow brainstorm-page <feature> [--style NAME] [--fragment] the brainstorm as a review page (Forkboard); --live DIR, --asks, --answer, --apply for the live page
|
|
460
489
|
python3 scripts/gate.py <feature> run Build, Test and Lint from commands.md
|
|
461
490
|
python3 scripts/floor-guard.py <feature> <NN> [base] check a ticket's diff, run from the repo root
|
|
462
491
|
```
|
|
@@ -478,6 +507,7 @@ The commands in `commands.md` run through `bash -c` on Linux and macOS and throu
|
|
|
478
507
|
| `FLOW_BUDGET` | unset | read by `start`: minutes of active work before fast mode turns on, then a re-plan offer; `off` drops it |
|
|
479
508
|
| `FLOW_BATCH` | `5` | fast mode: most small tickets one builder takes at once; `1` turns batches off |
|
|
480
509
|
| `FLOW_SCOUTS` | `on` | `off` lists no scouts before any phase |
|
|
510
|
+
| `FLOW_CODEINTEL` | `auto` | `off` keeps codeintel out of the scout's and reviewer's prompts; `auto` adds it when `codeintel doctor` reports an engine ready |
|
|
481
511
|
| `FLOW_TAKEOVER` | unset | `1` lets `start` take a feature over from a session that is gone |
|
|
482
512
|
| `FLOW_MAX_RETRIES` | `2` | builds or reviews per step before the run stops |
|
|
483
513
|
| `FLOW_MAX_REVIEW_ROUNDS` | `3` | times a ticket may be sent back before the run stops (a level 1 ticket: once) |
|
|
@@ -490,6 +520,7 @@ The commands in `commands.md` run through `bash -c` on Linux and macOS and throu
|
|
|
490
520
|
| `FLOW_DIR`, `FLOW_TICKETS` | `plans`, `tasks` | where the plans and the ticket folder live |
|
|
491
521
|
| `FLOW_NO_OPEN` | unset | `1` makes `flow-view.py` never open a browser |
|
|
492
522
|
| `FLOW_WATCH_SECONDS` | `3` | how often `flow-view.py --watch` rewrites the page |
|
|
523
|
+
| `FLOW_STYLE` | `route` | the brainstorm page's look when the brainstorm has no `Style:` line: `route`, `blueprint`, `sketch`, `paper`, `terminal` or `bright` (Forkboard's themes) |
|
|
493
524
|
|
|
494
525
|
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, a single review counting as one) that grows with the ticket count, recomputed at every pick, so a loop of failures cannot go on forever.
|
|
495
526
|
|
|
@@ -517,12 +548,16 @@ Then `cd` into it, start the agent and type `/feature-flow hello` (Codex: `$feat
|
|
|
517
548
|
```bash
|
|
518
549
|
bash tests/run.sh
|
|
519
550
|
python3 -m unittest discover -s tests/py
|
|
551
|
+
python3 -m unittest discover -s packages/forkboard/tests # Forkboard, the brainstorm page
|
|
552
|
+
(cd packages/forkboard/tests/browser && npm install && npm test) # the page in Chromium
|
|
520
553
|
```
|
|
521
554
|
|
|
522
555
|
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.
|
|
523
556
|
|
|
524
557
|
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`.
|
|
525
558
|
|
|
559
|
+
Two jobs test Forkboard ([packages/forkboard/](packages/forkboard/)) on its own: its unit tests and a build-and-install smoke test on Ubuntu, macOS and Windows, and the page driven in Chromium through Playwright (the diagram editor, no sideways scroll at phone width, and a live-page save that clashes with someone else's, reloads and saves again).
|
|
560
|
+
|
|
526
561
|
`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`.
|
|
527
562
|
|
|
528
563
|
## Limits
|
feature_flow_cli-0.25.0/feature_flow_cli.egg-info/PKG-INFO → feature_flow_cli-0.28.0/README.md
RENAMED
|
@@ -1,22 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: feature-flow-cli
|
|
3
|
-
Version: 0.25.0
|
|
4
|
-
Summary: A ticket-graph workflow for coding agents: installs the feature-flow skill into a repo
|
|
5
|
-
License-Expression: MIT
|
|
6
|
-
Project-URL: Homepage, https://github.com/hamilton-sky/feature-flow
|
|
7
|
-
Project-URL: Source, https://github.com/hamilton-sky/feature-flow
|
|
8
|
-
Keywords: claude-code,codex,agents,workflow,tickets
|
|
9
|
-
Classifier: Development Status :: 3 - Alpha
|
|
10
|
-
Classifier: Environment :: Console
|
|
11
|
-
Classifier: Intended Audience :: Developers
|
|
12
|
-
Classifier: Operating System :: OS Independent
|
|
13
|
-
Classifier: Programming Language :: Python :: 3
|
|
14
|
-
Classifier: Topic :: Software Development
|
|
15
|
-
Requires-Python: >=3.9
|
|
16
|
-
Description-Content-Type: text/markdown
|
|
17
|
-
License-File: LICENSE
|
|
18
|
-
Dynamic: license-file
|
|
19
|
-
|
|
20
1
|
# Feature Flow
|
|
21
2
|
|
|
22
3
|
**A ticket-graph workflow for coding agents.** Works with Claude Code and Codex.
|
|
@@ -42,7 +23,7 @@ One skill, 12 agent roles, a Python conductor, 6 Python scripts (`flow`, `flow-t
|
|
|
42
23
|
[docs/releases.md](docs/releases.md) lists what each release added.
|
|
43
24
|
|
|
44
25
|
- [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) · [See the graph](#see-the-graph)
|
|
45
|
-
- [Lanes](#tickets-side-by-side-lanes) · [Scouts](#scouts) · [Questions](#questions-for-the-architect-designer-or-product-owner) · [Acceptance tests](#acceptance-tests-the-tester) · [Fast path](#the-fast-path) · [Time budget](#a-time-budget) · [Eval](#recording-a-run-eval) · [Profiles](#profiles) · [Brainstorm](#brainstorm-before-planning) · [Import](#starting-from-spec-kit-or-openspec)
|
|
26
|
+
- [Lanes](#tickets-side-by-side-lanes) · [Scouts](#scouts) · [codeintel](#codeintel-optional) · [Questions](#questions-for-the-architect-designer-or-product-owner) · [Acceptance tests](#acceptance-tests-the-tester) · [Fast path](#the-fast-path) · [Time budget](#a-time-budget) · [Eval](#recording-a-run-eval) · [Profiles](#profiles) · [Brainstorm](#brainstorm-before-planning) · [Import](#starting-from-spec-kit-or-openspec)
|
|
46
27
|
- [Long features and handoff](#long-features-and-handoff) · [Commands and settings](#commands-and-settings) · [What is tested](#what-is-tested) · [Limits](#limits) · [Upgrading](#upgrading) · [Caution](#caution)
|
|
47
28
|
|
|
48
29
|
## Quick start
|
|
@@ -332,6 +313,17 @@ Before a planner, builder or reviewer starts, the session can spawn cheap helper
|
|
|
332
313
|
|
|
333
314
|
`scouts [lane]` lists `SCOUT <k> <agent> <reply file>` lines (or `NONE`) and clears the last ones' replies; `scout-prompt <k> [lane]` prints one scout's prompt. The session saves each reply into its file, and the phase's `prompt` adds them under `## Scout findings (unverified)`, cut to 60 lines each. A reply saved for one phase never reaches another. Retries and later rounds get no scouts. `FLOW_SCOUTS=off` turns them off.
|
|
334
315
|
|
|
316
|
+
## codeintel (optional)
|
|
317
|
+
|
|
318
|
+
[codeintel](https://github.com/hamilton-sky/codeintel) is a separate code-intelligence tool: one command answers who calls a function, what a change would break, or where the code that does something lives, from a call graph, a language server and semantic search. feature-flow never needs it. When it is installed and has the repository indexed, two roles get a short `## codeintel` section in their prompt:
|
|
319
|
+
|
|
320
|
+
- **Scouts** (`flow-scout`, not `flow-web`) are told to ask it `callers`, `impact` or `search` where grep would take several tries.
|
|
321
|
+
- **Reviewers** (every ticket review and the feature review) are told to run `codeintel query --op changed --target <base>`, which lists each function the change removed, re-signed or rewrote with the callers it did not update.
|
|
322
|
+
|
|
323
|
+
Each of them ends its reply with one line per question, `codeintel: <op> <target>: helped | no answer | wrong: <why>`, and is told to check an answer before relying on it and to fall back to grep. `eval` counts the queries and those lines, and the verdict form asks whether codeintel helped, made no difference or misled the run, with a line for an answer it got wrong: material for a codeintel issue.
|
|
324
|
+
|
|
325
|
+
To set it up: `pip install "feature-flow-cli[codeintel]"` (or `pip install codecortex`), then `codeintel setup --all .` in the repository. feature-flow asks `codeintel doctor --json` which engines are ready and keeps the answer for an hour in `.feature-flow/state/codeintel.json`; with nothing ready, or no `codeintel` on PATH, the prompts are as before. `FLOW_CODEINTEL=off` leaves it out. In a lane, the agents run it in the lane's worktree, where codeintel may not have an index, and then fall back to grep. The two tools are released on their own and neither imports the other.
|
|
326
|
+
|
|
335
327
|
## Questions for the architect, designer or product owner
|
|
336
328
|
|
|
337
329
|
A builder, reviewer or planner that hits a question it should not decide alone asks it instead of guessing, and the flow pauses that job until the right role answers.
|
|
@@ -377,6 +369,7 @@ Every finished run can leave a record of what it cost and what it caught, so a t
|
|
|
377
369
|
- **`plans/<feature>/eval.json`**: tickets, builds, reviews and rework; tickets settled when the plan was approved and never built; every catch by the check that made it (gate, floor guard, Done when, test first, footprint, reviewer, feature review); the follow-ups in `followups.md` and the deferred checks; who drove the run (`driver`: `person` or `agent`); where the run needed its driver (stops, questions, handoffs, resets); active minutes; and tokens.
|
|
378
370
|
- **`plans/<feature>/followups.md`**: what the run left for later, committed as it goes: the `minor` findings of every passing review, the findings a level 2 ticket passed with after its full rounds, and each `needs` check (below) that could not pass on this host.
|
|
379
371
|
- **Tokens** come from Claude Code's own session transcripts (`~/.claude/projects/`, or `CLAUDE_CONFIG_DIR`), never from the agent: every message of the sessions and subagents that worked in this repo during the run, each counted once, by model and, without lanes, split per ticket by time. Codex sessions are not read yet.
|
|
372
|
+
- **codeintel**: when an agent asked [codeintel](#codeintel-optional), `codeintel` in `eval.json` counts the queries by op and what the agents said of the answers (helped, no answer, wrong, and why), and the form below asks whether it helped.
|
|
380
373
|
- **`plans/<feature>/eval-verdict.md`**: a form for you. Was the plan right, how many fixes you made by hand, would you use it again, and for each catch whether it was real or a false alarm. Run `--eval` again and your answers land in `eval.json`. The false-alarm count is the only way to tell whether the checks are worth their cost.
|
|
381
374
|
- **Later:** `feature-flow eval <feature> --later`, a week or two on, lists the commits since the run whose subject says fix, revert, bug or regression on the paths each ticket's `Touches:` names: the defects the run let through.
|
|
382
375
|
|
|
@@ -402,10 +395,24 @@ When an idea is still fuzzy, the session can brainstorm it with you before it wr
|
|
|
402
395
|
|
|
403
396
|
Read only helpers work for the session during a brainstorm: a code scout, a web scout, the architect, designer and product owner, `flow-skeptic`, who attacks each finished option so the session that wrote the options is not the one that judges them, and `flow-lens`, who looks at the options through one lens the session picks for the idea: `positive`, `researcher`, `open-minded`, `artistic`, `ux` or `security` ([guides/lenses/](guides/lenses/)). Each gets its prompt from `brainstorm-prompt <helper> <question file>`, which counts them: `normal` allows 2 scouts, 1 web, 2 consultants, 1 skeptic and 2 lenses per brainstorm, `careful` 3, 2, 3, 1 and 3, and `FLOW_SCOUTS=off` no scouts. Helpers never write the brainstorm and never ask you anything; the session does both.
|
|
404
397
|
|
|
405
|
-
Before you pick, the session shows the brainstorm as a review page: `feature-flow brainstorm-page <feature>` (or `flow-status.py <feature> --brainstorm-page`) writes it beside the brainstorm as one HTML file. It has a sidebar that routes between the sections and shows how far you read, a system, light or dark switch, flowchart diagrams you can edit (drag, add boxes of seven kinds, colour them from 12 colours, rename and delete them, connect them with arrows, undo and redo, with the keyboard too), reset, and copy back as mermaid (the edited diagram once you change it; edits are kept only in that browser), and an arrow animation that stays off until you turn it on. Its look is a style
|
|
398
|
+
Before you pick, the session shows the brainstorm as a review page: `feature-flow brainstorm-page <feature>` (or `flow-status.py <feature> --brainstorm-page`) writes it beside the brainstorm as one HTML file. It has a sidebar that routes between the sections and shows how far you read, a system, light or dark switch, flowchart diagrams you can edit (arrange them left to right, top to bottom, radial or as a grid, drag, add boxes of seven kinds, colour them from 12 colours, rename and delete them, connect them with arrows, undo and redo, with the keyboard too), reset, and copy back as mermaid (the edited diagram once you change it; edits are kept only in that browser), and an arrow animation that stays off until you turn it on. Its look is a style: `route` (the default), `blueprint`, `sketch`, `paper`, `terminal` or `bright`, picked with `--style`, the brainstorm's `Style:` line or `FLOW_STYLE`; the session picks the one that fits the subject unless you name one, and colours boxes by role. A style is one CSS file of colour, font and box tokens in Forkboard's [themes/](packages/forkboard/forkboard/themes/), so adding one is adding a file. `--fragment` leaves out the html and body tags, for publishing the page as an artifact.
|
|
399
|
+
|
|
400
|
+

|
|
401
|
+
|
|
402
|
+
What the page is good at:
|
|
403
|
+
|
|
404
|
+
- **You review the design, not a wall of text.** Each option is a diagram you can see at once, with its pros, cons, size and risky areas right under it, and the sidebar jumps between options.
|
|
405
|
+
- **You answer by editing the diagram itself.** Drag, add, rename, connect or delete boxes, then Copy as mermaid and paste it into the chat. Your change reaches the session as a diagram, not a paraphrase.
|
|
406
|
+
- **More than one person can review it.** On the live page each reviewer keeps their own version of a diagram, compare with original marks what they added, removed or renamed, and their questions wake the session, which answers on the page.
|
|
407
|
+
- **One file, nothing to install or host.** The plain page is a single HTML file that needs no server and no account: open it from disk, or publish it as an artifact. It sends nothing anywhere (it only loads its fonts, and mermaid for a diagram that is not a flowchart), and the brainstorm's text is escaped, so a label cannot run a script.
|
|
408
|
+
- **Its own package, tested in a browser.** The page's editor, its phone layout and the live page's saves are driven in Chromium in CI, apart from the harness.
|
|
409
|
+
|
|
410
|
+
The page itself is **Forkboard** ([packages/forkboard/](packages/forkboard/)), a separate package in this repository with its own version and tests. feature-flow turns the brainstorm into a review document and Forkboard makes the page from it; Forkboard never reads or writes the brainstorm file. Every install of feature-flow carries its copy of Forkboard (as `feature_flow/_forkboard/`), so nothing extra needs installing.
|
|
406
411
|
|
|
407
412
|
**The live page (Claude only).** When the session can publish artifacts, it publishes the page live instead (`--live <folder>` writes `index.html` and a `state.json` beside it). Everyone you share it with (a claude.ai sign-in and edit access, no Claude Code needed) can save their own version of each diagram, pick between the original and anyone's version, turn on **Compare with original** to see added, removed and renamed boxes and arrows marked, ask Claude in the page to change a diagram (it uses that person's own Claude), and send the session questions from **Talk to Claude**. A save wakes the session: it reads the page's `state.json`, lists the open asks (`--asks`), answers each one (`--answer`), puts a version into the brainstorm when asked (`--apply <folder> <figure> <version>`), and publishes the next round to the same link. `/feature-flow <feature> brainstorm sketch` starts from your own drawing: the page opens with a small sketch of the idea, you draw yours and send it, and the options build on it. Without an Artifact tool (or on Codex) you get the plain page.
|
|
408
413
|
|
|
414
|
+
[docs/brainstorm.md](docs/brainstorm.md) walks through a brainstorm step by step, how to use the page, and Forkboard's own commands.
|
|
415
|
+
|
|
409
416
|
The result is `brainstorm.md` ([template](guides/templates/brainstorm.md)), committed with the plan. The planner follows its chosen option and copies its diagram into `spec.md`, the plan reviewer fails a plan that builds a rejected option or leaves a risky area below level 3, and a re-plan keeps the choice. It is skipped in `fast`, for an import, and with `auto`; with `agent`, the driving agent picks, the file says `Picked by: agent`, and the eval records it.
|
|
410
417
|
|
|
411
418
|
## Starting from Spec Kit or OpenSpec
|
|
@@ -457,6 +464,7 @@ feature-flow eval <feature> [--later] record the run in plan
|
|
|
457
464
|
python3 scripts/flow-status.py <feature> --mermaid the ticket graph, coloured by status (add "plain" for none)
|
|
458
465
|
python3 scripts/flow-status.py <feature> --json every ticket with status, blockers and readiness
|
|
459
466
|
python3 scripts/flow-view.py <feature> [--watch] the animated graph page, opened in your browser
|
|
467
|
+
feature-flow brainstorm-page <feature> [--style NAME] [--fragment] the brainstorm as a review page (Forkboard); --live DIR, --asks, --answer, --apply for the live page
|
|
460
468
|
python3 scripts/gate.py <feature> run Build, Test and Lint from commands.md
|
|
461
469
|
python3 scripts/floor-guard.py <feature> <NN> [base] check a ticket's diff, run from the repo root
|
|
462
470
|
```
|
|
@@ -478,6 +486,7 @@ The commands in `commands.md` run through `bash -c` on Linux and macOS and throu
|
|
|
478
486
|
| `FLOW_BUDGET` | unset | read by `start`: minutes of active work before fast mode turns on, then a re-plan offer; `off` drops it |
|
|
479
487
|
| `FLOW_BATCH` | `5` | fast mode: most small tickets one builder takes at once; `1` turns batches off |
|
|
480
488
|
| `FLOW_SCOUTS` | `on` | `off` lists no scouts before any phase |
|
|
489
|
+
| `FLOW_CODEINTEL` | `auto` | `off` keeps codeintel out of the scout's and reviewer's prompts; `auto` adds it when `codeintel doctor` reports an engine ready |
|
|
481
490
|
| `FLOW_TAKEOVER` | unset | `1` lets `start` take a feature over from a session that is gone |
|
|
482
491
|
| `FLOW_MAX_RETRIES` | `2` | builds or reviews per step before the run stops |
|
|
483
492
|
| `FLOW_MAX_REVIEW_ROUNDS` | `3` | times a ticket may be sent back before the run stops (a level 1 ticket: once) |
|
|
@@ -490,6 +499,7 @@ The commands in `commands.md` run through `bash -c` on Linux and macOS and throu
|
|
|
490
499
|
| `FLOW_DIR`, `FLOW_TICKETS` | `plans`, `tasks` | where the plans and the ticket folder live |
|
|
491
500
|
| `FLOW_NO_OPEN` | unset | `1` makes `flow-view.py` never open a browser |
|
|
492
501
|
| `FLOW_WATCH_SECONDS` | `3` | how often `flow-view.py --watch` rewrites the page |
|
|
502
|
+
| `FLOW_STYLE` | `route` | the brainstorm page's look when the brainstorm has no `Style:` line: `route`, `blueprint`, `sketch`, `paper`, `terminal` or `bright` (Forkboard's themes) |
|
|
493
503
|
|
|
494
504
|
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, a single review counting as one) that grows with the ticket count, recomputed at every pick, so a loop of failures cannot go on forever.
|
|
495
505
|
|
|
@@ -517,12 +527,16 @@ Then `cd` into it, start the agent and type `/feature-flow hello` (Codex: `$feat
|
|
|
517
527
|
```bash
|
|
518
528
|
bash tests/run.sh
|
|
519
529
|
python3 -m unittest discover -s tests/py
|
|
530
|
+
python3 -m unittest discover -s packages/forkboard/tests # Forkboard, the brainstorm page
|
|
531
|
+
(cd packages/forkboard/tests/browser && npm install && npm test) # the page in Chromium
|
|
520
532
|
```
|
|
521
533
|
|
|
522
534
|
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.
|
|
523
535
|
|
|
524
536
|
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`.
|
|
525
537
|
|
|
538
|
+
Two jobs test Forkboard ([packages/forkboard/](packages/forkboard/)) on its own: its unit tests and a build-and-install smoke test on Ubuntu, macOS and Windows, and the page driven in Chromium through Playwright (the diagram editor, no sideways scroll at phone width, and a live-page save that clashes with someone else's, reloads and saves again).
|
|
539
|
+
|
|
526
540
|
`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`.
|
|
527
541
|
|
|
528
542
|
## Limits
|
|
@@ -15,15 +15,15 @@ The conductor is `python3 scripts/flow.py <feature> <command>`. If `scripts/flow
|
|
|
15
15
|
|
|
16
16
|
Every conductor command (`start`, `next`, `prompt`, `verdict`, `fast`, `scouts`, `scout-prompt`, `reset`, `plan-prompt`, `plan-review-prompt`, `plan-accept`, `plan-scouts`, `plan-scout-prompt`, `consult-prompt`, `brainstorm-prompt`, `budget`, `replan-prompt`) 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
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
|
|
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 a561cb2bf1d8d8970e45065f24a736a02d9f803f09d652a26ca51b579f44cf7b
|
|
19
19
|
|
|
20
20
|
A full conductor command, here `next` (any other command and its arguments go where `next` is):
|
|
21
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
|
|
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 a561cb2bf1d8d8970e45065f24a736a02d9f803f09d652a26ca51b579f44cf7b <feature> next
|
|
23
23
|
|
|
24
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
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
|
|
26
|
+
- On the `next` right after a `BUILD <ticket> <NN> <sha>`, add `--after-build <ticket> <sha>` before the feature: `... <S>/flow-trust.py a561cb2bf1d8d8970e45065f24a736a02d9f803f09d652a26ca51b579f44cf7b --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
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
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
29
|
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""feature-flow brainstorm-page <feature>: the brainstorm as a review page, for the user to read before they pick.
|
|
2
|
+
|
|
3
|
+
usage: feature-flow brainstorm-page <feature> [--style NAME] [--fragment] [--out FILE]
|
|
4
|
+
feature-flow brainstorm-page <feature> --live DIR | --asks STATE | --answer STATE <n> <text> | --apply STATE <figure> <version>
|
|
5
|
+
Reads plans/<feature>/brainstorm.md, or .feature-flow/state/brainstorm-<feature>.md before the plan exists, turns
|
|
6
|
+
it into a review document and has Forkboard (packages/forkboard/, found by feature_flow/studio.py) make the page.
|
|
7
|
+
The page goes beside the brainstorm (same name, .html). The look is a Forkboard theme: --style, else the brainstorm's
|
|
8
|
+
`Style:` line, else FLOW_STYLE, else route. --fragment leaves out the doctype, html, head and body tags, for a host
|
|
9
|
+
that adds its own (an Artifact). --live writes DIR/index.html and DIR/state.json to publish as an artifact that
|
|
10
|
+
saves itself; --asks, --answer and --apply are the session's side of it, and --apply is the one part that writes
|
|
11
|
+
the brainstorm: Forkboard never touches it.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import re
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from feature_flow import git, studio
|
|
20
|
+
|
|
21
|
+
USAGE = ("usage: feature-flow brainstorm-page <feature> [--style NAME] [--fragment] [--out FILE]\n"
|
|
22
|
+
" feature-flow brainstorm-page <feature> --live DIR | --asks STATE | --answer STATE <n> <text> | --apply STATE <figure> <version>")
|
|
23
|
+
DEFAULT_STYLE = "route"
|
|
24
|
+
FENCE = re.compile(r"^```mermaid[ \t]*\n(.*?)^```[ \t]*$", re.S | re.M)
|
|
25
|
+
KINDS = {"chosen": "chosen", "rejected": "rejected", "open": "open", "risks for the planner": "callout"}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def source(top, feature):
|
|
29
|
+
"""The brainstorm to show: the committed one once the plan exists, else the one in the state folder."""
|
|
30
|
+
for path in (Path(top) / os.environ.get("FLOW_DIR", "plans") / feature / "brainstorm.md",
|
|
31
|
+
Path(top) / ".feature-flow" / "state" / ("brainstorm-%s.md" % feature)):
|
|
32
|
+
if path.is_file():
|
|
33
|
+
return path
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def parse(text):
|
|
38
|
+
"""(title, meta {key: value}, intro lines, [(heading, lines, [(subheading, lines)])])."""
|
|
39
|
+
title, meta, intro, sections = "", {}, [], []
|
|
40
|
+
for line in text.splitlines():
|
|
41
|
+
if line.startswith("# ") and not title and not sections:
|
|
42
|
+
title = line[2:].strip()
|
|
43
|
+
elif line.startswith("## "):
|
|
44
|
+
sections.append((line[3:].strip(), [], []))
|
|
45
|
+
elif line.startswith("### ") and sections:
|
|
46
|
+
sections[-1][2].append((line[4:].strip(), []))
|
|
47
|
+
elif sections:
|
|
48
|
+
(sections[-1][2][-1][1] if sections[-1][2] else sections[-1][1]).append(line)
|
|
49
|
+
else:
|
|
50
|
+
m = re.match(r"^([A-Z][A-Za-z ]{1,20}):\s*(.+)$", line)
|
|
51
|
+
if m and not any(l.strip() for l in intro):
|
|
52
|
+
meta[m.group(1).strip().lower()] = m.group(2).strip()
|
|
53
|
+
else:
|
|
54
|
+
intro.append(line)
|
|
55
|
+
return title, meta, intro, sections
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def document(text, feature):
|
|
59
|
+
"""The brainstorm as a Forkboard review document: which section is Chosen, Rejected or Open, and which option
|
|
60
|
+
the Chosen line picks, are the brainstorm's own conventions (guides/brainstorm.md), so they are read here."""
|
|
61
|
+
title, meta, intro, sections = parse(text)
|
|
62
|
+
chosen = ""
|
|
63
|
+
for heading, lines, subs in sections:
|
|
64
|
+
if heading.lower() == "chosen":
|
|
65
|
+
chosen = " ".join(l.strip() for l in lines if l.strip())[:200]
|
|
66
|
+
out = []
|
|
67
|
+
for heading, lines, subs in sections:
|
|
68
|
+
items = []
|
|
69
|
+
for sub, sub_lines in subs:
|
|
70
|
+
letter = re.match(r"^([A-Z])[.)]\s", sub)
|
|
71
|
+
items.append({"title": sub, "label": letter.group(1) if letter else "",
|
|
72
|
+
"chosen": bool(letter and re.match(r"^\W*%s\b" % letter.group(1), chosen)),
|
|
73
|
+
"markdown": "\n".join(sub_lines)})
|
|
74
|
+
out.append({"title": heading, "kind": KINDS.get(heading.lower(), "text"), "markdown": "\n".join(lines),
|
|
75
|
+
"items": items})
|
|
76
|
+
return {"schema": studio.MAJOR, "id": feature, "kind": "Brainstorm", "title": title, "meta": meta,
|
|
77
|
+
"intro": "\n".join(intro), "sections": out}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def apply(text, fb, st, figure, version):
|
|
81
|
+
"""The brainstorm text with figure `figure` replaced by that version of it, and what the rewrite dropped."""
|
|
82
|
+
graph, source_print = fb.live.version(st, figure, version)
|
|
83
|
+
blocks = list(FENCE.finditer(text))
|
|
84
|
+
if not str(figure).isdigit() or not 1 <= int(figure) <= len(blocks):
|
|
85
|
+
raise ValueError("the brainstorm file has %d diagrams, so no figure %s: build the page again" % (len(blocks), figure))
|
|
86
|
+
block = blocks[int(figure) - 1]
|
|
87
|
+
if source_print and fb.graph.fingerprint(block.group(1)) != source_print:
|
|
88
|
+
raise ValueError("figure %s in the brainstorm changed since the page was built: build the page again (--live), "
|
|
89
|
+
"publish it, and apply after the next save" % figure)
|
|
90
|
+
return text[:block.start(1)] + fb.graph.mermaid(graph) + "\n" + text[block.end(1):], fb.graph.lost(block.group(1))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def main(argv, out=None, err=None):
|
|
94
|
+
out = out or sys.stdout
|
|
95
|
+
err = err or sys.stderr
|
|
96
|
+
args, style, fragment, target, live, tool, extra = [], None, False, None, None, None, []
|
|
97
|
+
i = 0
|
|
98
|
+
while i < len(argv):
|
|
99
|
+
a = argv[i]
|
|
100
|
+
if a == "--fragment":
|
|
101
|
+
fragment = True
|
|
102
|
+
elif a in ("--live", "--asks") and i + 1 < len(argv) and tool is None and live is None:
|
|
103
|
+
live, tool = argv[i + 1], a[2:]
|
|
104
|
+
i += 1
|
|
105
|
+
elif a == "--answer" and i + 3 < len(argv) and tool is None:
|
|
106
|
+
live, tool, extra = argv[i + 1], "answer", argv[i + 2:i + 4]
|
|
107
|
+
i += 3
|
|
108
|
+
elif a == "--apply" and i + 3 < len(argv) and tool is None:
|
|
109
|
+
live, tool, extra = argv[i + 1], "apply", argv[i + 2:i + 4]
|
|
110
|
+
i += 3
|
|
111
|
+
elif a in ("--style", "--out") and i + 1 < len(argv):
|
|
112
|
+
if a == "--style":
|
|
113
|
+
style = argv[i + 1]
|
|
114
|
+
else:
|
|
115
|
+
target = argv[i + 1]
|
|
116
|
+
i += 1
|
|
117
|
+
elif a.startswith("-"):
|
|
118
|
+
err.write(USAGE + "\n")
|
|
119
|
+
return 2
|
|
120
|
+
else:
|
|
121
|
+
args.append(a)
|
|
122
|
+
i += 1
|
|
123
|
+
if len(args) != 1 or any(c in args[0] for c in "/\\:"):
|
|
124
|
+
err.write(USAGE + "\n")
|
|
125
|
+
return 2
|
|
126
|
+
feature = args[0]
|
|
127
|
+
top = git.toplevel() or os.getcwd()
|
|
128
|
+
path = source(top, feature)
|
|
129
|
+
if path is None:
|
|
130
|
+
err.write("no brainstorm for %s: write .feature-flow/state/brainstorm-%s.md first (brainstorm guide)\n"
|
|
131
|
+
% (feature, feature))
|
|
132
|
+
return 1
|
|
133
|
+
fb = studio.load()
|
|
134
|
+
if fb is None:
|
|
135
|
+
err.write(studio.MISSING)
|
|
136
|
+
return 1
|
|
137
|
+
text = path.read_text(encoding="utf-8", errors="replace")
|
|
138
|
+
if tool in ("asks", "answer", "apply"):
|
|
139
|
+
return live_tool(fb, tool, path, text, Path(live), extra, out, err)
|
|
140
|
+
names = fb.page.themes()
|
|
141
|
+
_, meta, _, _ = parse(text)
|
|
142
|
+
name = (style or meta.get("style") or os.environ.get("FLOW_STYLE") or DEFAULT_STYLE).strip().lower()
|
|
143
|
+
if name not in names:
|
|
144
|
+
err.write("no style %s: pick one of %s (Forkboard's themes)\n" % (name, ", ".join(names) or "none installed"))
|
|
145
|
+
return 1
|
|
146
|
+
css = fb.page.theme_css(name)
|
|
147
|
+
doc = document(text, feature)
|
|
148
|
+
if tool == "live": # the page and its state, to publish together as an artifact that saves itself
|
|
149
|
+
folder = Path(live)
|
|
150
|
+
folder.mkdir(parents=True, exist_ok=True)
|
|
151
|
+
st = fb.live.state(doc, fb.live.load(folder / "state.json"))
|
|
152
|
+
(folder / "index.html").write_text(fb.page.render(doc, css, name, True, True), encoding="utf-8")
|
|
153
|
+
(folder / "state.json").write_text(fb.live.dump(st), encoding="utf-8")
|
|
154
|
+
out.write("wrote %s and %s (round %d, %d figures, %d open asks)\n" % (
|
|
155
|
+
(folder / "index.html").as_posix(), (folder / "state.json").as_posix(), st["round"], len(st["figures"]),
|
|
156
|
+
sum(1 for a in st["asks"] if not a.get("answer"))))
|
|
157
|
+
return 0
|
|
158
|
+
dest = Path(target) if target else path.with_suffix(".html")
|
|
159
|
+
dest.write_text(fb.page.render(doc, css, name, fragment), encoding="utf-8")
|
|
160
|
+
out.write("wrote %s\n" % dest.as_posix())
|
|
161
|
+
return 0
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def live_tool(fb, tool, path, text, state_path, extra, out, err):
|
|
165
|
+
if state_path.is_dir():
|
|
166
|
+
state_path = state_path / "state.json"
|
|
167
|
+
st = fb.live.load(state_path)
|
|
168
|
+
if st is None:
|
|
169
|
+
err.write("no page state at %s: read the published page's state.json into it first (brainstorm guide)\n"
|
|
170
|
+
% state_path.as_posix())
|
|
171
|
+
return 1
|
|
172
|
+
try:
|
|
173
|
+
if tool == "asks":
|
|
174
|
+
out.write(fb.live.asks(st) + "\n")
|
|
175
|
+
elif tool == "answer":
|
|
176
|
+
if not extra[0].isdigit():
|
|
177
|
+
raise ValueError("the ask number must be a number, not %s" % extra[0])
|
|
178
|
+
state_path.write_text(fb.live.dump(fb.live.answer(st, int(extra[0]), extra[1])), encoding="utf-8")
|
|
179
|
+
out.write("answered ask %s in %s\n" % (extra[0], state_path.as_posix()))
|
|
180
|
+
else:
|
|
181
|
+
new, dropped = apply(text, fb, st, extra[0], extra[1])
|
|
182
|
+
path.write_text(new, encoding="utf-8")
|
|
183
|
+
if dropped:
|
|
184
|
+
err.write("note: figure %s had %s, which the page does not keep; they are gone from the brainstorm now\n"
|
|
185
|
+
% (extra[0], ", ".join(dropped)))
|
|
186
|
+
out.write("figure %s in %s is now version %s\n" % (extra[0], path.as_posix(), extra[1]))
|
|
187
|
+
except ValueError as e:
|
|
188
|
+
err.write("%s\n" % e)
|
|
189
|
+
return 1
|
|
190
|
+
return 0
|