feature-flow-cli 0.25.0__tar.gz → 0.26.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 (106) hide show
  1. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/MANIFEST.in +2 -1
  2. {feature_flow_cli-0.25.0/feature_flow_cli.egg-info → feature_flow_cli-0.26.0}/PKG-INFO +22 -2
  3. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/README.md +21 -1
  4. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/adapters/codex/feature-flow/SKILL.md +3 -3
  5. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/__init__.py +1 -1
  6. feature_flow_cli-0.26.0/feature_flow/brainstorm_page.py +190 -0
  7. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/install.py +8 -0
  8. feature_flow_cli-0.26.0/feature_flow/studio.py +47 -0
  9. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0/feature_flow_cli.egg-info}/PKG-INFO +22 -2
  10. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow_cli.egg-info/SOURCES.txt +14 -3
  11. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/brainstorm.md +1 -1
  12. feature_flow_cli-0.26.0/packages/forkboard/forkboard/__init__.py +8 -0
  13. feature_flow_cli-0.26.0/packages/forkboard/forkboard/__main__.py +3 -0
  14. feature_flow_cli-0.26.0/packages/forkboard/forkboard/assets/base.css +114 -0
  15. feature_flow_cli-0.26.0/packages/forkboard/forkboard/assets/editor.js +476 -0
  16. feature_flow_cli-0.26.0/packages/forkboard/forkboard/assets/live.css +28 -0
  17. feature_flow_cli-0.25.0/feature_flow/brainstorm_live.py → feature_flow_cli-0.26.0/packages/forkboard/forkboard/assets/live.js +0 -187
  18. feature_flow_cli-0.26.0/packages/forkboard/forkboard/cli.py +124 -0
  19. feature_flow_cli-0.26.0/packages/forkboard/forkboard/doc.py +35 -0
  20. feature_flow_cli-0.26.0/packages/forkboard/forkboard/graph.py +136 -0
  21. feature_flow_cli-0.26.0/packages/forkboard/forkboard/live.py +85 -0
  22. feature_flow_cli-0.26.0/packages/forkboard/forkboard/page.py +274 -0
  23. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/pyproject.toml +3 -0
  24. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/scripts/flow-trust.py +1 -2
  25. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/skills/feature-flow/SKILL.md +3 -3
  26. feature_flow_cli-0.25.0/feature_flow/brainstorm_page.py +0 -1066
  27. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/LICENSE +0 -0
  28. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/adapters/codex/feature-flow/agents/openai.yaml +0 -0
  29. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/agents/feature-planner.md +0 -0
  30. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/agents/flow-architect.md +0 -0
  31. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/agents/flow-designer.md +0 -0
  32. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/agents/flow-lens.md +0 -0
  33. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/agents/flow-po.md +0 -0
  34. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/agents/flow-scout.md +0 -0
  35. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/agents/flow-skeptic.md +0 -0
  36. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/agents/flow-tester.md +0 -0
  37. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/agents/flow-web.md +0 -0
  38. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/agents/plan-reviewer.md +0 -0
  39. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/agents/ticket-builder.md +0 -0
  40. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/agents/ticket-reviewer.md +0 -0
  41. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/__main__.py +0 -0
  42. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/checks.py +0 -0
  43. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/cli.py +0 -0
  44. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/codehash.py +0 -0
  45. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/command.py +0 -0
  46. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/conductor.py +0 -0
  47. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/evaluate.py +0 -0
  48. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/floorguard.py +0 -0
  49. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/gate.py +0 -0
  50. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/git.py +0 -0
  51. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/importer.py +0 -0
  52. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/lanes.py +0 -0
  53. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/proc.py +0 -0
  54. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/profiles.py +0 -0
  55. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/prompts.py +0 -0
  56. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/proof.py +0 -0
  57. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/released.py +0 -0
  58. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/report.py +0 -0
  59. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/scouts.py +0 -0
  60. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/state.py +0 -0
  61. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/status.py +0 -0
  62. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/suggest.py +0 -0
  63. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/tickets.py +0 -0
  64. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/uninstall.py +0 -0
  65. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/view.py +0 -0
  66. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow_cli.egg-info/dependency_links.txt +0 -0
  67. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow_cli.egg-info/entry_points.txt +0 -0
  68. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow_cli.egg-info/top_level.txt +0 -0
  69. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/brief.md +0 -0
  70. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/build.md +0 -0
  71. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/debug.md +0 -0
  72. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/lenses/artistic.md +0 -0
  73. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/lenses/open-minded.md +0 -0
  74. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/lenses/positive.md +0 -0
  75. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/lenses/researcher.md +0 -0
  76. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/lenses/security.md +0 -0
  77. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/lenses/ux.md +0 -0
  78. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/plan-review.md +0 -0
  79. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/plan.md +0 -0
  80. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/replan.md +0 -0
  81. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/review-feature.md +0 -0
  82. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/review-quality.md +0 -0
  83. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/review-single.md +0 -0
  84. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/review.md +0 -0
  85. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/show.md +0 -0
  86. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/templates/brainstorm.md +0 -0
  87. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/templates/commands.md +0 -0
  88. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/templates/learnings.md +0 -0
  89. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/templates/map.md +0 -0
  90. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/templates/spec.md +0 -0
  91. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/templates/ticket.md +0 -0
  92. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/templates/ui-mockup.md +0 -0
  93. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/test.md +0 -0
  94. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/install.py +0 -0
  95. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/install.sh +0 -0
  96. {feature_flow_cli-0.25.0/guides/styles → feature_flow_cli-0.26.0/packages/forkboard/forkboard/themes}/blueprint.css +0 -0
  97. {feature_flow_cli-0.25.0/guides/styles → feature_flow_cli-0.26.0/packages/forkboard/forkboard/themes}/route.css +0 -0
  98. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/scripts/floor-guard.py +0 -0
  99. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/scripts/flow-status.py +0 -0
  100. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/scripts/flow-view.html +0 -0
  101. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/scripts/flow-view.py +0 -0
  102. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/scripts/flow.py +0 -0
  103. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/scripts/gate.py +0 -0
  104. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/setup.cfg +0 -0
  105. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/skills/architect-review/SKILL.md +0 -0
  106. {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.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.26.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
@@ -402,10 +402,24 @@ When an idea is still fuzzy, the session can brainstorm it with you before it wr
402
402
 
403
403
  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
404
 
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.
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: `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 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.
406
+
407
+ ![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)
408
+
409
+ What the page is good at:
410
+
411
+ - **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.
412
+ - **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.
413
+ - **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.
414
+ - **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.
415
+ - **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.
416
+
417
+ 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
418
 
407
419
  **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
420
 
421
+ [docs/brainstorm.md](docs/brainstorm.md) walks through a brainstorm step by step, how to use the page, and Forkboard's own commands.
422
+
409
423
  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
424
 
411
425
  ## Starting from Spec Kit or OpenSpec
@@ -457,6 +471,7 @@ feature-flow eval <feature> [--later] record the run in plan
457
471
  python3 scripts/flow-status.py <feature> --mermaid the ticket graph, coloured by status (add "plain" for none)
458
472
  python3 scripts/flow-status.py <feature> --json every ticket with status, blockers and readiness
459
473
  python3 scripts/flow-view.py <feature> [--watch] the animated graph page, opened in your browser
474
+ 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
475
  python3 scripts/gate.py <feature> run Build, Test and Lint from commands.md
461
476
  python3 scripts/floor-guard.py <feature> <NN> [base] check a ticket's diff, run from the repo root
462
477
  ```
@@ -490,6 +505,7 @@ The commands in `commands.md` run through `bash -c` on Linux and macOS and throu
490
505
  | `FLOW_DIR`, `FLOW_TICKETS` | `plans`, `tasks` | where the plans and the ticket folder live |
491
506
  | `FLOW_NO_OPEN` | unset | `1` makes `flow-view.py` never open a browser |
492
507
  | `FLOW_WATCH_SECONDS` | `3` | how often `flow-view.py --watch` rewrites the page |
508
+ | `FLOW_STYLE` | `route` | the brainstorm page's look when the brainstorm has no `Style:` line: `route` or `blueprint` (Forkboard's themes) |
493
509
 
