Skip to content

feat: add interactive multi-repo batch mode#121

Merged
404-Page-Found merged 4 commits into
mainfrom
6-add-interactive-multi-repo-batch-mode
Jun 6, 2026
Merged

feat: add interactive multi-repo batch mode#121
404-Page-Found merged 4 commits into
mainfrom
6-add-interactive-multi-repo-batch-mode

Conversation

@404-Page-Found
Copy link
Copy Markdown
Contributor

@404-Page-Found 404-Page-Found commented Jun 6, 2026

Summary

Adds a commit-echo batch <directory> command that scans a directory for git repositories, identifies those with staged changes, and processes each one in sequence — generating suggestions, committing, and reporting results.

Closes #6

Key features

  • commit-echo batch — scans current directory for git repos with staged changes
  • commit-echo batch ./projects — scans a specific directory
  • commit-echo batch --recursive — searches subdirectories for nested repos
  • commit-echo batch --yes — unattended mode (auto-commits first suggestion per repo without prompts)
  • Prints clear summary at the end (succeeded/skipped/failed)

Architecture

  • New src/commands/batch.ts module with exported helpers (findGitRepositories, gitHasChanges, getGitDiff, gitCommit)
  • Reuses existing generateSuggestions / buildProfile / appendEntry from the codebase
  • Config + API key are loaded once and shared across all repos
  • Git operations use explicit cwd parameter instead of process.chdir()
  • --yes/ --auto flags respect both local and global (program-level) options

Testing

  • 13 new unit tests covering repo discovery, change detection, diff retrieval
  • All 120 tests pass (119 pass + 1 Windows-skip)
ℹ tests 120
ℹ pass 119
ℹ fail 0
ℹ cancelled 0
ℹ skipped 1

Commits

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a batch command to scan directories (optional recursive) for git repositories, generate commit-message suggestions, allow selecting/editing or auto-committing with --yes, verify configuration, record per-repo outcomes and save commit history, and display a concise batch summary. CLI help/examples updated.
  • Tests

    • Added end-to-end tests for repository discovery, staged/unstaged change detection, diff retrieval, and committing behavior (including commit body handling and error cases).

Add a  command that scans a directory for
git repositories, identifies those with staged changes, and processes each
one in sequence — generating suggestions, committing, and reporting results.

-  finds all git repos in ./projects
- Processes repos with staged changes in sequence
- Supports --yes for unattended batch operation (auto-commit first suggestion)
- Supports --recursive to search subdirectories for nested repos
- Prints clear summary of results per repo (succeeded/skipped/failed)
- Skips repos without staged changes with a notice
- 13 unit tests covering repo discovery, change detection, and diff retrieval

Closes #6

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@404-Page-Found 404-Page-Found linked an issue Jun 6, 2026 that may be closed by this pull request
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 6, 2026

Need the big picture first? Review this PR in Change Stack to see what changed before going file by file.

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6526b3db-2f50-43e4-9fc3-eda38bc47716

📥 Commits

Reviewing files that changed from the base of the PR and between 15f1327 and cc4cb76.

📒 Files selected for processing (1)
  • tests/batch.test.mjs
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/batch.test.mjs

📝 Walkthrough

Walkthrough

Adds a new batch subcommand that scans a directory for git repositories, detects staged changes, generates commit suggestions, optionally auto-commits or prompts interactively per repo, records history entries, and prints an aggregated summary.

Changes

Batch Commit Command

