Skip to content

CLI-664 Agent integrations for multi-file change-set analysis#471

Draft
nquinquenel wants to merge 6 commits into
masterfrom
feature/nq/CLI-664-wire-multi-file-agent
Draft

CLI-664 Agent integrations for multi-file change-set analysis#471
nquinquenel wants to merge 6 commits into
masterfrom
feature/nq/CLI-664-wire-multi-file-agent

Conversation

@nquinquenel

@nquinquenel nquinquenel commented Jun 17, 2026

Copy link
Copy Markdown
Member

Claude Code:
image

Cursor:
image


Summary by Gitar

  • Agent Integration:
    • Added ClaudeStop hook to perform aggregated git change-set analysis at the end of agent turns.
    • Registered Stop hook in Claude integration logic and updated CLAUDE.md documentation.
  • Analysis Workflow:
    • Updated instructions for Cursor and Copilot to perform change-set analysis using sonar analyze agentic --project <key> instead of single-file loops.
    • Added a --force flag for large change-sets (50+ files) in the SQAA protocol instructions.
  • Testing:
    • Added integration and unit tests for sonar hook claude-stop.
    • Updated existing integration tests to verify the new multi-file DEEP analysis behavior and Stop hook registration.

This will update automatically on new commits.

@netlify

netlify Bot commented Jun 17, 2026

Copy link
Copy Markdown

Deploy Preview for sonarqube-cli canceled.

Name Link
🔨 Latest commit 2f2ca13
🔍 Latest deploy log https://app.netlify.com/projects/sonarqube-cli/deploys/6a33b78e96dc1d00093a02db

@hashicorp-vault-sonar-prod

hashicorp-vault-sonar-prod Bot commented Jun 17, 2026

Copy link
Copy Markdown

CLI-664

Comment thread src/cli/commands/hook/format-sqaa-hook-context.ts
@nquinquenel nquinquenel marked this pull request as ready for review June 17, 2026 15:39
@nquinquenel nquinquenel force-pushed the feature/nq/CLI-664-wire-multi-file-agent branch from fd9444f to 88bd2a0 Compare June 17, 2026 15:39
@nquinquenel nquinquenel marked this pull request as draft June 17, 2026 18:37
@sonarqubecloud

sonarqubecloud Bot commented Jun 17, 2026

Copy link
Copy Markdown

Agentic Analysis: Early Results

Agentic Analysis and Context Augmentation are available on your project. Here are some issues that could have been prevented. Follow the links to learn how to put them into action.

2 issue(s) found across 1 file(s):

Rule File Line Message
typescript:S3776 src/cli/commands/analyze/sqaa.ts 101 Refactor this function to reduce its Cognitive Complexity from 21 to the 15 allowed.
typescript:S3776 src/cli/commands/analyze/sqaa.ts 303 Refactor this function to reduce its Cognitive Complexity from 17 to the 15 allowed.

Analyzed by SonarQube Agentic Analysis in 6.6 s

Comment thread src/cli/commands/analyze/analyze-all.ts
Comment thread src/cli/commands/analyze/sqaa-file-arg.ts
Comment thread src/cli/commands/integrate/claude/state.ts
@nquinquenel

Copy link
Copy Markdown
Member Author

gitar auto-apply:on

…dd legacy cleanup comment

Co-authored-by: Nicolas QUINQUENEL <14952624+nquinquenel@users.noreply.github.com>
@nquinquenel

Copy link
Copy Markdown
Member Author

gitar auto-apply:off

Comment thread src/cli/commands/analyze/sqaa-file-arg.ts Outdated
Comment thread src/cli/commands/analyze/analyze-all.ts
const entries: ResolvedSqaaFileEntry[] = [];

