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.
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/MANIFEST.in +2 -1
- {feature_flow_cli-0.25.0/feature_flow_cli.egg-info → feature_flow_cli-0.26.0}/PKG-INFO +22 -2
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/README.md +21 -1
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/adapters/codex/feature-flow/SKILL.md +3 -3
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/__init__.py +1 -1
- feature_flow_cli-0.26.0/feature_flow/brainstorm_page.py +190 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/install.py +8 -0
- feature_flow_cli-0.26.0/feature_flow/studio.py +47 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0/feature_flow_cli.egg-info}/PKG-INFO +22 -2
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow_cli.egg-info/SOURCES.txt +14 -3
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/brainstorm.md +1 -1
- feature_flow_cli-0.26.0/packages/forkboard/forkboard/__init__.py +8 -0
- feature_flow_cli-0.26.0/packages/forkboard/forkboard/__main__.py +3 -0
- feature_flow_cli-0.26.0/packages/forkboard/forkboard/assets/base.css +114 -0
- feature_flow_cli-0.26.0/packages/forkboard/forkboard/assets/editor.js +476 -0
- feature_flow_cli-0.26.0/packages/forkboard/forkboard/assets/live.css +28 -0
- 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
- feature_flow_cli-0.26.0/packages/forkboard/forkboard/cli.py +124 -0
- feature_flow_cli-0.26.0/packages/forkboard/forkboard/doc.py +35 -0
- feature_flow_cli-0.26.0/packages/forkboard/forkboard/graph.py +136 -0
- feature_flow_cli-0.26.0/packages/forkboard/forkboard/live.py +85 -0
- feature_flow_cli-0.26.0/packages/forkboard/forkboard/page.py +274 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/pyproject.toml +3 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/scripts/flow-trust.py +1 -2
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/skills/feature-flow/SKILL.md +3 -3
- feature_flow_cli-0.25.0/feature_flow/brainstorm_page.py +0 -1066
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/LICENSE +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/adapters/codex/feature-flow/agents/openai.yaml +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/agents/feature-planner.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/agents/flow-architect.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/agents/flow-designer.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/agents/flow-lens.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/agents/flow-po.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/agents/flow-scout.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/agents/flow-skeptic.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/agents/flow-tester.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/agents/flow-web.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/agents/plan-reviewer.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/agents/ticket-builder.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/agents/ticket-reviewer.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/__main__.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/checks.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/cli.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/codehash.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/command.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/conductor.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/evaluate.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/floorguard.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/gate.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/git.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/importer.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/lanes.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/proc.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/profiles.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/prompts.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/proof.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/released.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/report.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/scouts.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/state.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/status.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/suggest.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/tickets.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/uninstall.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow/view.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow_cli.egg-info/dependency_links.txt +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow_cli.egg-info/entry_points.txt +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/feature_flow_cli.egg-info/top_level.txt +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/brief.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/build.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/debug.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/lenses/artistic.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/lenses/open-minded.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/lenses/positive.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/lenses/researcher.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/lenses/security.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/lenses/ux.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/plan-review.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/plan.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/replan.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/review-feature.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/review-quality.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/review-single.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/review.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/show.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/templates/brainstorm.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/templates/commands.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/templates/learnings.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/templates/map.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/templates/spec.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/templates/ticket.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/templates/ui-mockup.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/guides/test.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/install.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/install.sh +0 -0
- {feature_flow_cli-0.25.0/guides/styles → feature_flow_cli-0.26.0/packages/forkboard/forkboard/themes}/blueprint.css +0 -0
- {feature_flow_cli-0.25.0/guides/styles → feature_flow_cli-0.26.0/packages/forkboard/forkboard/themes}/route.css +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/scripts/floor-guard.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/scripts/flow-status.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/scripts/flow-view.html +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/scripts/flow-view.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/scripts/flow.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/scripts/gate.py +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/setup.cfg +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/skills/architect-review/SKILL.md +0 -0
- {feature_flow_cli-0.25.0 → feature_flow_cli-0.26.0}/skills/automation-design/SKILL.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: feature-flow-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.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
|
|
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
|
+

|
|
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
|
|
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
|
+

