Skip to content

[codex] blast-radius subcommand#71

Merged
JordanCoin merged 2 commits intomainfrom
codex/blast-radius-cli
Apr 8, 2026
Merged

[codex] blast-radius subcommand#71
JordanCoin merged 2 commits intomainfrom
codex/blast-radius-cli

Conversation

@JordanCoin
Copy link
Copy Markdown
Owner

What changed

  • added a first-class codemap blast-radius subcommand implemented in Go
  • moved the built-in blast-radius defaults and output budgeting into the binary instead of a shell wrapper
  • removed scripts/codemap-blast-radius.sh so the CLI command is the single supported entry point
  • updated help text, README usage, and added integration coverage for markdown and JSON blast-radius output

Why

The blast-radius flow was still living in a shell script with jq and python3 dependencies. That made CI integration awkward and forced callers to know wrapper paths and environment defaults. This change makes codemap blast-radius the actual product surface with sensible built-in defaults and bounded output.

User impact

Callers can now use:

codemap blast-radius --ref main .
codemap blast-radius --json --ref main .
codemap blast-radius --text --ref main .

without needing the old script path or external wrapper logic.

Validation

  • go test ./...
  • manual smoke run of go run . blast-radius --ref HEAD <temp-repo>

@JordanCoin JordanCoin marked this pull request as ready for review April 8, 2026 19:03
Copilot AI review requested due to automatic review settings April 8, 2026 19:03
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR promotes “blast radius” from an external shell wrapper into a first-class Go subcommand (codemap blast-radius), consolidating defaults/output budgeting into the binary and removing the now-redundant script-based entry point.

Changes:

  • Added a new blast-radius subcommand implementation in Go with bounded Markdown/text/JSON output.
  • Refactored shared importers/ast-grep hint logic in main.go to be reused by the new subcommand.
  • Removed the old scripts/codemap-blast-radius.sh wrapper and updated README + tests accordingly.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