494
510
  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
511
 
@@ -517,12 +533,16 @@ Then `cd` into it, start the agent and type `/feature-flow hello` (Codex: `$feat
517
533
  ```bash
518
534
  bash tests/run.sh
519
535
  python3 -m unittest discover -s tests/py
536
+ python3 -m unittest discover -s packages/forkboard/tests # Forkboard, the brainstorm page
537
+ (cd packages/forkboard/tests/browser && npm install && npm test) # the page in Chromium
520
538
  ```
521
539
 
522
540
  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
541
 
524
542
  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
543
 
544
+ 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).
545
+
526
546
  `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
547
 
528
548
  ## Limits
@@ -383,10 +383,24 @@ When an idea is still fuzzy, the session can brainstorm it with you before it wr
383
383
 
384
384
  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.
385
385
 
386
- 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.
386
+ 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: `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 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.
387
+
388
+ ![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)
389
+
390
+ What the page is good at:
391
+
392
+ - **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.
393
+ - **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.
394
+ - **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.
395
+ - **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.
396
+ - **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.
397
+
398
+ 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.
387
399
 
388
400
  **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.
389
401
 
402
+ [docs/brainstorm.md](docs/brainstorm.md) walks through a brainstorm step by step, how to use the page, and Forkboard's own commands.
403
+
390
404
  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.
391
405
 
392
406
  ## Starting from Spec Kit or OpenSpec
@@ -438,6 +452,7 @@ feature-flow eval <feature> [--later] record the run in plan
438
452
  python3 scripts/flow-status.py <feature> --mermaid the ticket graph, coloured by status (add "plain" for none)
439
453
  python3 scripts/flow-status.py <feature> --json every ticket with status, blockers and readiness
440
454
  python3 scripts/flow-view.py <feature> [--watch] the animated graph page, opened in your browser
455
+ feature-flow brainstorm-page <feature> [--style NAME] [--fragment] the brainstorm as a review page (Forkboard); --live DIR, --asks, --answer, --apply for the live page
441
456
  python3 scripts/gate.py <feature> run Build, Test and Lint from commands.md
442
457
  python3 scripts/floor-guard.py <feature> <NN> [base] check a ticket's diff, run from the repo root
443
458
  ```
@@ -471,6 +486,7 @@ The commands in `commands.md` run through `bash -c` on Linux and macOS and throu
471
486
  | `FLOW_DIR`, `FLOW_TICKETS` | `plans`, `tasks` | where the plans and the ticket folder live |
472
487
  | `FLOW_NO_OPEN` | unset | `1` makes `flow-view.py` never open a browser |
473
488
  | `FLOW_WATCH_SECONDS` | `3` | how often `flow-view.py --watch` rewrites the page |
489
+ | `FLOW_STYLE` | `route` | the brainstorm page's look when the brainstorm has no `Style:` line: `route` or `blueprint` (Forkboard's themes) |
474
490
 