Layer / File(s) Summary
Data contracts and git utility functions
src/commands/batch.ts
BatchResult plus helper functions: findGitRepositories (flat/recursive discovery, skip dot dirs, sort), gitHasChanges (staged/unstaged via git diff --quiet variants), and getGitDiff (staged vs unstaged diff retrieval with wrapped errors).
Commit execution and suggestion display
src/commands/batch.ts
gitCommit writes message+body to a temp file, runs git commit -F, parses commit hash/summary from stdout, and ensures cleanup; displaySuggestions prints indexed suggestions with optional dimmed bodies.
Batch orchestration and results reporting
src/commands/batch.ts
batchCommand validates the target directory, loads config and verifies the API key once, discovers repos, inspects staged changes, calls generateSuggestions, then either auto-commits the top suggestion (--yes) or prompts interactively to confirm and select a suggestion; records entries via appendEntry and prints an aggregated summary with counts and truncated messages.
CLI subcommand wiring
src/index.ts
Imported and registered batchCommand as commit-echo batch, extended help examples, and added --recursive and --yes/--auto flag handling to resolve options before invoking batchCommand.
Test coverage for utilities and commit
tests/batch.test.mjs
Node.js node:test suite that sets up temporary git repos to verify findGitRepositories (discovery, recursion, hidden dirs, sorting), gitHasChanges (staged/unstaged detection), getGitDiff (staged/unstaged diff content and error when outside a repo), and gitCommit (returns hash/summary, includes commit body, errors on nothing to commit).

Sequence Diagram

sequenceDiagram
  participant User
  participant BatchCmd as batchCommand
  participant Finder as findGitRepositories
  participant Git as GitRepo
  participant LLM as generateSuggestions
  participant History as appendEntry

  User->>BatchCmd: invoke batch [--recursive] [--yes]
  BatchCmd->>Finder: discover repos
  BatchCmd->>BatchCmd: load config & validate API key
  loop each repo
    BatchCmd->>Git: gitHasChanges
    alt staged changes
      BatchCmd->>Git: getGitDiff (staged)
      BatchCmd->>LLM: generateSuggestions(diff, profile)
      alt --yes
        BatchCmd->>Git: gitCommit(first suggestion)
      else interactive
        BatchCmd->>User: prompt confirm & select suggestion
        opt user confirms
          BatchCmd->>Git: gitCommit(selected suggestion)
        end
      end
      BatchCmd->>History: appendEntry(commit metadata)
    else skip
      BatchCmd->>User: print skipped notice
    end
  end
  BatchCmd->>User: print aggregated summary
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through folders, sniffed each tree,

Found staged leaves and whispered "Commit with me."
Suggestions lined up, tidy as a row,
A nibble, a click — a history to show.
Hooray for batch runs — tidy carrots in tow!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR implements all core requirements from issue #6: batch command with directory scanning, --recursive flag, --yes auto-commit, staged-change detection, suggestion generation, and summary reporting. However, reviewers flagged unresolved issues: findGitRepositories doesn't check rootDir itself, getGitDiff lacks large-diff buffer handling, readdirSync lacks error handling, and gitCommit regex is fragile. Address reviewer concerns: check rootDir for .git before scanning children, increase maxBuffer in getGitDiff, add try/catch around readdirSync, and make commit-summary regex more robust for detached HEAD/initial commits.
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add interactive multi-repo batch mode' clearly and concisely summarizes the primary change: adding a new batch command for processing multiple repositories interactively.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the batch command feature: new batch.ts module with git helper functions, CLI wiring in index.ts, and comprehensive test coverage in batch.test.mjs. No extraneous changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 6-add-interactive-multi-repo-batch-mode

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/commands/batch.ts (1)

241-242: 💤 Low value

Consider hoisting buildProfile outside the repo loop.

buildProfile(config.historySize) reads from a global history file and is called identically for each repository. Moving it before the loop (alongside config and API key loading) avoids redundant file I/O on every iteration.

♻️ Suggested refactor
   // Verify API key once
   let apiKey: string;
   try {
     apiKey = assertApiKeyAvailable(config);
   } catch (err) {
     outro(pc.red(err instanceof Error ? err.message : 'Missing API key'));
     return;
   }

