Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
6340399
CLI-815 Install Claude Code CAG PostToolUse hook
michael-jabbour-sonarsource Jul 10, 2026
cdd0c0e
Rollback CAG hook when tool integrate fails
michael-jabbour-sonarsource Jul 10, 2026
5fbab74
Preserve CAG failure during hook rollback
michael-jabbour-sonarsource Jul 10, 2026
9171f36
Route Claude CAG hooks through generic __hook
michael-jabbour-sonarsource Jul 13, 2026
9abb91c
Preserve CAG hooks on retry setup failure
michael-jabbour-sonarsource Jul 13, 2026
687f137
Revert "Preserve CAG hooks on retry setup failure"
michael-jabbour-sonarsource Jul 13, 2026
e1cd3ed
Revert "Preserve CAG failure during hook rollback"
michael-jabbour-sonarsource Jul 13, 2026
be84a24
Revert "Rollback CAG hook when tool integrate fails"
michael-jabbour-sonarsource Jul 13, 2026
87b04ee
CLI-815 Add Claude CAG PreToolUse hook
michael-jabbour-sonarsource Jul 13, 2026
0e0a7e0
Merge remote-tracking branch 'origin/master' into mj/CLI-815_CAG_Post…
michael-jabbour-sonarsource Jul 16, 2026
0d15635
Merge branch 'master' into mj/CLI-815_CAG_PostToolUse_hook
michael-jabbour-sonarsource Jul 16, 2026
d781c53
Merge branch 'master' into mj/CLI-815_CAG_PostToolUse_hook
michael-jabbour-sonarsource Jul 17, 2026
35028fc
Merge remote-tracking branch 'origin/master' into mj/CLI-815_CAG_Post…
michael-jabbour-sonarsource Jul 21, 2026
9206978
Merge remote-tracking branch 'origin/master' into mj/CLI-815_CAG_Post…
michael-jabbour-sonarsource Jul 22, 2026
3b50ca1
Rename the hook
michael-jabbour-sonarsource Jul 22, 2026
d8a837b
Merge branch 'master' into mj/CLI-815_CAG_PostToolUse_hook
michael-jabbour-sonarsource Jul 23, 2026
19dcd65
Merge branch 'master' into mj/CLI-815_CAG_PostToolUse_hook
michael-jabbour-sonarsource Jul 23, 2026
8ab86c4
Merge branch 'master' into mj/CLI-815_CAG_PostToolUse_hook
michael-jabbour-sonarsource Jul 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ SQAA is **project-scoped and opt-in**: integrate orchestrators call `resolveSqaa

`sonar context [action] [args...]` is a passthrough to the locally-installed `sonar-context-augmentation` binary (CAG). It forwards args verbatim, propagates the child exit code, and injects context through `SONAR_CONTEXT_ORGANIZATION`, `SONAR_CONTEXT_PROJECT`, `SONAR_CONTEXT_TOKEN`, and `SONAR_CONTEXT_URL` env vars. Every CAG spawn (including the `--help` path and the integrate/post-update flows) also carries `SONAR_CONTEXT_INVOCATION_ID` — the per-CLI-process correlation id from `src/core/telemetry/invocation-id.ts`, shared with telemetry's `invocation_id` and forwarded by CAG to its daemon as the `x-sonar-invocation-id` header. The passthrough resolves project context from the recorded declarative CAG feature state for the current project rather than running full project auto-discovery. This lookup is git-worktree-aware so you can `sonar integrate` once and use `sonar context` from any worktree (including ones created later, and after the integrate-time worktree is removed): integrate records a stable `attrs.repoRoot` = the repository's **main working tree** (resolved via `resolveRecordedRepoRoot` in `src/core/host/git-worktree.ts`, which reads `git worktree list --porcelain`) while keeping `targetRoot` = the physical install dir for teardown; the passthrough matches the current directory (mapped to its main-worktree equivalent via `resolveWorktreeEquivalentPaths`) against `attrs.repoRoot ?? targetRoot`, falling back to `targetRoot` for state written by older CLI versions. When a recorded integration matches, the passthrough sets `SONAR_CONTEXT_WORKSPACE_ROOT` via `resolveContextWorkspaceRoot`: the **git working-tree root** containing the invocation when inside a repository (climbing up from subdirectories, including linked worktrees), or the feature's physical `targetRoot` when not in a git repo, so CAG can locate or lazily create a per-workspace daemon folder; org/project/server metadata still come from the recorded integration; it is left unset (and any inherited value dropped) when no integration matches. Implementation in `src/commands/context/`. The binary is downloaded by `sonar integrate claude` / `sonar integrate copilot` / `sonar integrate codex` / `sonar integrate antigravity` / `sonar integrate cursor` (skip with `--skip-context`); `sonar context` itself never auto-installs and emits a clear "not installed" error pointing the user back to integrate. `--global` integrations also skip CAG setup; install it by re-running `sonar integrate <agent>` from a project directory. The `--global` skip notice (`Skipping Context Augmentation: not supported with --global. Re-run without --global from a project directory to install it there.`) is only emitted when the org is actually entitled to CAG — unentitled orgs skip silently. The CLI owns the agent skill file declaratively as a `wholeFile` resource inside a single CAG feature and renders it by calling `sonar-context-augmentation tool print-skill --invocation-prefix "sonar context" --sca-enabled=<resolved>`, forwarding the recorded organization as `SONAR_CONTEXT_ORGANIZATION` (from the feature's `attrs.orgKey`, best-effort — omitted when unrecorded) so CAG's org-gated internal dogfooding tools are rendered into the skill for allowlisted organizations; project/URL/token are not passed to `print-skill`. The rendered file is written to `.claude/skills/sonar-context-augmentation/SKILL.md`, `.github/skills/sonar-context-augmentation/SKILL.md`, or `.agents/skills/sonar-context-augmentation/SKILL.md` depending on the agent. **Cursor** also writes to `.agents/skills/sonar-context-augmentation/SKILL.md` — the shared cross-tool skills directory it reads alongside Codex and Antigravity — rather than a Cursor-private `.cursor/skills` copy, so the three tools share one skill instead of duplicating it. Cursor loads skills on demand (distinct from Cursor's SQAA delivery, which is an always-applied `.cursor/rules/*.mdc` rule because that protocol must run every turn). That same feature also declares a `tool integrate --invocation-prefix "sonar context"` operation, but the operation is install-only: the framework passes `executionMode=install|update` into feature contexts, and CAG uses `shouldApply` so `tool integrate` runs during `sonar integrate <agent>` but not during post-update refreshes. Post-update still reinstalls the shared dependency, best-effort runs `sonar-context-augmentation tool stop --all` against the previously-installed binary before replacing it, and refreshes the skill file. Replay failures are debug-logged so they do not abort CLI startup.

For Claude Code project installs, that same CAG feature also writes a generated launcher under `.claude/hooks/sonar-context-augmentation/build-scripts/` and patches `.claude/settings.json` with `PreToolUse`, `PostToolUse`, and `PostToolUseFailure` entries using matcher `Bash|PowerShell|Monitor|Read`. The launcher delegates to `sonar context __hook Claude`, uses `sonar-context-augmentation` as the managed marker for idempotency/removal, coexists with the SQAA `PostToolUse` entry, and is refreshed on post-update like the skill file. CAG distinguishes the concrete Claude hook event from the stdin payload.

`--help`, `-h`, and bare `sonar context` (no action) are forwarded to CAG.

Before installing, `sonar integrate claude|copilot|codex|antigravity` pre-flights the CAG entitlement check: `SonarQubeClient.hasCagEntitlement(orgKey)` resolves the org UUID via `/organizations/organizations` then calls `GET /cag/cag-entitlement/{uuid}` (SonarQube Cloud only). The CLI gates on `hasEntitlement` (pure addon entitlement, independent of consumption); CAG setup proceeds for entitled orgs even when the consumption limit is reached — only tool invocations are blocked at runtime by the CAG daemon itself. If `hasEntitlement` is absent or false, CAG setup is skipped with a warning. Any error in the check is treated as "not entitled". The `sonar context` passthrough is not gated — CAG itself enforces entitlement per-request.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
CONTEXT_AUGMENTATION_FEATURE_PREVIEW,
} from '../feature-constants.ts';
import { contextAugmentationBinaryDependency } from '../registry/dependencies';
import { wholeFile } from '../registry/resources';
import { type ResourceDeclaration, wholeFile } from '../registry/resources';
import { askUser, skip } from '../registry/selection.ts';
import type { FeatureDeclaration, IntegrationContext } from '../registry/types.ts';

Expand All @@ -40,6 +40,7 @@ export const CONTEXT_AUGMENTATION_TOOL_INTEGRATION_OPERATION_ID =

export interface ContextAugmentationSkillFeatureOptions {
targetPath: (context: IntegrationContext) => string;
resources?: ResourceDeclaration[];
}

export function createContextAugmentationFeature<
Expand Down Expand Up @@ -67,6 +68,7 @@ export function createContextAugmentationFeature<
orgKey: getOptionalStringAttr(context, 'orgKey'),
}),
}),
...(options.resources ?? []),
],
operations: [
{
Expand Down
52 changes: 52 additions & 0 deletions src/commands/integrate/claude/declaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ import { askUser, jsonPatch, skip, textSnippet, wholeFile } from '../_common/reg
import { SQAA_HOOK_FEATURE_ID } from '../_common/sqaa-entitlement.ts';
import type { IntegrateAgentOptions } from '../_common/types.ts';
import {
getContextAugmentationHookTemplateUnix,
getContextAugmentationHookTemplateWindows,
getSecretPreToolTemplateUnix,
getSecretPreToolTemplateWindows,
getSecretPromptTemplateUnix,
Expand All @@ -66,6 +68,9 @@ const SETTINGS_FILE = 'settings.json';
const CLAUDE_MD_FILE = 'CLAUDE.md';
const PRETOOL_SCRIPT_REL = 'sonar-secrets/build-scripts/pretool-secrets';
const PROMPT_SCRIPT_REL = 'sonar-secrets/build-scripts/prompt-secrets';
const CONTEXT_HOOK_SCRIPT_REL =
'sonar-context-augmentation/build-scripts/context-augmentation-hook';
const CONTEXT_TOOL_MATCHER = 'Bash|PowerShell|Monitor|Read';

export const CLAUDE_INTEGRATION_ID = 'claude-code';

Expand Down Expand Up @@ -222,6 +227,53 @@ export const claudeIntegration: IntegrationDeclaration<ClaudeIntegrationOptions>
},
createContextAugmentationFeature<ClaudeIntegrationOptions>({
targetPath: resolveClaudeSkillPath,
resources: [
wholeFile({
id: 'context-augmentation-hook-script',
displayName: 'Claude Context Augmentation hook script',
targetPath: (context) =>
resolveAgentHookScriptPath(context, CLAUDE_CONFIG_DIR, CONTEXT_HOOK_SCRIPT_REL),
content: {
unix: getContextAugmentationHookTemplateUnix(),
windows: getContextAugmentationHookTemplateWindows(),
},
executable: true,
}),
jsonPatch({
id: 'claude-settings-context-augmentation-hook',
displayName: 'Claude Context Augmentation hook configuration',
targetPath: resolveClaudeSettingsPath,
defaultValue: { hooks: {} },
patch: (document, context) =>
upsertAgentHooks(document, [
createAgentHookEntry(
context,
CLAUDE_CONFIG_DIR,
'PreToolUse',
CONTEXT_TOOL_MATCHER,
'sonar-context-augmentation',
CONTEXT_HOOK_SCRIPT_REL,
),
createAgentHookEntry(
context,
CLAUDE_CONFIG_DIR,
'PostToolUse',
CONTEXT_TOOL_MATCHER,
'sonar-context-augmentation',
CONTEXT_HOOK_SCRIPT_REL,
),
createAgentHookEntry(
context,
CLAUDE_CONFIG_DIR,
'PostToolUseFailure',
CONTEXT_TOOL_MATCHER,
'sonar-context-augmentation',
CONTEXT_HOOK_SCRIPT_REL,
),
]),
removePatch: (document) => removeAgentHooks(document, ['sonar-context-augmentation']),
}),
],
}),
],
};
Expand Down
8 changes: 8 additions & 0 deletions src/commands/integrate/claude/hook-templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ export function getSecretPromptTemplateWindows(): string {
return windowsTemplate('sonar hook claude-prompt-submit');
}

export function getContextAugmentationHookTemplateUnix(): string {
return unixTemplate('sonar context __hook Claude');
}

export function getContextAugmentationHookTemplateWindows(): string {
return windowsTemplate('sonar context __hook Claude');
}

export function getSqaaPostToolTemplateUnix(projectKey: string): string {
return unixTemplate(formatSqaaPostToolHookCommandUnix('claude-post-tool-use', projectKey));
}
Expand Down
Loading