475
491
  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.
476
492
 
@@ -498,12 +514,16 @@ Then `cd` into it, start the agent and type `/feature-flow hello` (Codex: `$feat
498
514
  ```bash
499
515
  bash tests/run.sh
500
516
  python3 -m unittest discover -s tests/py
517
+ python3 -m unittest discover -s packages/forkboard/tests # Forkboard, the brainstorm page
518
+ (cd packages/forkboard/tests/browser && npm install && npm test) # the page in Chromium
501
519
  ```
502
520
 
503
521
  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.
504
522
 
505
523
  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`.
506
524
 
525
+ 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).
526
+
507
527
  `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`.
508
528
 
509
529
  ## 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.26.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
@@ -205,6 +205,10 @@ class Installer:
205
205
  if not os.path.isfile(package + "/__init__.py"):
206
206
  package = os.path.dirname(os.path.abspath(__file__))
207
207
  self.package = package
208
+ # Forkboard, the brainstorm's review page: packages/forkboard/ in a checkout; an installed package has it
209
+ # inside already, as feature_flow/_forkboard/, and copies it with the rest
210
+ forkboard = here + "/packages/forkboard/forkboard"
211
+ self.forkboard = forkboard if os.path.isfile(forkboard + "/__init__.py") else None
208
212
  self.added = self.updated = self.same = self.kept = 0