+  // Build style profile once (shared across all repos)
+  const profile = await buildProfile(config.historySize);
+
   const results: BatchResult[] = [];

   for (const repoPath of repos) {
     // ... inside loop ...
-    // Build style profile and generate suggestions
-    const profile = await buildProfile(config.historySize);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/batch.ts` around lines 241 - 242,
buildProfile(config.historySize) is being invoked inside the per-repository loop
causing redundant file I/O; move the await buildProfile(config.historySize) call
to execute once before the repo iteration (alongside config and API key
loading), store the returned profile in a variable, and then reference that
variable inside the loop (instead of calling buildProfile again). Ensure you
await the call before entering the loop and update any local references inside
the loop to use the hoisted profile; this touches the buildProfile call and the
repository iteration block in src/commands/batch.ts.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/commands/batch.ts`:
- Around line 241-242: buildProfile(config.historySize) is being invoked inside
the per-repository loop causing redundant file I/O; move the await
buildProfile(config.historySize) call to execute once before the repo iteration
(alongside config and API key loading), store the returned profile in a
variable, and then reference that variable inside the loop (instead of calling
buildProfile again). Ensure you await the call before entering the loop and
update any local references inside the loop to use the hoisted profile; this
touches the buildProfile call and the repository iteration block in
src/commands/batch.ts.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 86246d53-27d6-45d7-9fca-0214a1b53933

📥 Commits

Reviewing files that changed from the base of the PR and between 38a25ff and 6943bb5.

📒 Files selected for processing (3)
  • src/commands/batch.ts
  • src/index.ts
  • tests/batch.test.mjs

buildProfile(config.historySize) was called inside the per-repository
loop, causing redundant file I/O (reading + parsing the full commit
history file) once per repo. Move it before the loop so it executes
once, and the cached profile is reused across all repos.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@404-Page-Found
Copy link
Copy Markdown
Contributor Author

@coderabbitai

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 6, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== src/commands/batch.ts ==="
cat src/commands/batch.ts

echo ""
echo "=== src/index.ts (last 60 lines) ==="
tail -60 src/index.ts

echo ""
echo "=== tests/batch.test.mjs ==="
cat tests/batch.test.mjs

Length of output: 22391



@404-Page-Found Here's a thorough review of the PR. Overall it's a solid implementation — config/profile hoisted outside the loop, no process.chdir(), clean summary report. A few things worth addressing:


🐛 Issues

1. findGitRepositories never checks rootDir itself

If a user runs commit-echo batch /path/to/some-repo, the function scans inside that directory rather than treating it as the repo to process. They'd get zero results. Consider checking rootDir itself before scanning children:

export function findGitRepositories(rootDir: string, recursive: boolean): string[] {
  const repos: string[] = [];
  if (!existsSync(rootDir)) return repos;

  // If rootDir itself is a git repo, return it directly
  if (existsSync(join(rootDir, '.git'))) {
    repos.push(rootDir);
    return repos;
  }

  // ... existing loop
}

2. getGitDiff default maxBuffer of 1 MB can crash on large repos

execSync defaults to 1 MB. A monorepo with many staged files will throw RangeError: stdout maxBuffer exceeded. Pass an explicit limit (or use a streaming approach):

return execSync(cmd, { cwd, encoding: 'utf-8', maxBuffer: 100 * 1024 * 1024 }).trim();

3. readdirSync in findGitRepositories has no error handling

Directories with restricted permissions will throw an uncaught exception, crashing the whole batch run. Wrap with try/catch:

let entries;
try {
  entries = readdirSync(rootDir, { withFileTypes: true });
} catch {
  return repos; // skip unreadable directories
}

4. gitCommit regex is fragile for detached HEAD / initial commit output

The regex ^\[(?:.+\s)?([a-f0-9]{7,})\]\s+(.+)$ fails to match detached HEAD output like [HEAD detached at abc1234] message because the captured group would grab detached not the hash. A more robust pattern:

const match = summary.match(/\[.*?([a-f0-9]{7,})\]\s+(.+)$/i);

⚠️ Minor concerns

5. No body prompt in interactive batch mode

The single suggest command lets users add an optional commit body, but interactive batch mode skips this. Either add a body prompt (consistent UX) or note this limitation in the docs/help text.

6. Missing console.log('') spacer after the else (no-suggestions) path

All other exit paths print a blank line before moving to the next repo, but the final else block at line ~330 is missing the spacer, causing cramped output when suggestions aren't generated in non---yes mode.

} else {
  console.log(`    ${pc.yellow('↻ No suggestions generated, skipping')}`);
  results.push({ ... });
  console.log('');  // ← add this
}

7. Tests require a prior build step

