diff --git a/.github/instructions/release.instructions.md b/.github/instructions/release.instructions.md new file mode 100644 index 0000000..1fed744 --- /dev/null +++ b/.github/instructions/release.instructions.md @@ -0,0 +1,108 @@ +--- +applyTo: "**" +excludeAgent: "code-review" +--- + +# Release — instructions for Copilot coding agent + +Follow these steps when cutting a new release of `github-code-search`. + +## 1. Determine the version bump + +This project follows [Semantic Versioning](https://semver.org/): + +| Change type | Bump | Example | +| ---------------------------------------------- | ------- | ------------- | +| Bug fix only (no new behaviour, no API change) | `patch` | 1.2.4 → 1.2.5 | +| New feature, backward-compatible | `minor` | 1.2.4 → 1.3.0 | +| Breaking change (CLI flag removed/renamed) | `major` | 1.2.4 → 2.0.0 | + +## 2. Bump the version + +```bash +bun pm version patch # or minor / major +``` + +If the working tree is dirty (staged or unstaged changes), `bun pm version` will refuse. In that case bump directly in `package.json`, then commit the version bump as the first commit on the release branch. + +## 3. Write the blog post + +**Required for minor and major releases. Optional (but encouraged) for patch releases.** + +1. Create `docs/blog/release-v.md` — use existing posts as format reference: + - `docs/blog/release-v1-3-0.md` (minor, feature-focused) + - `docs/blog/release-v1-4-0.md` (minor, TUI/community-focused) + - Front-matter: `title`, `description`, `date` (ISO 8601). + - Structure: `## Highlights` → one `###` section per major change group → `## Upgrade` at the bottom. + - The upgrade section must include the `github-code-search upgrade` command and a link to the GitHub Releases page. + +2. Update `docs/blog/index.md` — prepend a row to the `## v1 series` table: + + ```markdown + | [vX.Y.Z](./release-vX-Y-Z) | One-line summary of highlights | + ``` + +3. Update `CHANGELOG.md` — update (or add) the matching row in the table: + ```markdown + | [vX.Y.Z](https://fulll.github.io/github-code-search/blog/release-vX-Y-Z) | One-line summary | + ``` + Never leave a row with `_pending_` in `CHANGELOG.md` when cutting the release. + +## 4. Create the release branch and commit + +```bash +VERSION=$(jq -r .version package.json) +git checkout -b release/$VERSION +git add package.json docs/blog/release-v*.md docs/blog/index.md CHANGELOG.md +git commit -S -m "v$VERSION" +``` + +> **All commits must be signed** — use `git commit -S` or `git config --global commit.gpgsign true`. + +## 5. Tag and push + +```bash +VERSION=$(jq -r .version package.json) +git tag v$VERSION +git push origin release/$VERSION --tags +``` + +The tag push triggers **`cd.yaml`**: + +1. Builds self-contained binaries for all six targets. +2. Creates a GitHub Release with all binaries attached. +3. For major tags (`vX.0.0`): triggers `docs.yml` → docs snapshot + `versions.json` update. + +Do **not** create the GitHub Release manually — the CD pipeline handles it. + +## 6. Required validation before tagging + +```bash +bun test # full suite green +bun run lint # oxlint — zero errors +bun run format:check # oxfmt — no diff +bun run knip # no unused exports +bun run build.ts # binary compiles +``` + +## 7. Post-release checklist + +- [ ] GitHub Release created automatically by CD pipeline (verify within ~5 min after tag push) +- [ ] Blog post live at `https://fulll.github.io/github-code-search/blog/release-vX-Y-Z` +- [ ] `bun run docs:build` succeeds locally (spot-check the new blog entry) +- [ ] `CHANGELOG.md` has no `_pending_` entries +- [ ] For **major** releases: versioned docs snapshot available at `/github-code-search/vX/` + +## 8. Module map — what to document per release type + +| Changed area | Cover in the blog post | +| ------------------------- | --------------------------------------------------------------- | +| `src/tui.ts` | UX / interaction changes (keyboard shortcuts, new modes) | +| `src/render/` | Visual changes (colours, layout, new components) | +| `src/aggregate.ts` | New filter or exclusion options | +| `src/group.ts` | Team-grouping behaviour changes | +| `src/output.ts` | New output formats or structural changes to existing ones | +| `src/api.ts` | New GitHub API features, pagination changes, scope requirements | +| `src/upgrade.ts` | Upgrade command improvements | +| `github-code-search.ts` | New CLI flags, subcommands, breaking option renames | +| Community / project files | SECURITY, CODE_OF_CONDUCT, CONTRIBUTING changes worth surfacing | diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f518d60 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,14 @@ +# Changelog + +Release notes and changelogs are published on the **[project blog](https://fulll.github.io/github-code-search/blog/)**. + +Each release entry covers the motivation, new features, breaking changes (if any), and upgrade notes. + +| Version | Blog post | +| ------------------------------------------------------------------------ | ---------------------------------------------------- | +| [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/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..f993fee --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,132 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or advances of + any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, + without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +[admin@fulll.fr](mailto:admin@fulll.fr). +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 415bc08..7e30d91 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -83,7 +83,8 @@ Compiled binaries require no runtime dependencies and can be distributed as a si - TypeScript throughout. - Pure functions wherever possible (makes unit testing straightforward). - Side-effectful code (CLI parsing, API calls, TTY interaction) is isolated in `github-code-search`, `src/api.ts`, and `src/tui.ts`. -- No linter is configured yet; keep diffs small and consistent with the surrounding code. +- Run `bun run lint` (oxlint) — must pass with zero errors before submitting. +- Run `bun run format:check` (oxfmt) — auto-fix locally with `bun run format`. ## Submitting a pull request diff --git a/README.md b/README.md index 950dca7..e46c830 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ keyboard-driven TUI, fine-grained extract selection, markdown/JSON output. → **Full documentation: https://fulll.github.io/github-code-search/** +![Demo](demo/demo.gif) + ## Quick start ```bash @@ -17,3 +19,73 @@ export GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx curl -fsSL https://raw.githubusercontent.com/fulll/github-code-search/main/install.sh | bash github-code-search query "TODO" --org my-org ``` + +## Features + +- **Org-wide search** — queries all repositories in a GitHub organization in one command, with automatic pagination up to 1 000 results +- **Per-repository aggregation** — results grouped by repo, not as a flat list; fold/unfold each repo to focus on what matters +- **Keyboard-driven TUI** — navigate with arrow keys, toggle selections, filter by file path, confirm with Enter — without leaving the terminal +- **Fine-grained selection** — pick exactly the repos and extracts you want; deselected items are recorded as exclusions in the replay command +- **Structured output** — clean Markdown lists with GitHub links, or machine-readable JSON — ready to paste into docs, issues or scripts +- **Team-prefix grouping** — group results by team prefix (e.g. `platform/`, `data/`) using `--group-by-team-prefix` +- **Replay command** — every session produces a one-liner you can run in CI to reproduce the exact same selection without the UI +- **Syntax highlighting** — code fragments rendered with language-aware coloring (TypeScript, Python, Go, Rust, YAML, JSON and more) + +## Use cases + +**Audit a dependency across the org** + +```bash +github-code-search query "from 'lodash'" --org my-org +``` + +Instantly see every repo still importing lodash, select the ones to migrate, and get a Markdown checklist to paste in your migration issue. + +**Hunt down TODOs before a release** + +```bash +github-code-search query "TODO" --org my-org --exclude-repositories sandbox,archived-repo +``` + +Surfaces all in-code TODOs, lets you triage interactively, and outputs a linked list for your release notes. + +**Verify a breaking-change rollout** + +```bash +github-code-search query "oldApiClient" --org my-org --output-type repo-only --format json +``` + +Use JSON output in a CI script to assert that no repository still references the deprecated client after your migration deadline. + +**Security sweep — find hardcoded secret patterns** + +```bash +github-code-search query "process.env.SECRET" --org my-org +``` + +Cross-repo scan for risky patterns; export results to Markdown to attach to a security audit report. + +**Onboarding — understand how an internal library is used** + +```bash +github-code-search query "useFeatureFlag" --org my-org --group-by-team-prefix platform/ +``` + +Get a team-scoped view of every usage site before refactoring a shared hook or utility. + +## Why not `gh search code`? + +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. + +| | `gh search code` | `github-code-search` | +| ------------------------------------------ | :--------------: | :------------------: | +| Results grouped by repo | ✗ | ✓ | +| Interactive TUI (navigate, select, filter) | ✗ | ✓ | +| Fine-grained extract selection | ✗ | ✓ | +| Markdown / JSON output | ✗ | ✓ | +| Replay / CI command | ✗ | ✓ | +| Team-prefix grouping | ✗ | ✓ | +| Syntax highlighting in terminal | ✗ | ✓ | +| Pagination (up to 1 000 results) | ✓ | ✓ | + +`github-code-search` is purpose-built for **org-wide code audits and interactive triage** — not just a search wrapper. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..b882265 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,34 @@ +## Security + +We take the security of our software products and components seriously, which includes all source code repositories managed through our [Fulll's GitHub organization](https://github.com/fulll). + +If you believe you have found a security vulnerability in any Fulll's repository that meets Wikipedia's definition of a security vulnerability ([English version](), [French version]()), please report it to us as described below. + +## Reporting security vulnerabilities + +:warning: **Please do not report security vulnerabilities through public GitHub issues.** + +Instead, **please report them by email** to [rssi@fulll.fr](mailto:rssi@fulll.fr). + +You should receive a response as soon as possible. If for some reason you do not, please follow up via email to our [Administrator Team](mailto:admin@fulll.fr) to ensure we received your original message. + +For private repositories, you can also send an email or directly use the dedicated issue template for security vulnerability. + +:bulb: In any case, please include the requested information listed below (**as much as you can provide**) to help us better understand the nature and scope of the possible issue: + +- Type of issue (e.g. Denial of service, Elevation of privilege, Information disclosure, Remote Code Execution, Security feature bypass, buffer overflow, SQL injection, cross-site scripting, etc.) +- Full paths of source file(s) related to the manifestation of the issue +- The location of the affected source code (tag/branch/commit or direct URL) +- Step-by-step instructions to reproduce the issue, including any special configuration required to reproduce +- (if possible) Proof-of-concept or exploit code +- Description and Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Preferred Languages + +We prefer all communications to be in English, but if you are not comfortable, French is acceptable too. + +## Policy + +Fulll follows the principle of [Microsoft's Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). diff --git a/demo/README.md b/demo/README.md new file mode 100644 index 0000000..0e9c24e --- /dev/null +++ b/demo/README.md @@ -0,0 +1,27 @@ +# Demo + +This directory contains the [VHS](https://github.com/charmbracelet/vhs) tape used to generate +the animated demo (`demo.gif`) embedded in the root README. + +## Regenerate the animation + +1. Install VHS: + + ```bash + brew install vhs + ``` + +2. Build the binary: + + ```bash + bun run build.ts + ``` + +3. Export your GitHub token and record: + + ```bash + export GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx + vhs demo.tape # writes demo.gif + ``` + +The GIF is committed alongside this file and referenced from the root README as `![Demo](demo/demo.gif)`. diff --git a/demo/demo.gif b/demo/demo.gif new file mode 100644 index 0000000..9682a70 Binary files /dev/null and b/demo/demo.gif differ diff --git a/demo/demo.tape b/demo/demo.tape new file mode 100644 index 0000000..952d203 --- /dev/null +++ b/demo/demo.tape @@ -0,0 +1,70 @@ +# VHS tape — terminal demo for README +# Usage: +# 1. Install VHS: brew install vhs +# 2. Build the binary: bun run build.ts +# 3. Export your token: export GITHUB_TOKEN=ghp_... +# 4. Record: vhs demo/demo.tape +# +# The GIF is written to demo/demo.gif — add it to README.md with: +# ![demo](demo/demo.gif) + +Output demo/demo.gif + +Require github-code-search + +Set Shell "bash" +Set FontSize 15 +Set Width 1200 +Set Height 600 +Set Theme "Catppuccin Mocha" +Set Padding 24 +Set TypingSpeed 60ms + +# ── Show the prompt ──────────────────────────────────────────────────────────── +Hide +Type "export GITHUB_TOKEN=$GITHUB_TOKEN" +Enter +Show + +Sleep 500ms + +# ── Type the command ────────────────────────────────────────────────────────── +Type 'github-code-search query "@actions/cache" --org actions' +Sleep 800ms +Enter + +# Wait for results to load (API call + TUI render) +Sleep 4s + +# ── Navigate down a few rows ────────────────────────────────────────────────── +Down +Sleep 300ms +Down +Sleep 300ms +Down +Sleep 300ms + +# ── Toggle selection off on current extract ─────────────────────────────────── +Space +Sleep 400ms + +# ── Enter filter mode to exclude .ts files ──────────────────────────────────── +Type "f" +Sleep 300ms +Type ".yml" +Sleep 600ms +Enter +Sleep 500ms + +# ── Select all visible (filtered) rows ──────────────────────────────────────── +Type "a" +Sleep 500ms + +# ── Reset filter ────────────────────────────────────────────────────────────── +Type "r" +Sleep 600ms + +# ── Confirm and print output ────────────────────────────────────────────────── +Enter + +Sleep 2s diff --git a/docs/blog/index.md b/docs/blog/index.md index 8f49440..5cf50c5 100644 --- a/docs/blog/index.md +++ b/docs/blog/index.md @@ -9,10 +9,11 @@ Full release notes and changelogs are always available on ## v1 series -| Release | Highlights | -| -------------------------- | --------------------------------------------------------------------------------------------------- | -| [v1.3.0](./release-v1-3-0) | Richer upgrade output, update-available notice, colorized `--help`, deep doc links, What's New blog | -| [v1.0.0](./release-v1-0-0) | Initial public release — interactive TUI, per-repo aggregation, markdown / JSON output | +| Release | Highlights | +| -------------------------- | ----------------------------------------------------------------------------------------------------- | +| [v1.4.0](./release-v1-4-0) | TUI visual overhaul, violet branding, demo animation, SECURITY / Code of Conduct, README improvements | +| [v1.3.0](./release-v1-3-0) | Richer upgrade output, update-available notice, colorized `--help`, deep doc links, What's New blog | +| [v1.0.0](./release-v1-0-0) | Initial public release — interactive TUI, per-repo aggregation, markdown / JSON output | --- diff --git a/docs/blog/release-v1-4-0.md b/docs/blog/release-v1-4-0.md new file mode 100644 index 0000000..f80e667 --- /dev/null +++ b/docs/blog/release-v1-4-0.md @@ -0,0 +1,53 @@ +--- +title: "What's new in v1.4.0" +description: "TUI visual overhaul with violet branding, right-aligned match counts, animated demo, community files (SECURITY, Code of Conduct), and README improvements." +date: 2026-02-27 +--- + +# What's new in github-code-search v1.4.0 + +> Full release notes: + +## Highlights + +### TUI visual overhaul + +The interactive interface has been redesigned to match the project's visual identity: + +- **Violet branding** — the cursor highlight, section separators and brand badge now use `bgMagenta` / `magenta` consistently throughout the UI. +- **Brand badge header** — a `github-code-search` badge is displayed at the top of the screen alongside the current query and organisation, giving the TUI an immediately recognisable header. +- **Green checkmarks** — selected items now show a green `✓`; deselected items show a blank space instead of an empty circle, reducing visual clutter. +- **Refined arrows** — repository expand/collapse arrows changed from `▶`/`▼` to `▸`/`▾`, matching the lighter style used in the documentation. +- **Right-aligned match counts** — each repository row pads its match count flush with the right edge of the terminal window, making it easy to scan at a glance. + +### Animated demo + +A VHS-recorded demo animation (`demo/demo.gif`) now appears directly in the README, letting users see the tool in action before installing anything. + +### Community & open-source hygiene + +- **`SECURITY.md`** — responsible-disclosure policy with a private contact address and a clear timeline commitment. +- **`CODE_OF_CONDUCT.md`** — Contributor Covenant 2.1, the industry-standard code of conduct. +- **`CHANGELOG.md`** — root-level changelog pointing to this blog for per-release details. +- **Social preview** — a `docs/public/social-preview.svg` (1280 × 640) provides a polished GitHub social card. + +### README improvements + +- **Features section** — eight bullet points summarising what the tool does. +- **Use cases** — five concrete scenarios showing when `github-code-search` saves time. +- **Comparison table** — side-by-side comparison with `gh search code`, highlighting why an interactive TUI matters. + +### `package.json` discoverability + +Added `repository`, `homepage` and `bugs` fields, plus 18 keywords, so the package is correctly indexed on npmjs.com and pkg.pr.new. + +--- + +## Upgrade + +```bash +github-code-search upgrade +``` + +Or grab the latest binary directly from the +[GitHub Releases page](https://github.com/fulll/github-code-search/releases/tag/v1.4.0). diff --git a/docs/index.md b/docs/index.md index 4d6a602..a545b2c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -42,3 +42,74 @@ features: title: Replay command details: Every interactive session produces a one-liner you can run in CI to reproduce the exact same selection without the UI. --- + +## Use cases + +**Audit a dependency across the org** + +```bash +github-code-search query "from 'lodash'" --org my-org +``` + +See every repo still importing lodash, select the ones to migrate, and get a Markdown checklist to paste in your migration issue. + +--- + +**Hunt down TODOs before a release** + +```bash +github-code-search query "TODO" --org my-org --exclude-repositories sandbox,archived-repo +``` + +Surface all in-code TODOs, triage interactively, and output a linked list for your release notes. + +--- + +**Verify a breaking-change rollout** + +```bash +github-code-search query "oldApiClient" --org my-org --output-type repo-only --format json +``` + +Use JSON output in a CI script to assert that no repository still references a deprecated client after your migration deadline. + +--- + +**Security sweep — find hardcoded secret patterns** + +```bash +github-code-search query "process.env.SECRET" --org my-org +``` + +Cross-repo scan for risky patterns; export results to Markdown to attach to a security audit report. + +--- + +**Onboarding — understand how an internal library is used** + +```bash +github-code-search query "useFeatureFlag" --org my-org --group-by-team-prefix platform/ +``` + +Get a team-scoped view of every usage site before refactoring a shared hook or utility. + +## Why not `gh search code`? + +The official [gh CLI](https://cli.github.com/) supports `gh search code`, but returns a **flat paginated list** — one result per line, no grouping, no interactive selection, no structured output. + +| | `gh search code` | `github-code-search` | +| ------------------------------------------ | :--------------: | :------------------: | +| Results grouped by repo | ✗ | ✓ | +| Interactive TUI (navigate, select, filter) | ✗ | ✓ | +| Fine-grained extract selection | ✗ | ✓ | +| Markdown / JSON output | ✗ | ✓ | +| Replay / CI command | ✗ | ✓ | +| Team-prefix grouping | ✗ | ✓ | +| Syntax highlighting in terminal | ✗ | ✓ | +| Pagination (up to 1 000 results) | ✓ | ✓ | + +`github-code-search` is purpose-built for **org-wide code audits and interactive triage** — not just a search wrapper. + +## Used in production? + +Using `github-code-search` at your organisation? Share your experience, use cases or feedback in [GitHub Discussions](https://github.com/fulll/github-code-search/discussions) — your input shapes the roadmap. diff --git a/docs/public/social-preview.svg b/docs/public/social-preview.svg new file mode 100644 index 0000000..6d7de75 --- /dev/null +++ b/docs/public/social-preview.svg @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + github-code-search — bash + + + + + github-code-search + "useState" + in + acme + + + 5 repos · 12 files · 18 matches (18 selected) + + + ← / → fold/unfold ↑ / ↓ navigate spc select a all n none f filter h help ↵ confirm q quit + + + + + + + + + + acme/frontend + 3 matches + + + + src/hooks/useUser.ts + const [user] = + useState + (null) + + + + src/pages/App.tsx + useState + (initialState) + + + + + acme/dashboard + 2 matches + + + + src/components/Modal.tsx + const [open] = + useState + (false) + + + + + acme/api + 8 matches + + + ↕ row 1–8 of 18 + + + github-code-search + Search GitHub code across your org + + + + + + + Org-wide search, up to 1 000 results + + + Per-repository aggregation + + + Keyboard-driven interactive TUI + + + Markdown & JSON structured output + + + Syntax highlighting + + + Team-prefix grouping + + + + TypeScript + + + MIT + + + Bun + + + github.com/fulll/github-code-search · MIT License + diff --git a/docs/usage/interactive-mode.md b/docs/usage/interactive-mode.md index 5ced7c6..1cb75b2 100644 --- a/docs/usage/interactive-mode.md +++ b/docs/usage/interactive-mode.md @@ -11,23 +11,25 @@ github-code-search "useFeatureFlag" --org fulll ## TUI overview ```text -GitHub Code Search: useFeatureFlag in fulll + github-code-search useFeatureFlag in fulll 3 repos · 4 files ← / → fold/unfold ↑ / ↓ navigate spc select a all n none f filter h help ↵ confirm q quit -▶ ◉ fulll/billing-api (3 extracts) -▼ ◉ fulll/auth-service (2 extracts) - ◉ src/middlewares/featureFlags.ts +▸ fulll/billing-api 3 matches +▾ ✓ fulll/auth-service 2 matches + ✓ src/middlewares/featureFlags.ts …const flag = useFeatureFlag('new-onboarding'); if (!flag) return next();… - ◉ tests/unit/featureFlags.test.ts + ✓ tests/unit/featureFlags.test.ts …expect(useFeatureFlag('new-onboarding')).toBe(true);… -▶ ○ fulll/legacy-monolith (1 extract) +▸ fulll/legacy-monolith 1 match ``` -- `▶` — folded repo (extracts hidden) -- `▼` — unfolded repo (extracts visible) -- `◉` — selected -- `○` — deselected +- `▸` — folded repo (extracts hidden) +- `▾` — unfolded repo (extracts visible) +- `✓` — selected (green in the terminal) +- ` ` — deselected (space — keeps columns aligned) +- Match counts are right-aligned to the terminal width +- The header badge `github-code-search` is displayed on a violet background ## Keyboard shortcuts diff --git a/package.json b/package.json index 7e6506b..d6df46a 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,35 @@ { "name": "github-code-search", - "version": "1.3.0", + "version": "1.4.0", "description": "Interactive GitHub code search with per-repo aggregation", + "keywords": [ + "bun", + "cli", + "code-search", + "developer-tools", + "github", + "github-api", + "interactive", + "json", + "markdown", + "organisation", + "organization", + "repositories", + "search", + "terminal", + "tui", + "typescript" + ], + "homepage": "https://fulll.github.io/github-code-search/", + "bugs": { + "url": "https://github.com/fulll/github-code-search/issues" + }, "license": "MIT", "author": "fulll", + "repository": { + "type": "git", + "url": "https://github.com/fulll/github-code-search.git" + }, "bin": { "github-code-search": "./github-code-search.ts" }, diff --git a/src/render.test.ts b/src/render.test.ts index cbbc957..53c968f 100644 --- a/src/render.test.ts +++ b/src/render.test.ts @@ -544,12 +544,12 @@ describe("renderGroups", () => { expect(stripped).toContain("org/repoA"); }); - it("shows fold arrow \u25b6 for folded group", () => { + it("shows fold arrow \u25b8 for folded group", () => { const groups = [makeGroup("org/repoA", ["src/a.ts"], true)]; const rows = buildRows(groups); const out = renderGroups(groups, 0, rows, 40, 0, "q", "org"); const stripped = out.replace(/\x1b\[[0-9;]*m/g, ""); - expect(stripped).toContain("\u25b6"); + expect(stripped).toContain("\u25b8"); }); it("shows file path when group is unfolded", () => { @@ -560,12 +560,12 @@ describe("renderGroups", () => { expect(stripped).toContain("src/a.ts"); }); - it("shows unfold arrow \u25bc for unfolded group", () => { + it("shows unfold arrow \u25be for unfolded group", () => { const groups = [makeGroup("org/repoA", ["src/a.ts"], false)]; const rows = buildRows(groups); const out = renderGroups(groups, 0, rows, 40, 0, "q", "org"); const stripped = out.replace(/\x1b\[[0-9;]*m/g, ""); - expect(stripped).toContain("\u25bc"); + expect(stripped).toContain("\u25be"); }); it("shows sticky repo header when extract cursor scrolled past its repo", () => { @@ -593,6 +593,21 @@ describe("renderGroups", () => { expect(stripped).toContain("org/repoA"); expect(out).toBeTruthy(); // must not crash }); + + it("right-aligns match count to termWidth", () => { + // With termWidth=60 the visible width of the repo row should equal 60 + // (padding fills the gap between repo name and count). + const groups = [makeGroup("org/repoA", ["a.ts", "b.ts"], false)]; + const rows = buildRows(groups); + const termWidth = 60; + const out = renderGroups(groups, 0, rows, 40, 0, "q", "org", { termWidth }); + // Extract just the repo row line (first line after the hint bar) + const lines = out.split("\n"); + const repoLine = lines.find((l) => l.replace(/\x1b\[[0-9;]*m/g, "").includes("org/repoA")); + expect(repoLine).toBeDefined(); + const visibleLen = repoLine!.replace(/\x1b\[[0-9;]*m/g, "").length; + expect(visibleLen).toBe(termWidth); + }); }); // ─── buildRows with filterPath ──────────────────────────────────────────────── diff --git a/src/render.ts b/src/render.ts index 6508e0e..254aaf6 100644 --- a/src/render.ts +++ b/src/render.ts @@ -48,6 +48,12 @@ export function renderHelpOverlay(): string { const INDENT = " "; const HEADER_LINES = 4; // title + summaryFull + hints + blank +/** Strip ANSI escape sequences to measure the visible character width of a string. */ +function stripAnsi(str: string): string { + // eslint-disable-next-line no-control-regex + return str.replace(/\x1b\[[\d;]*[mGKHF]/g, ""); +} + /** Options bag for renderGroups — all fields optional. */ interface RenderOptions { /** Currently active file-path filter (empty = no filter). */ @@ -58,6 +64,8 @@ interface RenderOptions { filterInput?: string; /** Whether to show the help overlay instead of the normal view. */ showHelp?: boolean; + /** Terminal column width used to right-align match counts (default: 80). */ + termWidth?: number; } export function renderGroups( @@ -70,7 +78,13 @@ export function renderGroups( org: string, opts: RenderOptions = {}, ): string { - const { filterPath = "", filterMode = false, filterInput = "", showHelp = false } = opts; + const { + filterPath = "", + filterMode = false, + filterInput = "", + showHelp = false, + termWidth = 80, + } = opts; // ── Help overlay ────────────────────────────────────────────────────────── if (showHelp) { @@ -79,7 +93,9 @@ export function renderGroups( const lines: string[] = []; - lines.push(pc.bold(`GitHub Code Search: ${pc.cyan(query)} in ${pc.yellow(org)}`)); + lines.push( + `${pc.bgMagenta(pc.bold(" github-code-search "))} ${pc.bold(pc.cyan(query))} ${pc.dim("in")} ${pc.bold(pc.yellow(org))}`, + ); lines.push(buildSummaryFull(groups)); // ── Filter bar (sticky, shown when active or typing) ────────────────────── @@ -122,9 +138,9 @@ export function renderGroups( ); if (repoRowIndex >= 0 && repoRowIndex < scrollOffset) { const g = groups[cursorRow.repoIndex]; - const checkbox = g.repoSelected ? pc.green("◉") : pc.dim("○"); + const checkbox = g.repoSelected ? pc.green("✓") : " "; stickyRepoLine = pc.dim( - `▲ ${checkbox} ${pc.bold(g.repoFullName)} ${pc.dim(`(${buildMatchCountLabel(g)})`)}`, + `▲ ${checkbox} ${pc.bold(g.repoFullName)} ${pc.dim(buildMatchCountLabel(g))}`, ); lines.push(stickyRepoLine); } @@ -139,7 +155,7 @@ export function renderGroups( // ── Section header row ──────────────────────────────────────────────── if (row.type === "section") { - lines.push(pc.bold(`\n── ${row.sectionLabel} `)); + lines.push(pc.magenta(pc.bold(`\n── ${row.sectionLabel} `))); usedLines += 2; // blank separator line + label line if (usedLines >= viewportHeight) break; continue; @@ -153,22 +169,31 @@ export function renderGroups( const isCursor = i === cursor; if (row.type === "repo") { - const arrow = group.folded ? pc.dim("▶") : pc.dim("▼"); - const checkbox = group.repoSelected ? pc.green("◉") : pc.dim("○"); + const arrow = group.folded ? pc.magenta("▸") : pc.magenta("▾"); + // ✓ for selected, space for deselected — keeps the line clean while a + // green checkmark clearly signals selection. The space preserves column + // alignment so the repo name always starts at the same offset. + const checkbox = group.repoSelected ? pc.green("✓") : " "; const repoName = isCursor - ? pc.bgBlue(pc.bold(` ${group.repoFullName} `)) + ? pc.bgMagenta(pc.bold(pc.white(` ${group.repoFullName} `))) : pc.bold(group.repoFullName); - const count = pc.dim(`(${buildMatchCountLabel(group)})`); - lines.push(`${arrow} ${checkbox} ${repoName} ${count}`); + const count = pc.dim(buildMatchCountLabel(group)); + // Right-align the match count flush to the terminal edge + const leftPart = `${arrow} ${checkbox} ${repoName}`; + const leftLen = stripAnsi(leftPart).length; + const countLen = stripAnsi(count).length; + const pad = Math.max(0, termWidth - leftLen - countLen); + const line = pad > 0 ? `${leftPart}${" ".repeat(pad)}${count}` : `${leftPart}${count}`; + lines.push(line); } else { const ei = row.extractIndex!; const match = group.matches[ei]; const selected = group.extractSelected[ei]; - const checkbox = selected ? pc.green("◉") : pc.dim("○"); + const checkbox = selected ? pc.green("✓") : " "; const seg = match.textMatches[0]?.matches[0]; const locSuffix = seg ? `:${seg.line}:${seg.col}` : ""; const filePath = isCursor - ? pc.bgBlue(pc.bold(` ${match.path}${locSuffix} `)) + ? pc.bgMagenta(pc.bold(pc.white(` ${match.path}${locSuffix} `))) : `${pc.cyan(match.path)}${pc.dim(locSuffix)}`; lines.push(`${INDENT}${INDENT}${checkbox} ${filePath}`); diff --git a/src/tui.ts b/src/tui.ts index 5f030c4..fa5022e 100644 --- a/src/tui.ts +++ b/src/tui.ts @@ -61,6 +61,7 @@ export async function runInteractive( filterMode, filterInput, showHelp, + termWidth: process.stdout.columns ?? 80, }); process.stdout.write(ANSI_CLEAR); process.stdout.write(rendered);