209
213
  self.written = {}
210
214
  # each folder this installs into, with its hash record; the conductor's own folder comes first
@@ -469,11 +473,15 @@ class Installer:
469
473
  self.copy_tree(here + "/guides", self.flow_dir + "/guides")
470
474
  self.copy_tree(here + "/agents", self.flow_dir + "/agents")
471
475
  self.copy_tree(self.package, self.flow_dir + "/feature_flow", skip=BUNDLE)
476
+ if self.forkboard:
477
+ self.copy_tree(self.forkboard, self.flow_dir + "/feature_flow" + "/_forkboard")
472
478
  else:
473
479
  self.copy_tree(here + "/scripts", self.target + "/scripts")
474
480
  self.copy_tree(here + "/guides", self.target + "/.feature-flow/guides")
475
481
  self.copy_tree(here + "/agents", self.target + "/.feature-flow/agents")
476
482
  self.copy_tree(self.package, self.target + "/.feature-flow/feature_flow", skip=BUNDLE)
483
+ if self.forkboard:
484
+ self.copy_tree(self.forkboard, self.target + "/.feature-flow/feature_flow" + "/_forkboard")
477
485
  listed = not self.dry and self.write_installed()
478
486
 
479
487
  self.out("")
@@ -0,0 +1,47 @@
1
+ """Finds Forkboard, the review page feature-flow's brainstorm uses (packages/forkboard/ in this repository).
2
+
3
+ It sits in one of three places: feature_flow/_forkboard/ (a repo or home install, and the installed package),
4
+ packages/forkboard/forkboard/ beside feature_flow/ (a checkout, or the Claude Code plugin), or wherever Python
5
+ finds an installed forkboard (pip install forkboard). The first two are loaded by path, as feature_flow itself is,
6
+ so a run under python3 -I finds them too.
7
+ """
8
+
9
+ import importlib
10
+ import importlib.util
11
+ import sys
12
+ from pathlib import Path
13
+
14
+ MAJOR = "bundle/1" # the review document format this feature-flow writes
15
+
16
+
17
+ def load():
18
+ """The forkboard package, or None when there is none or it reads another major of the document format."""
19
+ module = sys.modules.get("forkboard")
20
+ if module is None:
21
+ here = Path(__file__).resolve().parent
22
+ for folder in (here / "_forkboard", here.parent / "packages" / "forkboard" / "forkboard"):
23
+ if (folder / "__init__.py").is_file():
24
+ spec = importlib.util.spec_from_file_location("forkboard", str(folder / "__init__.py"),
25
+ submodule_search_locations=[str(folder)])
26
+ module = importlib.util.module_from_spec(spec)
27
+ sys.modules["forkboard"] = module
28
+ try:
29
+ spec.loader.exec_module(module)
30
+ except Exception:
31
+ del sys.modules["forkboard"]
32
+ raise
33
+ break
34
+ else:
35
+ try:
36
+ module = importlib.import_module("forkboard")
37
+ except ImportError:
38
+ return None
39
+ if str(getattr(module, "SCHEMA", "")).split(".")[0] != MAJOR:
40
+ return None
41
+ for name in ("doc", "graph", "page", "live"):
42
+ importlib.import_module("forkboard." + name)
43
+ return module
44
+
45
+
46
+ MISSING = ("forkboard is not here, so there is no review page: reinstall feature-flow (install.py or "
47
+ "pip install -U feature-flow-cli), or pip install forkboard\n")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: feature-flow-cli
3
- Version: 0.25.0
3
+ Version: 0.26.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
@@ -402,10 +402,24 @@ When an idea is still fuzzy, the session can brainstorm it with you before it wr
402
402
 