tests/batch.test.mjs imports from ../dist/commands/batch.js. If the dist isn't fresh, tests silently pass against stale code. Consider adding a note in README / CONTRIBUTING or a pre-test build step in package.json:

"pretest": "tsc --noEmit && tsc"

8. No test coverage for batchCommand / gitCommit

Understandable since these need network/API mocks, but edge cases like empty suggestions in --yes mode, or gitCommit failure handling, could benefit from tests with mocked generateSuggestions and spawnSync.


✅ What's well done

  • Hoisting buildProfile / loadOrPromptConfig / assertApiKeyAvailable outside the loop avoids redundant I/O.
  • gitCommit correctly uses a temp file + spawnSync (no shell injection risk).
  • Temp file cleanup in a finally block is solid.
  • --recursive correctly avoids descending into found repos.
  • --yes/--auto correctly resolved from both local and global flags in index.ts.

(ノ◕ヮ◕)ノ*:・゚✧

- check if rootDir itself is a git repo before scanning children
- bump getGitDiff maxBuffer to 100 MB for large monorepo diffs
- wrap readdirSync with try/catch to skip unreadable directories
- relax gitCommit regex to match detached HEAD output
- add optional body prompt in interactive batch mode (consistent UX)
- export gitCommit and add unit tests (message, body, empty commit)
- add tests for rootDir-as-repo edge case

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@404-Page-Found
Copy link
Copy Markdown
Contributor Author

@coderabbitai

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 6, 2026

Tip

For best results, initiate chat on the files or code changes.

@404-Page-Found It looks like your message came through empty! Did you have a question or follow-up on any of the review points? Happy to dig deeper into any of the issues flagged (e.g., the findGitRepositories self-check, maxBuffer limit, regex fragility, etc.) or help you work through a fix. Just let me know! 🐇

@404-Page-Found
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 6, 2026

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
tests/batch.test.mjs (1)

278-293: 💤 Low value

Consider relaxing the hash length assertion.

The assertion on line 288 checks for exactly 7 characters, but Git's abbreviated hash can be longer (7+) to ensure uniqueness. While this test will pass consistently in a fresh repo with few commits, a more robust assertion would verify the hash is valid hex rather than a specific length.

♻️ Optional: Make the assertion more flexible
-    assert.equal(result.hash.length, 7);
+    assert.ok(result.hash.length >= 7);
+    assert.match(result.hash, /^[a-f0-9]+$/);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/batch.test.mjs` around lines 278 - 293, The test 'gitCommit creates a
commit and returns hash and summary' asserts result.hash.length === 7 which is
too strict; update the assertion on result.hash (from gitCommit) to allow
variable-length abbreviated hashes by checking it is a hex string and at least 7
characters long (e.g., assert.ok(/^[0-9a-f]+$/.test(result.hash)) and
assert.ok(result.hash.length >= 7)) and keep the summary assertion unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/batch.test.mjs`:
- Around line 278-293: The test 'gitCommit creates a commit and returns hash and
summary' asserts result.hash.length === 7 which is too strict; update the
assertion on result.hash (from gitCommit) to allow variable-length abbreviated
hashes by checking it is a hex string and at least 7 characters long (e.g.,
assert.ok(/^[0-9a-f]+$/.test(result.hash)) and assert.ok(result.hash.length >=
7)) and keep the summary assertion unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 77965044-7b6b-4344-8036-1855e5ae0078

📥 Commits

Reviewing files that changed from the base of the PR and between 942a329 and 15f1327.

📒 Files selected for processing (2)
  • src/commands/batch.ts
  • tests/batch.test.mjs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/commands/batch.ts

assert.equal(result.hash.length, 7) was brittle — Git abbreviated hashes
can exceed 7 characters depending on repository size. Replace with a hex
regex check and a minimum-length assertion (≥7), which matches Git's
shortest-prefix behavior.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@404-Page-Found
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 6, 2026

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@404-Page-Found 404-Page-Found merged commit 8fc83e1 into main Jun 6, 2026
1 check passed
@404-Page-Found 404-Page-Found deleted the 6-add-interactive-multi-repo-batch-mode branch June 6, 2026 10:37
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.

Add interactive multi-repo batch mode

1 participant