Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ github-code-search query "useFeatureFlag" --org my-org --group-by-team-prefix pl

Get a team-scoped view of every usage site before refactoring a shared hook or utility.

**Skip template repositories**

```bash
github-code-search query "TODO" --org my-org --exclude-template-repositories
```

Exclude repositories that are marked as GitHub templates, so boilerplate repos don't clutter your results.

**Regex search — pattern-based code audit**

```bash
Expand Down
1 change: 1 addition & 0 deletions docs/reference/cli-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ github-code-search completions [--shell <shell>]
| `--format <format>` | `markdown` \| `json` | ❌ | `markdown` | Output format. See [Output formats](/usage/output-formats). |
| `--output-type <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 <prefixes>` | string | ❌ | `""` | Comma-separated team-name prefixes for grouping result repos by GitHub team (e.g. `squad-,chapter-`). Requires `read:org` scope. |
| `--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 <term>` | 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). |
Expand Down
17 changes: 15 additions & 2 deletions docs/usage/filtering.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Filtering

`github-code-search` provides three pre-query filtering options so you can exclude noise before results ever appear in the TUI or output.
`github-code-search` provides four pre-query filtering options so you can exclude noise before results ever appear in the TUI or output.
Comment thread
shouze marked this conversation as resolved.
Outdated

## `--exclude-repositories`

Expand Down Expand Up @@ -64,13 +64,26 @@ github-code-search "useFeatureFlag" --org fulll --include-archived
Archived repos are silently filtered out in the aggregation step before the TUI is shown. This flag overrides that behaviour.
:::

## `--exclude-template-repositories`

By default, template repositories are included in results. Pass `--exclude-template-repositories` to filter them out:

Comment thread
shouze marked this conversation as resolved.
```bash
github-code-search "useFeatureFlag" --org fulll --exclude-template-repositories
```

::: info
Template repositories (marked as templates on GitHub) are filtered out in the aggregation step — both in interactive and non-interactive mode. Useful when your organisation uses template repos for boilerplate that should not appear in search results.
:::
Comment thread
shouze marked this conversation as resolved.

## Combining filters

All three flags can be combined freely:
All four flags can be combined freely:

```bash
github-code-search "useFeatureFlag" --org fulll \
--include-archived \
--exclude-template-repositories \
--exclude-repositories legacy-monolith \
--exclude-extracts billing-api:src/flags.ts:0
```
Expand Down
10 changes: 10 additions & 0 deletions github-code-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,11 @@ function addSearchOptions(cmd: Command): Command {
"Include archived repositories in results (default: false)",
false,
)
.option(
"--exclude-template-repositories",
"Exclude template repositories from results (default: false)",
false,
)
.option(
"--group-by-team-prefix <prefixes>",
[
Expand Down Expand Up @@ -203,6 +208,7 @@ async function searchAction(
format: string;
outputType: string;
includeArchived: boolean;
excludeTemplateRepositories: boolean;
groupByTeamPrefix: string;
cache: boolean;
regexHint?: string;
Expand All @@ -219,6 +225,7 @@ async function searchAction(
const format: OutputFormat = opts.format === "json" ? "json" : "markdown";
const outputType: OutputType = opts.outputType === "repo-only" ? "repo-only" : "repo-and-matches";
const includeArchived = Boolean(opts.includeArchived);
const excludeTemplates = Boolean(opts.excludeTemplateRepositories);

const excludedRepos = new Set(
opts.excludeRepositories
Expand Down Expand Up @@ -305,6 +312,7 @@ async function searchAction(
excludedExtractRefs,
includeArchived,
regexFilter,
excludeTemplates,
);

// ─── Team-prefix grouping ─────────────────────────────────────────────────
Expand All @@ -327,6 +335,7 @@ async function searchAction(
console.log(
buildOutput(groups, query, org, excludedRepos, excludedExtractRefs, format, outputType, {
includeArchived,
excludeTemplates,
groupByTeamPrefix: opts.groupByTeamPrefix,
regexHint: opts.regexHint,
}),
Expand Down Expand Up @@ -378,6 +387,7 @@ async function searchAction(
format,
outputType,
includeArchived,
excludeTemplates,
opts.groupByTeamPrefix,
opts.regexHint ?? "",
);
Expand Down
24 changes: 23 additions & 1 deletion src/aggregate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,13 @@ describe("extractRef", () => {

// ─── aggregate ───────────────────────────────────────────────────────────────

function makeMatch(repo: string, path: string, archived = false): CodeMatch {
function makeMatch(repo: string, path: string, archived = false, isTemplate = false): CodeMatch {
return {
path,
repoFullName: repo,
htmlUrl: `https://github.com/${repo}/blob/main/${path}`,
archived,
isTemplate,
textMatches: [],
};
}
Expand Down Expand Up @@ -136,6 +137,26 @@ describe("aggregate", () => {
const groups = aggregate(matches, new Set(), new Set());
expect(groups).toHaveLength(0);
});