|
|
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
|
|
18
|
+
python3 -I -c "import hashlib,sys;b=open(sys.argv[1],'rb').read().replace(b'\r\n',b'\n');sys.exit(exec(compile(b,'flow-trust','exec')) if hashlib.sha256(b).hexdigest()==sys.argv[2] else 'flow-trust.py does not match this skill')" <S>/flow-trust.py a561cb2bf1d8d8970e45065f24a736a02d9f803f09d652a26ca51b579f44cf7b
|
|
19
19
|
|
|
20
20
|
A full conductor command, here `next` (any other command and its arguments go where `next` is):
|
|
21
21
|
|
|
22
|
-
FLOW_TRUST=<digest> FLOW_SESSION=<token> FLOW_INVOKE='$feature-flow' python3 -I -c "import hashlib,sys;b=open(sys.argv[1],'rb').read().replace(b'\r\n',b'\n');sys.exit(exec(compile(b,'flow-trust','exec')) if hashlib.sha256(b).hexdigest()==sys.argv[2] else 'flow-trust.py does not match this skill')" <S>/flow-trust.py
|
|
22
|
+
FLOW_TRUST=<digest> FLOW_SESSION=<token> FLOW_INVOKE='$feature-flow' python3 -I -c "import hashlib,sys;b=open(sys.argv[1],'rb').read().replace(b'\r\n',b'\n');sys.exit(exec(compile(b,'flow-trust','exec')) if hashlib.sha256(b).hexdigest()==sys.argv[2] else 'flow-trust.py does not match this skill')" <S>/flow-trust.py a561cb2bf1d8d8970e45065f24a736a02d9f803f09d652a26ca51b579f44cf7b <feature> next
|
|
23
23
|
|
|
24
24
|
- The first line of the output is `TRUST <digest>`. Keep that digest and pass it as `FLOW_TRUST` on the next conductor call. Treat the rest as the conductor's output and act on it as below (for `prompt`, `plan-prompt` and `plan-review-prompt`, the child agent's prompt is everything after the `TRUST` line). Use `FLOW_TRUST=new` only on this session's first conductor call, or when resuming from an old-style `HANDOFF` line (below).
|
|
25
25
|
- If you no longer have the last `TRUST` digest, stop and say so. Never guess it, and never use `new` in its place.
|
|
26
|
-
- On the `next` right after a `BUILD <ticket> <NN> <sha>`, add `--after-build <ticket> <sha>` before the feature: `... <S>/flow-trust.py
|
|
26
|
+
- On the `next` right after a `BUILD <ticket> <NN> <sha>`, add `--after-build <ticket> <sha>` before the feature: `... <S>/flow-trust.py a561cb2bf1d8d8970e45065f24a736a02d9f803f09d652a26ca51b579f44cf7b --after-build <ticket> <sha> <feature> next`. If the output has a `FLOW-EDIT <paths>` line after `TRUST`, the build changed feature-flow's own files and its ticket allows that: carry on; without `auto`, first tell the user which flow files changed.
|
|
27
27
|
- If the first line is `STOP` (from `flow-trust.py`), or the loader prints `flow-trust.py does not match this skill` (say to reinstall feature-flow: the skill and `flow-trust.py` do not match), report it, show the `python3 <S>/flow-status.py <feature>` table, and stop. A `STOP` first line may be followed by the conductor's own output: never act on that. Never fix it and carry on.
|
|
28
28
|
- A `HANDOFF` line from `flow-trust.py` has a third field after the invocation and the feature: the digest. When the feature is followed by a `<16 hex>.<16 hex>` word, the user is resuming from a `HANDOFF` line and that word is the digest: pass it as `FLOW_TRUST` on `start`. When a resumed session gets no digest (an old-style `HANDOFF` line), use `FLOW_TRUST=new` and warn the user that the flow's files could not be checked since the last session.
|
|
29
29
|
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""feature-flow brainstorm-page <feature>: the brainstorm as a review page, for the user to read before they pick.
|
|
2
|
+
|
|
3
|
+
usage: feature-flow brainstorm-page <feature> [--style NAME] [--fragment] [--out FILE]
|
|
4
|
+
feature-flow brainstorm-page <feature> --live DIR | --asks STATE | --answer STATE <n> <text> | --apply STATE <figure> <version>
|
|
5
|
+
Reads plans/<feature>/brainstorm.md, or .feature-flow/state/brainstorm-<feature>.md before the plan exists, turns
|
|
6
|
+
it into a review document and has Forkboard (packages/forkboard/, found by feature_flow/studio.py) make the page.
|
|
7
|
+
The page goes beside the brainstorm (same name, .html). The look is a Forkboard theme: --style, else the brainstorm's
|
|
8
|
+
`Style:` line, else FLOW_STYLE, else route. --fragment leaves out the doctype, html, head and body tags, for a host
|
|
9
|
+
that adds its own (an Artifact). --live writes DIR/index.html and DIR/state.json to publish as an artifact that
|
|
10
|
+
saves itself; --asks, --answer and --apply are the session's side of it, and --apply is the one part that writes
|
|
11
|
+
the brainstorm: Forkboard never touches it.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import re
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from feature_flow import git, studio
|
|
20
|
+
|
|
21
|
+
USAGE = ("usage: feature-flow brainstorm-page <feature> [--style NAME] [--fragment] [--out FILE]\n"
|
|
22
|
+
" feature-flow brainstorm-page <feature> --live DIR | --asks STATE | --answer STATE <n> <text> | --apply STATE <figure> <version>")
|
|
23
|
+
DEFAULT_STYLE = "route"
|
|
24
|
+
FENCE = re.compile(r"^```mermaid[ \t]*\n(.*?)^```[ \t]*$", re.S | re.M)
|
|
25
|
+
KINDS = {"chosen": "chosen", "rejected": "rejected", "open": "open", "risks for the planner": "callout"}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def source(top, feature):
|
|
29
|
+
"""The brainstorm to show: the committed one once the plan exists, else the one in the state folder."""
|
|
30
|
+
for path in (Path(top) / os.environ.get("FLOW_DIR", "plans") / feature / "brainstorm.md",
|
|
31
|
+
Path(top) / ".feature-flow" / "state" / ("brainstorm-%s.md" % feature)):
|
|
32
|
+
if path.is_file():
|
|
33
|
+
return path
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def parse(text):
|
|
38
|
+
"""(title, meta {key: value}, intro lines, [(heading, lines, [(subheading, lines)])])."""
|
|
39
|
+
title, meta, intro, sections = "", {}, [], []
|
|
40
|
+
for line in text.splitlines():
|
|
41
|
+
if line.startswith("# ") and not title and not sections:
|
|
42
|
+
title = line[2:].strip()
|
|
43
|
+
elif line.startswith("## "):
|
|
44
|
+
sections.append((line[3:].strip(), [], []))
|
|
45
|
+
elif line.startswith("### ") and sections:
|
|
46
|
+
sections[-1][2].append((line[4:].strip(), []))
|
|
47
|
+
elif sections:
|
|
48
|
+
(sections[-1][2][-1][1] if sections[-1][2] else sections[-1][1]).append(line)
|
|
49
|
+
else:
|
|
50
|
+
m = re.match(r"^([A-Z][A-Za-z ]{1,20}):\s*(.+)$", line)
|
|
51
|
+
if m and not any(l.strip() for l in intro):
|
|
52
|
+
meta[m.group(1).strip().lower()] = m.group(2).strip()
|
|
53
|
+
else:
|
|
54
|
+
intro.append(line)
|
|
55
|
+
return title, meta, intro, sections
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def document(text, feature):
|
|
59
|
+
"""The brainstorm as a Forkboard review document: which section is Chosen, Rejected or Open, and which option
|
|
60
|
+
the Chosen line picks, are the brainstorm's own conventions (guides/brainstorm.md), so they are read here."""
|
|
61
|
+
title, meta, intro, sections = parse(text)
|
|
62
|
+
chosen = ""
|
|
63
|
+
for heading, lines, subs in sections:
|
|
64
|
+
if heading.lower() == "chosen":
|
|
65
|
+
chosen = " ".join(l.strip() for l in lines if l.strip())[:200]
|
|
66
|
+
out = []
|
|
67
|
+
for heading, lines, subs in sections:
|
|
68
|
+
items = []
|
|
69
|
+
for sub, sub_lines in subs:
|
|
70
|
+
letter = re.match(r"^([A-Z])[.)]\s", sub)
|
|
71
|
+
items.append({"title": sub, "label": letter.group(1) if letter else "",
|
|
72
|
+
"chosen": bool(letter and re.match(r"^\W*%s\b" % letter.group(1), chosen)),
|
|
73
|
+
"markdown": "\n".join(sub_lines)})
|
|
74
|
+
out.append({"title": heading, "kind": KINDS.get(heading.lower(), "text"), "markdown": "\n".join(lines),
|
|
75
|
+
"items": items})
|
|
76
|
+
return {"schema": studio.MAJOR, "id": feature, "kind": "Brainstorm", "title": title, "meta": meta,
|
|
77
|
+
"intro": "\n".join(intro), "sections": out}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def apply(text, fb, st, figure, version):
|
|
81
|
+
"""The brainstorm text with figure `figure` replaced by that version of it, and what the rewrite dropped."""
|
|
82
|
+
graph, source_print = fb.live.version(st, figure, version)
|
|
83
|
+
blocks = list(FENCE.finditer(text))
|
|
84
|
+
if not str(figure).isdigit() or not 1 <= int(figure) <= len(blocks):
|
|
85
|
+
raise ValueError("the brainstorm file has %d diagrams, so no figure %s: build the page again" % (len(blocks), figure))
|
|
86
|
+
block = blocks[int(figure) - 1]
|
|
87
|
+
if source_print and fb.graph.fingerprint(block.group(1)) != source_print:
|
|
88
|
+
raise ValueError("figure %s in the brainstorm changed since the page was built: build the page again (--live), "
|
|
89
|
+
"publish it, and apply after the next save" % figure)
|
|
90
|
+
return text[:block.start(1)] + fb.graph.mermaid(graph) + "\n" + text[block.end(1):], fb.graph.lost(block.group(1))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def main(argv, out=None, err=None):
|
|
94
|
+
out = out or sys.stdout
|
|
95
|
+
err = err or sys.stderr
|
|
96
|
+
args, style, fragment, target, live, tool, extra = [], None, False, None, None, None, []
|
|
97
|
+
i = 0
|
|
98
|
+
while i < len(argv):
|
|
99
|
+
a = argv[i]
|
|
100
|
+
if a == "--fragment":
|
|
101
|
+
fragment = True
|
|
102
|
+
elif a in ("--live", "--asks") and i + 1 < len(argv) and tool is None and live is None:
|
|
103
|
+
live, tool = argv[i + 1], a[2:]
|
|
104
|
+
i += 1
|
|
105
|
+
elif a == "--answer" and i + 3 < len(argv) and tool is None:
|
|
106
|
+
live, tool, extra = argv[i + 1], "answer", argv[i + 2:i + 4]
|
|
107
|
+
i += 3
|
|
108
|
+
elif a == "--apply" and i + 3 < len(argv) and tool is None:
|
|
109
|
+
live, tool, extra = argv[i + 1], "apply", argv[i + 2:i + 4]
|
|
110
|
+
i += 3
|
|
111
|
+
elif a in ("--style", "--out") and i + 1 < len(argv):
|
|
112
|
+
if a == "--style":
|
|
113
|
+
style = argv[i + 1]
|
|
114
|
+
else:
|
|
115
|
+
target = argv[i + 1]
|
|
116
|
+
i += 1
|
|
117
|
+
elif a.startswith("-"):
|
|
118
|
+
err.write(USAGE + "\n")
|
|
119
|
+
return 2
|
|
120
|
+
else:
|
|
121
|
+
args.append(a)
|
|
122
|
+
i += 1
|
|
123
|
+
if len(args) != 1 or any(c in args[0] for c in "/\\:"):
|
|
124
|
+
err.write(USAGE + "\n")
|
|
125
|
+
return 2
|
|
126
|
+
feature = args[0]
|
|
127
|
+
top = git.toplevel() or os.getcwd()
|
|
128
|
+
path = source(top, feature)
|
|
129
|
+
if path is None:
|
|
130
|
+
err.write("no brainstorm for %s: write .feature-flow/state/brainstorm-%s.md first (brainstorm guide)\n"
|
|
131
|
+
% (feature, feature))
|
|
132
|
+
return 1
|
|
133
|
+
fb = studio.load()
|
|
134
|
+
if fb is None:
|
|
135
|
+
err.write(studio.MISSING)
|
|
136
|
+
return 1
|
|
137
|
+
text = path.read_text(encoding="utf-8", errors="replace")
|
|
138
|
+
if tool in ("asks", "answer", "apply"):
|
|
139
|
+
return live_tool(fb, tool, path, text, Path(live), extra, out, err)
|
|
140
|
+
names = fb.page.themes()
|
|
141
|
+
_, meta, _, _ = parse(text)
|
|
142
|
+
name = (style or meta.get("style") or os.environ.get("FLOW_STYLE") or DEFAULT_STYLE).strip().lower()
|
|
143
|
+
if name not in names:
|
|
144
|
+
err.write("no style %s: pick one of %s (Forkboard's themes)\n" % (name, ", ".join(names) or "none installed"))
|
|
145
|
+
return 1
|
|
146
|
+
css = fb.page.theme_css(name)
|
|
147
|
+
doc = document(text, feature)
|
|
148
|
+
if tool == "live": # the page and its state, to publish together as an artifact that saves itself
|
|
149
|
+
folder = Path(live)
|
|
150
|
+
folder.mkdir(parents=True, exist_ok=True)
|
|
151
|
+
st = fb.live.state(doc, fb.live.load(folder / "state.json"))
|
|
152
|
+
(folder / "index.html").write_text(fb.page.render(doc, css, name, True, True), encoding="utf-8")
|
|
153
|
+
(folder / "state.json").write_text(fb.live.dump(st), encoding="utf-8")
|
|
154
|
+
out.write("wrote %s and %s (round %d, %d figures, %d open asks)\n" % (
|
|
155
|
+
(folder / "index.html").as_posix(), (folder / "state.json").as_posix(), st["round"], len(st["figures"]),
|
|
156
|
+
sum(1 for a in st["asks"] if not a.get("answer"))))
|
|
157
|
+
return 0
|
|
158
|
+
dest = Path(target) if target else path.with_suffix(".html")
|
|
159
|
+
dest.write_text(fb.page.render(doc, css, name, fragment), encoding="utf-8")
|
|
160
|
+
out.write("wrote %s\n" % dest.as_posix())
|
|
161
|
+
return 0
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def live_tool(fb, tool, path, text, state_path, extra, out, err):
|
|
165
|
+
if state_path.is_dir():
|
|
166
|
+
state_path = state_path / "state.json"
|
|
167
|
+
st = fb.live.load(state_path)
|
|
168
|
+
if st is None:
|
|
169
|
+
err.write("no page state at %s: read the published page's state.json into it first (brainstorm guide)\n"
|
|
170
|
+
% state_path.as_posix())
|
|
171
|
+
return 1
|
|
172
|
+
try:
|
|
173
|
+
if tool == "asks":
|
|
174
|
+
out.write(fb.live.asks(st) + "\n")
|
|
175
|
+
elif tool == "answer":
|
|
176
|
+
if not extra[0].isdigit():
|
|
177
|
+
raise ValueError("the ask number must be a number, not %s" % extra[0])
|
|
178
|
+
state_path.write_text(fb.live.dump(fb.live.answer(st, int(extra[0]), extra[1])), encoding="utf-8")
|
|
179
|
+
out.write("answered ask %s in %s\n" % (extra[0], state_path.as_posix()))
|
|
180
|
+
else:
|
|
181
|
+
new, dropped = apply(text, fb, st, extra[0], extra[1])
|
|
182
|
+
path.write_text(new, encoding="utf-8")
|
|
183
|
+
if dropped:
|
|
184
|
+
err.write("note: figure %s had %s, which the page does not keep; they are gone from the brainstorm now\n"
|
|
185
|
+
% (extra[0], ", ".join(dropped)))
|
|
186
|
+
out.write("figure %s in %s is now version %s\n" % (extra[0], path.as_posix(), extra[1]))
|
|
187
|
+
except ValueError as e:
|
|
188
|
+
err.write("%s\n" % e)
|
|
189
|
+
return 1
|
|
190
|
+
return 0
|
|
@@ -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.
|
|
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
|
|
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
|
+

|
|
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`
|
|
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
|