Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
16 changes: 16 additions & 0 deletions .github/instructions/bug-fixing.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,19 @@ Add a one-line comment above the fix if the root cause is non-obvious:
- Steps to reproduce (before the fix).
- Steps to verify (after the fix).
- Reference to the issue number.

## 8. Release after merge

Once the PR is merged into `main`, publish a **patch** release:

```bash
bun pm version patch # bumps package.json: 1.2.4 → 1.2.5
git checkout -b release/$(jq -r .version package.json)
git add package.json
git commit -S -m "v$(jq -r .version package.json)"
git tag v$(jq -r .version package.json)
git push origin release/$(jq -r .version package.json) --tags
```

The tag push triggers `cd.yaml` which builds all-platform binaries and creates the GitHub Release automatically.
See the full release guide in `AGENTS.md § Release process`.
25 changes: 25 additions & 0 deletions .github/instructions/implement-feature.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,28 @@ bun run build.ts # binary compiles without errors
- **All commits must be signed** (GPG or SSH). Configure once with `git config --global commit.gpgsign true`.
Commits pushed via the GitHub API (Copilot Coding Agent, MCP tools) are automatically Verified by GitHub.
- PR description: motivation, what changed, how to test manually.

## 8. Release after merge

Once the PR is merged into `main`, publish a **minor** (new feature) or **major** (breaking change) release:

```bash
bun pm version minor # new feature: 1.2.4 → 1.3.0
# or
bun pm version major # breaking change: 1.2.4 → 2.0.0

git checkout -b release/$(jq -r .version package.json)
git add package.json
git commit -S -m "v$(jq -r .version package.json)"
git tag v$(jq -r .version package.json)
git push origin release/$(jq -r .version package.json) --tags
```

The tag push triggers `cd.yaml` which builds all-platform binaries and creates the GitHub Release automatically.

For **minor and major releases**, also write the blog post **before pushing the tag**:

- Create `docs/blog/release-v<X-Y-Z>.md` with feature highlights
- Add a row in `docs/blog/index.md`

See the full release guide in `AGENTS.md § Release process`.
71 changes: 67 additions & 4 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -194,11 +194,74 @@ jobs:
docs/public/versions.json > /tmp/versions_new.json
mv /tmp/versions_new.json docs/public/versions.json

- name: Commit versions.json to main
uses: stefanzweifel/git-auto-commit-action@v5.0.1
- name: Generate blog post stub for new major version
run: |
set -euo pipefail
# Convert vX.0.0 → v<maj>-0-0 for the file name (e.g. v3.0.0 → v3-0-0)
TAG="$GITHUB_REF_NAME"
SLUG="${TAG//./-}" # v3.0.0 → v3-0-0
BLOG_FILE="docs/blog/release-${SLUG}.md"
RELEASE_DATE="$(date -u +%Y-%m-%d)"
# Idempotent — skip creation if the file already exists (manually authored).
if [ -f "$BLOG_FILE" ]; then
echo "Blog post $BLOG_FILE already exists — skipping stub generation."
else
# Fix: use Python to write the file so heredoc indentation never
# leaks into the generated Markdown (which would break frontmatter).
python3 - <<PY
import pathlib
tag = "$TAG"
release_date = "$RELEASE_DATE"
blog_file = "$BLOG_FILE"
content = (
f'---\ntitle: "What\'s new in {tag}"\n'
f'description: "Highlights of github-code-search {tag}"\n'
f'date: {release_date}\n---\n\n'
f'# What\'s new in github-code-search {tag}\n\n'
f'> Full release notes: <https://github.com/fulll/github-code-search/releases/tag/{tag}>\n\n'
'<!-- TODO: fill in feature highlights, usage examples and screenshots. -->\n'
)
pathlib.Path(blog_file).write_text(content, encoding="utf-8")
print(f"Created blog stub: {blog_file}")
PY
fi

- name: Update blog/index.md table with new major version
run: |
set -euo pipefail
TAG="$GITHUB_REF_NAME"
SLUG="${TAG//./-}"
# Add a row to the blog index table only if the version isn't already listed.
if grep -qF "release-${SLUG}" docs/blog/index.md; then
echo "Blog index already contains ${TAG} — skipping."
else
python3 - <<PY
import re, pathlib

path = pathlib.Path("docs/blog/index.md")
content = path.read_text()
tag = "$TAG"
slug = "$SLUG"
new_row = f"| [{tag}](./release-{slug}) | <!-- TODO: add summary --> |"
# Insert the new row after the last markdown table row (line ending with |).
updated = re.sub(
r"(\|[^\n]+\|)(\s*\Z)",
lambda m: m.group(1) + "\n" + new_row + m.group(2),
content,
count=1,
flags=re.DOTALL,
)
path.write_text(updated)
print(f"Inserted row for {tag} into blog/index.md")
PY
fi

