-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Problem
When --group-by-team-prefix groups repos that belong to several teams simultaneously, a combined section like squad-frontend + squad-mobile appears. For downstream tooling such as github-issue-ops, each section must have a single unambiguous owner. There is currently no way to resolve this ambiguity interactively.
Proposed UX
Pressing p on any section header whose label contains + (i.e. a multi-team section) enters team pick mode — analogous to filter mode (f):
- The section header re-renders the team names as a horizontal list. The currently focused team is highlighted (bold + full colour, wrapped in
[ ]); the others are dimmed. ←/→cycle through the candidate teams.Enterconfirms the pick: the section label collapses to the single chosen team, and all repos in that section are reassigned to it. Moved repos are annotated with a◈badge in the TUI.Esccancels — no change.
Sections that already have a single team label are not eligible for pick mode (pressing p on them does nothing).
Navigation note: Section header rows are navigable (↑/↓ can land on them — do not skip them). The cursor highlights the section header with
bgMagenta+──prefix. Pressingpwhile the cursor is on a multi-team section header enters pick mode.
CLI — replay
A new repeatable option --pick-team <combined>=<chosen> encodes the choice for non-interactive mode. Example:
github-code-search query "..." --org myorg \
--group-by-team-prefix squad- \
--pick-team "squad-frontend + squad-mobile"=squad-frontendThe = separator is safe because it never appears in team slugs or section labels. The combined label must be quoted in the shell when it contains spaces.
When --pick-team is given for a label that does not exist (typo, or the combined section was not formed), a warning is emitted on stderr listing the available combined sections — the run continues without error.
CLI picks are propagated to the interactive TUI via an initialPickTeams parameter so they appear in the replay command when the user switches to interactive mode.
Acceptance criteria
-
pon a single-team section header does nothing -
pon a multi-team section header enters pick mode;←/→navigate with[ focused ]/ dimmed rendering -
Enterconfirms: section label updates, repos are reassigned, moved repos show a◈badge in the TUI -
Esccancels with no side effect -
--pick-team "A + B"=Ain non-interactive mode produces identical output to the TUI confirmation - The replay command emits
--pick-teamwhen a pick was confirmed in the TUI or via CLI -
applyTeamPickreturns the same object reference when the combined label is not found (identity check — no silent mutation) -
--pick-teamwith an unmatched label emits a stderr warning listing available combined labels; the run continues - CLI picks are passed to
runInteractiveviainitialPickTeams;confirmedPicksis pre-seeded from it so picks appear in the replay command - Repos moved by
applyTeamPickhavepickedFromset to the original combined label; a◈badge is shown next to the repo name in the TUI - Section header rows are navigable (↑/↓ lands on them);
ponly activates pick mode whensectionLabel.includes(" + ")is true - Multiple
--pick-teamflags targeting the same destination team aggregate all repos correctly - Combined label order must be alphabetical (
"squad-a + squad-b"— same order produced bygroupByTeamPrefix); mismatched order triggers a stderr warning -
bun test,bun run lint,bun run knipall pass -
bun run docs:buildcompletes without errors
Edge cases
| Scenario | Expected behaviour |
|---|---|
p on a single-team section |
No-op |
--pick-team label not found |
Stderr warning with list of available combined labels; run continues |
Multiple --pick-team targeting the same destination team |
All repos merged into that team's section |
3-team combined section (e.g. a + b + c) |
applyTeamPick handles correctly; all 3 candidates shown in pick bar |
Combined label order mismatch ("b + a" instead of "a + b") |
Warning on stderr — label must match alphabetical order from groupByTeamPrefix |
--pick-team without --group-by-team-prefix |
Section list is empty; stderr warning for each unmatched pick; output unchanged |
| CLI picks passed to TUI | confirmedPicks pre-seeded; picks appear in replay command |
| Repo moved via pick in TUI | pickedFrom set to original combined label; ◈ badge shown next to repo name |
Implementation guide
Recommended order:
-
src/types.ts— addpickedFrom?: stringtoRepoGroup:/** Set by applyTeamPick on repos moved from a combined section. * Stores the original combined label for future split-mode use. */ pickedFrom?: string;
-
src/group.ts— add two pure helpers:applyTeamPick(sections: TeamSection[], combinedLabel: string, chosenTeam: string): TeamSection[]
Filters out the matching combined section and re-inserts its repos (with
pickedFromset to the original combined label) into the chosen team's existing section (or creates a new single-team section). Returns the same reference when the combined label is not found — callers must identity-check the return value.rebuildTeamSections(groups: RepoGroup[]): TeamSection[]
Reconstructs
TeamSection[]from a flatRepoGroup[]usingsectionLabelmarkers (used internally byapplyTeamPick). -
src/render/team-pick.ts(new file) — pure function:renderTeamPickHeader(candidateTeams: string[], focusedIndex: number): string
Returns a string where the focused team is wrapped in
[ ]+ bold + full colour; the others are dimmed (picocolorsonly).⚠️ Do NOT re-export this symbol fromsrc/render.ts— it is consumed only internally insiderender.ts. Re-exporting it would causeknipto flag it as an unused export. -
src/render.ts— importrenderTeamPickHeaderdirectly fromrender/team-pick.ts(internal use). Add ateamPickModebranch in section-row rendering:- Pick mode active on this section → render
renderTeamPickHeaderbar - Cursor on this section (no pick mode) →
bgMagenta(bold("── <label> "))+[p: pick team]hint when multi-team - Default →
pc.magenta(pc.bold("\n── <label> "))
Add a
◈badge (dimmed) after the repo name whengroup.pickedFromis set. - Pick mode active on this section → render
-
src/tui.ts— addteamPickModestate:{ active: boolean; sectionLabel: string; candidates: string[]; focusedIndex: number }
Key bindings (only when
teamPickMode.active === true):←/→— movefocusedIndexEnter— callapplyTeamPick, exit mode, rebuild rowsEsc— exit mode without change
pon a section row whose label contains" + "→ split by" + "to get candidates, enter pick mode withfocusedIndex: 0.Add
initialPickTeams: Record<string, string> = {}parameter torunInteractive; pre-seedconfirmedPickswith{ ...initialPickTeams }.⚠️ Do NOT skip section rows in ↑/↓ navigation. Anywhile (row.type === "section")skip loop must be removed — section headers must be reachable by the cursor, otherwisepwould be inaccessible. -
src/output.ts— addpickTeams?: Record<string, string>toReplayOptions; inbuildReplayCommand, emit--pick-team "<combined>"=<chosen>per entry. -
github-code-search.ts— add--pick-team <combined>=<chosen>(repeatable via.option+collect); parse each=pair; aftergroupByTeamPrefixand beforeflattenTeamSections, callapplyTeamPickfor each pair. Identity-check the return: if unchanged, emit a stderr warning listing available combined labels. Pass resolved picks torunInteractiveviainitialPickTeams. -
Tests
src/group.test.ts—applyTeamPick(two-team, three-team, identity check when label not found,pickedFromfield set on moved repos),rebuildTeamSectionssrc/render/team-pick.test.ts—renderTeamPickHeader(focused bracket style, unfocused dim, 3-team, ANSI codes)src/output.test.ts— verify--pick-teamis emitted in the replay whenpickTeamsis set
Documentation
Three files must be updated:
1. docs/usage/team-grouping.md
Correct the existing note in ## Interactive mode with sections — section headers are navigable (remove any statement that ↑/↓ skips them).
Add a ## Team pick mode section after ## Interactive mode with sections:
## Team pick mode
When a section header shows multiple teams (e.g. `squad-frontend + squad-mobile`), pressing `p` on it enters **team pick mode**. Use this to assign the entire section to a single owner before exporting results to downstream tooling.
### In the TUI
The section header switches to a horizontal pick bar:
── [ squad-frontend ] squad-mobile
The highlighted team (bold, full colour, wrapped in `[ ]`) is the current selection. The others are dimmed.
| Key | Action |
| ----------- | ----------------------------------------- |
| `←` / `→` | Move focus between candidate teams |
| `Enter` | Confirm — section label updates in place |
| `Esc` | Cancel — no change |
`p` on a section that already has a single team label does nothing.
Repos moved into a team by pick mode are annotated with a `◈` badge next to their name.
### Non-interactive — `--pick-team`
github-code-search query "useFeatureFlag" --org fulll
--group-by-team-prefix squad-
--pick-team "squad-frontend + squad-mobile"=squad-frontend
The flag is repeatable — add one `--pick-team` per combined section to resolve. The replay command emits `--pick-team` automatically when a pick was confirmed in the TUI.
::: tip Combined with --dispatch
`--pick-team` resolves ownership at the **section level** (all repos in the section move to one team). For finer-grained control — assigning individual repos or extracts to different teams — see `--dispatch` (#86).
:::
2. docs/reference/keyboard-shortcuts.md
Add a ## Team ownership section after the ## Filtering section:
## Team ownership
Available only when `--group-by-team-prefix` is active.
| Key | Action |
| --- | --- |
| `p` | On a **multi-team** section header: enter team pick mode to assign the section to a single owner. Does nothing on single-team section headers. |
### Pick mode bindings
When pick mode is active (after pressing `p` on a multi-team section header):
| Key | Action |
| --------- | --------------------------------------------------------- |
| `←` / `→` | Move focus between candidate teams (highlighted / dimmed) |
| `Enter` | Confirm the pick and exit pick mode |
| `Esc` | Cancel and exit pick mode without changes |3. docs/reference/cli-options.md
In the ## Search options table, add a row for --pick-team after the --group-by-team-prefix row:
| `--pick-team <combined>=<chosen>` | string (repeatable) | ❌ | — | Assign a combined team section to a single owner. Format: `"<combined label>"=<chosen team>`. Repeatable — one flag per combined section. Only applies with `--group-by-team-prefix`. See [Team pick mode](/usage/team-grouping#team-pick-mode). |Notes
- Parse candidate teams from the section label by splitting on
" + "(space-surrounded plus sign). picocolorsis the only styling dependency — do not addchalkor similar.- Keep
knipclean: every exported symbol must be used.renderTeamPickHeadermust not be re-exported fromrender.ts. - Add a comment above the new
tui.tspick-mode block:// Feat: team pick mode — resolve multi-team section ownership — see issue #85 RepoGroup.pickedFromis the foundation for a future split mode feature (issue Team dispatch — split a multi-team section by assigning each repo/extract to exactly one team #86) that will let individual repos be re-assigned across teams independently.