Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions build-scripts/ux-report/generate-ux-report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ uxDescribe('SonarQube Cloud — Happy Path', () => {
harness
.state()
.withAuth(cloudUrl, TOKEN, ORG)
.withSqaaExtension(harness.cwd.path, PROJECT, ORG, cloudUrl);
.withSqaaFeature(harness.cwd.path, PROJECT, ORG, cloudUrl);
});
afterEach(async () => {
await harness.dispose();
Expand Down Expand Up @@ -665,7 +665,7 @@ uxDescribe('SonarQube Cloud — Error Paths', () => {
.start();
h.state()
.withAuth(server.baseUrl(), TOKEN, ORG)
.withSqaaExtension(h.cwd.path, PROJECT, ORG, server.baseUrl());
.withSqaaFeature(h.cwd.path, PROJECT, ORG, server.baseUrl());
h.cwd.writeFile('src/index.ts', 'const x = 1;');
return h.run('analyze agentic --file src/index.ts');
}),
Expand All @@ -680,7 +680,7 @@ uxDescribe('SonarQube Cloud — Error Paths', () => {
.start();
h.state()
.withAuth(server.baseUrl(), TOKEN, ORG)
.withSqaaExtension(h.cwd.path, PROJECT, ORG, server.baseUrl());
.withSqaaFeature(h.cwd.path, PROJECT, ORG, server.baseUrl());
h.cwd.writeFile('src/index.ts', 'const x = 1;');
return h.run('analyze agentic --file src/index.ts');
}),
Expand Down
41 changes: 25 additions & 16 deletions src/cli/commands/analyze/sqaa-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ import type { ResolvedAuth } from '../../../lib/auth-resolver';
import logger from '../../../lib/logger';
import { spawnProcess } from '../../../lib/process';
import { loadState } from '../../../lib/repository/state-repository';
import type { HookExtension } from '../../../lib/state';
import { findExtensionsByProject } from '../../../lib/state-manager';
import { canonicalProjectRoot } from '../../../lib/state-manager';
import { blank, confirmPrompt, text, warn } from '../../../ui';
import { CommandFailedError } from '../_common/error.js';
import { SQAA_HOOK_FEATURE_ID } from '../integrate/_common/sqaa-entitlement';
import { CLAUDE_INTEGRATION_ID } from '../integrate/claude/declaration';

const LARGE_CHANGESET_HINT =
'For faster feedback, try targeting your changes:\n' +
Expand Down Expand Up @@ -106,34 +107,42 @@ export function resolveCloudAuth(
}

/**
* Look up the project key for the current project from the agentExtensions registry.
* Look up the project key for the current project from the declarative
* integration state (`integrations.installed`).
*
* The registry keys extensions by project root (the directory passed to
* `sonar integrate claude`), so when the user runs SQAA from a subdirectory we
* have to resolve the git repository top-level first — otherwise `process.cwd()`
* is a non-match against the registered root and we incorrectly skip with
* "no project configured".
* The SQAA hook feature is recorded per install target root (the directory
* passed to `sonar integrate claude`), so when the user runs SQAA from a
* subdirectory we resolve the git repository top-level first — otherwise
* `process.cwd()` is a non-match against the recorded root and we incorrectly
* skip with "no project configured".
*
* Falls back to `process.cwd()` when not inside a git repository so the
* single-file path still works outside git.
*/
export async function resolveSqaaProjectKey(projectRoot?: string): Promise<string | null> {
try {
const root = projectRoot ?? (await tryResolveRepoRoot(process.cwd()));
const root = canonicalProjectRoot(projectRoot ?? (await tryResolveRepoRoot(process.cwd())));
const state = loadState();
const extensions = findExtensionsByProject(state, 'claude-code', root);
const sqaaExt = extensions.find(
(e): e is HookExtension => e.kind === 'hook' && e.name === 'sonar-sqaa',

const claude = state.integrations.installed.find(
(integration) => integration.integrationId === CLAUDE_INTEGRATION_ID,
);
const sqaaFeature = claude?.features.find(
(feature) =>
feature.featureId === SQAA_HOOK_FEATURE_ID &&
feature.scope === 'project' &&
canonicalProjectRoot(feature.targetRoot) === root,
);

if (!sqaaExt?.projectKey) {
logger.debug('Vortex agentic analysis skipped: no project key found in extensions registry');
const projectKey = sqaaFeature?.attrs?.projectKey;
if (typeof projectKey !== 'string' || projectKey.length === 0) {
logger.debug('Vortex agentic analysis skipped: no project key found in integration state');
return null;
}

return sqaaExt.projectKey;
return projectKey;
} catch {
logger.debug('Vortex agentic analysis skipped: failed to resolve extensions');
logger.debug('Vortex agentic analysis skipped: failed to resolve integration state');
return null;
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/cli/commands/integrate/_common/sqaa-entitlement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import { AGENTIC_ANALYSIS_DOCS_URL } from '../../../../lib/config-constants';
import { SonarQubeClient, type SqaaEntitlementStatus } from '../../../../sonarqube/client';
import { info, warn } from '../../../../ui';

export const SQAA_HOOK_FEATURE_ID = 'sonar-sqaa-hook';

/**
* Consistent notice shown across all agent integrations when SonarQube Agentic
* Analysis is skipped because the integration is global.
Expand Down
126 changes: 0 additions & 126 deletions src/cli/commands/integrate/_common/state.ts

This file was deleted.

3 changes: 2 additions & 1 deletion src/cli/commands/integrate/claude/declaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {
import { removeJsonMcpServer, upsertJsonMcpServer } from '../_common/mcp-config';
import type { IntegrationContext, IntegrationDeclaration } from '../_common/registry';
import { askUser, jsonPatch, skip, textSnippet, wholeFile } from '../_common/registry';
import { SQAA_HOOK_FEATURE_ID } from '../_common/sqaa-entitlement';
import type { IntegrateAgentOptions } from '../_common/types';
import {
getSecretPreToolTemplateUnix,
Expand Down Expand Up @@ -124,7 +125,7 @@ export const claudeIntegration: IntegrationDeclaration<ClaudeIntegrationOptions>
],
}),
{
id: 'sonar-sqaa-hook',
id: SQAA_HOOK_FEATURE_ID,
displayName: 'Vortex agentic analysis hook',
benefitDescription: AGENTIC_ANALYSIS_FEATURE_BENEFIT,
previewDescription: AGENTIC_ANALYSIS_FEATURE_PREVIEW,
Expand Down
15 changes: 1 addition & 14 deletions src/cli/commands/integrate/claude/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,7 @@
import { homedir } from 'node:os';

import type { ResolvedAuth } from '../../../../lib/auth-resolver';
import {
OBSOLETE_A3S_MARKER,
removeObsoleteHookArtifacts,
runMigrations,
} from '../../../../lib/migration';
import { OBSOLETE_A3S_MARKER, removeObsoleteHookArtifacts } from '../../../../lib/migration';
import type { IntegrationStateAttribute } from '../../../../lib/state';
import {
displayAgentIntegratePrelude,
Expand All @@ -43,7 +39,6 @@ import type { IntegrateAgentOptions } from '../_common/types';
import { supportedIntegrations } from '../index.js';
import { CLAUDE_INTEGRATION_ID, type ClaudeIntegrationOptions } from './declaration';
import { detectGlobalSecretsHook } from './hooks';
import { updateStateAfterConfiguration } from './state';

export interface ConfigurationData {
serverURL: string;
Expand All @@ -68,7 +63,6 @@ export async function integrateClaude(
? undefined
: await detectGlobalSecretsHook(homedir());
const skipSecretsHooks = !!existingGlobalHookPath;
const globalDir = ctx.isGlobal ? homedir() : undefined;

const sqaaEnabled = await resolveSqaaSetup({
serverURL: config.serverURL,
Expand All @@ -77,10 +71,6 @@ export async function integrateClaude(
isGlobal: ctx.isGlobal,
});

await runMigrations(ctx.project.rootDir, globalDir, sqaaEnabled, config.projectKey, {
skipSecretsHooks,
});

const contextAugmentation = options.skipContext
? null
: await resolveContextAugmentationSetup({
Expand Down Expand Up @@ -127,9 +117,6 @@ export async function integrateClaude(
installError = error instanceof Error ? error : new Error(String(error));
}
await removeObsoleteHookArtifacts(ctx.project.rootDir, OBSOLETE_A3S_MARKER);
await updateStateAfterConfiguration(config, ctx.project.rootDir, ctx.isGlobal, sqaaEnabled, {
skipSecretsHooks,
});
if (installError) {
throw installError;
}
Expand Down
96 changes: 0 additions & 96 deletions src/cli/commands/integrate/claude/state.ts

This file was deleted.

Loading