You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Issue #122 introduced the brainstormer module, and #123 introduced GhClient. There is still no operator-facing way to actually invoke the brainstormer from the command line. Right now an operator who wants to seed the backlog from a ProductVision has to drop into Python or write an ad-hoc script, and there's no safe preview before issues are filed on GitHub. That defeats the sprint loop's promise of "vision → tickets" in one command.
Concrete example: an operator updates forge-loop.yaml with two new axes ("billing", "observability") and wants to see what epics + tickets the brainstormer would propose before anything lands on the repo. Today they cannot. We need a CLI that defaults to a dry-run (prints the BrainstormReport as YAML to stdout) and only files issues when --apply is passed.
Acceptance criteria
forge-loop brainstorm (no flags) loads the configured ProductVision, invokes Brainstormer.run(), and prints the resulting BrainstormReport as YAML to stdout. Exit code 0. No GitHub calls made.
Each filed epic carries labels: axis:<name> + epic. Body contains the epic's customer story and acceptance criteria as rendered Markdown.
Each filed ticket carries labels: axis:<name> + loop:ready. Body contains a Parent: #<epic-number> cross-link to the epic that was just filed in the same run (so epics must be filed first and their returned issue numbers threaded into ticket bodies).
Missing or invalid ProductVision (no forge-loop.yaml, empty axes, or ProductVision validation error) → exit code 2 with a clear stderr message. No partial state.
Partial failure during --apply (some issues filed, some GhClient calls raised) → exit code 1. Stdout/stderr report which titles succeeded with their issue numbers and which failed with the error.
New subcommand wired into the existing Typer app in src/forge_loop/cli.py alongside the other @app.command definitions (do not invent a new dispatch pattern).
--apply is opt-in only; never default to writing GitHub state.
Test matrix
Unit tests (in tests/test_cli_brainstorm.py, using typer.testing.CliRunner + MockGhClient from forge_loop.gh_client):
test_brainstorm_dry_run_prints_yaml: stub Brainstormer.run to return a fixed BrainstormReport with 1 epic + 2 tickets; assert YAML output parses back to the same shape and MockGhClient.create_issue was never called.
test_brainstorm_apply_files_epics_first: assert epic is filed before its tickets and ticket bodies contain #<epic-number> matching the epic's mocked return.
test_brainstorm_apply_labels_epic: filed epic has both axis:<name> and epic labels.
test_brainstorm_apply_labels_ticket: filed ticket has both axis:<name> and loop:ready labels.
test_brainstorm_missing_vision_exits_2: no vision file present → exit 2, no create_issue calls.
Adversarial / sad-path: test_brainstorm_partial_failure_exits_1: MockGhClient.create_issue raises on the 2nd ticket; assert exit code 1, the successful issues are reported on stdout, the failing title + error are reported on stderr, and the epic+first-ticket were still filed (no rollback expected).
Auto-loop integration (calling brainstorm --apply from the runner) — that's a follow-up ticket.
Persistent dry-run cache / "approve later" flow.
New label management, label creation, or label migration. Assume labels already exist (the init subcommand provisions them).
Closing or de-duplicating against existing open issues — brainstormer's filter already does this.
Rich TUI / interactive confirmation — keep this stdin/stdout only.
File pointers
src/forge_loop/cli.py — add a new @app.command("brainstorm") function next to the other top-level commands. Follow the existing _cmd_* + Typer-callback pattern visible in _cmd_init and _cmd_run.
Problem
Issue #122 introduced the brainstormer module, and #123 introduced
GhClient. There is still no operator-facing way to actually invoke the brainstormer from the command line. Right now an operator who wants to seed the backlog from aProductVisionhas to drop into Python or write an ad-hoc script, and there's no safe preview before issues are filed on GitHub. That defeats the sprint loop's promise of "vision → tickets" in one command.Concrete example: an operator updates
forge-loop.yamlwith two new axes ("billing", "observability") and wants to see what epics + tickets the brainstormer would propose before anything lands on the repo. Today they cannot. We need a CLI that defaults to a dry-run (prints theBrainstormReportas YAML to stdout) and only files issues when--applyis passed.Acceptance criteria
forge-loop brainstorm(no flags) loads the configuredProductVision, invokesBrainstormer.run(), and prints the resultingBrainstormReportas YAML to stdout. Exit code 0. No GitHub calls made.forge-loop brainstorm --applyfiles each proposed epic and ticket viaGhClient.create_issue(issue refactor(gh): migrate from gh-CLI subprocess to githubkit SDK — typed, async-capable, no subprocess plumbing #83 client). Exit code 0 on full success.axis:<name>+epic. Body contains the epic's customer story and acceptance criteria as rendered Markdown.axis:<name>+loop:ready. Body contains aParent: #<epic-number>cross-link to the epic that was just filed in the same run (so epics must be filed first and their returned issue numbers threaded into ticket bodies).ProductVision(noforge-loop.yaml, empty axes, orProductVisionvalidation error) → exit code 2 with a clear stderr message. No partial state.--apply(some issues filed, someGhClientcalls raised) → exit code 1. Stdout/stderr report which titles succeeded with their issue numbers and which failed with the error.appinsrc/forge_loop/cli.pyalongside the other@app.commanddefinitions (do not invent a new dispatch pattern).--applyis opt-in only; never default to writing GitHub state.Test matrix
Unit tests (in
tests/test_cli_brainstorm.py, usingtyper.testing.CliRunner+MockGhClientfromforge_loop.gh_client):test_brainstorm_dry_run_prints_yaml: stubBrainstormer.runto return a fixedBrainstormReportwith 1 epic + 2 tickets; assert YAML output parses back to the same shape andMockGhClient.create_issuewas never called.test_brainstorm_apply_files_epics_first: assert epic is filed before its tickets and ticket bodies contain#<epic-number>matching the epic's mocked return.test_brainstorm_apply_labels_epic: filed epic has bothaxis:<name>andepiclabels.test_brainstorm_apply_labels_ticket: filed ticket has bothaxis:<name>andloop:readylabels.test_brainstorm_missing_vision_exits_2: no vision file present → exit 2, nocreate_issuecalls.test_brainstorm_invalid_vision_exits_2: malformed vision (empty axes) → exit 2.test_brainstorm_partial_failure_exits_1:MockGhClient.create_issueraises on the 2nd ticket; assert exit code 1, the successful issues are reported on stdout, the failing title + error are reported on stderr, and the epic+first-ticket were still filed (no rollback expected).test_brainstorm_apply_no_proposals: brainstormer returns empty report → exit 0, zerocreate_issuecalls, helpful message printed.Integration: extend
tests/test_cli.pyonly if needed to register the new subcommand in the existing CLI smoke test. No live-GitHub e2e in this ticket.Out of scope
Brainstormeritself (feat(brainstormer): .forge/product-vision.md + axes.yaml discovery + schema #122 territory) — wire it in, don't refactor it.GhClient/MockGhClient(refactor(gh): migrate from gh-CLI subprocess to githubkit SDK — typed, async-capable, no subprocess plumbing #83/feat(brainstormer): Brainstormer skill module — reads vision, scans backlog, proposes axis-aligned epics+tickets #123 territory) — consume the existing interface.brainstorm --applyfrom the runner) — that's a follow-up ticket.initsubcommand provisions them).File pointers
src/forge_loop/cli.py— add a new@app.command("brainstorm")function next to the other top-level commands. Follow the existing_cmd_*+ Typer-callback pattern visible in_cmd_initand_cmd_run.src/forge_loop/brainstormer.py— readBrainstormer,BrainstormReport,ProposedEpic,ProposedTicket(already exported).src/forge_loop/gh_client.py— useGhClient.create_issueandMockGhClientfor tests. Helperlist_open_ticketsalready exists if needed.src/forge_loop/product_vision.py—ProductVisionloader / validator (use existing entry point; do not re-parse YAML inline).src/forge_loop/config.py— for resolving the vision path from config (investigate the existingcfgaccessor used by_cmd_init).tests/test_cli_brainstorm.py— new test module.tests/test_cli.py— only touch if the CLI smoke test enumerates subcommands.Original report
Parent
Part of #121. Depends on #123.
What
New
forge-loop brainstormCLI subcommand. Dry-run by default (prints proposed epics + tickets to stdout).--applyactually files them on GitHub.Acceptance
forge-loop brainstormreads vision, runs brainstormer, printsBrainstormReportas YAML.forge-loop brainstorm --applyfiles epics + tickets viaGhClient(issue refactor(gh): migrate from gh-CLI subprocess to githubkit SDK — typed, async-capable, no subprocess plumbing #83 client). Each filed issue carries:axis:<name>label,epiclabel (for epics) orloop:readylabel (for tickets), parent-epic cross-link in the body.File pointers
src/forge_loop/cli.py— add subcommandtests/test_cli_brainstorm.py(new)