gh-pr-todo

A GitHub CLI extension that extracts TODO-style comments from pull request diffs, helping you track action items and reminders in your code changes.
Features
- PR-Focused Detection: Extracts TODO-style comments only from pull request diff additions
- Syntax-Aware Parsing: Uses Tree-sitter for accurate comment detection in supported languages, with regex fallback for others
- Configurable Marker Policy: Customize marker types, severities, and ignored types with CLI flags or YAML config
- Config Initialization: Create project or global config files with
gh pr-todo init
- CI and GitHub Actions Support: Emit workflow annotations and fail CI only for marker types configured as
error
- Flexible Output: Colorized output with grouping, file-name-only, and count-only modes
Installation
gh ext install Suree33/gh-pr-todo
Prerequisites:
Usage
Basic Usage
Navigate to your repository with an open pull request and run:
gh pr-todo
Advanced Usage
You can specify PR numbers, URLs, or branches, and you can target another repository with -R/--repo or a PR URL:
# Specify a specific PR number
gh pr-todo 123
# Specify a PR from a different repository
gh pr-todo 456 -R owner/repo
# Specify a PR by URL
gh pr-todo https://github.com/owner/repo/pull/789
# Specify a branch
gh pr-todo feature-branch
# Specify a branch from a different repository
gh pr-todo feature-branch -R owner/repo
# Display only names of the files containing TODO-style comments
gh pr-todo --name-only
# Display only the number of TODO-style comments
gh pr-todo -c
# Group TODO-style comments by file (or type)
gh pr-todo --group-by file
# Override severities for one or more TODO types
# Format: --severity LEVEL=TYPE[,TYPE...]
gh pr-todo --severity warning=TODO,HACK --severity error=FIXME
# Ignore specific marker types from detection (affects all output modes)
gh pr-todo --ignore NOTE,HACK
Command Options
[<number> | <url> | <branch>]: Specify a PR by number, URL, or branch name
-R, --repo [HOST/]OWNER/REPO: Select another repository using the [HOST/]OWNER/REPO format (requires a PR number, URL, or branch argument)
--group-by: Group TODO-style comments by file or type
--name-only: Display only names of the files containing TODO-style comments. If both --name-only and --count are specified, --name-only takes precedence
-c, --count: Display only the number of TODO-style comments
--severity LEVEL=TYPE[,TYPE...]: Override severity for one or more TODO types; repeatable, whitespace-tolerant, and last assignment wins for duplicate types
--ignore TYPE[,TYPE...]: Ignore specified marker types; repeatable, case-insensitive, whitespace-tolerant. Ignored types are not detected or reported in any mode, including annotations and CI failure counts
-h, --help: Display help information
--no-ci-fail: Disable non-zero exit when error-level TODOs are found in CI (see below)
Initializing Configuration
Use gh pr-todo init to create a default configuration file. Without an explicit location, it prompts in terminals and falls back to a plain text prompt when redirected:
gh pr-todo init
For non-interactive scripts, pass a location:
gh pr-todo init --repo # Project (.gh-pr-todo.yml)
gh pr-todo init --global # user config dir/gh-pr-todo/config.yml
Prompted locations are:
Project (.gh-pr-todo.yml) — repository-scoped config, created at the root of the current Git repository; shown in the interactive selector only when run inside a Git repository
Global (user config dir/gh-pr-todo/config.yml) — global config, shared across all repos (typically $XDG_CONFIG_HOME/gh-pr-todo/config.yml on Linux, falling back to ~/.config/gh-pr-todo/config.yml; actual path depends on your OS)
If the selected config file already exists, init refuses to overwrite it unless --force is passed. --force can be combined with --repo or --global. --repo and --global are mutually exclusive, and using both returns an error. If you choose the repo-root location and .github/gh-pr-todo.yml exists, init reports it instead because that narrower config takes precedence over the root project config:
gh pr-todo init --force
gh pr-todo init --repo --force
The default configuration matches the runtime severity policy:
severity:
notice:
- TODO
- NOTE
warning:
- FIXME
- HACK
- XXX
- BUG
error: []
ignore: []
Configuration File
Severity overrides and ignored types can be persisted in YAML configuration files. Config files use the following schema:
severity:
notice|warning|error: [TYPE...]
ignore:
- TYPE
Example (.gh-pr-todo.yml):
severity:
warning:
- TODO
error:
- FIXME
ignore:
- NOTE
Config File Paths and Precedence
Config files use whole-file replacement by precedence: each existing file replaces the entire previous configuration. This applies to both severity overrides and ignored types.
Local files (in order of increasing precedence):
- User config dir
gh-pr-todo/config.yml (global, shared across all repos; usually ~/.config/gh-pr-todo/config.yml on Linux)
<repo>/.gh-pr-todo.yml (repository root)
<repo>/.github/gh-pr-todo.yml (narrower repo scope)
- CLI
--severity and --ignore flags (highest priority)
If a repo config file exists (root or .github), the global config is not applied. Within a repo, .github/gh-pr-todo.yml replaces .gh-pr-todo.yml entirely. CLI flags are applied on top of the resolved config.
Configured TODO types are automatically detected in PR diffs alongside the built-in types. You can define custom types like SECURITY or PERF and they will be recognized.
Remote Config Precedence
When targeting another repository with --repo or a PR URL, remote configuration is resolved with the following precedence:
- Remote PR head branch config
- Remote PR base branch config
- Remote default branch config
- Global local config, only when no remote config exists
- CLI
--severity and --ignore flags (highest priority)
For each remote scope, .github/gh-pr-todo.yml replaces .gh-pr-todo.yml entirely. A remote config file replaces the global config entirely; global config is only used as a fallback when no remote config exists.
Ignoring Marker Types
The ignore config key lists marker types to exclude entirely from detection. Ignored types are not parsed or reported in any mode:
- Default output
--count
--name-only
--group-by file or --group-by type
- GitHub Actions annotations
- CI failure counts
Example:
# .gh-pr-todo.yml
severity:
warning:
- TODO
error:
- FIXME
ignore:
- NOTE
- HACK
This config makes the tool ignore NOTE and HACK markers while still detecting TODO and FIXME. Ignored types can be built-in or custom and take precedence over severity: even if a type has error severity, ignoring it removes it from detection entirely.
Use the CLI --ignore flag to extend the ignore set temporarily:
gh pr-todo --ignore NOTE,HACK
CI Mode
When the CI environment variable is truthy (e.g. 1, true, parsed via Go's strconv.ParseBool), gh pr-todo exits with status 1 if any error-level TODO-style comments are detected in the PR diff. By default, no built-in keyword type is mapped to error-level, so gh pr-todo does not fail CI based on default keywords alone. Use configuration files or --severity to promote recognized TODO keywords to error when you want CI failures, for example --severity error=FIXME. GITHUB_ACTIONS=true (set by the GitHub Actions runner) is treated as CI=true even when CI is missing or falsy.
# GitHub Actions example — CI=true is set automatically
- run: gh pr-todo ${{ github.event.pull_request.number }}
Pass --no-ci-fail to suppress non-zero exit even when error-level TODOs exist:
gh pr-todo --count --severity error=FIXME --no-ci-fail
GitHub Actions Annotations
When GITHUB_ACTIONS=true (set automatically by the GitHub Actions runner), gh pr-todo additionally emits workflow commands so each TODO appears as an annotation on the workflow run and pull request:
Default annotation severities:
TODO, NOTE → ::notice annotations
FIXME, HACK, XXX, BUG → ::warning annotations
You can override them with configuration files or --severity, for example:
--severity warning=TODO,NOTE
--severity error=FIXME
Annotations reflect the resolved severity of each keyword and are independent of CI exit behavior: warning annotations are displayed but do not cause a non-zero exit by default. Only error-level TODOs cause CI failure.
Each annotation is anchored to the file and line of the TODO, with the keyword used as the annotation title. Regular human-readable output is still printed, and the spinner is suppressed to keep Actions logs clean.
Workflow commands are only emitted in the default mode. The machine-readable modes --count and --name-only keep their plain output unchanged so that count=$(gh pr-todo --count) and similar shell pipelines stay reliable in Actions.
Example Output
✔ Fetching PR diff...
Found 3 TODO-style comment(s)
* src/api/users.go:42
// TODO: Add input validation for email format
* components/Header.tsx:15
// FIXME: Memory leak in event listener cleanup
* docs/setup.md:8
<!-- NOTE: Update this section after v2.0 release -->
The tool recognizes TODO-style comments in various formats:
| Format |
Example |
| C-style |
// TODO: Fix this bug |
| C-style block |
/* HACK: Quick fix for demo */ |
| Shell/Python |
# FIXME: Optimization needed |
| HTML/XML |
<!-- NOTE: Review this section --> |
| Assembly/Config |
; XXX: Temporary workaround |
Supported Keywords
Default Keywords
TODO
FIXME
HACK
NOTE
XXX
BUG
Custom Keywords
Additional keywords can be defined via configuration files or the --severity CLI flag. Any custom type assigned a severity will be detected in PR diffs alongside the default keywords. For example:
# .gh-pr-todo.yml
severity:
error:
- SECURITY
warning:
- PERF
This configures SECURITY and PERF as recognized TODO markers.
Validation Rules
- Severity keys (
notice, warning, error) are case-insensitive.
- Empty lists (
warning: []) are allowed and ignored.
- A TODO type must not appear under multiple severity levels in the same file.
- The old
TYPE: level format is not supported.
Development
Building from Source
git clone https://github.com/Suree33/gh-pr-todo.git
cd gh-pr-todo
go build -o gh-pr-todo .
Project Structure
├── main.go # CLI entry point
├── internal/
│ ├── config/
│ │ ├── config.go # YAML config parsing and local loading
│ │ └── remote.go # Remote config loading
│ ├── github/
│ │ └── client.go # GitHub API client (diffs, file contents, remote config)
│ ├── output/
│ │ ├── printer.go # Terminal output rendering
│ │ └── workflow.go # GitHub Actions annotation commands
│ └── parser.go # Diff parsing logic (Tree-sitter + regex)
├── pkg/
│ └── types/
│ ├── groupby.go # GroupBy enum
│ └── todo.go # TODO type definitions
Contributing
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature)
- Commit your changes (
git commit -m 'Add amazing feature')
- Push to the branch (
git push origin feature/amazing-feature)
- Open a Pull Request
License
This project is licensed under the terms specified in the LICENSE file.
Issues & Feature Requests
Found a bug or have a feature idea? Please open an issue on GitHub.