Skip to content

Commit 624c4fc

Browse files
authored
Merge pull request #114 from fulll/feat/regex-cli
feat: wire regex support into CLI — UX messaging, --regex-hint, docs, homepage
2 parents c02aaf1 + 1df768a commit 624c4fc

18 files changed

Lines changed: 390 additions & 98 deletions

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.

bun.lock

Lines changed: 48 additions & 46 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,25 @@ The three pure functions called by the CLI parser to transform raw API results
1010
into a filtered, grouped, formatted output.
1111

1212
```mermaid
13-
%%{init: {"theme": "base", "themeVariables": {"fontFamily": "Poppins, Aestetico, Arial, sans-serif", "primaryColor": "#66CCFF", "primaryTextColor": "#000000", "lineColor": "#0000CC", "tertiaryColor": "#FFCC33"}, "themeCSS": ".label,.nodeLabel,.cluster-label > span{font-family:Poppins,Arial,sans-serif;letter-spacing:.2px} .cluster-label > span{font-weight:600;font-size:13px} .edgePath .path{stroke-width:2px}"}}%%
13+
%%{init: {"theme": "base", "themeVariables": {"fontFamily": "Poppins, Aestetico, Arial, sans-serif", "primaryColor": "#9933FF", "primaryTextColor": "#ffffff", "lineColor": "#0000CC", "tertiaryColor": "#FFCC33"}, "themeCSS": ".label,.nodeLabel,.cluster-label > span{font-family:Poppins,Arial,sans-serif;letter-spacing:.2px} .cluster-label > span{font-weight:600;font-size:13px} .edgePath .path{stroke-width:2px}"}}%%
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
@@ -37,11 +41,6 @@ C4Component
3741
Rel(cli, completions, "Generate<br/>script")
3842
UpdateRelStyle(cli, completions, $offsetX="-90", $offsetY="-17")
3943
40-
UpdateElementStyle(cli, $bgColor="#FFCC33", $borderColor="#0000CC", $fontColor="#000000")
41-
UpdateElementStyle(aggregate, $bgColor="#9933FF", $borderColor="#0000CC", $fontColor="#ffffff")
42-
UpdateElementStyle(group, $bgColor="#9933FF", $borderColor="#0000CC", $fontColor="#ffffff")
43-
UpdateElementStyle(outputFn, $bgColor="#9933FF", $borderColor="#0000CC", $fontColor="#ffffff")
44-
UpdateElementStyle(completions, $bgColor="#9933FF", $borderColor="#0000CC", $fontColor="#ffffff")
4544
```
4645

4746
## 3b — TUI render layer
@@ -52,7 +51,7 @@ The render-layer modules called by the TUI on every redraw. Most live in
5251
provides shared pattern-matching helpers used by several render modules.
5352

5453
```mermaid
55-
%%{init: {"theme": "base", "themeVariables": {"fontFamily": "Poppins, Aestetico, Arial, sans-serif", "primaryColor": "#66CCFF", "primaryTextColor": "#000000", "lineColor": "#0000CC", "tertiaryColor": "#FFCC33"}, "themeCSS": ".label,.nodeLabel,.cluster-label > span{font-family:Poppins,Arial,sans-serif;letter-spacing:.2px} .cluster-label > span{font-weight:600;font-size:13px} .edgePath .path{stroke-width:2px}"}}%%
54+
%%{init: {"theme": "base", "themeVariables": {"fontFamily": "Poppins, Aestetico, Arial, sans-serif", "primaryColor": "#9933FF", "primaryTextColor": "#ffffff", "lineColor": "#0000CC", "tertiaryColor": "#FFCC33"}, "themeCSS": ".label,.nodeLabel,.cluster-label > span{font-family:Poppins,Arial,sans-serif;letter-spacing:.2px} .cluster-label > span{font-weight:600;font-size:13px} .edgePath .path{stroke-width:2px}"}}%%
5655
C4Component
5756
title Level 3b: TUI render layer
5857
@@ -97,14 +96,6 @@ C4Component
9796
Rel(selection, filterMatch, "Uses pattern<br/>matchers")
9897
UpdateRelStyle(selection, filterMatch, $offsetX="165", $offsetY="-25")
9998
100-
UpdateElementStyle(tui, $bgColor="#FFCC33", $borderColor="#0000CC", $fontColor="#000000")
101-
UpdateElementStyle(rows, $bgColor="#9933FF", $borderColor="#0000CC", $fontColor="#ffffff")
102-
UpdateElementStyle(filterMatch, $bgColor="#9933FF", $borderColor="#0000CC", $fontColor="#ffffff")
103-
UpdateElementStyle(summary, $bgColor="#9933FF", $borderColor="#0000CC", $fontColor="#ffffff")
104-
UpdateElementStyle(filter, $bgColor="#9933FF", $borderColor="#0000CC", $fontColor="#ffffff")
105-
UpdateElementStyle(selection, $bgColor="#9933FF", $borderColor="#0000CC", $fontColor="#ffffff")
106-
UpdateElementStyle(highlight, $bgColor="#9933FF", $borderColor="#0000CC", $fontColor="#ffffff")
107-
UpdateElementStyle(outputFn, $bgColor="#9933FF", $borderColor="#0000CC", $fontColor="#ffffff")
10899
```
109100

110101
## Component descriptions

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: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,53 @@ 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+
```text
110+
⚠ Regex mode — No meaningful search term could be extracted from the regex pattern. Use --regex-hint <term> to specify the term to send to the GitHub API.
111+
```
112+
113+
Use `--regex-hint` to override the API search term while still applying the full regex filter locally:
114+
115+
```bash
116+
github-code-search '/"axios":\s*"[~^]?[0-9]/ filename:package.json' \
117+
--org fulll \
118+
--regex-hint '"axios"'
119+
```
120+
121+
::: warning API coverage
122+
The GitHub Code Search API returns **at most 1,000 results** per query. The regex filter
123+
is applied to those results; results beyond the API cap can never be seen. Refine the
124+
query with qualifiers (`language:`, `path:`, `filename:`) to keep the result set small.
125+
:::
126+
80127
## API limits
81128

82129
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.

0 commit comments

Comments
 (0)