Skip to content

feat(brainstormer): axis label namespace + axis-aware status/dispatch filter #126

@hadamrd

Description

@hadamrd

Parent

Part of #121.

Problem

The brainstormer/PO produces issues across many concerns (CLI, runner, dispatch, observability, etc.), but the loop has no way to group or focus work by topic. Operators staring at forge-loop status see a flat list of loop:ready issues and cannot tell whether the backlog is dominated by, say, "dispatch" work or "observability" work. Likewise, forge-loop run greedily grabs whatever is ready, so an operator who wants to grind a single area for a sprint has no knob.

Concrete example: today an operator has 14 open loop:ready issues — 6 touch dispatcher internals, 5 touch the CLI, 3 are docs. They want to spend the afternoon hardening dispatch. There is no way to tell the dispatcher "only pull dispatch-tagged issues," and no way to see at a glance that 6/14 are dispatch.

Solution: introduce an axis:<name> GitHub label namespace, surface it in status, and let run filter by it.

Acceptance criteria

  • A new label namespace axis:<name> is documented (README or docs/): any lowercase slug is valid; PO/brainstormer may attach one or more to an issue.
  • forge-loop status (human + --json) groups open loop:ready issues by their axis:* label(s). Issues with no axis:* are bucketed as unaligned and a soft warning is emitted (yellow line in human output; "unaligned_count" field in JSON).
  • Issues carrying multiple axis:* labels appear under each axis (de-duplicated in totals).
  • forge-loop run --axis <name> filters dispatch so only issues whose labels include axis:<name> are eligible. The flag is optional; omitting it preserves today's behavior exactly.
  • --axis accepts repeated use (--axis dispatch --axis cli) → union filter. Unknown axis values are allowed (no validation against a fixed list) but logged at INFO.
  • forge-loop status --axis <name> likewise narrows the grouped view to that axis (sanity-check before running).
  • The dispatcher logs the active axis filter at startup so operator-facing logs make the focused-sprint mode auditable.
  • All existing status and dispatch tests continue to pass unchanged.

Test matrix

Unit

  • tests/test_cli_axis_filter.py (new):
    • status groups issues by axis:* label; snapshot the rendered table.
    • status --json returns { "axes": { "<name>": [...], "unaligned": [...] }, "unaligned_count": N }.
    • status emits the unaligned warning iff unaligned_count > 0.
    • Multi-axis issue appears in each bucket; total count reflects unique issues.
  • tests/test_dispatch_axis_filter.py (new) or extend existing dispatch test:
    • run --axis dispatch against MockGhClient only picks up issues labelled axis:dispatch.
    • run --axis dispatch --axis cli picks up the union.
    • run (no flag) behavior is byte-identical to today (regression guard).
    • Issue labelled axis:dispatch but missing loop:ready is not picked (ready-gate still wins).

Integration

  • End-to-end CLI invocation in a temp git repo using the MockGhClient: seed 6 issues across 2 axes + 1 unaligned, run status --json, assert structure; run run --axis dispatch --dry-run (if dry-run exists; otherwise mock the worker spawn), assert only dispatch issues are claimed.

Adversarial / sad path

  • Issue with malformed label axis: (empty slug) → treated as unaligned, warning logged, no crash.
  • --axis with an axis that matches zero issues → dispatcher exits cleanly with a one-line "no eligible issues for axis=X" message (exit code 0, not an error).
  • Label list with mixed case Axis:Dispatch vs axis:dispatch → normalize to lowercase before matching.

Out of scope

  • Do not auto-assign axis labels from issue text (that is a separate brainstormer enhancement; this ticket is plumbing only).
  • Do not introduce a closed enum / registry of allowed axis names. Free-form slugs.
  • Do not change the priority label namespace (priority:*) or loop:* namespace.
  • Do not persist axis filter into config/state — it is a per-invocation flag only.
  • No TUI changes (cli_tui.py) in this ticket; CLI surface only.
  • No multi-repo / multi-host changes.

File pointers

  • src/forge_loop/cli.py — extend _cmd_status and the run command handler; add --axis option to both.
  • src/forge_loop/runner/dispatch.py — accept and apply an axis_filter: list[str] | None argument; filter eligible issues before claim.
  • src/forge_loop/gh_client.py — (investigate) if issue listing currently strips labels, ensure labels survive to the dispatcher.
  • src/forge_loop/_testing/ — (investigate) extend MockGhClient fixtures to carry axis:* labels for tests.
  • tests/test_cli_axis_filter.py (new).
  • tests/test_dispatch_axis_filter.py (new) — or fold into existing dispatch test if one exists (investigate).
  • README.md or docs/ — document the axis:* namespace (one short section).

Worker note

AC is wide — touches CLI (status + run), dispatcher, gh client, mock fixtures, two new test files, AND docs. You are at high risk of running out of turns before pushing. Apply COMMIT DISCIPLINE (wip-commit every ~20 turns or every 5 file-edits) aggressively from the start. Suggested commit order: (1) label-parsing helper + unit test, (2) status grouping + tests, (3) run --axis filter + tests, (4) docs. Run the EXIT CHECKLIST even if docs feel incomplete — partial progress committed is better than a silent dry-exit.

Original report

What

Add axis:<name> label namespace. forge-loop status shows backlog grouped by axis. Dispatcher can optionally filter --axis <name> to grind one axis at a time.

Acceptance

  • forge-loop status groups open loop:ready issues by their axis:* label; counts unlabeled as "unaligned" with a warning.
  • forge-loop run --axis <name> filters dispatch to issues carrying axis:<name>. Useful for focused sprints.
  • Tests: status output snapshots; dispatch filter against MockGhClient.

File pointers

  • src/forge_loop/cli.py — extend status + run
  • src/forge_loop/runner/dispatch.py — axis filter
  • tests/test_cli_axis_filter.py (new)

Metadata

Metadata

Assignees

No one assigned

    Labels

    po:expandedPO subagent expanded the issue body

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions