opencode-sonarqube 2.1.0 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -7
- package/dist/index.js +123 -83
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,16 +4,17 @@ OpenCode Plugin for SonarQube integration - Enterprise-level code quality from t
|
|
|
4
4
|
|
|
5
5
|
[](https://sonarqube.example.com)
|
|
6
6
|
[](https://sonarqube.example.com)
|
|
7
|
-
[](https://sonarqube.example.com)
|
|
8
8
|
[](./LICENSE)
|
|
9
|
+
[](https://www.npmjs.com/package/opencode-sonarqube)
|
|
9
10
|
|
|
10
11
|
## Features
|
|
11
12
|
|
|
12
13
|
- **Automatic Analysis**: Triggers SonarQube analysis when the AI agent becomes idle
|
|
13
|
-
- **
|
|
14
|
+
- **16 Tool Actions**: Comprehensive SonarQube integration for AI agents
|
|
14
15
|
- **Clean as You Code**: Focus on new code issues with `newissues` action
|
|
15
16
|
- **Custom Command**: Use `/sonarqube` command for quick analysis
|
|
16
|
-
- **Security Hotspots**: Review and
|
|
17
|
+
- **Security Hotspots**: Review, resolve, and bulk-dismiss security hotspots directly via API
|
|
17
18
|
- **Quality Gate Integration**: Shows pass/fail status with detailed metrics
|
|
18
19
|
- **Git Integration**: Detects git operations and suggests quality checks
|
|
19
20
|
- **System Prompt Injection**: AI always knows current quality status
|
|
@@ -100,6 +101,44 @@ Add these to your `~/.zshrc` or `~/.bashrc` to make them permanent.
|
|
|
100
101
|
- Tell the AI: "Setup SonarQube for this project"
|
|
101
102
|
- Or use: `sonarqube({ action: "setup" })`
|
|
102
103
|
|
|
104
|
+
## Updating
|
|
105
|
+
|
|
106
|
+
The plugin is installed per project via `package.json`. To update to the latest version:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# Update in the current project
|
|
110
|
+
bun add opencode-sonarqube@latest
|
|
111
|
+
|
|
112
|
+
# Or with npm
|
|
113
|
+
npm install opencode-sonarqube@latest
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Then restart OpenCode to load the new version.
|
|
117
|
+
|
|
118
|
+
**Tip — Always get the latest version automatically:**
|
|
119
|
+
|
|
120
|
+
By default, `bun add` pins a specific version (e.g., `"^2.1.0"`). To always pull the newest release on every OpenCode restart, change your `package.json` dependency to:
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"dependencies": {
|
|
125
|
+
"opencode-sonarqube": "latest"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
This ensures you always run the most recent version without manual updates.
|
|
131
|
+
|
|
132
|
+
**Update all projects at once:**
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
# Run this from any directory to update all projects that use the plugin
|
|
136
|
+
for dir in $(grep -rl '"opencode-sonarqube"' ~/Projekte/*/package.json 2>/dev/null | xargs -I{} dirname {}); do
|
|
137
|
+
echo "Updating: $dir"
|
|
138
|
+
(cd "$dir" && bun add opencode-sonarqube@latest)
|
|
139
|
+
done
|
|
140
|
+
```
|
|
141
|
+
|
|
103
142
|
## Configuration
|
|
104
143
|
|
|
105
144
|
### Environment Variables (Required)
|
|
@@ -192,6 +231,7 @@ The plugin adds a `sonarqube` tool with these actions:
|
|
|
192
231
|
| `newissues` | Get only issues in NEW code (Clean as You Code) |
|
|
193
232
|
| `worstfiles` | Show files with most issues (prioritize refactoring) |
|
|
194
233
|
| `hotspots` | Get security hotspots that need manual review |
|
|
234
|
+
| `reviewhotspot` | Review/resolve hotspots as SAFE, FIXED, or ACKNOWLEDGED |
|
|
195
235
|
| `duplications` | Find code duplications across the project |
|
|
196
236
|
|
|
197
237
|
### Status & Validation
|
|
@@ -216,15 +256,18 @@ The plugin adds a `sonarqube` tool with these actions:
|
|
|
216
256
|
```typescript
|
|
217
257
|
sonarqube({
|
|
218
258
|
action: "analyze" | "issues" | "newissues" | "worstfiles" | "status" |
|
|
219
|
-
"validate" | "hotspots" | "
|
|
220
|
-
"profile" | "branches" | "metrics" | "setup",
|
|
259
|
+
"validate" | "hotspots" | "reviewhotspot" | "duplications" | "rule" |
|
|
260
|
+
"history" | "profile" | "branches" | "metrics" | "setup",
|
|
221
261
|
scope: "all" | "new" | "changed", // What to analyze
|
|
222
262
|
severity: "blocker" | "critical" | "major" | "minor" | "info" | "all",
|
|
223
263
|
fix: true | false, // Include fix suggestions
|
|
224
264
|
projectKey: "override-key", // Optional override
|
|
225
265
|
force: true | false, // Force re-initialization
|
|
226
266
|
ruleKey: "typescript:S1234", // For "rule" action
|
|
227
|
-
branch: "feature-branch"
|
|
267
|
+
branch: "feature-branch", // For multi-branch analysis
|
|
268
|
+
hotspotKey: "uuid-of-hotspot", // For "reviewhotspot" action (omit for bulk review)
|
|
269
|
+
resolution: "SAFE" | "FIXED" | "ACKNOWLEDGED", // Hotspot review resolution
|
|
270
|
+
comment: "Reason for the decision" // Optional review comment
|
|
228
271
|
})
|
|
229
272
|
```
|
|
230
273
|
|
|
@@ -316,6 +359,15 @@ sonarqube({ action: "rule", ruleKey: "typescript:S3776" })
|
|
|
316
359
|
|
|
317
360
|
// Enterprise validation before release
|
|
318
361
|
sonarqube({ action: "validate" })
|
|
362
|
+
|
|
363
|
+
// List security hotspots
|
|
364
|
+
sonarqube({ action: "hotspots" })
|
|
365
|
+
|
|
366
|
+
// Review a single hotspot as safe
|
|
367
|
+
sonarqube({ action: "reviewhotspot", hotspotKey: "abc-123", resolution: "SAFE", comment: "Form field name, not a password" })
|
|
368
|
+
|
|
369
|
+
// Bulk-review ALL pending hotspots as safe
|
|
370
|
+
sonarqube({ action: "reviewhotspot", resolution: "SAFE" })
|
|
319
371
|
```
|
|
320
372
|
|
|
321
373
|
## CLI Usage
|
|
@@ -477,7 +529,7 @@ This project maintains enterprise-level quality:
|
|
|
477
529
|
| Metric | Value |
|
|
478
530
|
|--------|-------|
|
|
479
531
|
| Test Coverage | 96% |
|
|
480
|
-
| Tests |
|
|
532
|
+
| Tests | 722 |
|
|
481
533
|
| Bugs | 0 |
|
|
482
534
|
| Vulnerabilities | 0 |
|
|
483
535
|
| Code Smells | 0 |
|
package/dist/index.js
CHANGED
|
@@ -1,20 +1,5 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
4
2
|
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
-
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
-
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
-
for (let key of __getOwnPropNames(mod))
|
|
11
|
-
if (!__hasOwnProp.call(to, key))
|
|
12
|
-
__defProp(to, key, {
|
|
13
|
-
get: () => mod[key],
|
|
14
|
-
enumerable: true
|
|
15
|
-
});
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
3
|
var __export = (target, all) => {
|
|
19
4
|
for (var name in all)
|
|
20
5
|
__defProp(target, name, {
|
|
@@ -19328,6 +19313,10 @@ class SonarQubeAPI {
|
|
|
19328
19313
|
};
|
|
19329
19314
|
}
|
|
19330
19315
|
}
|
|
19316
|
+
function createSonarQubeAPIWithCredentials(url2, user, password, logger4) {
|
|
19317
|
+
const client = createClientWithCredentials(url2, user, password, logger4?.child("client"));
|
|
19318
|
+
return new SonarQubeAPI(client, logger4);
|
|
19319
|
+
}
|
|
19331
19320
|
function createSonarQubeAPIWithToken(url2, token, logger4) {
|
|
19332
19321
|
const client = createClientWithToken(url2, token, logger4?.child("client"));
|
|
19333
19322
|
return new SonarQubeAPI(client, logger4);
|
|
@@ -19394,6 +19383,20 @@ function groupIssuesBySeverity(issues) {
|
|
|
19394
19383
|
}
|
|
19395
19384
|
function formatIssuesForAgent(issues, qualityGateStatus) {
|
|
19396
19385
|
if (issues.length === 0) {
|
|
19386
|
+
if (qualityGateStatus !== "OK") {
|
|
19387
|
+
return `## SonarQube Analysis Results
|
|
19388
|
+
|
|
19389
|
+
**Quality Gate: ${qualityGateStatus}**
|
|
19390
|
+
|
|
19391
|
+
No code issues found, but the Quality Gate is not passing.
|
|
19392
|
+
This is typically caused by **unreviewed security hotspots** or **code duplication thresholds**.
|
|
19393
|
+
|
|
19394
|
+
**To fix:**
|
|
19395
|
+
- \`sonarqube({ action: "hotspots" })\` — List security hotspots
|
|
19396
|
+
- \`sonarqube({ action: "reviewhotspot", resolution: "SAFE" })\` — Bulk-review all hotspots as safe
|
|
19397
|
+
- \`sonarqube({ action: "status" })\` — See which conditions are failing
|
|
19398
|
+
- \`sonarqube({ action: "duplications" })\` — Check code duplication`;
|
|
19399
|
+
}
|
|
19397
19400
|
return `## SonarQube Analysis Results
|
|
19398
19401
|
|
|
19399
19402
|
**Quality Gate: ${qualityGateStatus}**
|
|
@@ -20333,8 +20336,20 @@ async function handleStatus(ctx) {
|
|
|
20333
20336
|
output += `
|
|
20334
20337
|
### Failed Conditions
|
|
20335
20338
|
`;
|
|
20339
|
+
const hasHotspotCondition = failedConditions.some((c) => c.metricKey.includes("security_hotspots") || c.metricKey.includes("hotspot"));
|
|
20336
20340
|
for (const condition of failedConditions) {
|
|
20337
20341
|
output += `- **${condition.metricKey}**: ${condition.actualValue} (threshold: ${condition.errorThreshold})
|
|
20342
|
+
`;
|
|
20343
|
+
}
|
|
20344
|
+
if (hasHotspotCondition) {
|
|
20345
|
+
output += `
|
|
20346
|
+
### How to Fix Security Hotspot Condition
|
|
20347
|
+
The Quality Gate is failing because unreviewed security hotspots exist.
|
|
20348
|
+
You can review them directly:
|
|
20349
|
+
|
|
20350
|
+
1. **List hotspots:** \`sonarqube({ action: "hotspots" })\`
|
|
20351
|
+
2. **Bulk-review all as safe:** \`sonarqube({ action: "reviewhotspot", resolution: "SAFE" })\`
|
|
20352
|
+
3. **Review individually:** \`sonarqube({ action: "reviewhotspot", hotspotKey: "<key>", resolution: "SAFE", comment: "reason" })\`
|
|
20338
20353
|
`;
|
|
20339
20354
|
}
|
|
20340
20355
|
}
|
|
@@ -20363,6 +20378,9 @@ Please fix the failed checks before proceeding.`;
|
|
|
20363
20378
|
return output;
|
|
20364
20379
|
}
|
|
20365
20380
|
// src/tools/handlers/security.ts
|
|
20381
|
+
function createAdminAPI(ctx) {
|
|
20382
|
+
return createSonarQubeAPIWithCredentials(ctx.config.url, ctx.config.user, ctx.config.password);
|
|
20383
|
+
}
|
|
20366
20384
|
async function handleHotspots(ctx) {
|
|
20367
20385
|
const { api: api2, projectKey } = ctx;
|
|
20368
20386
|
const hotspots = await api2.issues.getSecurityHotspots(projectKey);
|
|
@@ -20437,12 +20455,13 @@ async function handleReviewHotspot(ctx, hotspotKey, resolution, comment) {
|
|
|
20437
20455
|
if (!validResolutions.includes(res)) {
|
|
20438
20456
|
return `**Error:** Invalid resolution "${resolution}". Must be one of: SAFE, FIXED, ACKNOWLEDGED`;
|
|
20439
20457
|
}
|
|
20458
|
+
const adminApi = createAdminAPI(ctx);
|
|
20440
20459
|
if (!hotspotKey) {
|
|
20441
20460
|
const toReview = await api2.issues.getSecurityHotspotsToReview(projectKey);
|
|
20442
20461
|
if (toReview.length === 0) {
|
|
20443
20462
|
return formatSuccess("Review Hotspots", "No pending hotspots to review. All hotspots have already been reviewed.");
|
|
20444
20463
|
}
|
|
20445
|
-
const result = await
|
|
20464
|
+
const result = await adminApi.issues.bulkReviewHotspots(toReview.map((h) => h.key), res, comment ?? `Bulk reviewed as ${res} via opencode-sonarqube plugin`);
|
|
20446
20465
|
let output = `## Hotspot Bulk Review Complete
|
|
20447
20466
|
|
|
20448
20467
|
**Project:** \`${projectKey}\`
|
|
@@ -20464,7 +20483,7 @@ async function handleReviewHotspot(ctx, hotspotKey, resolution, comment) {
|
|
|
20464
20483
|
return output;
|
|
20465
20484
|
}
|
|
20466
20485
|
try {
|
|
20467
|
-
await
|
|
20486
|
+
await adminApi.issues.reviewHotspot(hotspotKey, "REVIEWED", res, comment ?? `Reviewed as ${res} via opencode-sonarqube plugin`);
|
|
20468
20487
|
return `## Hotspot Reviewed
|
|
20469
20488
|
|
|
20470
20489
|
**Hotspot:** \`${hotspotKey}\`
|
|
@@ -21263,6 +21282,90 @@ Git operation completed with changes. Consider running:
|
|
|
21263
21282
|
}
|
|
21264
21283
|
};
|
|
21265
21284
|
};
|
|
21285
|
+
const buildSystemTransformContext = async (_input) => {
|
|
21286
|
+
safeLog(`=== system.transform ENTERED ===`);
|
|
21287
|
+
const inputAny = _input;
|
|
21288
|
+
const sessionID = inputAny?.sessionID;
|
|
21289
|
+
const sessionDir = sessionID ? getDirectoryForSession(sessionID) : undefined;
|
|
21290
|
+
const dir = sessionDir || effectiveDirectory;
|
|
21291
|
+
safeLog(` dir used: "${dir}"`);
|
|
21292
|
+
const now = Date.now();
|
|
21293
|
+
const cachedEntry = transformCacheMap.get(dir);
|
|
21294
|
+
if (cachedEntry && now - cachedEntry.timestamp < TRANSFORM_CACHE_TTL_MS) {
|
|
21295
|
+
return cachedEntry.data;
|
|
21296
|
+
}
|
|
21297
|
+
await loadPluginConfig();
|
|
21298
|
+
const sonarConfig = pluginConfig?.["sonarqube"];
|
|
21299
|
+
const config3 = loadConfig(sonarConfig);
|
|
21300
|
+
if (!config3 || config3.level === "off") {
|
|
21301
|
+
transformCacheMap.set(dir, { data: undefined, timestamp: now });
|
|
21302
|
+
return;
|
|
21303
|
+
}
|
|
21304
|
+
const state = await getProjectState(dir);
|
|
21305
|
+
if (!state?.projectKey) {
|
|
21306
|
+
transformCacheMap.set(dir, { data: undefined, timestamp: now });
|
|
21307
|
+
return;
|
|
21308
|
+
}
|
|
21309
|
+
const api2 = createSonarQubeAPI(config3, state);
|
|
21310
|
+
const [qgStatus, counts, newCodeResponse] = await Promise.all([
|
|
21311
|
+
api2.qualityGate.getStatus(state.projectKey),
|
|
21312
|
+
api2.issues.getCounts(state.projectKey),
|
|
21313
|
+
api2.issues.search({
|
|
21314
|
+
projectKey: state.projectKey,
|
|
21315
|
+
inNewCode: true,
|
|
21316
|
+
resolved: false,
|
|
21317
|
+
pageSize: 1
|
|
21318
|
+
}).catch(() => ({ paging: { total: 0 } }))
|
|
21319
|
+
]);
|
|
21320
|
+
const qgFailed = qgStatus.projectStatus.status !== "OK";
|
|
21321
|
+
const newCodeIssues = newCodeResponse.paging.total;
|
|
21322
|
+
const hasIssues = counts.blocker > 0 || counts.critical > 0;
|
|
21323
|
+
if (!hasIssues && !qgFailed && newCodeIssues === 0) {
|
|
21324
|
+
transformCacheMap.set(dir, { data: undefined, timestamp: now });
|
|
21325
|
+
return;
|
|
21326
|
+
}
|
|
21327
|
+
const hotspotInfo = await buildHotspotInfo(api2, state.projectKey);
|
|
21328
|
+
const systemContext = formatSystemContext(qgStatus.projectStatus.status, qgFailed, counts, newCodeIssues, hotspotInfo, config3.level);
|
|
21329
|
+
transformCacheMap.set(dir, { data: systemContext, timestamp: now });
|
|
21330
|
+
return systemContext;
|
|
21331
|
+
};
|
|
21332
|
+
const buildHotspotInfo = async (api2, projectKey) => {
|
|
21333
|
+
try {
|
|
21334
|
+
const hotspotsToReview = await api2.issues.getSecurityHotspotsToReview(projectKey);
|
|
21335
|
+
if (hotspotsToReview.length > 0) {
|
|
21336
|
+
return `**Security Hotspots:** ${hotspotsToReview.length} unreviewed hotspots (may cause Quality Gate to fail)
|
|
21337
|
+
**To fix:** \`sonarqube({ action: "hotspots" })\` to list, then \`sonarqube({ action: "reviewhotspot", resolution: "SAFE" })\` to bulk-review
|
|
21338
|
+
`;
|
|
21339
|
+
}
|
|
21340
|
+
} catch {}
|
|
21341
|
+
return "";
|
|
21342
|
+
};
|
|
21343
|
+
const formatSystemContext = (qgStatusText, qgFailed, counts, newCodeIssues, hotspotInfo, level) => {
|
|
21344
|
+
const lines = [
|
|
21345
|
+
"## SonarQube Code Quality Status",
|
|
21346
|
+
"",
|
|
21347
|
+
`**Quality Gate:** ${qgStatusText}${qgFailed ? " (FAILED)" : ""}`,
|
|
21348
|
+
`**Outstanding Issues:** ${counts.blocker} blockers, ${counts.critical} critical, ${counts.major} major`
|
|
21349
|
+
];
|
|
21350
|
+
if (newCodeIssues > 0) {
|
|
21351
|
+
lines.push(`**New Code Issues:** ${newCodeIssues} issues in recent changes`);
|
|
21352
|
+
}
|
|
21353
|
+
if (hotspotInfo) {
|
|
21354
|
+
lines.push(hotspotInfo);
|
|
21355
|
+
}
|
|
21356
|
+
if (counts.blocker > 0) {
|
|
21357
|
+
lines.push("**IMPORTANT:** There are BLOCKER issues that must be fixed before shipping code.");
|
|
21358
|
+
}
|
|
21359
|
+
if (level === "enterprise") {
|
|
21360
|
+
lines.push("This project follows enterprise-level quality standards (zero tolerance for issues).");
|
|
21361
|
+
}
|
|
21362
|
+
lines.push("", "**Recommended Actions:**", '- `sonarqube({ action: "newissues" })` - See issues in your recent changes (Clean as You Code)', '- `sonarqube({ action: "worstfiles" })` - Find files needing most attention', '- `sonarqube({ action: "issues" })` - See all issues');
|
|
21363
|
+
if (hotspotInfo) {
|
|
21364
|
+
lines.push('- `sonarqube({ action: "reviewhotspot", resolution: "SAFE" })` - Bulk-review all security hotspots as safe');
|
|
21365
|
+
}
|
|
21366
|
+
return lines.join(`
|
|
21367
|
+
`);
|
|
21368
|
+
};
|
|
21266
21369
|
safeLog(`=== PLUGIN INIT COMPLETE, returning hooks ===`);
|
|
21267
21370
|
safeLog(` This instance's pluginImportUrl: "${pluginImportUrl}"`);
|
|
21268
21371
|
safeLog(` This instance's effectiveDirectory: "${effectiveDirectory}"`);
|
|
@@ -21297,73 +21400,10 @@ Git operation completed with changes. Consider running:
|
|
|
21297
21400
|
}
|
|
21298
21401
|
}, "experimental.session.compacting"),
|
|
21299
21402
|
"experimental.chat.system.transform": safeAsync(async (_input, output) => {
|
|
21300
|
-
|
|
21301
|
-
|
|
21302
|
-
|
|
21303
|
-
safeLog(` sessionID: "${sessionID}"`);
|
|
21304
|
-
const sharedState = readSharedState();
|
|
21305
|
-
safeLog(` sharedState sessions: ${JSON.stringify(sharedState.sessionToDirectory)}`);
|
|
21306
|
-
safeLog(` sharedState directories: ${JSON.stringify(sharedState.registeredDirectories)}`);
|
|
21307
|
-
const sessionDir = sessionID ? getDirectoryForSession(sessionID) : undefined;
|
|
21308
|
-
safeLog(` sessionDir from shared state: "${sessionDir}"`);
|
|
21309
|
-
safeLog(` effectiveDirectory (fallback): "${effectiveDirectory}"`);
|
|
21310
|
-
const dir = sessionDir || effectiveDirectory;
|
|
21311
|
-
safeLog(` FINAL dir used: "${dir}"`);
|
|
21312
|
-
const now = Date.now();
|
|
21313
|
-
const cachedEntry = transformCacheMap.get(dir);
|
|
21314
|
-
if (cachedEntry && now - cachedEntry.timestamp < TRANSFORM_CACHE_TTL_MS) {
|
|
21315
|
-
if (cachedEntry.data) {
|
|
21316
|
-
output.system.push(cachedEntry.data);
|
|
21317
|
-
}
|
|
21318
|
-
return;
|
|
21403
|
+
const systemContext = await buildSystemTransformContext(_input);
|
|
21404
|
+
if (systemContext) {
|
|
21405
|
+
output.system.push(systemContext);
|
|
21319
21406
|
}
|
|
21320
|
-
await loadPluginConfig();
|
|
21321
|
-
const sonarConfig = pluginConfig?.["sonarqube"];
|
|
21322
|
-
const config3 = loadConfig(sonarConfig);
|
|
21323
|
-
if (!config3 || config3.level === "off") {
|
|
21324
|
-
safeLog(` config level is off or null, returning early`);
|
|
21325
|
-
transformCacheMap.set(dir, { data: undefined, timestamp: now });
|
|
21326
|
-
return;
|
|
21327
|
-
}
|
|
21328
|
-
const state = await getProjectState(dir);
|
|
21329
|
-
if (!state?.projectKey) {
|
|
21330
|
-
transformCacheMap.set(dir, { data: undefined, timestamp: now });
|
|
21331
|
-
return;
|
|
21332
|
-
}
|
|
21333
|
-
const api2 = createSonarQubeAPI(config3, state);
|
|
21334
|
-
const [qgStatus, counts, newCodeResponse] = await Promise.all([
|
|
21335
|
-
api2.qualityGate.getStatus(state.projectKey),
|
|
21336
|
-
api2.issues.getCounts(state.projectKey),
|
|
21337
|
-
api2.issues.search({
|
|
21338
|
-
projectKey: state.projectKey,
|
|
21339
|
-
inNewCode: true,
|
|
21340
|
-
resolved: false,
|
|
21341
|
-
pageSize: 1
|
|
21342
|
-
}).catch(() => ({ paging: { total: 0 } }))
|
|
21343
|
-
]);
|
|
21344
|
-
const hasIssues = counts.blocker > 0 || counts.critical > 0;
|
|
21345
|
-
const qgFailed = qgStatus.projectStatus.status !== "OK";
|
|
21346
|
-
const newCodeIssues = newCodeResponse.paging.total;
|
|
21347
|
-
if (!hasIssues && !qgFailed && newCodeIssues === 0) {
|
|
21348
|
-
transformCacheMap.set(dir, { data: undefined, timestamp: now });
|
|
21349
|
-
return;
|
|
21350
|
-
}
|
|
21351
|
-
const systemContext = `## SonarQube Code Quality Status
|
|
21352
|
-
|
|
21353
|
-
**Quality Gate:** ${qgStatus.projectStatus.status}${qgFailed ? " (FAILED)" : ""}
|
|
21354
|
-
**Outstanding Issues:** ${counts.blocker} blockers, ${counts.critical} critical, ${counts.major} major
|
|
21355
|
-
${newCodeIssues > 0 ? `**New Code Issues:** ${newCodeIssues} issues in recent changes
|
|
21356
|
-
` : ""}
|
|
21357
|
-
${counts.blocker > 0 ? `**IMPORTANT:** There are BLOCKER issues that must be fixed before shipping code.
|
|
21358
|
-
` : ""}
|
|
21359
|
-
${config3.level === "enterprise" ? `This project follows enterprise-level quality standards (zero tolerance for issues).
|
|
21360
|
-
` : ""}
|
|
21361
|
-
**Recommended Actions:**
|
|
21362
|
-
- \`sonarqube({ action: "newissues" })\` - See issues in your recent changes (Clean as You Code)
|
|
21363
|
-
- \`sonarqube({ action: "worstfiles" })\` - Find files needing most attention
|
|
21364
|
-
- \`sonarqube({ action: "issues" })\` - See all issues`;
|
|
21365
|
-
transformCacheMap.set(dir, { data: systemContext, timestamp: now });
|
|
21366
|
-
output.system.push(systemContext);
|
|
21367
21407
|
}, "experimental.chat.system.transform"),
|
|
21368
21408
|
tool: {
|
|
21369
21409
|
sonarqube: tool({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-sonarqube",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"description": "OpenCode Plugin for SonarQube integration - Enterprise-level code quality from the start",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"homepage": "https://github.com/mguttmann/opencode-sonarqube#readme",
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@opencode-ai/plugin": "^1.1.34",
|
|
41
|
-
"opencode-sonarqube": "
|
|
41
|
+
"opencode-sonarqube": "latest",
|
|
42
42
|
"zod": "^3.24.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|