opencode-sonarqube 2.0.2 → 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 +152 -26
  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":
@@ -20980,10 +21095,11 @@ ${blockerNote}Use \`sonarqube({ action: "issues" })\` to see details or \`sonarq
20980
21095
  return;
20981
21096
  }
20982
21097
  const payload = event.properties;
20983
- if (payload?.id) {
20984
- currentSessionId = payload.id;
21098
+ const sessionId = payload?.info?.id;
21099
+ if (sessionId) {
21100
+ currentSessionId = sessionId;
20985
21101
  if (event.type === "session.created") {
20986
- mapSessionToDirectory(payload.id, effectiveDirectory);
21102
+ mapSessionToDirectory(sessionId, effectiveDirectory);
20987
21103
  }
20988
21104
  }
20989
21105
  };
@@ -20992,8 +21108,8 @@ ${blockerNote}Use \`sonarqube({ action: "issues" })\` to see details or \`sonarq
20992
21108
  return;
20993
21109
  }
20994
21110
  const payload = event.properties;
20995
- if (payload?.path && !shouldIgnoreFile2(payload.path)) {
20996
- hooks.fileEdited({ filePath: payload.path });
21111
+ if (payload?.file && !shouldIgnoreFile2(payload.file)) {
21112
+ hooks.fileEdited({ filePath: payload.file });
20997
21113
  }
20998
21114
  };
20999
21115
  const injectAnalysisResults = async (message, _config, sessionId) => {
@@ -21262,6 +21378,7 @@ Actions:
21262
21378
  - status: Get quality gate status and metrics
21263
21379
  - validate: Check if project meets enterprise quality standards
21264
21380
  - hotspots: Get security hotspots that need review
21381
+ - reviewhotspot: Review/resolve security hotspots (mark as SAFE, FIXED, or ACKNOWLEDGED)
21265
21382
  - duplications: Find code duplications across the project
21266
21383
  - rule: Explain a specific SonarQube rule
21267
21384
  - history: Show past analysis history
@@ -21275,16 +21392,22 @@ Example usage:
21275
21392
  - sonarqube({ action: "newissues" }) - Issues only in your recent changes
21276
21393
  - sonarqube({ action: "worstfiles" }) - Files needing most attention
21277
21394
  - sonarqube({ action: "issues", severity: "critical" }) - Get critical+ issues
21278
- - 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`,
21279
21399
  args: {
21280
- 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"),
21281
21401
  scope: tool.schema.enum(["all", "new", "changed"]).optional().describe("Scope of analysis"),
21282
21402
  severity: tool.schema.enum(["blocker", "critical", "major", "minor", "info", "all"]).optional().describe("Filter issues by minimum severity"),
21283
21403
  fix: tool.schema.boolean().optional().describe("Include fix suggestions in response"),
21284
21404
  projectKey: tool.schema.string().optional().describe("Override project key"),
21285
21405
  force: tool.schema.boolean().optional().describe("Force re-initialization"),
21286
21406
  ruleKey: tool.schema.string().optional().describe("Rule key to explain (e.g., 'typescript:S1234')"),
21287
- 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")
21288
21411
  },
21289
21412
  async execute(args, _ctx) {
21290
21413
  const result = await executeSonarQubeTool({
@@ -21295,7 +21418,10 @@ Example usage:
21295
21418
  projectKey: args.projectKey,
21296
21419
  force: args.force ?? false,
21297
21420
  ruleKey: args.ruleKey,
21298
- branch: args.branch
21421
+ branch: args.branch,
21422
+ hotspotKey: args.hotspotKey,
21423
+ resolution: args.resolution,
21424
+ comment: args.comment
21299
21425
  }, {
21300
21426
  directory: getDirectory(),
21301
21427
  config: pluginConfig
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-sonarqube",
3
- "version": "2.0.2",
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",