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.
Files changed (112) hide show
  1. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/MANIFEST.in +2 -1
  2. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/PKG-INFO +38 -3
  3. feature_flow_cli-0.25.0/feature_flow_cli.egg-info/PKG-INFO → feature_flow_cli-0.28.0/README.md +35 -21
  4. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/adapters/codex/feature-flow/SKILL.md +3 -3
  5. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/__init__.py +1 -1
  6. feature_flow_cli-0.28.0/feature_flow/brainstorm_page.py +190 -0
  7. feature_flow_cli-0.28.0/feature_flow/codeintel.py +127 -0
  8. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/conductor.py +2 -2
  9. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/evaluate.py +68 -42
  10. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/install.py +8 -0
  11. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/prompts.py +4 -0
  12. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/scouts.py +7 -2
  13. feature_flow_cli-0.28.0/feature_flow/studio.py +47 -0
  14. feature_flow_cli-0.25.0/README.md → feature_flow_cli-0.28.0/feature_flow_cli.egg-info/PKG-INFO +56 -2
  15. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow_cli.egg-info/SOURCES.txt +20 -3
  16. feature_flow_cli-0.28.0/feature_flow_cli.egg-info/requires.txt +3 -0
  17. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/brainstorm.md +14 -1
  18. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/templates/brainstorm.md +7 -1
  19. feature_flow_cli-0.28.0/packages/forkboard/forkboard/__init__.py +8 -0
  20. feature_flow_cli-0.28.0/packages/forkboard/forkboard/__main__.py +3 -0
  21. feature_flow_cli-0.28.0/packages/forkboard/forkboard/assets/base.css +116 -0
  22. feature_flow_cli-0.28.0/packages/forkboard/forkboard/assets/editor.js +533 -0
  23. feature_flow_cli-0.28.0/packages/forkboard/forkboard/assets/live.css +28 -0
  24. 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
  25. feature_flow_cli-0.28.0/packages/forkboard/forkboard/cli.py +127 -0
  26. feature_flow_cli-0.28.0/packages/forkboard/forkboard/doc.py +35 -0
  27. feature_flow_cli-0.28.0/packages/forkboard/forkboard/graph.py +136 -0
  28. feature_flow_cli-0.28.0/packages/forkboard/forkboard/live.py +85 -0
  29. feature_flow_cli-0.28.0/packages/forkboard/forkboard/page.py +304 -0
  30. {feature_flow_cli-0.25.0/guides/styles → feature_flow_cli-0.28.0/packages/forkboard/forkboard/themes}/blueprint.css +1 -0
  31. feature_flow_cli-0.28.0/packages/forkboard/forkboard/themes/bright.css +29 -0
  32. feature_flow_cli-0.28.0/packages/forkboard/forkboard/themes/paper.css +35 -0
  33. {feature_flow_cli-0.25.0/guides/styles → feature_flow_cli-0.28.0/packages/forkboard/forkboard/themes}/route.css +1 -0
  34. feature_flow_cli-0.28.0/packages/forkboard/forkboard/themes/sketch.css +30 -0
  35. feature_flow_cli-0.28.0/packages/forkboard/forkboard/themes/terminal.css +30 -0
  36. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/pyproject.toml +7 -0
  37. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/scripts/flow-trust.py +1 -2
  38. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/skills/feature-flow/SKILL.md +3 -3
  39. feature_flow_cli-0.25.0/feature_flow/brainstorm_page.py +0 -1066
  40. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/LICENSE +0 -0
  41. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/adapters/codex/feature-flow/agents/openai.yaml +0 -0
  42. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/agents/feature-planner.md +0 -0
  43. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/agents/flow-architect.md +0 -0
  44. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/agents/flow-designer.md +0 -0
  45. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/agents/flow-lens.md +0 -0
  46. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/agents/flow-po.md +0 -0
  47. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/agents/flow-scout.md +0 -0
  48. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/agents/flow-skeptic.md +0 -0
  49. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/agents/flow-tester.md +0 -0
  50. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/agents/flow-web.md +0 -0
  51. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/agents/plan-reviewer.md +0 -0
  52. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/agents/ticket-builder.md +0 -0
  53. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/agents/ticket-reviewer.md +0 -0
  54. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/__main__.py +0 -0
  55. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/checks.py +0 -0
  56. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/cli.py +0 -0
  57. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/codehash.py +0 -0
  58. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/command.py +0 -0
  59. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/floorguard.py +0 -0
  60. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/gate.py +0 -0
  61. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/git.py +0 -0
  62. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/importer.py +0 -0
  63. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/lanes.py +0 -0
  64. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/proc.py +0 -0
  65. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/profiles.py +0 -0
  66. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/proof.py +0 -0
  67. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/released.py +0 -0
  68. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/report.py +0 -0
  69. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/state.py +0 -0
  70. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/status.py +0 -0
  71. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/suggest.py +0 -0
  72. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/tickets.py +0 -0
  73. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/uninstall.py +0 -0
  74. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow/view.py +0 -0
  75. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow_cli.egg-info/dependency_links.txt +0 -0
  76. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow_cli.egg-info/entry_points.txt +0 -0
  77. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/feature_flow_cli.egg-info/top_level.txt +0 -0
  78. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/brief.md +0 -0
  79. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/build.md +0 -0
  80. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/debug.md +0 -0
  81. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/lenses/artistic.md +0 -0
  82. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/lenses/open-minded.md +0 -0
  83. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/lenses/positive.md +0 -0
  84. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/lenses/researcher.md +0 -0
  85. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/lenses/security.md +0 -0
  86. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/lenses/ux.md +0 -0
  87. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/plan-review.md +0 -0
  88. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/plan.md +0 -0
  89. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/replan.md +0 -0
  90. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/review-feature.md +0 -0
  91. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/review-quality.md +0 -0
  92. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/review-single.md +0 -0
  93. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/review.md +0 -0
  94. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/show.md +0 -0
  95. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/templates/commands.md +0 -0
  96. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/templates/learnings.md +0 -0
  97. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/templates/map.md +0 -0
  98. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/templates/spec.md +0 -0
  99. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/templates/ticket.md +0 -0
  100. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/templates/ui-mockup.md +0 -0
  101. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/guides/test.md +0 -0
  102. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/install.py +0 -0
  103. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/install.sh +0 -0
  104. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/scripts/floor-guard.py +0 -0
  105. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/scripts/flow-status.py +0 -0
  106. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/scripts/flow-view.html +0 -0
  107. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/scripts/flow-view.py +0 -0
  108. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/scripts/flow.py +0 -0
  109. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/scripts/gate.py +0 -0
  110. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/setup.cfg +0 -0
  111. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/skills/architect-review/SKILL.md +0 -0
  112. {feature_flow_cli-0.25.0 → feature_flow_cli-0.28.0}/skills/automation-design/SKILL.md +0 -0