403
403
  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
404
 
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.
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: `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 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.
406
+
407
+ ![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)
408
+
409
+ What the page is good at:
410
+
411
+ - **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.
412
+ - **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.
413
+ - **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.
414
+ - **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.
415
+ - **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.
416
+
417
+ 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
418
 
407
419
  **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
420
 
421
+ [docs/brainstorm.md](docs/brainstorm.md) walks through a brainstorm step by step, how to use the page, and Forkboard's own commands.
422
+
409
423
  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
424
 
411
425
  ## Starting from Spec Kit or OpenSpec
@@ -457,6 +471,7 @@ feature-flow eval <feature> [--later] record the run in plan
457
471
  python3 scripts/flow-status.py <feature> --mermaid the ticket graph, coloured by status (add "plain" for none)
458
472
  python3 scripts/flow-status.py <feature> --json every ticket with status, blockers and readiness
459
473
  python3 scripts/flow-view.py <feature> [--watch] the animated graph page, opened in your browser
474
+ 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
475
  python3 scripts/gate.py <feature> run Build, Test and Lint from commands.md
461
476
  python3 scripts/floor-guard.py <feature> <NN> [base] check a ticket's diff, run from the repo root
462
477
  ```
@@ -490,6 +505,7 @@ The commands in `commands.md` run through `bash -c` on Linux and macOS and throu
490
505
  | `FLOW_DIR`, `FLOW_TICKETS` | `plans`, `tasks` | where the plans and the ticket folder live |
491
506
  | `FLOW_NO_OPEN` | unset | `1` makes `flow-view.py` never open a browser |
492
507
  | `FLOW_WATCH_SECONDS` | `3` | how often `flow-view.py --watch` rewrites the page |
508
+ | `FLOW_STYLE` | `route` | the brainstorm page's look when the brainstorm has no `Style:` line: `route` or `blueprint` (Forkboard's themes) |
493
509
 
494
510
  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
511
 
