diff --git a/CHANGELOG.md b/CHANGELOG.md index 410b2bb..b5eb99c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,23 +4,24 @@ Release notes and changelogs are published on the **[project blog](https://fulll Each release entry covers the motivation, new features, breaking changes (if any), and upgrade notes. -| Version | Blog post | -| -------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | -| [v1.12.0](https://fulll.github.io/github-code-search/blog/release-v1-12-0) | Mouse-driven TUI, team re-pick/undo, query-title heading and matched token in output, regex quoting fixes | -| [v1.11.0](https://fulll.github.io/github-code-search/blog/release-v1-11-0) | Team pick mode, `--exclude-template-repositories`, security hardening and Windows installer fixes | -| [v1.10.0](https://fulll.github.io/github-code-search/blog/release-v1-10-0) | Native regex syntax `/pattern/flags` — automatic term extraction, top-level alternation and `--regex-hint` | -| [v1.9.0](https://fulll.github.io/github-code-search/blog/release-v1-9-0) | Windows support — native x64, x64-modern, x64-baseline and ARM64 binaries with one-line PowerShell installer | -| [v1.8.3](https://fulll.github.io/github-code-search/blog/release-v1-8-3) | Fix TUI layout: header/footer anchoring, viewport packing, narrow-terminal clipping, active-row contrast | -| [v1.8.2](https://fulll.github.io/github-code-search/blog/release-v1-8-2) | Fix rate-limit errors aborting multi-page searches; auto-wait and retry with live progress | -| [v1.8.1](https://fulll.github.io/github-code-search/blog/release-v1-8-1) | Fix silent hang after pagination bar — concurrency cap + progress bar for line-number resolution | -| [v1.8.0](https://fulll.github.io/github-code-search/blog/release-v1-8-0) | Purple TUI theme, fetch progress bar, position indicator, line-anchored file links, Esc to close help | -| [v1.7.0](https://fulll.github.io/github-code-search/blog/release-v1-7-0) | Shell completions (bash/zsh/fish) + extended syntax highlighting (PHP, C/C++, Swift, Terraform/HCL, Dockerfile) | -| [v1.6.1](https://fulll.github.io/github-code-search/blog/release-v1-6-1) | Fix TUI only displaying first text fragment when a file has multiple matches | -| [v1.6.0](https://fulll.github.io/github-code-search/blog/release-v1-6-0) | Power navigation: global fold/unfold, gg/G top/bottom, paged scroll, open-in-browser | -| [v1.5.0](https://fulll.github.io/github-code-search/blog/release-v1-5-0) | Advanced filter targets, regex mode, word-jump, scroll fix | -| [v1.4.0](https://fulll.github.io/github-code-search/blog/release-v1-4-0) | TUI visual overhaul, community files, demo animation | -| [v1.3.0](https://fulll.github.io/github-code-search/blog/release-v1-3-0) | Team-prefix grouping, replay command, JSON output | -| [v1.0.0](https://fulll.github.io/github-code-search/blog/release-v1-0-0) | Initial release | +| Version | Blog post | +| -------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| v2.0.0 _(pending)_ | _Hierarchical team-prefix grouping (nested `/` chains, auto-nesting, consolidated rendering, `--pick-team` at every level) — breaking change to `--group-by-team-prefix` / JSON `section` shape. Blog post pending, see [EPIC #125](https://github.com/fulll/github-code-search/issues/125)._ | +| [v1.12.0](https://fulll.github.io/github-code-search/blog/release-v1-12-0) | Mouse-driven TUI, team re-pick/undo, query-title heading and matched token in output, regex quoting fixes | +| [v1.11.0](https://fulll.github.io/github-code-search/blog/release-v1-11-0) | Team pick mode, `--exclude-template-repositories`, security hardening and Windows installer fixes | +| [v1.10.0](https://fulll.github.io/github-code-search/blog/release-v1-10-0) | Native regex syntax `/pattern/flags` — automatic term extraction, top-level alternation and `--regex-hint` | +| [v1.9.0](https://fulll.github.io/github-code-search/blog/release-v1-9-0) | Windows support — native x64, x64-modern, x64-baseline and ARM64 binaries with one-line PowerShell installer | +| [v1.8.3](https://fulll.github.io/github-code-search/blog/release-v1-8-3) | Fix TUI layout: header/footer anchoring, viewport packing, narrow-terminal clipping, active-row contrast | +| [v1.8.2](https://fulll.github.io/github-code-search/blog/release-v1-8-2) | Fix rate-limit errors aborting multi-page searches; auto-wait and retry with live progress | +| [v1.8.1](https://fulll.github.io/github-code-search/blog/release-v1-8-1) | Fix silent hang after pagination bar — concurrency cap + progress bar for line-number resolution | +| [v1.8.0](https://fulll.github.io/github-code-search/blog/release-v1-8-0) | Purple TUI theme, fetch progress bar, position indicator, line-anchored file links, Esc to close help | +| [v1.7.0](https://fulll.github.io/github-code-search/blog/release-v1-7-0) | Shell completions (bash/zsh/fish) + extended syntax highlighting (PHP, C/C++, Swift, Terraform/HCL, Dockerfile) | +| [v1.6.1](https://fulll.github.io/github-code-search/blog/release-v1-6-1) | Fix TUI only displaying first text fragment when a file has multiple matches | +| [v1.6.0](https://fulll.github.io/github-code-search/blog/release-v1-6-0) | Power navigation: global fold/unfold, gg/G top/bottom, paged scroll, open-in-browser | +| [v1.5.0](https://fulll.github.io/github-code-search/blog/release-v1-5-0) | Advanced filter targets, regex mode, word-jump, scroll fix | +| [v1.4.0](https://fulll.github.io/github-code-search/blog/release-v1-4-0) | TUI visual overhaul, community files, demo animation | +| [v1.3.0](https://fulll.github.io/github-code-search/blog/release-v1-3-0) | Team-prefix grouping, replay command, JSON output | +| [v1.0.0](https://fulll.github.io/github-code-search/blog/release-v1-0-0) | Initial release | > For the full list of commits between releases, see the > [GitHub Releases page](https://github.com/fulll/github-code-search/releases). diff --git a/docs/reference/cli-options.md b/docs/reference/cli-options.md index 1db6fb7..b867912 100644 --- a/docs/reference/cli-options.md +++ b/docs/reference/cli-options.md @@ -31,20 +31,21 @@ github-code-search completions [--shell ] ## Search options -| Option | Type | Required | Default | Description | -| ----------------------------------- | --------------------------------- | -------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `--org ` | string | ✅ | — | GitHub organization to search in. Automatically injected as `org:` in the query. | -| `--exclude-repositories ` | string | ❌ | `""` | Comma-separated list of repositories to exclude. Short form (`repoA,repoB`) or full form (`org/repoA,org/repoB`) both accepted. | -| `--exclude-extracts ` | string | ❌ | `""` | Comma-separated extract refs to exclude. Format: `repoName:path/to/file:index`. Short form (without org prefix) accepted. | -| `--no-interactive` | boolean (flag) | ❌ | `true` (on) | Disable interactive mode. Interactive mode is **on** by default; pass this flag to disable it. Also triggered by `CI=true`. | -| `--format ` | `markdown` \| `json` | ❌ | `markdown` | Output format. See [Output formats](/usage/output-formats). | -| `--output-type ` | `repo-and-matches` \| `repo-only` | ❌ | `repo-and-matches` | Controls output detail level. `repo-only` lists repository names only, without individual extracts. | -| `--include-archived` | boolean (flag) | ❌ | `false` | Include archived repositories in results (excluded by default). | -| `--exclude-template-repositories` | boolean (flag) | ❌ | `false` | Exclude template repositories from results (included by default). See [Filtering](/usage/filtering#--exclude-template-repositories). | -| `--group-by-team-prefix ` | string | ❌ | `""` | Comma-separated team-name prefixes for grouping result repos by GitHub team (e.g. `squad-,chapter-`). Requires `read:org` scope. | -| `--pick-team ` | string (repeatable) | ❌ | — | Assign a combined team section to a single owner. Format: `""=`. Repeatable — one flag per combined section. Only applies with `--group-by-team-prefix`. See [Team pick mode](/usage/team-grouping#team-pick-mode). | -| `--no-cache` | boolean (flag) | ❌ | `true` (on) | Bypass the 24 h team-list cache and re-fetch teams from GitHub. Cache is **on** by default; pass this flag to disable it. Only applies with `--group-by-team-prefix`. | -| `--regex-hint ` | string | ❌ | — | Override the API search term used when the query is a regex (`/pattern/`). Useful when auto-extraction produces a term that is too broad or too narrow. See [Regex queries](/usage/search-syntax#regex-queries). | +| Option | Type | Required | Default | Description | +| ------------------------------------ | --------------------------------- | -------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--org ` | string | ✅ | — | GitHub organization to search in. Automatically injected as `org:` in the query. | +| `--exclude-repositories ` | string | ❌ | `""` | Comma-separated list of repositories to exclude. Short form (`repoA,repoB`) or full form (`org/repoA,org/repoB`) both accepted. | +| `--exclude-extracts ` | string | ❌ | `""` | Comma-separated extract refs to exclude. Format: `repoName:path/to/file:index`. Short form (without org prefix) accepted. | +| `--no-interactive` | boolean (flag) | ❌ | `true` (on) | Disable interactive mode. Interactive mode is **on** by default; pass this flag to disable it. Also triggered by `CI=true`. | +| `--format ` | `markdown` \| `json` | ❌ | `markdown` | Output format. See [Output formats](/usage/output-formats). | +| `--output-type ` | `repo-and-matches` \| `repo-only` | ❌ | `repo-and-matches` | Controls output detail level. `repo-only` lists repository names only, without individual extracts. | +| `--include-archived` | boolean (flag) | ❌ | `false` | Include archived repositories in results (excluded by default). | +| `--exclude-template-repositories` | boolean (flag) | ❌ | `false` | Exclude template repositories from results (included by default). See [Filtering](/usage/filtering#--exclude-template-repositories). | +| `--group-by-team-prefix ` | string | ❌ | `""` | Team-name prefixes for grouping result repos by GitHub team, as a hierarchy. `/` nests levels within one chain (`gamme-/squad-`), `,` separates independent chains (`gamme-/squad-,chapter-`). Requires `read:org` scope. See [Team grouping](/usage/team-grouping). | +| `--group-by-team-prefix-consolidate` | boolean (flag) | ❌ | `false` | Collapse unambiguous single-branch nesting chains into one heading with an `(including …)` suffix. Only applies with `--group-by-team-prefix`, and only to markdown/TUI output — ignored (with a warning) for `--format json`. See [Advanced: consolidated rendering](/usage/team-grouping#advanced-consolidated-rendering). | +| `--pick-team ` | string (repeatable) | ❌ | — | Assign a combined team section to a single owner. Format: `""=`, where the combined side is a bare label (auto-resolved when unambiguous) or a fully-qualified `"parent > combined"` path. Repeatable — one flag per combined section. Only applies with `--group-by-team-prefix`. See [Team pick mode](/usage/team-grouping#team-pick-mode). | +| `--no-cache` | boolean (flag) | ❌ | `true` (on) | Bypass the 24 h team-list cache and re-fetch teams from GitHub. Cache is **on** by default; pass this flag to disable it. Only applies with `--group-by-team-prefix`. | +| `--regex-hint ` | string | ❌ | — | Override the API search term used when the query is a regex (`/pattern/`). Useful when auto-extraction produces a term that is too broad or too narrow. See [Regex queries](/usage/search-syntax#regex-queries). | ## Global options diff --git a/docs/usage/team-grouping.md b/docs/usage/team-grouping.md index 1e45128..20ec0ad 100644 --- a/docs/usage/team-grouping.md +++ b/docs/usage/team-grouping.md @@ -1,6 +1,6 @@ # Team grouping -`--group-by-team-prefix` organises result repositories by their GitHub team membership. It is especially useful in large organisations with multiple squads or chapters. +`--group-by-team-prefix` organises result repositories by their GitHub team membership, as a **hierarchy** of headings. It is especially useful in large organisations with multiple gammes, chapters or squads. ## Prerequisites @@ -15,27 +15,84 @@ github-code-search "useFeatureFlag" --org fulll \ --group-by-team-prefix squad- ``` -Pass one or more **comma-separated prefixes**. The tool fetches all org teams whose **slugs** (derived from the team name) start with any of the given prefixes, then groups repositories accordingly. +Pass one or more team-name prefixes. The tool fetches all org teams whose **slugs** (derived from the team name) start with any of the given prefixes, then groups repositories accordingly. + +## Chain syntax: nesting and independent chains + +The value of `--group-by-team-prefix` is a small grammar: + +- `/` nests levels **within one chain** — repos are grouped by the first prefix, then each resulting section is sub-grouped by the next prefix, and so on. +- `,` separates **independent chains** — each is grouped on its own, in order, against whatever repos the previous chains haven't already claimed. + +```bash +# One 2-level chain: group by gamme- first, then by squad- within each gamme +github-code-search "useFeatureFlag" --org fulll \ + --group-by-team-prefix gamme-/squad- +``` ```bash -# Multiple prefixes +# A 2-level chain (gamme-/squad-) plus an independent 1-level chain (chapter-) github-code-search "useFeatureFlag" --org fulll \ - --group-by-team-prefix squad-,chapter- + --group-by-team-prefix gamme-/squad-,chapter- ``` +A chain can have as many levels as you need (`gamme-/squad-/chapter-`, …). Malformed segments (a stray leading/trailing/double `,` or `/`) are dropped with a warning on stderr rather than silently producing an empty prefix. + ## Grouping algorithm -The grouping is applied sequentially, one prefix at a time: +Within **one level** of a chain, repos are bucketed exactly the same way regardless of depth: + +1. Repos belonging to **exactly 1** matching team at this level → one section per team, sorted alphabetically. +2. Repos belonging to **2** matching teams → one section per combination (e.g. `squad-a + squad-b`), sorted alphabetically. +3. Repos belonging to **3+** matching teams → same, in ascending combination-size order. +4. Repos matching **no team** at this level → collected into an `other` section. + +Then, for a chain with more levels, **every section produced above is recursively sub-grouped** by the next prefix — including its own `other` bucket, which becomes a nested `other` at the next depth. + +Independent chains (separated by `,`) are processed in order, each consuming repos from the pool not yet claimed by an earlier chain. Repos matched by no chain at all end up in a single top-level `other` section. -1. **First prefix** (`squad-`) - - Repos belonging to **exactly 1** matching team → one section per team, sorted alphabetically. - - Repos belonging to **2** matching teams → one section per combination, sorted alphabetically. - - Repos belonging to **3+** matching teams → same, in ascending combination-size order. -2. **Next prefix** (`chapter-`) — applied to repos **not yet assigned** in the previous step. -3. Repos matching **no prefix** → collected into an `other` section at the end. +### Automatic nesting of overlapping team names + +Within one level, if a team's name is a **prefix of another team's name** (e.g. `gamme-lead-client` and `gamme-lead-client-p1`), the tool nests the more specific team under the more general one automatically — instead of listing them as unrelated siblings: + +```text +## gamme-lead-client +### gamme-lead-client-p1 +``` + +This cascades across any number of overlapping names, and applies independently at every depth of a chain. + +## Advanced: consolidated rendering + +Deeply nested hierarchies can produce a long run of single-child headings that don't carry much extra information on their own. `--group-by-team-prefix-consolidate` collapses any such **unambiguous single-branch chain** into one heading with an `(including …)` suffix: + +```bash +github-code-search "useFeatureFlag" --org fulll \ + --group-by-team-prefix gamme-/squad- \ + --group-by-team-prefix-consolidate +``` + +```text +## gamme-lead-client (including squad-dashboard) +``` + +instead of: + +```text +## gamme-lead-client +### squad-dashboard +``` + +A heading is only collapsed into its parent's suffix when the next level has **exactly one** child — a level with 2+ children (a genuine fork) always keeps its own heading, and the `other` bucket reads as `unset` in the suffix (e.g. `gamme-lead-client (including squad-dashboard, unset)`). + +::: warning JSON output ignores consolidation +`--group-by-team-prefix-consolidate` only affects **markdown** output and the interactive TUI. `--format json` always emits the full, uncollapsed hierarchy in each result's `section` path — JSON is a data contract and consolidating it would lose information a downstream consumer might need. A warning is printed if you combine both flags. +::: ## Non-interactive output +### Flat (single-level) output + ```text 4 repos · 5 files · 6 matches selected @@ -60,31 +117,81 @@ The grouping is applied sequentially, one prefix at a time: - [ ] [src/legacy.js:5:1](https://github.com/fulll/legacy-monolith/blob/main/src/legacy.js#L5) ``` +### Nested (`gamme-/squad-`) output + +Nested levels render as consecutive markdown headings (`##`, `###`, `####`, …, capped at H6) — a sibling section that shares an ancestor with the previous one doesn't repeat that ancestor's heading: + +```text +7 repos · 7 files · 8 matches selected + +## gamme-lead-client +### squad-bank + +- **fulll/bank** (1 match) + - [ ] [src/index.ts:3:14](https://github.com/fulll/bank/blob/main/src/index.ts#L3) + +## gamme-lead-mobile +### squad-core + squad-mobile + +- **fulll/tools-mobile** (1 match) + - [ ] [src/index.ts:1:1](https://github.com/fulll/tools-mobile/blob/main/src/index.ts#L1) + +### other + +- **fulll/wizard-mobile** (1 match) + - [ ] [src/index.ts:2:5](https://github.com/fulll/wizard-mobile/blob/main/src/index.ts#L2) + +## other + +- **fulll/github-code-search** (1 match) + - [ ] [src/index.ts:7:1](https://github.com/fulll/github-code-search/blob/main/src/index.ts#L7) +``` + +### JSON output + +Each result carries its full hierarchy path (root first) in a `section` array, regardless of `--group-by-team-prefix-consolidate`: + +```json +{ + "results": [ + { + "repo": "fulll/tools-mobile", + "section": ["gamme-lead-mobile", "squad-core + squad-mobile"], + "matches": [{ "path": "src/index.ts", "url": "...", "line": 1, "col": 1 }] + } + ] +} +``` + ## Interactive mode with sections -In the TUI, team sections appear as separator lines between repository rows: +In the TUI, team sections appear as separator lines between repository rows, indented by 2 spaces per nesting level: ```text -── squad-frontend -▶ ◉ fulll/auth-service (2 matches) -── squad-mobile -▶ ◉ fulll/frontend-app (1 match) +── gamme-lead-client + ── squad-bank +▶ ◉ fulll/bank (1 match) +── gamme-lead-mobile + ── squad-core + squad-mobile +▶ ◉ fulll/tools-mobile (1 match) + ── other +▶ ◉ fulll/wizard-mobile (1 match) ── other -▶ ◉ fulll/legacy-monolith (1 match) +▶ ◉ fulll/github-code-search (1 match) ``` -Section header rows **are navigable** — `↑` / `↓` can land on them. Pressing `p` while the cursor rests on a multi-team section header enters [team pick mode](#team-pick-mode). +Section header rows **are navigable** at any depth — `↑` / `↓` can land on them. Pressing `p` while the cursor rests on a multi-team section header enters [team pick mode](#team-pick-mode), regardless of its nesting level. ## 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. +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 — including any nested sub-sections underneath it — to a single owner before exporting results to downstream tooling. ### In the TUI -The section header switches to a horizontal pick bar: +The section header switches to a horizontal pick bar, at whatever depth the cursor was on: ``` -── [ squad-frontend ] squad-mobile + ── [ squad-core ] squad-mobile ``` The highlighted team (bold, full colour, wrapped in `[ ]`) is the current selection. The others are dimmed. @@ -107,30 +214,38 @@ github-code-search query "useFeatureFlag" --org fulll \ --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. +The combined label can be: + +- **A bare label** (as above) — auto-resolved as long as it's **unambiguous** anywhere in the hierarchy. Since a label like `other` (or even a specific combination) can legitimately appear under more than one parent, an ambiguous bare label is rejected with the list of full paths to choose from. +- **A fully-qualified path**, joined with `>`, when the label is ambiguous or you'd rather be explicit: + + ```bash + --pick-team "gamme-lead-client > squad-a + squad-b"=squad-a + ``` + +The flag is repeatable — add one `--pick-team` per combined section to resolve. The replay command emits `--pick-team` automatically (with a fully-qualified path when the pick was made on a nested section) when a pick was confirmed in the TUI. > **Note:** Per-repo re-picks performed in the TUI (pressing `t` on a `◈` repo) are **not** encoded in the replay command. They are interactive-only adjustments and must be repeated manually if you re-run the command. -If the combined label is not found (typo, or the section was not formed), a warning is emitted on stderr listing the available combined sections — the run continues without error. +If the combined label or path is not found (typo, ambiguous, or the section was not formed), a warning is emitted on stderr listing the available combined sections — the run continues without error. ## Re-pick & undo pick -After using `--pick-team` (or the interactive `p` shortcut) to assign a combined section to a team, individual repos marked `◈` can be re-assigned or restored to their original combined section at any time. +After using `--pick-team` (or the interactive `p` shortcut) to assign a combined section to a team, individual repos marked `◈` can be re-assigned or restored to their original combined section at any time — regardless of how deeply nested the original section was. ### TUI — re-pick mode Navigate to any **picked repo** (marked `◈`) and press **`t`** to enter re-pick mode. ```text -── squad-frontend -▶ ◈ fulll/frontend-app ← press t here -▶ ◈ fulll/mobile-sdk + ── squad-core +▶ ◈ fulll/tools-mobile ← press t here ``` The hints bar shows a horizontal pick bar — exactly like team pick mode — with the current focused team highlighted in `[ brackets ]`: ```text -Re-pick: [ squad-frontend ] squad-mobile 0/u restore ← → move ↵ confirm Esc/t cancel +Re-pick: [ squad-core ] squad-mobile 0/u restore ← → move ↵ confirm Esc/t cancel ``` | Key | Action | @@ -142,12 +257,11 @@ Re-pick: [ squad-frontend ] squad-mobile 0/u restore ← → move ↵ confir ### Undoing a pick (merge) -Pressing `0` or `u` in re-pick mode restores **all** repos from the same combined section back to where they came from (e.g. `squad-frontend + squad-mobile`). Every `◈` badge from that section is removed and all repos are treated as unassigned again. +Pressing `0` or `u` in re-pick mode restores **all** repos from the same combined section back to where they came from (e.g. `squad-core + squad-mobile`). Every `◈` badge from that section is removed and all repos are treated as unassigned again. ```text -── squad-frontend + squad-mobile ← all repos restored -▶ ◉ fulll/frontend-app -▶ ◉ fulll/mobile-sdk + ── squad-core + squad-mobile ← all repos restored +▶ ◉ fulll/tools-mobile ``` In **non-interactive mode**, undoing a pick is implicit: simply omit the `--pick-team` flag for that combined section in the replay command.