feat: add --tools filter to issues command#4
Conversation
Allow filtering issues by tool using UUIDs or human-friendly names. Resolution logic: exact match on name/shortName, then substring search on name/shortName/prefix (only when unambiguous). Ambiguous or unknown inputs produce a clear error message. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 33 |
| Duplication | 16 |
AI Reviewer: first review requested successfully. AI can make mistakes. Always validate suggestions.
TIP This summary will be updated as you push new changes. Give us feedback
There was a problem hiding this comment.
Pull Request Overview
The implementation aligns with the requirement to add a --tools filter to the issues command. All required test scenarios for tool resolution—including UUID passthrough, name matching, and substring searches—have been addressed.
Codacy analysis indicates the PR is up to standards, though there are notable increases in complexity and code clones within the test files (src/commands/issues.test.ts and src/utils/formatting.test.ts). The most critical finding is the use of process-level side effects in utility logic, which should be corrected to ensure consistent error handling across the CLI. There are no major security flaws or logic bugs preventing a merge after these minor refactors.
About this PR
- There is a noticeable increase in complexity and code duplication within the test files. Consider abstracting common setup, mocking, and assertion logic into helper functions to improve maintainability as the CLI command set grows.
Test suggestions
- Verify UUIDs are passed through directly without triggering an API call for the tool list.
- Verify exact case-insensitive match on tool name resolves to the correct UUID.
- Verify exact case-insensitive match on tool shortName resolves to the correct UUID.
- Verify unique substring search (across name, shortName, or prefix) resolves when exactly one tool matches.
- Verify an error is raised with a list of candidates when a substring match is ambiguous.
- Verify an error is raised when a tool name or UUID is not found.
- Verify mixed inputs of UUIDs and names are resolved correctly in a single command execution.
- Verify the tool list is fetched using pagination until all tools are retrieved.
🗒️ Improve review quality by adding custom instructions
| uuids.push(matches[0].uuid); | ||
| } else if (matches.length === 0) { | ||
| console.error(ansis.red(`Error: Tool "${input}" not found.`)); | ||
| process.exit(1); |
There was a problem hiding this comment.
🟡 MEDIUM RISK
Utility functions should throw Errors instead of calling process.exit(). This allows the caller (which already uses handleError) to manage the failure gracefully and ensures consistent error formatting.
Try running the following prompt in your coding agent:
Refactor
resolveToolUuidsinsrc/utils/formatting.tsto throw descriptive Errors instead of usingconsole.errorandprocess.exit(1). Then update the associated tests insrc/utils/formatting.test.tsto useexpect().rejects.toThrow().
There was a problem hiding this comment.
Good catch — refactoring to throw errors instead of process.exit(). The caller in issues.ts will catch these via the existing handleError() flow.
| } | ||
| } | ||
|
|
||
| return uuids; |
There was a problem hiding this comment.
⚪ LOW RISK
Suggestion: Ensure the returned list of UUIDs contains only unique values to avoid redundant filters in the API request.
| return uuids; | |
| return [...new Set(uuids)]; |
There was a problem hiding this comment.
Good point — adding deduplication.
| it("should error when tool name is ambiguous", async () => { | ||
| vi.mocked(ToolsService.listTools).mockResolvedValue(mockToolList as any); | ||
|
|
||
| const mockExit = vi.spyOn(process, "exit").mockImplementation(() => { | ||
| throw new Error("process.exit called"); | ||
| }); | ||
| const mockStderr = vi.spyOn(console, "error").mockImplementation(() => {}); | ||
|
|
||
| const program = createProgram(); | ||
| await expect( | ||
| program.parseAsync([ | ||
| "node", "test", "issues", "gh", "test-org", "test-repo", | ||
| "--tools", "mark", |
There was a problem hiding this comment.
⚪ LOW RISK
Nitpick: The new test cases for the --tools filter introduce significant duplication. Extracting a helper function to handle the common CLI setup and service mocking would improve maintainability and simplify the test suite.
Try running the following prompt in your IDE agent:
In
src/commands/issues.test.ts, extract a helper function for the--toolsfilter tests to reduce duplication in command invocation and error handling assertions.
There was a problem hiding this comment.
The existing test suite in this file follows the same inline pattern. Introducing abstractions in tests reduces readability — keeping it as-is for consistency.
There was a problem hiding this comment.
Pull request overview
Adds a --tools filter to the issues CLI command by resolving user-provided tool identifiers (UUIDs or friendly names) into tool UUIDs and sending them via the existing toolUuids request field.
Changes:
- Added
-T, --tools <tools>option tocodacy issues, accepting comma-separated UUIDs or names. - Implemented
resolveToolUuids()to resolve inputs using UUID passthrough, exact match, then unique substring match. - Added unit + integration tests covering UUID passthrough, resolution, and error cases.
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/utils/formatting.ts | Adds resolveToolUuids() helper to map tool inputs to UUIDs. |
| src/utils/formatting.test.ts | Adds unit tests for resolveToolUuids(). |
| src/commands/issues.ts | Wires --tools into the issues search request, fetching/paginating tool list when needed. |
| src/commands/issues.test.ts | Adds integration tests for --tools behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| console.error(ansis.red(`Error: Tool "${input}" not found.`)); | ||
| process.exit(1); | ||
| } else { | ||
| const names = matches.map((t) => t.name).join(", "); | ||
| console.error( | ||
| ansis.red(`Error: Tool "${input}" is ambiguous, matches: ${names}`), | ||
| ); | ||
| process.exit(1); |
There was a problem hiding this comment.
Agreed — refactoring to throw errors. Same fix as the Codacy comment above.
| const names = matches.map((t) => t.name).join(", "); | ||
| console.error( | ||
| ansis.red(`Error: Tool "${input}" is ambiguous, matches: ${names}`), | ||
| ); | ||
| process.exit(1); | ||
| } |
There was a problem hiding this comment.
Fixed together with the not-found branch.
| it("should error on ambiguous substring match", async () => { | ||
| const mockExit = vi.spyOn(process, "exit").mockImplementation(() => { | ||
| throw new Error("process.exit called"); | ||
| }); | ||
| vi.spyOn(console, "error").mockImplementation(() => {}); | ||
|
|
||
| await expect(resolveToolUuids(["mark"], fetchTools)).rejects.toThrow( | ||
| "process.exit called", | ||
| ); | ||
|
|
||
| expect(console.error).toHaveBeenCalledWith( | ||
| expect.stringContaining("ambiguous"), | ||
| ); | ||
|
|
||
| mockExit.mockRestore(); | ||
| (console.error as any).mockRestore(); | ||
| }); |
There was a problem hiding this comment.
Good catch — fixing the spy references.
| it("should resolve a substring match via prefix when only one tool matches", async () => { | ||
| vi.mocked(ToolsService.listTools).mockResolvedValue(mockToolList as any); | ||
| vi.mocked(AnalysisService.searchRepositoryIssues).mockResolvedValue({ | ||
| data: [], | ||
| } as any); | ||
|
|
||
| const program = createProgram(); | ||
| // "eslint9" matches shortName "eslint9" exactly | ||
| await program.parseAsync([ | ||
| "node", "test", "issues", "gh", "test-org", "test-repo", | ||
| "--tools", "eslint9", | ||
| ]); | ||
|
|
There was a problem hiding this comment.
You're right — renaming to reflect what it actually tests, and adding a separate test for true substring/prefix matching.
- Refactor resolveToolUuids to throw errors instead of process.exit(), letting the caller's handleError() manage the exit flow - Deduplicate resolved UUIDs via Set - Fix misleading test name and add true substring match test - Fix test spy restoration to use stored variable references Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
-T, --tools <tools>option to theissuescommand, accepting comma-separated tool UUIDs or human-friendly name stringsresolveToolUuids()insrc/utils/formatting.tswith a three-tier resolution strategy:nameorshortName(e.g.eslint→ ESLint,eslint9→ ESLint 9)name,shortName, andprefixfields; only resolves when exactly one tool matches. Ambiguous inputs (e.g.markmatching both Markdownlint and Remarklint) produce a clear error listing all matchesToolsService.listTools) is fetched lazily — only when at least one input is not a UUID — and handles pagination to collect all toolstoolUuidsfield onSearchRepositoryIssuesBodyExamples
Test plan
resolveToolUuids()informatting.test.ts(UUID passthrough, exact name, exact shortName, substring, ambiguous error, not-found error, mixed inputs)--toolsinissues.test.ts(UUID direct, name resolution, shortName resolution, prefix match, ambiguous error, not-found error, mixed values)npm run build)🤖 Generated with Claude Code