@@ -517,12 +533,16 @@ Then `cd` into it, start the agent and type `/feature-flow hello` (Codex: `$feat
517
533
  ```bash
518
534
  bash tests/run.sh
519
535
  python3 -m unittest discover -s tests/py
536
+ python3 -m unittest discover -s packages/forkboard/tests # Forkboard, the brainstorm page
537
+ (cd packages/forkboard/tests/browser && npm install && npm test) # the page in Chromium
520
538
  ```
521
539
 
522
540
  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
541
 
524
542
  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
543
 
544
+ 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).
545
+
526
546
  `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
547
 
528
548
  ## Limits
@@ -20,7 +20,6 @@ agents/ticket-builder.md
20
20
  agents/ticket-reviewer.md
21
21
  feature_flow/__init__.py
22
22
  feature_flow/__main__.py
23
- feature_flow/brainstorm_live.py
24
23
  feature_flow/brainstorm_page.py
25
24
  feature_flow/checks.py
26
25
  feature_flow/cli.py
@@ -43,6 +42,7 @@ feature_flow/report.py
43
42
  feature_flow/scouts.py
44
43
  feature_flow/state.py
45
44
  feature_flow/status.py
45
+ feature_flow/studio.py
46
46
  feature_flow/suggest.py
47
47
  feature_flow/tickets.py
48
48
  feature_flow/uninstall.py
@@ -71,8 +71,6 @@ guides/lenses/positive.md
71
71
  guides/lenses/researcher.md
72
72
  guides/lenses/security.md
73
73
  guides/lenses/ux.md
74
- guides/styles/blueprint.css
75
- guides/styles/route.css
76
74
  guides/templates/brainstorm.md
77
75
  guides/templates/commands.md
78
76
  guides/templates/learnings.md
@@ -80,6 +78,19 @@ guides/templates/map.md
80
78
  guides/templates/spec.md
81
79
  guides/templates/ticket.md
82
80
  guides/templates/ui-mockup.md
81
+ packages/forkboard/forkboard/__init__.py
82
+ packages/forkboard/forkboard/__main__.py
83
+ packages/forkboard/forkboard/cli.py
84
+ packages/forkboard/forkboard/doc.py
85
+ packages/forkboard/forkboard/graph.py
86
+ packages/forkboard/forkboard/live.py
87
+ packages/forkboard/forkboard/page.py
88
+ packages/forkboard/forkboard/assets/base.css
89
+ packages/forkboard/forkboard/assets/editor.js
90
+ packages/forkboard/forkboard/assets/live.css
91
+ packages/forkboard/forkboard/assets/live.js
92
+ packages/forkboard/forkboard/themes/blueprint.css
93
+ packages/forkboard/forkboard/themes/route.css
83
94
  scripts/floor-guard.py
84
95
  scripts/flow-status.py
85
96
  scripts/flow-trust.py
@@ -49,7 +49,7 @@ If the choice turns on module boundaries, a screen or scope, ask the `architect`
49
49
 
50
50
  ## Step 4: The user picks
51
51
 
52
- Write the brainstorm file (below) with the options and no Chosen yet, then make it a review page: `python3 scripts/flow-status.py <feature> --brainstorm-page` writes `.feature-flow/state/brainstorm-<feature>.html` beside it, with a sidebar for the sections, a light, dark or system switch, diagrams whose boxes the user can drag, and an arrow animation they can turn on. The look is the brainstorm's `Style:` line (`route` unless the user asks for another style in `guides/styles/`). If you can publish an artifact, add `--fragment` and publish that file; otherwise give the user the file's path to open. Then show the options and your pick, and ask: which one? They may pick, edit, mix, or drop the brainstorm. On a drop, delete the file and write the brief as before.
52
+ Write the brainstorm file (below) with the options and no Chosen yet, then make it a review page: `python3 scripts/flow-status.py <feature> --brainstorm-page` writes `.feature-flow/state/brainstorm-<feature>.html` beside it, with a sidebar for the sections, a light, dark or system switch, diagrams whose boxes the user can drag, and an arrow animation they can turn on. The look is the brainstorm's `Style:` line (`route`, or `blueprint` if the user asks for it). If you can publish an artifact, add `--fragment` and publish that file; otherwise give the user the file's path to open. Then show the options and your pick, and ask: which one? They may pick, edit, mix, or drop the brainstorm. On a drop, delete the file and write the brief as before.
53
53
 
54
54
  Write the file with the shell, not a file tool, from [templates/brainstorm.md](templates/brainstorm.md), and again with Chosen and Rejected filled in once the user picks (make the page again if they want to see it):
55
55
 
@@ -0,0 +1,8 @@
1
+ """Forkboard: review a design on its diagrams before the code is written.
2
+
3
+ A tool (feature-flow, or anything that can write JSON) hands Forkboard a review document; Forkboard makes it a
4
+ page people read, edit and ask questions on, and gives the tool back what they did. See README.md.
5
+ """
6
+
7
+ __version__ = "0.1.0"
8
+ SCHEMA = "bundle/1" # the review document's format; a reader refuses a major it does not know
@@ -0,0 +1,3 @@
1
+ from .cli import main_exit
2
+
3
+ main_exit()