it("excludes template repos when excludeTemplates = true", () => {
const matches: CodeMatch[] = [
makeMatch("myorg/repoA", "src/a.ts", false, false),
makeMatch("myorg/templateRepo", "src/b.ts", false, true),
];
const groups = aggregate(matches, new Set(), new Set(), false, null, true);
expect(groups).toHaveLength(1);
expect(groups[0].repoFullName).toBe("myorg/repoA");
});

it("includes template repos when excludeTemplates = false (default)", () => {
const matches: CodeMatch[] = [
makeMatch("myorg/repoA", "src/a.ts", false, false),
makeMatch("myorg/templateRepo", "src/b.ts", false, true),
];
const groups = aggregate(matches, new Set(), new Set());
expect(groups).toHaveLength(2);
expect(groups.map((g) => g.repoFullName)).toContain("myorg/templateRepo");
});
});

// ─── aggregate with regexFilter ───────────────────────────────────────────────
Expand All @@ -146,6 +167,7 @@ function makeMatchWithFragments(repo: string, path: string, fragments: string[])
repoFullName: repo,
htmlUrl: `https://github.com/${repo}/blob/main/${path}`,
archived: false,
isTemplate: false,
textMatches: fragments.map((fragment) => ({ fragment, matches: [] })),
};
}
Expand Down
2 changes: 2 additions & 0 deletions src/aggregate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export function aggregate(
excludedExtractRefs: Set<string>,
includeArchived = false,
regexFilter?: RegExp | null,
excludeTemplates = false,
): RepoGroup[] {
// Compile the global regex once per aggregate() call rather than once per
// fragment inside recomputeSegments — avoids repeated RegExp construction
Expand All @@ -97,6 +98,7 @@ export function aggregate(
for (const m of matches) {
if (excludedRepos.has(m.repoFullName)) continue;
if (!includeArchived && m.archived) continue;
if (excludeTemplates && m.isTemplate) continue;
Comment thread
shouze marked this conversation as resolved.
Outdated
// Fix: when a regex filter is active, replace each TextMatch's API-provided
// segments (which point at the literal search term) with segments derived
// from the actual regex match positions — see issue #111 / fix highlight bug
Expand Down
3 changes: 2 additions & 1 deletion src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ interface RawTextMatch {
interface RawCodeItem {
path: string;
html_url: string;
repository: { full_name: string; archived?: boolean };
repository: { full_name: string; archived?: boolean; is_template?: boolean };
text_matches?: RawTextMatch[];
}

Expand Down Expand Up @@ -277,6 +277,7 @@ export async function fetchAllResults(
repoFullName: item.repository.full_name,
htmlUrl: item.html_url,
archived: item.repository.archived === true,
isTemplate: item.repository.is_template === true,
textMatches: (item.text_matches ?? []).map((m) => {
const fragment: string = m.fragment ?? "";
const fragmentStartLine = fileContent ? computeFragmentStartLine(fileContent, fragment) : 1;
Expand Down
6 changes: 6 additions & 0 deletions src/completions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ const OPTIONS = [
takesArg: false,
values: [],
},
{
flag: "exclude-template-repositories",
description: "Exclude template repositories",
takesArg: false,
values: [],
},
{
flag: "no-cache",
description: "Bypass the 24 h team-list cache",
Expand Down
18 changes: 16 additions & 2 deletions src/output.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,25 @@ describe("buildReplayCommand", () => {
expect(cmd).not.toContain("--include-archived");
});

it("includes --exclude-template-repositories when excludeTemplates is true", () => {
const groups = [makeGroup("myorg/repoA", ["a.ts"])];
const opts: ReplayOptions = { excludeTemplates: true };
const cmd = buildReplayCommand(groups, QUERY, ORG, new Set(), new Set(), opts);
expect(cmd).toContain("--exclude-template-repositories");
});

it("does not include --exclude-template-repositories when excludeTemplates is false (default)", () => {
const groups = [makeGroup("myorg/repoA", ["a.ts"])];
const opts: ReplayOptions = { excludeTemplates: false };
const cmd = buildReplayCommand(groups, QUERY, ORG, new Set(), new Set(), opts);
expect(cmd).not.toContain("--exclude-template-repositories");
});

it("includes --group-by-team-prefix when groupByTeamPrefix is set", () => {
const groups = [makeGroup("myorg/repoA", ["a.ts"])];
const opts: ReplayOptions = { groupByTeamPrefix: "squad-,chapter-" };
const cmd = buildReplayCommand(groups, QUERY, ORG, new Set(), new Set(), opts);
expect(cmd).toContain("--group-by-team-prefix squad-,chapter-");
expect(cmd).toContain("--group-by-team-prefix 'squad-,chapter-'");
});

it("does not include --group-by-team-prefix when groupByTeamPrefix is empty (default)", () => {
Expand Down Expand Up @@ -567,6 +581,6 @@ describe("buildOutput", () => {
groupByTeamPrefix: "squad-",
});
const parsed = JSON.parse(out);
expect(parsed.replayCommand).toContain("--group-by-team-prefix squad-");
expect(parsed.replayCommand).toContain("--group-by-team-prefix 'squad-'");
});
});
14 changes: 11 additions & 3 deletions src/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface ReplayOptions {
format?: OutputFormat;
outputType?: OutputType;
includeArchived?: boolean;
excludeTemplates?: boolean;
groupByTeamPrefix?: string;
/** When set, appends `--regex-hint <term>` to the replay command so the
* result set from a regex query can be reproduced exactly. */
Expand All @@ -49,7 +50,8 @@ export function buildReplayCommand(
// Fix: forward all input options so the replay command is fully reproducible — see issue #11
options: ReplayOptions = {},
): string {
const { format, outputType, includeArchived, groupByTeamPrefix, regexHint } = options;
const { format, outputType, includeArchived, excludeTemplates, groupByTeamPrefix, regexHint } =
options;
const parts: string[] = [`github-code-search ${shellQuote(query)} --org ${org} --no-interactive`];
Comment thread
shouze marked this conversation as resolved.
Outdated

const excludedReposList: string[] = [...excludedRepos].map((r) => shortRepo(r, org));
Expand Down Expand Up @@ -90,8 +92,11 @@ export function buildReplayCommand(
if (includeArchived) {
parts.push("--include-archived");
}
if (excludeTemplates) {
parts.push("--exclude-template-repositories");
}
Comment thread
shouze marked this conversation as resolved.
if (groupByTeamPrefix) {
parts.push(`--group-by-team-prefix ${groupByTeamPrefix}`);
parts.push(`--group-by-team-prefix ${shellQuote(groupByTeamPrefix)}`);
}
if (regexHint) {
parts.push(`--regex-hint ${shellQuote(regexHint)}`);
Expand Down Expand Up @@ -264,7 +269,10 @@ export function buildOutput(
excludedExtractRefs: Set<string>,
format: OutputFormat,
outputType: OutputType = "repo-and-matches",
extraOptions: Pick<ReplayOptions, "includeArchived" | "groupByTeamPrefix" | "regexHint"> = {},
extraOptions: Pick<
ReplayOptions,
"includeArchived" | "excludeTemplates" | "groupByTeamPrefix" | "regexHint"
> = {},
): string {
const options: ReplayOptions = { format, outputType, ...extraOptions };
if (format === "json") {
Expand Down
2 changes: 2 additions & 0 deletions src/tui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export async function runInteractive(
format: OutputFormat,
outputType: OutputType = "repo-and-matches",
includeArchived = false,
excludeTemplates = false,
groupByTeamPrefix = "",
regexHint = "",
): Promise<void> {
Expand Down Expand Up @@ -371,6 +372,7 @@ export async function runInteractive(
console.log(
buildOutput(groups, query, org, excludedRepos, excludedExtractRefs, format, outputType, {
includeArchived,
excludeTemplates,
groupByTeamPrefix,
regexHint: regexHint || undefined,
}),
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface CodeMatch {
htmlUrl: string;
textMatches: TextMatch[];
archived: boolean;
isTemplate?: boolean;
}

export interface RepoGroup {
Expand Down
Loading