opencode-sonarqube 2.0.3 → 2.1.0

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.
Files changed (2) hide show
  1. package/dist/index.js +146 -21
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,5 +1,20 @@
1
1
  import { createRequire } from "node:module";
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
2
4
  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
+ };
3
18
  var __export = (target, all) => {
4
19
  for (var name in all)
5
20
  __defProp(target, name, {
@@ -17808,6 +17823,36 @@ class IssuesAPI extends BaseAPI {
17808
17823
  const hotspots = await this.getSecurityHotspots(projectKey);
17809
17824
  return hotspots.filter((h) => h.status === "TO_REVIEW");
17810
17825
  }
17826
+ async reviewHotspot(hotspotKey, status, resolution, comment) {
17827
+ this.logger.info(`Reviewing hotspot ${hotspotKey}: status=${status}, resolution=${resolution}`);
17828
+ const body = {
17829
+ hotspot: hotspotKey,
17830
+ status
17831
+ };
17832
+ if (resolution && status === "REVIEWED") {
17833
+ body.resolution = resolution;
17834
+ }
17835
+ if (comment) {
17836
+ body.comment = comment;
17837
+ }
17838
+ await this.client.post("/api/hotspots/change_status", body);
17839
+ this.logger.info(`Hotspot ${hotspotKey} reviewed successfully`);
17840
+ }
17841
+ async bulkReviewHotspots(hotspotKeys, resolution, comment) {
17842
+ let success2 = 0;
17843
+ let failed = 0;
17844
+ const errors4 = [];
17845
+ for (const key of hotspotKeys) {
17846
+ try {
17847
+ await this.reviewHotspot(key, "REVIEWED", resolution, comment);
17848
+ success2++;
17849
+ } catch (error45) {
17850
+ failed++;
17851
+ errors4.push(`${key}: ${error45 instanceof Error ? error45.message : String(error45)}`);
17852
+ }
17853
+ }
17854
+ return { success: success2, failed, errors: errors4 };
17855
+ }
17811
17856
  }
17812
17857
  // src/api/quality-gate/api.ts
17813
17858
  init_types2();
@@ -20338,38 +20383,103 @@ async function handleHotspots(ctx) {
20338
20383
  output += `### Hotspots Requiring Review
20339
20384
 
20340
20385
  `;
20341
- output += `| Risk | File | Line | Issue |
20386
+ output += `| # | Risk | Key | File | Line | Issue |
20342
20387
  `;
20343
- output += `|------|------|------|-------|
20388
+ output += `|---|------|-----|------|------|-------|
20344
20389
  `;
20345
- for (const hotspot of toReview.slice(0, 20)) {
20390
+ for (let i = 0;i < Math.min(toReview.length, 50); i++) {
20391
+ const hotspot = toReview[i];
20346
20392
  const risk = hotspot.vulnerabilityProbability;
20347
20393
  const file2 = hotspot.component.split(":").pop() ?? hotspot.component;
20348
20394
  const line = hotspot.line ?? "?";
20349
- const msg = hotspot.message.length > 60 ? hotspot.message.substring(0, 57) + "..." : hotspot.message;
20350
- output += `| ${risk} | ${file2} | ${line} | ${msg} |
20395
+ const msg = hotspot.message.length > 50 ? hotspot.message.substring(0, 47) + "..." : hotspot.message;
20396
+ output += `| ${i + 1} | ${risk} | \`${hotspot.key.substring(0, 8)}\` | ${file2} | ${line} | ${msg} |
20351
20397
  `;
20352
20398
  }
20353
- if (toReview.length > 20) {
20399
+ if (toReview.length > 50) {
20354
20400
  output += `
20355
- *... and ${toReview.length - 20} more hotspots*
20401
+ *... and ${toReview.length - 50} more hotspots*
20356
20402
  `;
20357
20403
  }
20358
20404
  output += `
20359
- ### Review Guidelines
20405
+ ### How to Review Hotspots
20406
+
20407
+ You can review hotspots directly using this tool:
20408
+
20409
+ **Review a single hotspot:**
20410
+ \`\`\`
20411
+ sonarqube({ action: "reviewhotspot", hotspotKey: "<key>", resolution: "SAFE", comment: "Reviewed: no security risk because..." })
20412
+ \`\`\`
20360
20413
 
20361
- 1. **HIGH risk**: Review immediately - potential security vulnerability
20362
- 2. **MEDIUM risk**: Review soon - possible security concern
20363
- 3. **LOW risk**: Review when convenient - minor security consideration
20414
+ **Review ALL pending hotspots as SAFE:**
20415
+ \`\`\`
20416
+ sonarqube({ action: "reviewhotspot", resolution: "SAFE", comment: "Bulk review: all hotspots verified safe" })
20417
+ \`\`\`
20364
20418
 
20365
- For each hotspot, determine if it's:
20366
- - **Safe**: The code is secure despite the warning
20367
- - **Fixed**: The vulnerability has been remediated
20368
- - **At Risk**: Requires code changes to fix
20419
+ **Resolutions:**
20420
+ - \`SAFE\` The code is secure despite the warning (most common)
20421
+ - \`FIXED\` The vulnerability has been remediated
20422
+ - \`ACKNOWLEDGED\` Known risk, accepted
20423
+
20424
+ ### Review Guidelines
20425
+
20426
+ 1. **HIGH risk**: Review the code carefully — potential security vulnerability
20427
+ 2. **MEDIUM risk**: Check regex patterns, randomness, protocol usage
20428
+ 3. **LOW risk**: Usually informational — hardcoded IPs, HTTP URLs in non-production code
20369
20429
  `;
20370
20430
  }
20371
20431
  return output;
20372
20432
  }
20433
+ async function handleReviewHotspot(ctx, hotspotKey, resolution, comment) {
20434
+ const { api: api2, projectKey } = ctx;
20435
+ const validResolutions = ["SAFE", "FIXED", "ACKNOWLEDGED"];
20436
+ const res = resolution?.toUpperCase() ?? "SAFE";
20437
+ if (!validResolutions.includes(res)) {
20438
+ return `**Error:** Invalid resolution "${resolution}". Must be one of: SAFE, FIXED, ACKNOWLEDGED`;
20439
+ }
20440
+ if (!hotspotKey) {
20441
+ const toReview = await api2.issues.getSecurityHotspotsToReview(projectKey);
20442
+ if (toReview.length === 0) {
20443
+ return formatSuccess("Review Hotspots", "No pending hotspots to review. All hotspots have already been reviewed.");
20444
+ }
20445
+ const result = await api2.issues.bulkReviewHotspots(toReview.map((h) => h.key), res, comment ?? `Bulk reviewed as ${res} via opencode-sonarqube plugin`);
20446
+ let output = `## Hotspot Bulk Review Complete
20447
+
20448
+ **Project:** \`${projectKey}\`
20449
+ **Resolution:** ${res}
20450
+ **Reviewed:** ${result.success} hotspots
20451
+ **Failed:** ${result.failed} hotspots
20452
+ `;
20453
+ if (result.errors.length > 0) {
20454
+ output += `
20455
+ ### Errors
20456
+ `;
20457
+ for (const err of result.errors.slice(0, 10)) {
20458
+ output += `- ${err}
20459
+ `;
20460
+ }
20461
+ }
20462
+ output += `
20463
+ > Run \`sonarqube({ action: "status" })\` to check the updated Quality Gate.`;
20464
+ return output;
20465
+ }
20466
+ try {
20467
+ await api2.issues.reviewHotspot(hotspotKey, "REVIEWED", res, comment ?? `Reviewed as ${res} via opencode-sonarqube plugin`);
20468
+ return `## Hotspot Reviewed
20469
+
20470
+ **Hotspot:** \`${hotspotKey}\`
20471
+ **Status:** REVIEWED
20472
+ **Resolution:** ${res}
20473
+ ${comment ? `**Comment:** ${comment}` : ""}
20474
+
20475
+ > Run \`sonarqube({ action: "hotspots" })\` to see remaining hotspots.`;
20476
+ } catch (error45) {
20477
+ const msg = error45 instanceof Error ? error45.message : String(error45);
20478
+ return `**Error reviewing hotspot:** ${msg}
20479
+
20480
+ Make sure the hotspot key is correct. Use \`sonarqube({ action: "hotspots" })\` to list hotspot keys.`;
20481
+ }
20482
+ }
20373
20483
  // src/tools/handlers/duplications.ts
20374
20484
  async function handleDuplications(ctx) {
20375
20485
  const { api: api2, projectKey } = ctx;
@@ -20496,14 +20606,17 @@ Run \`sonarqube({ action: "analyze" })\` to generate metrics.`));
20496
20606
  // src/tools/sonarqube.ts
20497
20607
  var logger10 = new Logger("sonarqube-tool");
20498
20608
  var SonarQubeToolArgsSchema = exports_external2.object({
20499
- action: exports_external2.enum(["analyze", "issues", "newissues", "status", "init", "setup", "validate", "hotspots", "duplications", "rule", "history", "profile", "branches", "metrics", "worstfiles"]).describe("Action to perform: analyze (run scanner), issues (all issues), newissues (only new code issues), status (quality gate), init/setup (initialize), validate (enterprise check), hotspots (security review), duplications (code duplicates), rule (explain rule), history (past analyses), profile (quality profile), branches (branch status), metrics (detailed metrics), worstfiles (files with most issues)"),
20609
+ action: exports_external2.enum(["analyze", "issues", "newissues", "status", "init", "setup", "validate", "hotspots", "reviewhotspot", "duplications", "rule", "history", "profile", "branches", "metrics", "worstfiles"]).describe("Action to perform: analyze (run scanner), issues (all issues), newissues (only new code issues), status (quality gate), init/setup (initialize), validate (enterprise check), hotspots (security review), duplications (code duplicates), rule (explain rule), history (past analyses), profile (quality profile), branches (branch status), metrics (detailed metrics), worstfiles (files with most issues)"),
20500
20610
  scope: exports_external2.enum(["all", "new", "changed"]).optional().default("all").describe("Scope of analysis: all files, only new code, or changed files"),
20501
20611
  severity: exports_external2.enum(["blocker", "critical", "major", "minor", "info", "all"]).optional().default("all").describe("Filter issues by minimum severity level"),
20502
20612
  fix: exports_external2.boolean().optional().default(false).describe("If true, include fix suggestions in the response"),
20503
20613
  projectKey: exports_external2.string().optional().describe("Override the project key (usually auto-detected)"),
20504
20614
  force: exports_external2.boolean().optional().default(false).describe("Force re-initialization even if already set up"),
20505
20615
  ruleKey: exports_external2.string().optional().describe("Rule key to explain (e.g., 'typescript:S1234') - required for 'rule' action"),
20506
- branch: exports_external2.string().optional().describe("Branch name for multi-branch analysis (default: main branch)")
20616
+ branch: exports_external2.string().optional().describe("Branch name for multi-branch analysis (default: main branch)"),
20617
+ hotspotKey: exports_external2.string().optional().describe("Hotspot key to review (UUID). If omitted with 'reviewhotspot' action, all TO_REVIEW hotspots are bulk-reviewed."),
20618
+ resolution: exports_external2.enum(["SAFE", "FIXED", "ACKNOWLEDGED"]).optional().describe("Hotspot review resolution: SAFE (no risk), FIXED (remediated), ACKNOWLEDGED (accepted risk)"),
20619
+ comment: exports_external2.string().optional().describe("Review comment explaining the decision")
20507
20620
  });
20508
20621
  async function executeSonarQubeTool(args, context) {
20509
20622
  const directory = context.directory ?? process.cwd();
@@ -20556,6 +20669,8 @@ ${setupResult.message}`);
20556
20669
  return await handleValidate(ctx);
20557
20670
  case "hotspots":
20558
20671
  return await handleHotspots(ctx);
20672
+ case "reviewhotspot":
20673
+ return await handleReviewHotspot(ctx, args.hotspotKey, args.resolution, args.comment);
20559
20674
  case "duplications":
20560
20675
  return await handleDuplications(ctx);
20561
20676
  case "rule":
@@ -21263,6 +21378,7 @@ Actions:
21263
21378
  - status: Get quality gate status and metrics
21264
21379
  - validate: Check if project meets enterprise quality standards
21265
21380
  - hotspots: Get security hotspots that need review
21381
+ - reviewhotspot: Review/resolve security hotspots (mark as SAFE, FIXED, or ACKNOWLEDGED)
21266
21382
  - duplications: Find code duplications across the project
21267
21383
  - rule: Explain a specific SonarQube rule
21268
21384
  - history: Show past analysis history
@@ -21276,16 +21392,22 @@ Example usage:
21276
21392
  - sonarqube({ action: "newissues" }) - Issues only in your recent changes
21277
21393
  - sonarqube({ action: "worstfiles" }) - Files needing most attention
21278
21394
  - sonarqube({ action: "issues", severity: "critical" }) - Get critical+ issues
21279
- - sonarqube({ action: "status" }) - Check quality gate status`,
21395
+ - sonarqube({ action: "status" }) - Check quality gate status
21396
+ - sonarqube({ action: "hotspots" }) - List security hotspots
21397
+ - sonarqube({ action: "reviewhotspot", resolution: "SAFE" }) - Bulk-review all hotspots as SAFE
21398
+ - sonarqube({ action: "reviewhotspot", hotspotKey: "abc123", resolution: "SAFE", comment: "No risk" }) - Review single hotspot`,
21280
21399
  args: {
21281
- action: tool.schema.enum(["analyze", "issues", "newissues", "status", "init", "setup", "validate", "hotspots", "duplications", "rule", "history", "profile", "branches", "metrics", "worstfiles"]).describe("Action to perform"),
21400
+ action: tool.schema.enum(["analyze", "issues", "newissues", "status", "init", "setup", "validate", "hotspots", "reviewhotspot", "duplications", "rule", "history", "profile", "branches", "metrics", "worstfiles"]).describe("Action to perform"),
21282
21401
  scope: tool.schema.enum(["all", "new", "changed"]).optional().describe("Scope of analysis"),
21283
21402
  severity: tool.schema.enum(["blocker", "critical", "major", "minor", "info", "all"]).optional().describe("Filter issues by minimum severity"),
21284
21403
  fix: tool.schema.boolean().optional().describe("Include fix suggestions in response"),
21285
21404
  projectKey: tool.schema.string().optional().describe("Override project key"),
21286
21405
  force: tool.schema.boolean().optional().describe("Force re-initialization"),
21287
21406
  ruleKey: tool.schema.string().optional().describe("Rule key to explain (e.g., 'typescript:S1234')"),
21288
- branch: tool.schema.string().optional().describe("Branch name for multi-branch projects")
21407
+ branch: tool.schema.string().optional().describe("Branch name for multi-branch projects"),
21408
+ hotspotKey: tool.schema.string().optional().describe("Hotspot key (UUID) to review. If omitted with reviewhotspot action, all pending hotspots are bulk-reviewed."),
21409
+ resolution: tool.schema.string().optional().describe("Hotspot review resolution: SAFE, FIXED, or ACKNOWLEDGED"),
21410
+ comment: tool.schema.string().optional().describe("Review comment explaining the decision")
21289
21411
  },
21290
21412
  async execute(args, _ctx) {
21291
21413
  const result = await executeSonarQubeTool({
@@ -21296,7 +21418,10 @@ Example usage:
21296
21418
  projectKey: args.projectKey,
21297
21419
  force: args.force ?? false,
21298
21420
  ruleKey: args.ruleKey,
21299
- branch: args.branch
21421
+ branch: args.branch,
21422
+ hotspotKey: args.hotspotKey,
21423
+ resolution: args.resolution,
21424
+ comment: args.comment
21300
21425
  }, {
21301
21426
  directory: getDirectory(),
21302
21427
  config: pluginConfig
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-sonarqube",
3
- "version": "2.0.3",
3
+ "version": "2.1.0",
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",