This file provides context for AI coding agents (GitHub Copilot, Claude, Gemini, etc.) working in this repository.
github-issue-ops is a CLI (powered by Bun) to industrialize GitHub issue campaigns (tech debt, security, migration, compliance). It takes the output of github-code-search (or any Markdown/JSON input) and creates a structured GitHub issue campaign: an EPIC in a central repo, a checklist that can be refreshed, and sub-issues dispatched per repo. An issue command with 3 subcommands (create, refresh, dispatch) and an upgrade subcommand are exposed via Commander.
| Tool | Version |
|---|---|
| Bun | ≥ 1.0 (runtime, bundler, test runner, package manager) |
| TypeScript | via Bun (no separate tsc invocation needed at runtime) |
| oxlint | linter (bun run lint) |
| oxfmt | formatter (bun run format) |
| knip | dead-code detector (bun run knip) |
There is no Node.js / npm involved. Always use bun commands.
bun install # install dependencies (reads bunfig.toml + package.json)bun run build.ts # compile a self-contained binary → dist/github-issue-ops
bun run build.ts --target=bun-darwin-arm64 # cross-compile (see CONTRIBUTING.md for all targets)bun test # run the whole test suite
bun test --watch # re-run on file changes (development)All tests use Bun's built-in test runner. The setup file is src/test-setup.ts.
bun run lint # oxlint — must pass before submitting
bun run format # oxfmt write (auto-fix)
bun run format:check # oxfmt check (CI check)
bun run knip # detect unused exports / filesgithub-issue-ops.ts # CLI entry point — Commander: issue (create/refresh/dispatch), upgrade
build.ts # Build script (Bun.build)
bunfig.toml # Bun configuration (test preload)
tsconfig.json # TypeScript configuration
knip.json # knip (dead-code) configuration
src/
types.ts # All shared TypeScript interfaces (source of truth)
upgrade.ts # Auto-upgrade logic (fetch latest GitHub release, replace binary)
test-setup.ts # Global test setup (Bun preload)
api/
github-api.ts # GitHub REST API client (issues, labels, teams, CODEOWNERS, templates)
api-utils.ts # Shared retry (fetchWithRetry) and pagination (paginatedFetch) helpers
core/
metadata.ts # Embed/extract machine-readable metadata (HTML comment in issue body)
checklist.ts # Build/parse/diff/apply Markdown checklists
ownership.ts # Resolver chain: teams → CODEOWNERS → mapping → fallback
dedup.ts # Detect existing dispatch issues (idempotence)
input/
stdin.ts # readStdin + auto-detect format (json/markdown) + parseResults
output/
format.ts # Body builders (EPIC body, sub-issue body, plan display)
tui/
editor.ts # Spawn $EDITOR on a tmp file for interactive body editing
commands/
create.ts # issue create — orchestration
refresh.ts # issue refresh — orchestration
dispatch.ts # issue dispatch — orchestration
*.test.ts # Unit tests co-located with source files
- Pure functions first. All business logic lives in pure, side-effect-free functions (
core/,input/,output/). This makes them straightforward to unit-test. - Side effects are isolated. API calls (
api/), editor spawn (tui/), and CLI parsing (github-issue-ops.ts) are the only side-effectful surfaces. types.tsis the single source of truth for all shared interfaces. Any new shared type must go there.- No classes — the codebase uses plain TypeScript interfaces and functions throughout.
api/api-utils.tshosts shared retry/pagination helpers used exclusively byapi/github-api.ts.
Machine-readable metadata is stored in the EPIC body as a trailing HTML comment (invisible in GitHub rendering):
<!-- github-issue-ops:metadata
{"version":1,"replayCommand":"...","createdAt":"...","config":{}}
-->
This allows refresh and dispatch to find the replay command without any external storage.
GitHub's practical API limit for issue bodies is ~65 536 characters. The constant BODY_LIMIT = 65_000 is defined in src/core/checklist.ts. On issue create: error if exceeded. On issue refresh: truncate + push overflow as a comment.
- Test files:
<module>.test.ts, co-located with their source file. - Use
describe/it/expectfrom Bun's test runner. - Only pure functions are unit-tested;
api/github-api.tsandcommands/*.tsare not. api/api-utils.tsis tested by mockingglobalThis.fetch.- Tests must be self-contained: no network calls, no filesystem side effects.
All commits must be cryptographically signed (GPG or SSH).
git config --global commit.gpgsign true
⚠️ Do NOT use MCP REST API push tools (mcp_github_push_files,mcp_github_create_or_update_file). Always commit locally viagit commit -Sand push withgit push.
| Branch type | Pattern | Example |
|---|---|---|
| Feature | feat/<short-description> |
feat/dispatch-ownership-chain |
| Bug fix | fix/<short-description> |
fix/body-overflow-on-refresh |
| Refactoring | refactor/<short-description> |
refactor/extract-checklist-mod |
| Documentation | docs/<short-description> |
docs/vitepress-init |
Commit messages use imperative mood: Add …, Fix …, Extract ….
bun pm version patch # bug fix
bun pm version minor # new feature
bun pm version major # breaking change
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) --tagsPushing a tag vX.Y.Z triggers cd.yaml which compiles 6 targets and creates a GitHub Release.