for (const path of rawArgs) {
const absolutePath = resolve(cwd, normalizePath(path));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Edge Case: normalizePath clobbers literal backslashes in POSIX file paths

normalizePath (src/lib/fs-utils.ts:24) unconditionally replaces every backslash with a forward slash, and it is now applied to user-supplied --file arguments at src/cli/commands/analyze/sqaa-file-arg.ts:46 before resolve. On POSIX systems a backslash is a legal filename character, so a real file like weird ame.ts in the working directory would be rewritten to weird/name.ts and then fail the statSync existence check (File not found) or resolve to the wrong path. This is intentional and desirable on Windows (where \ is the separator), but unconditional replacement misbehaves on Linux/macOS.

Impact is low because filenames containing backslashes are rare, so this is minor. If cross-platform separator handling is the goal, consider only normalizing backslashes on Windows (e.g. guard with process.platform === 'win32' or use path.win32/path.sep-aware logic) so legitimate POSIX filenames are preserved.

Only collapse backslashes to forward slashes on Windows, preserving literal backslashes in POSIX filenames.:

export const normalizePath = (p: string): string =>
  process.platform === 'win32' ? p.replaceAll('\', '/') : p;
  • Apply fix

Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎

@gitar-bot

gitar-bot Bot commented Jun 18, 2026

Copy link
Copy Markdown
Code Review 👍 Approved with suggestions 7 resolved / 8 findings

Implements multi-file change-set analysis for agent integrations and resolves several analysis flow bugs, including force-flag propagation and duplicate path handling. Verify the normalizePath implementation to ensure it does not clobber literal backslashes on POSIX systems.

💡 Edge Case: normalizePath clobbers literal backslashes in POSIX file paths

📄 src/cli/commands/analyze/sqaa-file-arg.ts:46

normalizePath (src/lib/fs-utils.ts:24) unconditionally replaces every backslash with a forward slash, and it is now applied to user-supplied --file arguments at src/cli/commands/analyze/sqaa-file-arg.ts:46 before resolve. On POSIX systems a backslash is a legal filename character, so a real file like weird ame.ts in the working directory would be rewritten to weird/name.ts and then fail the statSync existence check (File not found) or resolve to the wrong path. This is intentional and desirable on Windows (where \ is the separator), but unconditional replacement misbehaves on Linux/macOS.

Impact is low because filenames containing backslashes are rare, so this is minor. If cross-platform separator handling is the goal, consider only normalizing backslashes on Windows (e.g. guard with process.platform === 'win32' or use path.win32/path.sep-aware logic) so legitimate POSIX filenames are preserved.

Only collapse backslashes to forward slashes on Windows, preserving literal backslashes in POSIX filenames.
export const normalizePath = (p: string): string =>
  process.platform === 'win32' ? p.replaceAll('\', '/') : p;
✅ 7 resolved
Bug: --force not forwarded from bare analyze to multi-file SQAA

📄 src/cli/commands/analyze/analyze-all.ts:76-83 📄 src/cli/commands/analyze/sqaa.ts:128-130
In analyzeAll (text mode), the force flag is destructured from options but is not passed through to analyzeSqaa:

const { file: rawFiles, staged, base, project, force, format } = options;
if (rawFiles?.length) {
  ...
  await analyzeSqaa({ file: rawFiles, project, format }, auth, { requireProject: false });
}

When a user runs the bare sonar analyze --file ... --force with more than SQAA_LARGE_CHANGESET_THRESHOLD (50) explicit files, the multi-file branch in analyzeSqaa evaluates if (!force && format !== 'json' && entries.length > 50) with force === undefined, so the interactive large-changeset confirmation still fires despite --force being supplied. This contradicts the documented purpose of --force ("Skip the large change set confirmation prompt"). Forward force (and ideally branch) so the flag behaves consistently across analyze and analyze agentic.

Edge Case: Duplicate --file entry aborts entire multi-file SQAA analysis

📄 src/cli/commands/analyze/sqaa-file-arg.ts:65-79 📄 src/cli/commands/integrate/_common/instructions-templates.ts:56-70
resolveSqaaFileArgs throws InvalidOptionError("Duplicate --file entry: ...") whenever two --file values resolve to the same absolute path (e.g. the same file passed once unscoped and once as :TEST, or simply listed twice). Since the new SQAA instructions template directs agents to emit one --file per edited file ("List every file you edited; ... Run SonarQube Agentic Analysis once with one --file per edited file"), an agent that accidentally lists a file twice will cause the whole analysis command to fail rather than degrade gracefully. Given the command is now generated by LLM agents from natural-language instructions, hard-failing on an accidental duplicate is fragile. Consider de-duplicating (keeping the first, or the scoped, occurrence) instead of throwing, so a single repeated path does not abort analysis of all other files.

Quality: Legacy CLAUDE.md SQAA instructions block is not cleaned up on migration

📄 src/cli/commands/integrate/claude/declaration.ts:138-152
This PR replaces the previous sqaa-instructions feature (a textSnippet written into project CLAUDE.md under the claude-agentic-analysis-protocol markers) with the new sonar-sqaa-hook feature. The new feature only writes hook scripts and patches .claude/settings.json; nothing removes the previously written <!-- sonar:begin:claude-agentic-analysis-protocol -->/end block from CLAUDE.md. If users integrated with a version that wrote those instructions, re-integrating will leave the orphaned instructions block in CLAUDE.md indefinitely (it now duplicates/conflicts with the hook-driven flow). If the instructions-based variant ever shipped, consider adding a one-time cleanup (e.g. a remove-marker patch for claude-agentic-analysis-protocol) similar to the legacy hook cleanup that this PR removed.

Edge Case: statSync error message/throw not robust for non-file paths

📄 src/cli/commands/analyze/sqaa-file-arg.ts:50-57
After existsSync passes, statSync(absolutePath).isFile() is used to reject directories. Two minor issues:

  1. isFile() returns false for any non-regular file (symlink-to-dir, FIFO, socket, block device), so the message ${path} is a directory can be misleading for those cases.
  2. statSync is unguarded: if the path is removed or becomes unreadable between the existsSync check and the statSync call (TOCTOU), statSync throws a raw Node Error rather than a CliError, so it bypasses the remediationHint/exit-code handling provided by runCommand().

Consider using statSync(absolutePath, { throwIfNoEntry: false }) (or wrapping in try/catch) and rewording the message to reflect non-regular files, e.g. is not a regular file.

Bug: Duplicate --file paths deduped for secrets but not for SQAA

📄 src/cli/commands/analyze/analyze-all.ts:78-83 📄 src/cli/commands/analyze/sqaa-file-arg.ts:44-58
resolveSqaaFileArgs(rawFiles) now silently skips duplicate --file entries, and its deduped result feeds analyzeSecrets({ paths }). However analyzeSqaa is still passed the raw rawFiles array (including duplicates) on line 82. Unless analyzeSqaa re-runs the same dedup internally, SQAA may analyze/upload the same file twice while the secrets scan does not, an inconsistency introduced when duplicates changed from a hard error to a silent skip. Consider passing the deduped paths to analyzeSqaa as well (or confirming it dedups internally).

...and 2 more resolved from earlier reviews

🤖 Prompt for agents
Code Review: Implements multi-file change-set analysis for agent integrations and resolves several analysis flow bugs, including force-flag propagation and duplicate path handling. Verify the `normalizePath` implementation to ensure it does not clobber literal backslashes on POSIX systems.

1. 💡 Edge Case: normalizePath clobbers literal backslashes in POSIX file paths
   Files: src/cli/commands/analyze/sqaa-file-arg.ts:46

   `normalizePath` (src/lib/fs-utils.ts:24) unconditionally replaces every backslash with a forward slash, and it is now applied to user-supplied `--file` arguments at src/cli/commands/analyze/sqaa-file-arg.ts:46 before `resolve`. On POSIX systems a backslash is a legal filename character, so a real file like `weird
   ame.ts` in the working directory would be rewritten to `weird/name.ts` and then fail the `statSync` existence check (File not found) or resolve to the wrong path. This is intentional and desirable on Windows (where `\` is the separator), but unconditional replacement misbehaves on Linux/macOS.
   
   Impact is low because filenames containing backslashes are rare, so this is minor. If cross-platform separator handling is the goal, consider only normalizing backslashes on Windows (e.g. guard with `process.platform === 'win32'` or use `path.win32`/`path.sep`-aware logic) so legitimate POSIX filenames are preserved.

   Fix (Only collapse backslashes to forward slashes on Windows, preserving literal backslashes in POSIX filenames.):
   export const normalizePath = (p: string): string =>
     process.platform === 'win32' ? p.replaceAll('\', '/') : p;

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
2 New issues
62.9% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

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