@@ -3,6 +3,7 @@ graft skills
3
3
  graft agents
4
4
  graft guides
5
5
  graft adapters/codex
6
+ graft packages/forkboard/forkboard
6
7
  include scripts/*.py scripts/*.html
7
8
  include LICENSE README.md install.py install.sh
8
- global-exclude .DS_Store
9
+ global-exclude .DS_Store *.pyc
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: feature-flow-cli
3
- Version: 0.25.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 from [guides/styles/](guides/styles/): `route` (the default) or `blueprint`, picked with `--style`, the brainstorm's `Style:` line or `FLOW_STYLE`. A style is one CSS file of colour and font tokens, so adding one is adding a file. `--fragment` leaves out the html and body tags, for publishing the page as an artifact.
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
+ ![A Forkboard review page for a rate-limits brainstorm: a sidebar with the sections and options, option A's diagram with the API gateway box selected and the editing tools above it, and its pros, cons, size and risky areas below](docs/forkboard.png)
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
@@ -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 from [guides/styles/](guides/styles/): `route` (the default) or `blueprint`, picked with `--style`, the brainstorm's `Style:` line or `FLOW_STYLE`. A style is one CSS file of colour and font tokens, so adding one is adding a file. `--fragment` leaves out the html and body tags, for publishing the page as an artifact.
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
+ ![A Forkboard review page for a rate-limits brainstorm: a sidebar with the sections and options, option A's diagram with the API gateway box selected and the editing tools above it, and its pros, cons, size and risky areas below](docs/forkboard.png)
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 9532117f7d6cb0fff2b8d9886de79f727641bc02399e8922a67346bdc229c04b
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 9532117f7d6cb0fff2b8d9886de79f727641bc02399e8922a67346bdc229c04b <feature> next
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 9532117f7d6cb0fff2b8d9886de79f727641bc02399e8922a67346bdc229c04b --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.
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
 
@@ -1,3 +1,3 @@
1
1
  """Feature Flow: a ticket-graph workflow for coding agents."""
2
2
 
3
- __version__ = "0.25.0"
3
+ __version__ = "0.28.0"
@@ -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