feat: pattern guards, reanalyze-and-wait, and overview noise tuning#13
Conversation
Bundles three CLI improvements that were developed together: - pattern config-file & coding-standard awareness: new `pattern <tool> <id>` info mode (same card as `patterns`); `pattern`/`patterns` skip listing and refuse updates when a tool uses a local config file; `pattern` refuses to modify coding-standard-enforced patterns; the `issues --overview` noise suggestions render a manual "update your config file / coding standard" step instead of a command when a pattern can't be disabled via the CLI. - `--reanalyze-and-wait` (`-w`) blocking variant for `repository` and `pull-request`: triggers a reanalysis, polls to completion, and prints issue deltas by pattern/severity/category (new utils/reanalyze-wait.ts). - `issues --overview` polish: human-friendly False Positives labels and the noisy-pattern detection that seeds the suggestions above. Shared `printPatternCard`/`PATTERN_JSON_FIELDS` moved into utils/formatting.ts so both pattern commands render identically. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 124 |
| Duplication | 27 |
AI Reviewer: first review requested successfully. AI can make mistakes. Always validate suggestions.
TIP This summary will be updated as you push new changes.
There was a problem hiding this comment.
Code Review
This pull request introduces several enhancements to the Codacy Cloud CLI, including a blocking --reanalyze-and-wait (-w) option for the repository and pull-request commands, improved noise-reduction suggestions and human-friendly labels in issues --overview, and local configuration file and coding standard guards for pattern commands. The review feedback is highly constructive and should be addressed: first, optimize buildNoiseSuggestions by slicing the noisy patterns list to MAX_NOISE_SUGGESTIONS before initiating concurrent network requests; second, wrap the polling requests in pollForAnalysis in a try/catch block to make the CLI resilient to transient network errors during long-running analyses.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| async function buildNoiseSuggestions( | ||
| noisy: PatternsCount[], | ||
| globalTools: Tool[], | ||
| repoTools: AnalysisTool[], | ||
| ctx: { provider: string; organization: string; repository: string }, | ||
| ): Promise<NoiseSuggestion[]> { | ||
| const results = await Promise.all( | ||
| noisy.map(async (pattern): Promise<NoiseSuggestion | null> => { | ||
| const tool = resolvePatternTool(pattern.id, globalTools); | ||
| if (!tool) return null; // can't identify the tool — discard silently | ||
| // The `pattern` command matches the tool by name; hyphenate spaces per its convention. | ||
| const toolToken = tool.name.replace(/\s+/g, "-"); | ||
| const repoTool = findRepoTool(repoTools, tool); | ||
|
|
||
| // A local configuration file overrides Codacy-side pattern config, so the | ||
| // pattern must be disabled in that file rather than via the CLI. | ||
| if (repoTool?.settings.usesConfigurationFile) { | ||
| return { | ||
| title: pattern.title, | ||
| total: pattern.total, | ||
| action: `Update your local ${tool.name} configuration file to disable the pattern`, | ||
| }; | ||
| } | ||
|
|
||
| // A pattern enforced by a coding standard must be changed in the standard. | ||
| if (repoTool) { | ||
| const standards = await fetchPatternStandards( | ||
| ctx, | ||
| repoTool.uuid, | ||
| pattern.id, | ||
| ); | ||
| if (standards.length > 0) { | ||
| return { | ||
| title: pattern.title, | ||
| total: pattern.total, | ||
| action: `Update ${standards.join(", ")} to disable the pattern`, | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| title: pattern.title, | ||
| total: pattern.total, | ||
| command: `codacy pattern ${toolToken} ${pattern.id} --disable`, | ||
| }; | ||
| }), | ||
| ); | ||
| return results.filter((s): s is NoiseSuggestion => s !== null); | ||
| } |
There was a problem hiding this comment.
Efficiency Issue: Redundant Concurrent API Requests
Currently, buildNoiseSuggestions maps over all noisy patterns in noisy and triggers concurrent asynchronous calls to fetchPatternStandards (which makes network requests to listRepositoryToolPatterns) for every single one of them.
However, printNoiseSuggestions only displays up to MAX_NOISE_SUGGESTIONS (10) suggestions. If a repository has dozens of noisy patterns, this results in a large number of redundant network requests.
Recommendation
We can optimize this by:
- Synchronously resolving the tools first using
globalTools. - Filtering out patterns with unresolvable tools.
- Slicing the candidate list to
MAX_NOISE_SUGGESTIONSbefore performing any asynchronous network calls. This avoids template literals with backticks in the suggestion to adhere to the strict formatting rules.
async function buildNoiseSuggestions(
noisy: PatternsCount[],
globalTools: Tool[],
repoTools: AnalysisTool[],
ctx: { provider: string; organization: string; repository: string },
): Promise<NoiseSuggestion[]> {
// First, resolve tools synchronously and filter out patterns with unresolvable tools
const patternsWithTools = noisy
.map((pattern) => {
const tool = resolvePatternTool(pattern.id, globalTools);
return { pattern, tool };
})
.filter((item): item is { pattern: PatternsCount; tool: Tool } => item.tool !== undefined && item.tool !== null);
// Since we only display up to MAX_NOISE_SUGGESTIONS, slice the list before making asynchronous API calls
const candidates = patternsWithTools.slice(0, MAX_NOISE_SUGGESTIONS);
const results = await Promise.all(
candidates.map(async ({ pattern, tool }): Promise<NoiseSuggestion> => {
const toolToken = tool.name.replace(/\s+/g, "-");
const repoTool = findRepoTool(repoTools, tool);
// A local configuration file overrides Codacy-side pattern config, so the
// pattern must be disabled in that file rather than via the CLI.
if (repoTool?.settings.usesConfigurationFile) {
return {
title: pattern.title,
total: pattern.total,
action: "Update your local " + tool.name + " configuration file to disable the pattern",
};
}
// A pattern enforced by a coding standard must be changed in the standard.
if (repoTool) {
const standards = await fetchPatternStandards(
ctx,
repoTool.uuid,
pattern.id,
);
if (standards.length > 0) {
return {
title: pattern.title,
total: pattern.total,
action: "Update " + standards.join(", ") + " to disable the pattern",
};
}
}
return {
title: pattern.title,
total: pattern.total,
command: "codacy pattern " + toolToken + " " + pattern.id + " --disable",
};
}),
);
return results;
}There was a problem hiding this comment.
Good call — done. buildNoiseSuggestions now resolves each pattern's owning tool synchronously, drops the unresolvable ones, and slices to MAX_NOISE_SUGGESTIONS before issuing any listRepositoryToolPatterns calls — so we never fetch coding-standard status for patterns we won't display. The "… (N more)" hint is preserved via the pre-slice resolvable count.
🤖 Generated by /pr-fixup command
| // Phase A — wait until the new analysis starts (or has already finished). | ||
| while (!inProgress(status) && !done(status)) { | ||
| if (timedOut()) return { status, timedOut: true }; | ||
| await timers.sleep(pollMs); | ||
| status = await getStatus(); | ||
| } | ||
|
|
||
| // Phase B — analysis is running; wait for it to finish. | ||
| if (!done(status)) { | ||
| spinner.text = "Analysis in progress. This may take a few minutes..."; | ||
| while (!done(status)) { | ||
| if (timedOut()) return { status, timedOut: true }; | ||
| await timers.sleep(pollMs); | ||
| status = await getStatus(); | ||
| } | ||
| } |
There was a problem hiding this comment.
Resilience: Handle Transient Network Errors During Polling
When polling for analysis completion (which can take up to 20 minutes), transient network glitches, rate limits, or temporary server issues are highly likely to occur.
Currently, if getStatus() throws an error during polling, the entire CLI process will crash immediately. Wrapping the polling calls inside the loops in a try/catch block will make the CLI resilient to transient network drops, allowing it to simply retry on the next tick.
// Phase A — wait until the new analysis starts (or has already finished).
while (!inProgress(status) && !done(status)) {
if (timedOut()) return { status, timedOut: true };
await timers.sleep(pollMs);
try {
status = await getStatus();
} catch (err) {
// Ignore transient network errors during polling and retry
}
}
// Phase B — analysis is running; wait for it to finish.
if (!done(status)) {
spinner.text = "Analysis in progress. This may take a few minutes...";
while (!done(status)) {
if (timedOut()) return { status, timedOut: true };
await timers.sleep(pollMs);
try {
status = await getStatus();
} catch (err) {
// Ignore transient network errors during polling and retry
}
}
}There was a problem hiding this comment.
Holding off on this one. Swallowing all errors during a 20-minute poll would also mask persistent failures (auth/permission/404), looping silently until the timeout, and it adds complexity to a file we're simultaneously simplifying for the complexity gate. The maxWaitMs cap already bounds the loop. A bounded consecutive-failure retry would be the right way to add resilience without hiding hard failures — noting it as a possible follow-up rather than applying the catch-all here.
🤖 Generated by /pr-fixup command
There was a problem hiding this comment.
Pull Request Overview
The PR successfully implements the functional requirements for pattern guards, the reanalyze-and-wait polling mode, and noise reduction in the issues overview. However, Codacy results are currently not up to standards due to high complexity and new quality issues.
A critical blocker is the usage of an undefined function fetchAllTools in src/commands/issues.ts, which will cause a runtime failure. Additionally, src/utils/reanalyze-wait.ts is identified as a high-risk file: it possesses high cyclomatic complexity (70), is entirely uncovered by tests, and contains a logic issue regarding potential clock skew during analysis polling. These issues should be resolved before merging.
About this PR
- The configuration file guard logic (checking if a tool uses a local configuration and blocking updates) is duplicated across
src/commands/pattern.tsandsrc/commands/patterns.ts. This logic should be centralized in a shared utility to ensure consistent behavior.
1 comment outside of the diff
src/commands/patterns.ts
line 192🔴 HIGH RISK
The.actionhandler for thepatternscommand is doing too much. Separation of the bulk-update flow from the list-rendering flow would make the command much easier to test and modify.Try running the following prompt in your IDE agent:
Refactor the action handler for the
patternscommand insrc/commands/patterns.ts. Separate the bulk update logic (handleBulkUpdate) and the pattern listing logic into distinct functions to improve maintainability and NLOC.
Test suggestions
- Verify pattern info mode displays card/JSON correctly when no action flag is provided
- Verify config-file guard prevents modification in both 'pattern' and 'patterns' commands
- Verify coding-standard guard prevents modification of enforced patterns in the 'pattern' command
- Verify 'issues --overview' correctly relabels False Positive buckets
- Verify noise detection logic identifies patterns exceeding both the percentage share and frequency multiplier thresholds
- Verify noise suggestions correctly toggle between runnable CLI commands and manual instructions based on modifiability
- Verify the polling loop correctly uses the trigger time (t0) to avoid tracking stale analyses
- Verify issue snapshot and diff logic correctly calculates signed net deltas across all dimensions (severity, category, pattern)
- Automate unit test for clock skew scenarios in
isAnalysisInProgress(high risk, 0 coverage) - Automate unit tests for
renderReanalyzeReportwith mock delta payloads (high risk, 0 coverage)
Prompt proposal for missing tests
Consider implementing these tests if applicable:
1. Automate unit test for clock skew scenarios in `isAnalysisInProgress` (high risk, 0 coverage)
2. Automate unit tests for `renderReanalyzeReport` with mock delta payloads (high risk, 0 coverage)
TIP Improve review quality by adding custom instructions
TIP How was this review? Give us feedback
| * dim ⬛ when disabled. (`enabled` is OR'd with `enabledBy` to work around an | ||
| * API quirk where a standard-enforced pattern can report `enabled: false`.) | ||
| */ | ||
| export function printPatternCard(cp: ConfiguredPattern): void { |
There was a problem hiding this comment.
🔴 HIGH RISK
The printPatternCard function is becoming a 'God function' for pattern rendering. Breaking it into smaller components (e.g., printPatternHeader, printPatternMetadata, and printPatternDocumentation) would improve readability and reduce the complexity score.
Try running the following prompt in your IDE agent:
Refactor the
printPatternCardfunction insrc/utils/formatting.tsto reduce its cyclomatic complexity. Extract the logic for rendering metadata, documentation (Why/How), and parameters into smaller, private helper functions.
There was a problem hiding this comment.
Done — split into printPatternHeader, printPatternMeta, printPatternDocs, and printPatternParams. printPatternCard now just composes them, bringing cyclomatic complexity back under the limit.
🤖 Generated by /pr-fixup command
| // tools tell us which ones are config-file-driven and let us check | ||
| // coding-standard enforcement per pattern. | ||
| const [globalTools, repoToolsResponse] = await Promise.all([ | ||
| fetchAllTools(), |
There was a problem hiding this comment.
🔴 HIGH RISK
fetchAllTools is not defined or imported. Try running the following prompt in your coding agent:
Implement a
fetchAllToolshelper insrc/utils/formatting.tsthat pages throughToolsService.listToolsto return all global tools, then import it intosrc/commands/issues.ts.
There was a problem hiding this comment.
False positive. fetchAllTools is defined locally in this file (async function fetchAllTools() ~line 384) and is used both here and by buildFilterBody. The CI build-and-test job (which runs tsc + the test suite) passes on Node 20 and 22 — it would fail at compile time if the function were undefined.
🤖 Generated by /pr-fixup command
| } | ||
|
|
||
| /** Print the human-readable delta report. */ | ||
| export function renderReanalyzeReport( |
There was a problem hiding this comment.
🟡 MEDIUM RISK
This function is exceeding complexity limits by handling all three dimensions of the reanalyze report (pattern, severity, category) in one block. Extracting each dimension's rendering logic into specialized functions would clarify the flow.
Try running the following prompt in your IDE agent:
Split
renderReanalyzeReportinsrc/utils/reanalyze-wait.tsinto smaller functions. CreaterenderPatternDeltas,renderSeverityDeltas, andrenderCategoryDeltashelpers.
There was a problem hiding this comment.
Done — extracted renderPatternDeltas, renderSeverityDeltas, and renderCategoryDeltas. renderReanalyzeReport now composes them and is under both the cyclomatic-complexity and line-count limits.
🤖 Generated by /pr-fixup command
| * reanalysis (`startedAnalysis` more recent than t0) and hasn't finished yet | ||
| * (`startedAnalysis` more recent than `endedAnalysis`). | ||
| */ | ||
| export function isAnalysisInProgress( |
There was a problem hiding this comment.
🟡 MEDIUM RISK
To account for potential clock skew between the client and server, subtract a safety margin from triggeredAt before using it for comparisons in isAnalysisInProgress and isAnalysisDone.
There was a problem hiding this comment.
Deferring this one. startedAnalysis/endedAnalysis are server-generated timestamps while triggeredAt is the client trigger time, so a skew margin would only matter in an edge case and trades deterministic detection for a magic constant. Flagging for discussion rather than adding it speculatively — happy to introduce a small margin if clock skew turns out to cause missed detections in practice.
🤖 Generated by /pr-fixup command
| repository, | ||
| ), | ||
| ]); | ||
| suggestions = await buildNoiseSuggestions( |
There was a problem hiding this comment.
⚪ LOW RISK
Suggestion: Limit the number of patterns processed to avoid redundant API calls for patterns that won't be displayed.
There was a problem hiding this comment.
Done — patterns are now capped to MAX_NOISE_SUGGESTIONS before any per-pattern API calls (resolved synchronously and sliced first), so there's no burst of redundant requests on repos with many noisy patterns.
🤖 Generated by /pr-fixup command
There was a problem hiding this comment.
Pull request overview
This PR bundles several CLI UX and workflow improvements around pattern management, issue overview noise reduction, and a new blocking reanalysis mode that waits for completion and reports issue deltas.
Changes:
- Add
--reanalyze-and-wait (-w)torepositoryandpull-request, including polling + delta reporting (human + JSON). - Enhance
pattern/patternswith shared rendering + JSON projection, plus guards for config-file-driven tools and coding-standard-enforced patterns. - Polish
issues --overviewby relabeling false-positive buckets and adding “noisy pattern” disable suggestions.
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/utils/reanalyze-wait.ts | New shared polling/snapshot/delta/render utilities for --reanalyze-and-wait. |
| src/utils/reanalyze-wait.test.ts | Unit tests for snapshots, diffing, polling behavior, rendering, and JSON output. |
| src/utils/formatting.ts | Add formatDuration, isBeingAnalyzed, and centralize pattern card rendering + JSON field list. |
| src/utils/formatting.test.ts | Tests for formatDuration and isBeingAnalyzed. |
| src/commands/repository.ts | Add --reanalyze-and-wait flow for repositories using overview baseline + polling. |
| src/commands/repository.test.ts | Tests for repository --reanalyze-and-wait behavior and JSON output. |
| src/commands/pull-request.ts | Add --reanalyze-and-wait flow for PRs using PR-issue baselines + polling. |
| src/commands/pull-request.test.ts | Tests for PR --reanalyze-and-wait behavior and JSON output. |
| src/commands/patterns.ts | Reuse shared printPatternCard/PATTERN_JSON_FIELDS and add config-file tool guard. |
| src/commands/patterns.test.ts | Coverage for configuration-file guard behavior in patterns. |
| src/commands/pattern.ts | Add info mode, JSON output, config-file guard, and coding-standard enforcement guard. |
| src/commands/pattern.test.ts | New tests for info mode + guards (config-file and standard-enforced patterns). |
| src/commands/issues.ts | Relabel false-positive buckets and add noisy-pattern suggestions that adapt to tool/pattern constraints. |
| src/commands/issues.test.ts | Tests for false-positive relabeling and noise-suggestion paths (command vs manual steps). |
| src/commands/AGENTS.md | Document new modes/guards and shared utilities for agents/maintainers. |
| SPECS/README.md | Update spec tracking entries for the new features. |
| SPECS/commands/tools-and-patterns.md | Document pattern info mode and configuration-file/coding-standard guards. |
| SPECS/commands/issues.md | Document false-positive relabeling + noisy-pattern suggestion logic and extra API calls. |
| SPECS/commands/analysis.md | Document --reanalyze-and-wait behavior, polling rules, and outputs. |
| README.md | Update command summaries to mention new behavior (pattern info mode, reanalyze-and-wait). |
| AGENTS.md | Add repo-wide convention for polling via utils/reanalyze-wait.ts + timers.sleep. |
| .changeset/reanalyze-and-wait.md | Changeset entry for --reanalyze-and-wait release note. |
| .changeset/pattern-config-file-awareness.md | Changeset entry for pattern guards + info mode. |
| .changeset/issues-overview-noise.md | Changeset entry for issues overview false-positive labels + noise suggestions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Resolve disable suggestions for noisy patterns. The extra tools fetch | ||
| // only happens when there's something to suggest, and stays under the | ||
| // spinner so the user sees progress rather than a stall. | ||
| const noisy = detectNoisyPatterns(counts.patterns); |
There was a problem hiding this comment.
Done — buildNoiseSuggestions now resolves owning tools synchronously, drops unresolvable patterns, and slices to MAX_NOISE_SUGGESTIONS before making any listRepositoryToolPatterns calls. No per-pattern network calls happen for patterns that won't be displayed.
🤖 Generated by /pr-fixup command
| .sort( | ||
| (a, b) => | ||
| SEVERITY_ORDER.indexOf(a.severity!) - | ||
| SEVERITY_ORDER.indexOf(b.severity!), | ||
| ); |
There was a problem hiding this comment.
Good catch — fixed. Added a severityRank helper that maps unknown severities to the end of the canonical order (instead of indexOf returning -1 and sorting them first), and added a regression test (sorts unknown severities last instead of first).
🤖 Generated by /pr-fixup command
Review feedback addressedThanks for the thorough reviews. Pushed a fixup commit: Fixed (Codacy quality gate — the 8 new issues)
Fixed (review comments)
Dismissed
Deferred / flagged for discussion
🤖 Generated by /pr-fixup command |
- Reduce cyclomatic complexity flagged by Codacy: split renderReanalyzeReport, printPatternCard, and extract helpers from diffSnapshots/snapshotFromPrIssues — all under the limits now. - Cap buildNoiseSuggestions to MAX_NOISE_SUGGESTIONS before any per-pattern API calls (Gemini/Copilot/Codacy efficiency note); preserve the "… (N more)" hint. - Sort unknown severities last in diffSnapshots via severityRank instead of indexOf returning -1 (Copilot); add regression test. - Replace non-literal RegExp in issues tests with a literal to clear the Semgrep finding. - Centralize the config-file guard wording in utils/formatting.ts so pattern/patterns stay consistent (Codacy DRY note). - Reword the AGENTS.md polling bullet to satisfy the doc linter. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Extract renderDeltaSections from renderReanalyzeReport and patternIcon from printPatternHeader so both drop under the cyclomatic-complexity medium limit (7). - Split the AGENTS.md "Polling / waiting" bullet into short sub-bullets to satisfy the sentence-complexity doc lint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| const startedAt = now(); | ||
| const timedOut = () => now() - startedAt > maxWaitMs; |
Summary
Bundles three CLI improvements that were developed together on the same working tree (three changesets included):
pattern <tool> <id>now shows pattern info (same card aspatterns,--output jsonsupported).pattern/patternsskip listing and refuse updates when a tool uses a local config file;patternrefuses to modify coding-standard-enforced patterns. Theissues --overviewnoise suggestions adapt per pattern: a runnablecodacy pattern … --disablecommand when possible, otherwise a manual "update your config file / coding standard" step.--reanalyze-and-wait(-w) — blocking reanalysis variant forrepositoryandpull-request: triggers analysis, polls to completion (10s interval, 20min cap), then prints issue deltas by pattern/severity/category. Newsrc/utils/reanalyze-wait.ts.issues --overviewpolish — human-friendly False Positives labels and the noisy-pattern detection that seeds the suggestions above.Shared
printPatternCard/PATTERN_JSON_FIELDSmoved intosrc/utils/formatting.tsso both pattern commands render identically.Test plan
npm run check-typesnpm test(371 tests)npm run buildcodacy pattern <gh> <org> <repo> <tool> <id>(info),--disableon a config-file tool and on a standard-enforced pattern, andissues --overviewon a repo with a config-file-driven noisy tool🤖 Generated with Claude Code