scripts/codemap-blast-radius.sh Deleted legacy wrapper script in favor of native CLI subcommand.
README.md Updated blast-radius usage examples to use codemap blast-radius.
main.go Dispatches blast-radius subcommand pre global-flag parsing; refactors helper rendering/hint code usage.
main_test.go Ensures --help output includes the new blast-radius command.
blast_radius.go New Go implementation for bundle building, budgeting, rendering, and snippet extraction.
blast_radius_test.go Adds integration-style coverage validating Markdown + JSON blast-radius output.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +879 to +886
func blastLineMatchesTerm(line string, term blastSnippetTerm) bool {
switch term.Kind {
case "symbol":
pattern := regexp.MustCompile(`\b` + regexp.QuoteMeta(term.Value) + `\b`)
return pattern.MatchString(line)
default:
return strings.Contains(line, term.Value)
}
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blastLineMatchesTerm recompiles a regexp for every line/term check (regexp.MustCompile inside the hot loop). This is unnecessarily expensive for large files and can dominate runtime when snippet search runs across many impacted/context files. Consider precompiling the regex once per term (e.g., store a compiled *regexp.Regexp on the term, or compile once per term iteration in findBlastRadiusSnippet) and reusing it across lines.

Copilot uses AI. Check for mistakes.
blast_radius.go Outdated
Comment on lines +254 to +279
fmt.Println("codemap blast-radius - Build a compact, bounded blast-radius bundle")
fmt.Println()
fmt.Println("Usage:")
fmt.Println(" codemap blast-radius [--json|--markdown|--text] [--ref <base-ref>] [path]")
fmt.Println()
fmt.Println("Examples:")
fmt.Println(" codemap blast-radius --ref main .")
fmt.Println(" codemap blast-radius --json --ref develop /path/to/repo")
fmt.Println()
fmt.Println("Flags:")
fs.PrintDefaults()
fmt.Println()
fmt.Println("Environment overrides:")
fmt.Println(" CODEMAP_BLAST_MAX_TOTAL_CHARS")
fmt.Println(" CODEMAP_BLAST_MAX_CHANGED_FILES")
fmt.Println(" CODEMAP_BLAST_MAX_AFFECTED")
fmt.Println(" CODEMAP_BLAST_MAX_CONTEXT")
fmt.Println(" CODEMAP_BLAST_MAX_SNIPPETS")
fmt.Println(" CODEMAP_BLAST_MAX_SNIPPETS_PER_CHANGED")
fmt.Println(" CODEMAP_BLAST_SNIPPET_RADIUS")
fmt.Println(" CODEMAP_BLAST_MAX_SNIPPET_CHARS")
fmt.Println(" CODEMAP_BLAST_MAX_DIFF_CHARS")
fmt.Println(" CODEMAP_BLAST_MAX_DEPS_CHARS")
fmt.Println(" CODEMAP_BLAST_MAX_IMPORTERS_CHARS")
fmt.Println(" CODEMAP_BLAST_MAX_IMPORTER_FILES")
fmt.Println(" CODEMAP_BLAST_MAX_IMPORTERS_PER_FILE")
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

printBlastRadiusUsage writes directly to stdout via fmt.Println, but the subcommand’s FlagSet is configured with fs.SetOutput(os.Stderr). This can split usage output across stdout/stderr (especially when Parse prints errors/usage), which is brittle for callers and tests. Consider writing usage to fs.Output() (or otherwise consistently routing help vs error output).

Suggested change
fmt.Println("codemap blast-radius - Build a compact, bounded blast-radius bundle")
fmt.Println()
fmt.Println("Usage:")
fmt.Println(" codemap blast-radius [--json|--markdown|--text] [--ref <base-ref>] [path]")
fmt.Println()
fmt.Println("Examples:")
fmt.Println(" codemap blast-radius --ref main .")
fmt.Println(" codemap blast-radius --json --ref develop /path/to/repo")
fmt.Println()
fmt.Println("Flags:")
fs.PrintDefaults()
fmt.Println()
fmt.Println("Environment overrides:")
fmt.Println(" CODEMAP_BLAST_MAX_TOTAL_CHARS")
fmt.Println(" CODEMAP_BLAST_MAX_CHANGED_FILES")
fmt.Println(" CODEMAP_BLAST_MAX_AFFECTED")
fmt.Println(" CODEMAP_BLAST_MAX_CONTEXT")
fmt.Println(" CODEMAP_BLAST_MAX_SNIPPETS")
fmt.Println(" CODEMAP_BLAST_MAX_SNIPPETS_PER_CHANGED")
fmt.Println(" CODEMAP_BLAST_SNIPPET_RADIUS")
fmt.Println(" CODEMAP_BLAST_MAX_SNIPPET_CHARS")
fmt.Println(" CODEMAP_BLAST_MAX_DIFF_CHARS")
fmt.Println(" CODEMAP_BLAST_MAX_DEPS_CHARS")
fmt.Println(" CODEMAP_BLAST_MAX_IMPORTERS_CHARS")
fmt.Println(" CODEMAP_BLAST_MAX_IMPORTER_FILES")
fmt.Println(" CODEMAP_BLAST_MAX_IMPORTERS_PER_FILE")
var out io.Writer = fs.Output()
if out == nil {
out = os.Stderr
}
fmt.Fprintln(out, "codemap blast-radius - Build a compact, bounded blast-radius bundle")
fmt.Fprintln(out)
fmt.Fprintln(out, "Usage:")
fmt.Fprintln(out, " codemap blast-radius [--json|--markdown|--text] [--ref <base-ref>] [path]")
fmt.Fprintln(out)
fmt.Fprintln(out, "Examples:")
fmt.Fprintln(out, " codemap blast-radius --ref main .")
fmt.Fprintln(out, " codemap blast-radius --json --ref develop /path/to/repo")
fmt.Fprintln(out)
fmt.Fprintln(out, "Flags:")
fs.PrintDefaults()
fmt.Fprintln(out)
fmt.Fprintln(out, "Environment overrides:")
fmt.Fprintln(out, " CODEMAP_BLAST_MAX_TOTAL_CHARS")
fmt.Fprintln(out, " CODEMAP_BLAST_MAX_CHANGED_FILES")
fmt.Fprintln(out, " CODEMAP_BLAST_MAX_AFFECTED")
fmt.Fprintln(out, " CODEMAP_BLAST_MAX_CONTEXT")
fmt.Fprintln(out, " CODEMAP_BLAST_MAX_SNIPPETS")
fmt.Fprintln(out, " CODEMAP_BLAST_MAX_SNIPPETS_PER_CHANGED")
fmt.Fprintln(out, " CODEMAP_BLAST_SNIPPET_RADIUS")
fmt.Fprintln(out, " CODEMAP_BLAST_MAX_SNIPPET_CHARS")
fmt.Fprintln(out, " CODEMAP_BLAST_MAX_DIFF_CHARS")
fmt.Fprintln(out, " CODEMAP_BLAST_MAX_DEPS_CHARS")
fmt.Fprintln(out, " CODEMAP_BLAST_MAX_IMPORTERS_CHARS")
fmt.Fprintln(out, " CODEMAP_BLAST_MAX_IMPORTER_FILES")
fmt.Fprintln(out, " CODEMAP_BLAST_MAX_IMPORTERS_PER_FILE")

Copilot uses AI. Check for mistakes.
blast_radius.go Outdated
Comment on lines +173 to +174
fs.BoolVar(&markdownMode, "markdown", false, "Emit Markdown output (default)")
fs.BoolVar(&markdownMode, "md", false, "Emit Markdown output (default)")
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The --markdown/--md flags are labeled as “default”, but their default value is false and Markdown is emitted even when the flags are omitted. This makes --markdown redundant and can confuse users reading PrintDefaults() output. Consider removing --markdown/--md and treating Markdown as the implicit default, or replacing the three booleans with a single --format flag defaulting to markdown.

Suggested change
fs.BoolVar(&markdownMode, "markdown", false, "Emit Markdown output (default)")
fs.BoolVar(&markdownMode, "md", false, "Emit Markdown output (default)")
fs.BoolVar(&markdownMode, "markdown", false, "Emit Markdown output")
fs.BoolVar(&markdownMode, "md", false, "Emit Markdown output")

Copilot uses AI. Check for mistakes.
blast_radius.go Outdated
Comment on lines +382 to +389
if isGitHubURL(root) && localErr != nil {
repoName := extractRepoName(root)
tempDir, err := cloneRepo(root, repoName)
if err != nil {
return "", cleanup, err
}
cleanup = func() { _ = os.RemoveAll(tempDir) }
root = tempDir
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolveBlastRadiusRoot clones when os.Stat(root) returns any error (localErr != nil). That includes non-"not exist" errors (e.g., permission denied), which can unexpectedly trigger a network clone and ignore an existing local path. Consider only cloning when os.IsNotExist(localErr) is true, and otherwise return the stat error to the caller.

Suggested change
if isGitHubURL(root) && localErr != nil {
repoName := extractRepoName(root)
tempDir, err := cloneRepo(root, repoName)
if err != nil {
return "", cleanup, err
}
cleanup = func() { _ = os.RemoveAll(tempDir) }
root = tempDir
if isGitHubURL(root) {
if os.IsNotExist(localErr) {
repoName := extractRepoName(root)
tempDir, err := cloneRepo(root, repoName)
if err != nil {
return "", cleanup, err
}
cleanup = func() { _ = os.RemoveAll(tempDir) }
root = tempDir
} else if localErr != nil {
return "", cleanup, localErr
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dbf8d83ab3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

blast_radius.go Outdated
} else {
fmt.Fprintf(os.Stderr, "Error building blast radius: %v\n", err)
}
os.Exit(1)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid os.Exit after deferring remote clone cleanup

When root is a remote URL, resolveBlastRadiusRoot clones into a temp directory and defer cleanup() is set, but this error path calls os.Exit(1), which skips deferred functions. In runs where bundle construction fails (for example invalid --ref or missing ast-grep with changed files), each invocation leaks a temp clone on disk instead of removing it.

Useful? React with 👍 / 👎.

blast_radius.go Outdated
func blastLineMatchesTerm(line string, term blastSnippetTerm) bool {
switch term.Kind {
case "symbol":
pattern := regexp.MustCompile(`\b` + regexp.QuoteMeta(term.Value) + `\b`)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Precompile symbol-match regex outside the line loop

findBlastRadiusSnippet checks every line of candidate files, and for symbol terms it calls blastLineMatchesTerm per line; that helper recompiles the same regex every call via regexp.MustCompile. On large files or many snippet candidates this creates thousands of unnecessary compilations and can materially slow blast-radius generation.

Useful? React with 👍 / 👎.

@JordanCoin JordanCoin merged commit 8e0735c into main Apr 8, 2026
12 checks passed
@JordanCoin JordanCoin deleted the codex/blast-radius-cli branch April 8, 2026 19:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants