diff --git a/CHANGELOG.md b/CHANGELOG.md index b5eb99c..708a8ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,24 +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 | -| -------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 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 | +| Version | Blog post | +| -------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| v2.0.0 _(pending)_ | _Hierarchical team-prefix grouping (nested `/` chains, auto-nesting, `--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 b867912..fde4c48 100644 --- a/docs/reference/cli-options.md +++ b/docs/reference/cli-options.md @@ -31,21 +31,20 @@ 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 | ❌ | `""` | 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). | +| 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). | +| `--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 20ec0ad..c262645 100644 --- a/docs/usage/team-grouping.md +++ b/docs/usage/team-grouping.md @@ -62,33 +62,6 @@ Within one level, if a team's name is a **prefix of another team's name** (e.g. 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 @@ -149,7 +122,7 @@ Nested levels render as consecutive markdown headings (`##`, `###`, `####`, …, ### JSON output -Each result carries its full hierarchy path (root first) in a `section` array, regardless of `--group-by-team-prefix-consolidate`: +Each result carries its full hierarchy path (root first) in a `section` array: ```json { diff --git a/github-code-search.ts b/github-code-search.ts index beca7f9..2f8b65e 100644 --- a/github-code-search.ts +++ b/github-code-search.ts @@ -22,7 +22,6 @@ import { formatRetryWait } from "./src/api-utils.ts"; import { buildOutput } from "./src/output.ts"; import { applyTeamPickInTree, - consolidateTeamHierarchy, findCombinedSectionPaths, flattenTeamHierarchy, groupByTeamHierarchy, @@ -190,16 +189,6 @@ function addSearchOptions(cmd: Command): Command { ].join("\n"), "", ) - .option( - "--group-by-team-prefix-consolidate", - [ - "Collapse unambiguous single-branch nesting chains into one heading", - 'with an "(including ...)" suffix instead of one heading per level.', - "Only applies with --group-by-team-prefix.", - "Docs: https://fulll.github.io/github-code-search/usage/team-grouping", - ].join("\n"), - false, - ) .option( "--pick-team ", [ @@ -243,7 +232,6 @@ async function searchAction( includeArchived: boolean; excludeTemplateRepositories: boolean; groupByTeamPrefix: string; - groupByTeamPrefixConsolidate?: boolean; pickTeam: string[]; cache: boolean; regexHint?: string; @@ -366,10 +354,7 @@ async function searchAction( ); // ─── Team-prefix grouping ───────────────────────────────────────────────── - const pickTeams: Record = {}; // Whether consolidation was actually applied (requested AND not --format - // json, which always needs the full, uncollapsed hierarchy) — forwarded to - // the replay command and the TUI so they stay consistent with `groups`. - let consolidateApplied = false; + const pickTeams: Record = {}; if (!opts.groupByTeamPrefix && opts.pickTeam && opts.pickTeam.length > 0) { for (const assignment of opts.pickTeam) { process.stderr.write( @@ -397,9 +382,6 @@ async function searchAction( let sections = groupByTeamHierarchy(groups, chains); - // Apply --pick-team assignments BEFORE consolidation: consolidating - // first would change (or erase) the identity of the combined sections - // --pick-team addresses, silently breaking resolution — see review on #190. for (const assignment of opts.pickTeam) { const resolution = resolvePickTeamAssignment(sections, assignment); if ("error" in resolution) { @@ -422,21 +404,6 @@ async function searchAction( ); } - // Consolidation is a display-only concern: JSON output is a data - // contract and must reflect the real, uncollapsed hierarchy (its - // `section` path per result), so skip it entirely for --format json — - // see review on #190. - consolidateApplied = Boolean(opts.groupByTeamPrefixConsolidate) && format !== "json"; - if (opts.groupByTeamPrefixConsolidate && !consolidateApplied) { - process.stderr.write( - "warning: --group-by-team-prefix-consolidate is ignored with --format json " + - "(JSON output always reflects the full, uncollapsed hierarchy)\n", - ); - } - if (consolidateApplied) { - sections = consolidateTeamHierarchy(sections); - } - groups = flattenTeamHierarchy(sections); } } @@ -447,7 +414,6 @@ async function searchAction( includeArchived, excludeTemplates, groupByTeamPrefix: opts.groupByTeamPrefix, - consolidateTeamSections: consolidateApplied, regexHint: opts.regexHint, pickTeams: Object.keys(pickTeams).length > 0 ? pickTeams : undefined, }), @@ -508,7 +474,6 @@ async function searchAction( includeArchived, excludeTemplates, opts.groupByTeamPrefix, - consolidateApplied, opts.regexHint ?? "", Object.keys(pickTeams).length > 0 ? pickTeams : {}, ); diff --git a/src/completions.test.ts b/src/completions.test.ts index 93c2464..4ca14d6 100644 --- a/src/completions.test.ts +++ b/src/completions.test.ts @@ -31,10 +31,6 @@ describe("generateCompletion", () => { expect(script).toContain("--regex-hint"); }); - it("contains --group-by-team-prefix-consolidate", () => { - expect(generateCompletion("bash")).toContain("--group-by-team-prefix-consolidate"); - }); - it("contains format values (markdown, json)", () => { const script = generateCompletion("bash"); expect(script).toContain("markdown"); @@ -79,10 +75,6 @@ describe("generateCompletion", () => { expect(script).toContain("--regex-hint"); }); - it("contains --group-by-team-prefix-consolidate", () => { - expect(generateCompletion("zsh")).toContain("--group-by-team-prefix-consolidate"); - }); - it("contains a 'compdef' directive (zsh-style)", () => { const script = generateCompletion("zsh"); expect(script).toContain("compdef "); @@ -115,10 +107,6 @@ describe("generateCompletion", () => { expect(script).toContain("regex-hint"); }); - it("contains group-by-team-prefix-consolidate", () => { - expect(generateCompletion("fish")).toContain("group-by-team-prefix-consolidate"); - }); - it("uses fish 'complete -c' syntax", () => { const script = generateCompletion("fish"); expect(script).toContain("complete -c github-code-search"); diff --git a/src/completions.ts b/src/completions.ts index ba70899..005ce3e 100644 --- a/src/completions.ts +++ b/src/completions.ts @@ -45,12 +45,6 @@ const OPTIONS = [ takesArg: true, values: [], }, - { - flag: "group-by-team-prefix-consolidate", - description: "Collapse single-branch nesting chains into one heading", - takesArg: false, - values: [], - }, { flag: "pick-team", description: "Assign a combined team section to a single owner (repeatable)", diff --git a/src/group.test.ts b/src/group.test.ts index 427aeca..be905a4 100644 --- a/src/group.test.ts +++ b/src/group.test.ts @@ -2,7 +2,6 @@ import { describe, expect, it } from "bun:test"; import { applyTeamPick, applyTeamPickInTree, - consolidateTeamHierarchy, findCombinedSectionPaths, flattenTeamHierarchy, flattenTeamSections, @@ -333,104 +332,6 @@ describe("groupByTeamHierarchy — auto-nesting of overlapping team names", () = }); }); -// ─── consolidateTeamHierarchy ───────────────────────────────────────────────── - -describe("consolidateTeamHierarchy", () => { - it("collapses a single-branch chain into one heading with an 'including' suffix", () => { - const groups = [makeGroup("org/a", ["gamme-client", "squad-dashboard"])]; - const sections = groupByTeamHierarchy(groups, [["gamme-", "squad-"]]); - const consolidated = consolidateTeamHierarchy(sections); - expect(consolidated).toHaveLength(1); - expect(consolidated[0].label).toBe("gamme-client (including squad-dashboard)"); - expect(consolidated[0].level).toBe(0); - expect(consolidated[0].children ?? []).toHaveLength(0); - expect(consolidated[0].groups.map((g) => g.repoFullName)).toEqual(["org/a"]); - }); - - it("collapses a 3-level single-branch chain into one heading", () => { - const groups = [makeGroup("org/a", ["gamme-client", "squad-dashboard", "chapter-fe"])]; - const sections = groupByTeamHierarchy(groups, [["gamme-", "squad-", "chapter-"]]); - const consolidated = consolidateTeamHierarchy(sections); - expect(consolidated[0].label).toBe("gamme-client (including squad-dashboard, chapter-fe)"); - expect(consolidated[0].children ?? []).toHaveLength(0); - }); - - it("reads a nested 'other' bucket as 'unset' in the suffix", () => { - const groups = [makeGroup("org/a", ["gamme-client"])]; // no squad- team → nested "other" - const sections = groupByTeamHierarchy(groups, [["gamme-", "squad-"]]); - const consolidated = consolidateTeamHierarchy(sections); - expect(consolidated[0].label).toBe("gamme-client (including unset)"); - }); - - it("does NOT collapse a level where a node has 2+ children", () => { - const groups = [ - makeGroup("org/a", ["gamme-client", "squad-dashboard"]), - makeGroup("org/b", ["gamme-client", "squad-billing"]), - ]; - const sections = groupByTeamHierarchy(groups, [["gamme-", "squad-"]]); - const consolidated = consolidateTeamHierarchy(sections); - expect(consolidated[0].label).toBe("gamme-client"); - const childLabels = (consolidated[0].children ?? []).map((c) => c.label).toSorted(); - expect(childLabels).toEqual(["squad-billing", "squad-dashboard"]); - expect(consolidated[0].children!.every((c) => c.level === 1)).toBe(true); - }); - - it("leaves a leaf section (no children) unchanged", () => { - const groups = [makeGroup("org/a", ["squad-front"])]; - const sections = groupByTeamHierarchy(groups, [["squad-"]]); - const consolidated = consolidateTeamHierarchy(sections); - expect(consolidated[0].label).toBe("squad-front"); - expect(consolidated[0].level).toBe(0); - }); - - it("stops collapsing before a child that itself forks into 2+ children", () => { - const groups = [ - makeGroup("org/a", ["gamme-x"]), - makeGroup("org/b", ["gamme-x-y"]), - makeGroup("org/c", ["gamme-x-y-c1"]), - makeGroup("org/d", ["gamme-x-y-c2"]), - ]; - const sections = groupByTeamHierarchy(groups, [["gamme-"]]); - const consolidated = consolidateTeamHierarchy(sections); - expect(consolidated).toHaveLength(1); - // "gamme-x-y" is the fork point (2 children) — it must remain its own - // heading rather than being absorbed into "gamme-x"'s suffix. - expect(consolidated[0].label).toBe("gamme-x"); - expect(consolidated[0].groups.map((g) => g.repoFullName)).toEqual(["org/a"]); - expect(consolidated[0].children).toHaveLength(1); - const fork = consolidated[0].children![0]; - expect(fork.label).toBe("gamme-x-y"); - expect(fork.groups.map((g) => g.repoFullName)).toEqual(["org/b"]); - const forkChildLabels = (fork.children ?? []).map((c) => c.label).toSorted(); - expect(forkChildLabels).toEqual(["gamme-x-y-c1", "gamme-x-y-c2"]); - }); - - it("accumulates groups from every merged node, not just the deepest one", () => { - const groups = [ - makeGroup("org/a", ["gamme-x"]), - makeGroup("org/b", ["gamme-x-y"]), - makeGroup("org/c", ["gamme-x-y-z"]), - ]; - const sections = groupByTeamHierarchy(groups, [["gamme-"]]); - const consolidated = consolidateTeamHierarchy(sections); - expect(consolidated[0].label).toBe("gamme-x (including gamme-x-y, gamme-x-y-z)"); - expect(consolidated[0].groups.map((g) => g.repoFullName).toSorted()).toEqual([ - "org/a", - "org/b", - "org/c", - ]); - expect(consolidated[0].children ?? []).toHaveLength(0); - }); - - it("is a pure function — does not mutate the input tree", () => { - const groups = [makeGroup("org/a", ["gamme-client", "squad-dashboard"])]; - const sections = groupByTeamHierarchy(groups, [["gamme-", "squad-"]]); - const before = JSON.stringify(sections); - consolidateTeamHierarchy(sections); - expect(JSON.stringify(sections)).toBe(before); - }); -}); - // ─── flattenTeamHierarchy ───────────────────────────────────────────────────── describe("flattenTeamHierarchy", () => { diff --git a/src/group.ts b/src/group.ts index a51de30..fb3ecab 100644 --- a/src/group.ts +++ b/src/group.ts @@ -214,65 +214,6 @@ function assignLevels(node: TeamSection, lvl: number): TeamSection { return node; } -// ─── Advanced consolidated rendering ────────────────────────────────────────── - -/** - * Collapses chains of single-child nesting in a `groupByTeamHierarchy` tree - * into one node, so a run of unambiguous nesting (a parent with exactly one - * child, that child with exactly one child, …) renders as a single heading - * with an "(including …)" suffix listing the collapsed labels, instead of - * one heading per level. A node whose next level has 0 or 2+ children is - * left as-is at that point (only unambiguous single-branch chains collapse); - * a child that itself forks into 2+ children stops the collapse *before* it - * so the fork point remains its own heading rather than disappearing into - * the suffix. - * - * `groups` from every node absorbed into the collapsed heading (the root and - * each merged intermediate) are accumulated — overlap-nested parents can own - * repos directly (see `TeamSection`), and those must not be dropped. - * - * The `"other"` label reads as `"unset"` inside the suffix (e.g. `"gamme- - * lead-client (including p1, unset)"`), matching how an unassigned bucket - * reads in prose, without changing the underlying section's `label`. - * - * Pure function — no mutation of the input tree; `level` is recomputed on - * the resulting (shallower) tree. - */ -export function consolidateTeamHierarchy(sections: TeamSection[]): TeamSection[] { - return sections.map((s) => assignLevels(consolidateNode(s), s.level ?? 0)); -} - -function consolidateNode(node: TeamSection): TeamSection { - const collapsedLabels: string[] = []; - let current = node; - let groups = node.groups; - while (current.children && current.children.length === 1) { - const only = current.children[0]; - // A forking grandchild must remain its own heading — stop before it. - if (only.children && only.children.length > 1) break; - collapsedLabels.push(only.label === "other" ? "unset" : only.label); - groups = [...groups, ...only.groups]; - current = only; - } - - const label = - collapsedLabels.length > 0 - ? `${node.label} (including ${collapsedLabels.join(", ")})` - : node.label; - - const children = - current.children && current.children.length > 0 - ? current.children.map((c) => consolidateNode(c)) - : undefined; - - return { - label, - groups, - level: node.level, - ...(children ? { children } : {}), - }; -} - /** * Recursively drops an empty `children` array so the field is only present * when a section actually has nested sub-sections, matching `TeamSection`'s diff --git a/src/output.test.ts b/src/output.test.ts index 833a264..8eed3b5 100644 --- a/src/output.test.ts +++ b/src/output.test.ts @@ -214,22 +214,6 @@ describe("buildReplayCommand", () => { expect(cmd).not.toContain("--regex-hint"); }); - it("includes --group-by-team-prefix-consolidate when consolidateTeamSections is true", () => { - const groups = [makeGroup("myorg/repoA", ["a.ts"])]; - const opts: ReplayOptions = { - groupByTeamPrefix: "gamme-/squad-", - consolidateTeamSections: true, - }; - const cmd = buildReplayCommand(groups, QUERY, ORG, new Set(), new Set(), opts); - expect(cmd).toContain("--group-by-team-prefix-consolidate"); - }); - - it("does not include --group-by-team-prefix-consolidate when consolidateTeamSections is false (default)", () => { - const groups = [makeGroup("myorg/repoA", ["a.ts"])]; - const cmd = buildReplayCommand(groups, QUERY, ORG, new Set(), new Set()); - expect(cmd).not.toContain("--group-by-team-prefix-consolidate"); - }); - it("emits --pick-team for each entry in pickTeams", () => { const groups = [makeGroup("myorg/repoA", ["a.ts"])]; const opts: ReplayOptions = { @@ -869,14 +853,4 @@ describe("buildOutput", () => { const parsed = JSON.parse(out); expect(parsed.replayCommand).toContain("--group-by-team-prefix 'squad-'"); }); - - it("threads consolidateTeamSections into the replay command", () => { - const groups = [makeGroup("myorg/repoA", ["src/foo.ts"])]; - const out = buildOutput(groups, QUERY, ORG, new Set(), new Set(), "json", "repo-and-matches", { - groupByTeamPrefix: "gamme-/squad-", - consolidateTeamSections: true, - }); - const parsed = JSON.parse(out); - expect(parsed.replayCommand).toContain("--group-by-team-prefix-consolidate"); - }); }); diff --git a/src/output.ts b/src/output.ts index fcc4396..e50665b 100644 --- a/src/output.ts +++ b/src/output.ts @@ -35,9 +35,6 @@ export interface ReplayOptions { includeArchived?: boolean; excludeTemplates?: boolean; groupByTeamPrefix?: string; - /** Mirrors `--group-by-team-prefix-consolidate` — collapses single-branch - * nesting chains into one heading (see `consolidateTeamHierarchy`). */ - consolidateTeamSections?: boolean; /** When set, appends `--regex-hint ` to the replay command so the * result set from a regex query can be reproduced exactly. */ regexHint?: string; @@ -62,7 +59,6 @@ export function buildReplayCommand( includeArchived, excludeTemplates, groupByTeamPrefix, - consolidateTeamSections, regexHint, pickTeams, } = options; @@ -114,9 +110,6 @@ export function buildReplayCommand( if (groupByTeamPrefix) { parts.push(`--group-by-team-prefix ${shellQuote(groupByTeamPrefix)}`); } - if (consolidateTeamSections) { - parts.push("--group-by-team-prefix-consolidate"); - } if (regexHint) { parts.push(`--regex-hint ${shellQuote(regexHint)}`); } @@ -397,12 +390,7 @@ export function buildOutput( outputType: OutputType = "repo-and-matches", extraOptions: Pick< ReplayOptions, - | "includeArchived" - | "excludeTemplates" - | "groupByTeamPrefix" - | "consolidateTeamSections" - | "regexHint" - | "pickTeams" + "includeArchived" | "excludeTemplates" | "groupByTeamPrefix" | "regexHint" | "pickTeams" > = {}, ): string { const options: ReplayOptions = { format, outputType, ...extraOptions }; diff --git a/src/tui.ts b/src/tui.ts index b45a822..6d53a9e 100644 --- a/src/tui.ts +++ b/src/tui.ts @@ -143,7 +143,6 @@ export async function runInteractive( includeArchived = false, excludeTemplates = false, groupByTeamPrefix = "", - consolidateTeamSections = false, regexHint = "", initialPickTeams: Record = {}, ): Promise { @@ -702,7 +701,6 @@ export async function runInteractive( includeArchived, excludeTemplates, groupByTeamPrefix, - consolidateTeamSections, regexHint: regexHint || undefined, pickTeams: Object.keys(confirmedPicks).length > 0 ? confirmedPicks : undefined, }),