Skip to content

Commit 2f4ba11

Browse files
committed
Wire regex support into CLI + docs, homepage and comparison table
- Parse /pattern/flags in query subcommand via isRegexQuery() / buildApiQuery() - Pass regexFilter to aggregate(); display UX warning when query is broadened - Add --regex-hint <term> escape hatch for edge cases - docs/usage/search-syntax.md: new Regex queries section with 4 examples - docs/reference/cli-options.md: document --regex-hint flag - docs/architecture/components.md: add src/regex.ts to C4 Level 3a diagram - docs/.vitepress/theme/ComparisonTable.vue: add Regex queries row (web UI only note) - docs/.vitepress/theme/UseCaseTabs.vue: add Semver / version audit use case - AGENTS.md: add src/regex.ts entry + symptom table row - README.md: add Regex search use case Closes #112
1 parent 490cab3 commit 2f4ba11

File tree

8 files changed

+129
-14
lines changed

8 files changed

+129
-14
lines changed

AGENTS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ src/
8080
completions.ts # Pure shell-completion generators: generateCompletion(),
8181
# detectShell(), getCompletionFilePath() — no I/O
8282
group.ts # groupByTeamPrefix — team-prefix grouping logic
83+
regex.ts # Pure query parser: isRegexQuery(), buildApiQuery()
84+
# Detects /pattern/ syntax, derives safe API term,
85+
# returns RegExp for local client-side filtering — no I/O
8386
render.ts # Façade re-exporting sub-modules + top-level
8487
# renderGroups() / renderHelpOverlay()
8588
tui.ts # Interactive keyboard-driven UI (navigation, filter mode,

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ github-code-search query "useFeatureFlag" --org my-org --group-by-team-prefix pl
8383

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

86+
**Regex search — pattern-based code audit**
87+
88+
```bash
89+
github-code-search query "/from.*['\"\`]axios/" --org my-org
90+
```
91+
92+
Use `/pattern/` syntax to run a regex search. The CLI automatically derives a safe API query term and filters results locally — no manual post-processing needed. Use `--regex-hint` to override the derived term when auto-extraction is too broad.
93+
8694
## Why not `gh search code`?
8795