- name: Commit blog stub and versions.json to main
# Pin to exact commit SHA to prevent supply-chain attacks.
uses: stefanzweifel/git-auto-commit-action@8621497c8c39c72f3e2a999a26b4ca1b5058a842 # v5.0.1
with:
# No [skip ci] — the push to main matches paths: docs/** and re-triggers
# the deploy job, which merges the new snapshot into the Pages artifact.
commit_message: "docs: add ${{ steps.ver.outputs.major }} to versions.json"
file_pattern: docs/public/versions.json
commit_message: "docs: add ${{ steps.ver.outputs.major }} to versions.json and blog"
file_pattern: docs/public/versions.json docs/blog/
branch: main
67 changes: 67 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,73 @@ For epics spanning multiple PRs, create a long-lived **feature branch** (`feat/<

---

## Release process

### Deciding the version bump

This project follows [Semantic Versioning](https://semver.org/):

| Change type | Bump | Example |
| ------------------------------------------ | ------- | ------------- |
| Bug fix (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 |

### Step-by-step

```bash
# 1. Bump the version in package.json (pick one)
bun pm version patch # bug fix
bun pm version minor # new feature
bun pm version major # breaking change

# 2. Create the release branch and commit
git checkout -b release/$(jq -r .version package.json)
git add package.json
git commit -S -m "v$(jq -r .version package.json)"

# 3. Write (or update) the blog post for the release
# • Required for minor and major releases.
# • Patch releases: optional — a brief note in the GitHub Release is sufficient.
# File: docs/blog/release-v<X-Y-Z>.md (e.g. docs/blog/release-v1-3-0.md)
# Update docs/blog/index.md table too.

# 4. Tag and push — this triggers the CD pipeline
git tag v$(jq -r .version package.json)
git push origin release/$(jq -r .version package.json) --tags
```

### What the CI does automatically

Pushing a tag `vX.Y.Z` triggers **`cd.yaml`**:

1. Compiles the binary for all six targets (linux-x64, linux-arm64, linux-x64-baseline, darwin-x64, darwin-arm64, windows-x64).
2. Creates a **GitHub Release** with all binaries attached.
`generate_release_notes: true` — GitHub auto-populates the release body from merged PR titles and commit messages since the previous tag.
3. Legacy platform aliases are also published for backward-compat with pre-v1.2.1 binaries.

Pushing a **major** tag (`vX.0.0`) additionally triggers **`docs.yml` → snapshot job**:

1. Builds a versioned docs snapshot at `/github-code-search/vX/`.
2. Auto-generates `docs/blog/release-vX-0-0.md` stub if it does not exist yet.
3. Prepends the new entry to `docs/public/versions.json` and commits back to `main`.

### Blog post requirement

| Release type | Blog post | Location |
| ------------ | ----------------------------------------------------------- | --------------------------------- |
| **Major** | Required (written by hand — CI stub automates the skeleton) | `docs/blog/release-vX-0-0.md` |
| **Minor** | Required | `docs/blog/release-vX-Y-0.md` |
| **Patch** | Optional | GitHub Release body is sufficient |

For minor/major releases update `docs/blog/index.md` to add a row in the version table:

```markdown
| [vX.Y.Z](./release-vX-Y-Z) | One-line highlights |
```

---

## Development notes

- **TypeScript throughout** — no `.js` files in `src/`.
Expand Down
54 changes: 54 additions & 0 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
@@ -1,6 +1,49 @@
import { defineConfig } from "vitepress";
import { readdirSync } from "node:fs";
import { fileURLToPath } from "node:url";
import versionsData from "../public/versions.json";

// ─── Semantic version helpers for blog sidebar sort ───────────────────────────
function parseBlogVersion(filename: string): number[] {
// "release-v1-2-3.md" → [1, 2, 3]
return filename
.replace(/^release-v/, "")
.replace(/\.md$/, "")
.split("-")
.map(Number);
}

function compareVersionArrays(a: number[], b: number[]): number {
for (let i = 0; i < Math.max(a.length, b.length); i++) {
const diff = (b[i] ?? 0) - (a[i] ?? 0);
if (diff !== 0) return diff;
}
return 0;
}

// ── Blog sidebar — built dynamically from docs/blog/*.md files ────────────────
// Files are sorted newest-first using semantic version comparison so that
// multi-digit components (e.g. v1.10.0) sort correctly before v1.9.0.
// The index.md is excluded from the per-post list since it is the section root.
function buildBlogSidebarItems(): { text: string; link: string }[] {
// Fix: use import.meta.url instead of __dirname which may be undefined in ESM
const blogDir = fileURLToPath(new URL("../blog", import.meta.url));
let files: string[] = [];
try {
files = readdirSync(blogDir)
.filter((f) => f.endsWith(".md") && f !== "index.md")
.toSorted((a, b) => compareVersionArrays(parseBlogVersion(a), parseBlogVersion(b)));
} catch {
// blog dir may not exist during the very first build
}
return files.map((f) => {
const slug = f.replace(/\.md$/, "");
// slug: release-v1-0-0 → display: v1.0.0
const label = slug.replace(/^release-/, "").replace(/-/g, ".");
return { text: label, link: `/blog/${slug}` };
});
}

// ── Versioning convention ────────────────────────────────────────────────────
// • main branch → always publishes the "latest" docs at /github-code-search/
// • Major release tag (vX.0.0) → CI takes a snapshot:
Expand Down Expand Up @@ -68,6 +111,11 @@ export default defineConfig({
activeMatch: "^/getting-started/",
},
{ text: "Usage", link: "/usage/search-syntax", activeMatch: "^/usage/" },
{
text: "What's New",
link: "/blog/",
activeMatch: "^/blog/",
},
{
text: "Reference",
link: "/reference/cli-options",
Expand Down Expand Up @@ -146,6 +194,12 @@ export default defineConfig({
],
},
],
"/blog/": [
{
text: "What's New",
items: [{ text: "All releases", link: "/blog/" }, ...buildBlogSidebarItems()],
},
],
"/architecture/": [
{
text: "Architecture",
Expand Down
21 changes: 21 additions & 0 deletions docs/blog/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# What's New

Release highlights for every version of `github-code-search`.
Each post covers the main features, fixes, and upgrade notes for that release.
Full release notes and changelogs are always available on
[GitHub Releases](https://github.com/fulll/github-code-search/releases).

---

## 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 |

---

::: tip Upgrading?
Run `github-code-search upgrade` to update to the latest version in one command.
:::
73 changes: 73 additions & 0 deletions docs/blog/release-v1-0-0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
title: "What's new in v1.0.0"
description: "Initial public release of github-code-search — interactive TUI, per-repo aggregation, markdown/JSON output."
date: 2025-01-01
---

# What's new in github-code-search v1.0.0

> Full release notes: <https://github.com/fulll/github-code-search/releases/tag/v1.0.0>

## The beginning

`github-code-search` is an interactive CLI for searching GitHub code across an entire organisation.
It wraps the [GitHub Code Search API](https://docs.github.com/en/rest/search/search#search-code)
and adds a keyboard-driven TUI on top, so you can browse results, fold/unfold extracts, and select
the ones you care about before printing structured output.

## Highlights

### Keyboard-driven TUI

Search results are loaded and displayed in an interactive terminal UI.
Navigate with <kbd>↑</kbd> / <kbd>↓</kbd>, fold/unfold extracts with <kbd>→</kbd> / <kbd>←</kbd>,
select results with <kbd>Space</kbd>, and confirm with <kbd>Enter</kbd>.
Press <kbd>?</kbd> to show the full keyboard shortcut reference.

→ [Interactive mode guide](/usage/interactive-mode)

### Per-repository aggregation

All code matches for the same repository are grouped together, with per-file headings.
No more scanning through a flat list of individual matches.

### Markdown and JSON output

Use `--format markdown` (default) for human-readable output, or `--format json` for
machine-readable output suitable for further processing.

Use `--output-type repo-only` to print only repository names (no code extracts).

→ [Output formats guide](/usage/output-formats)

### Filtering

Exclude noisy repositories with `--exclude-repositories` or specific extracts with
`--exclude-extracts`. Both flags accept short (`repoName`) and long (`org/repoName`) forms.

→ [Filtering guide](/usage/filtering)

### Team grouping

Group results by team prefix using `--group-by-team-prefix`. Results are arranged in sections
per team, making it easy to see which squads own the code that matches your search.

→ [Team grouping guide](/usage/team-grouping)

### Non-interactive / CI mode

Use `--no-interactive` (or set `CI=true`) to pipe output directly to downstream tools.
Syntax highlighting and TUI chrome are stripped so the output is clean.

→ [Non-interactive mode guide](/usage/non-interactive-mode)

### Built-in upgrade command

```bash
github-code-search upgrade
```

Checks for a newer binary on GitHub Releases and replaces the running binary in-place.
No package manager required.

→ [Upgrade guide](/usage/upgrade)
Loading