8896
The official [`gh` CLI](https://cli.github.com/) does support `gh search code`, but it returns a **flat paginated list** — one result per line, no grouping, no interactive selection, no structured output.

docs/.vitepress/theme/ComparisonTable.vue

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ const ROWS: Row[] = [
5959
gcs: true,
6060
docLink: "/usage/interactive-mode",
6161
},
62+
{
63+
feature: "Regex queries (/pattern/flags)",
64+
desc: "Use full regular expressions in queries — top-level alternation (A|B|C) maps to GitHub OR, client-side filtering applies the real pattern. GitHub supports regex in the web UI only, not in the REST API or gh CLI.",
65+
gh: false,
66+
gcs: true,
67+
docLink: "/usage/search-syntax",
68+
},
6269
{
6370
feature: "Pagination (up to 1\u202f000 results)",
6471
desc: "Both tools auto-paginate the GitHub search API \u2014 up to 1\u202f000 results per query.",

docs/.vitepress/theme/UseCaseTabs.vue

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ const USE_CASES: UseCase[] = [
5050
"Get a team-scoped view of every usage site before refactoring a shared hook or utility. Essential for onboarding or large-scale refactors.",
5151
command: `github-code-search query "useFeatureFlag" --org my-org --group-by-team-prefix platform/`,
5252
},
53+
{
54+
id: "semver",
55+
label: "Semver / version audit",
56+
headline: "Which repos are pinned to a vulnerable minor version?",
57+
description:
58+
"Use regex syntax to target a precise version range — something a plain keyword search cannot do. Find every repo still locked to axios 1.x, react 17.x, or any other outdated pin, then export the list to a migration issue.",
59+
command: `github-code-search query '/"axios": "1./' --org my-org`,
60+
},
5361
];
5462
5563
const active = ref(0);

docs/architecture/components.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,21 @@ into a filtered, grouped, formatted output.
1414
C4Component
1515
title Level 3a: CLI data pipeline
1616
17-
UpdateLayoutConfig($c4ShapeInRow="4", $c4BoundaryInRow="1")
17+
UpdateLayoutConfig($c4ShapeInRow="5", $c4BoundaryInRow="1")
1818
1919
Container(cli, "CLI parser", "github-code-search.ts", "Orchestrates filter,<br/>group, output and<br/>shell completions")
2020
2121
Container_Boundary(core, "Pure-function core — no I/O") {
22+
Component(regexParser, "Query parser", "src/regex.ts", "isRegexQuery()<br/>buildApiQuery()")
2223
Component(aggregate, "Filter & aggregation", "src/aggregate.ts", "aggregate()<br/>exclude repos & extracts")
2324
Component(group, "Team grouping", "src/group.ts", "groupByTeamPrefix()<br/>flattenTeamSections()")
2425
Component(outputFn, "Output formatter", "src/output.ts", "buildOutput()<br/>markdown or JSON")
2526
Component(completions, "Shell completions", "src/completions.ts", "generateCompletion()<br/>detectShell()<br/>getCompletionFilePath()")
2627
}
2728
29+
Rel(cli, regexParser, "Parse regex<br/>query")
30+
UpdateRelStyle(cli, regexParser, $offsetX="35", $offsetY="-17")
31+
2832
Rel(cli, aggregate, "Filter<br/>CodeMatch[]")
2933
UpdateRelStyle(cli, aggregate, $offsetX="0", $offsetY="-17")
3034
@@ -38,6 +42,7 @@ C4Component
3842
UpdateRelStyle(cli, completions, $offsetX="-90", $offsetY="-17")
3943
4044
UpdateElementStyle(cli, $bgColor="#FFCC33", $borderColor="#0000CC", $fontColor="#000000")
45+
UpdateElementStyle(regexParser, $bgColor="#9933FF", $borderColor="#0000CC", $fontColor="#ffffff")
4146
UpdateElementStyle(aggregate, $bgColor="#9933FF", $borderColor="#0000CC", $fontColor="#ffffff")
4247
UpdateElementStyle(group, $bgColor="#9933FF", $borderColor="#0000CC", $fontColor="#ffffff")
4348
UpdateElementStyle(outputFn, $bgColor="#9933FF", $borderColor="#0000CC", $fontColor="#ffffff")

docs/reference/cli-options.md

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,18 @@ github-code-search completions [--shell <shell>]
3131

3232
## Search options
3333

34-
| Option | Type | Required | Default | Description |
35-
| ----------------------------------- | --------------------------------- | -------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
36-
| `--org <org>` | string ||| GitHub organization to search in. Automatically injected as `org:<org>` in the query. |
37-
| `--exclude-repositories <repos>` | string || `""` | Comma-separated list of repositories to exclude. Short form (`repoA,repoB`) or full form (`org/repoA,org/repoB`) both accepted. |
38-
| `--exclude-extracts <refs>` | string || `""` | Comma-separated extract refs to exclude. Format: `repoName:path/to/file:index`. Short form (without org prefix) accepted. |
39-
| `--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`. |
40-
| `--format <format>` | `markdown` \| `json` || `markdown` | Output format. See [Output formats](/usage/output-formats). |
41-
| `--output-type <type>` | `repo-and-matches` \| `repo-only` || `repo-and-matches` | Controls output detail level. `repo-only` lists repository names only, without individual extracts. |
42-
| `--include-archived` | boolean (flag) || `false` | Include archived repositories in results (excluded by default). |
43-
| `--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. |
44-
| `--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`. |
34+
| Option | Type | Required | Default | Description |
35+
| ----------------------------------- | --------------------------------- | -------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
36+
| `--org <org>` | string ||| GitHub organization to search in. Automatically injected as `org:<org>` in the query. |
37+
| `--exclude-repositories <repos>` | string || `""` | Comma-separated list of repositories to exclude. Short form (`repoA,repoB`) or full form (`org/repoA,org/repoB`) both accepted. |
38+
| `--exclude-extracts <refs>` | string || `""` | Comma-separated extract refs to exclude. Format: `repoName:path/to/file:index`. Short form (without org prefix) accepted. |
39+
| `--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`. |
40+
| `--format <format>` | `markdown` \| `json` || `markdown` | Output format. See [Output formats](/usage/output-formats). |
41+
| `--output-type <type>` | `repo-and-matches` \| `repo-only` || `repo-and-matches` | Controls output detail level. `repo-only` lists repository names only, without individual extracts. |
42+
| `--include-archived` | boolean (flag) || `false` | Include archived repositories in results (excluded by default). |
43+
| `--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. |
44+
| `--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`. |
45+
| `--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). |
4546

4647
## Global options
4748

docs/usage/search-syntax.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,54 @@ github-code-search "useFeatureFlag repo:fulll/billing-api repo:fulll/auth-servic
7777
github-code-search "password= language:TypeScript NOT filename:test" --org fulll
7878
```
7979

80+
## Regex queries
81+
82+
`github-code-search` supports regex syntax using the `/pattern/flags` notation, just like the GitHub web UI.
83+
84+
Because the GitHub Code Search API does not natively support regex, the CLI automatically extracts a representative literal term from the regex to send to the API, then filters the returned results locally with the full pattern. In most cases this is fully transparent.
85+
86+
```bash
87+
# Imports using the axios module (any quote style)
88+
github-code-search "/from.*['\"\`]axios/" --org fulll
89+
90+
# Axios dependency in package.json (any semver prefix)
91+
github-code-search '"axios": "[~^]?[0-9]" filename:package.json' --org fulll
92+
93+
# Old library require() calls
94+
github-code-search "/require\\(['\"](old-lib)['\"]\\)/" --org fulll
95+
96+
# Any of TODO, FIXME or HACK comments
97+
github-code-search "/TODO|FIXME|HACK/" --org fulll
98+
```
99+
100+
::: tip Top-level alternation
101+
When the regex contains a **top-level `|`** (e.g. `TODO|FIXME|HACK`), the CLI sends
102+
an `A OR B OR C` query to the GitHub API so that **all branches are covered** — no results are missed.
103+
:::
104+
105+
### When auto-extraction is not precise enough
106+
107+
If the extracted term is very short (fewer than 3 characters), the CLI will exit with a warning and ask you to provide a manual hint:
108+
109+
```
110+
⚠ Regex mode — could not extract a term longer than 2 chars from /[~^]?[0-9]/
111+
Provide a manual hint with --regex-hint <term>.
112+
```
113+
114+
Use `--regex-hint` to override the API search term while still applying the full regex filter locally:
115+
116+
```bash
117+
github-code-search '/"axios":\s*"[~^]?[0-9]/ filename:package.json' \
118+
--org fulll \
119+
--regex-hint '"axios"'
120+
```
121+
122+
::: warning API coverage
123+
The GitHub Code Search API returns **at most 1,000 results** per query. The regex filter
124+
is applied to those results; results beyond the API cap can never be seen. Refine the
125+
query with qualifiers (`language:`, `path:`, `filename:`) to keep the result set small.
126+
:::
127+
80128
## API limits
81129

82130
The GitHub Code Search API returns at most **1,000 results** per query. If your query returns more, refine it with qualifiers (especially `language:` or `path:`) to stay below the limit.

github-code-search.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { groupByTeamPrefix, flattenTeamSections } from "./src/group.ts";
2424
import { checkForUpdate } from "./src/upgrade.ts";
2525
import { runInteractive } from "./src/tui.ts";
2626
import { generateCompletion, detectShell } from "./src/completions.ts";
27+
import { buildApiQuery, isRegexQuery } from "./src/regex.ts";
2728
import type { OutputFormat, OutputType } from "./src/types.ts";
2829

2930
// Version + build metadata injected at compile time via --define (see build.ts).
@@ -179,6 +180,15 @@ function addSearchOptions(cmd: Command): Command {
179180
.option(
180181
"--no-cache",
181182
"Bypass the 24 h team-list cache and re-fetch teams from GitHub (only applies with --group-by-team-prefix).",
183+
)
184+
.option(
185+
"--regex-hint <term>",
186+
[
187+
"Override the search term sent to the GitHub API when using a regex query.",
188+
"Useful when auto-extraction produces a term that is too broad or too narrow.",
189+
'Example: --regex-hint "axios" (for query /from.*[\'"]axios/)',
190+
"Docs: https://fulll.github.io/github-code-search/usage/search-syntax#regex-queries",
191+
].join("\n"),
182192
);
183193
}
184194

@@ -195,6 +205,7 @@ async function searchAction(
195205
includeArchived: boolean;
196206
groupByTeamPrefix: string;
197207
cache: boolean;
208+
regexHint?: string;
198209
},
199210
): Promise<void> {
200211
// ─── GitHub API token ───────────────────────────────────────────────────────
@@ -264,8 +275,32 @@ async function searchAction(
264275
return activeCooldown;
265276
};
266277

267-
const rawMatches = await fetchAllResults(query, org, GITHUB_TOKEN!, onRateLimit);
268-
let groups = aggregate(rawMatches, excludedRepos, excludedExtractRefs, includeArchived);
278+
// ─── Regex query detection ───────────────────────────────────────────────
279+
let effectiveQuery = query;
280+
let regexFilter: RegExp | undefined;
281+
if (isRegexQuery(query)) {
282+
const { apiQuery, regexFilter: rf, warn } = buildApiQuery(query);
283+
if (warn && !opts.regexHint) {
284+
console.error(
285+
pc.yellow(`⚠ Regex mode — ${warn}\n Provide a manual hint with --regex-hint <term>.`),
286+
);
287+
process.exit(1);
288+
}
289+
effectiveQuery = opts.regexHint ?? apiQuery;
290+
regexFilter = rf ?? undefined;
291+
process.stderr.write(
292+
pc.dim(` ℹ Regex mode — GitHub query: "${effectiveQuery}", local filter: ${query}\n`),
293+
);
294+
}
295+
296+
const rawMatches = await fetchAllResults(effectiveQuery, org, GITHUB_TOKEN!, onRateLimit);
297+
let groups = aggregate(
298+
rawMatches,
299+
excludedRepos,
300+
excludedExtractRefs,
301+
includeArchived,
302+
regexFilter,
303+
);
269304

270305
// ─── Team-prefix grouping ─────────────────────────────────────────────────
271306
if (opts.groupByTeamPrefix) {

0 commit comments

Comments
 (0)