secufusion-mcp 1.1.0 → 1.2.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/index.js +64 -22
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -2675,8 +2675,13 @@ server.tool("run_pre_pr_checks_with_reviewer_agent", "Unified, single-call PR re
2675
2675
  skip_checks: z.array(z.enum(["tenant_isolation", "n_plus_one", "missing_index", "kafka_sync", "early_return", "ai_review", "context_review"])).optional().default([]).describe("Checks to skip. Mechanical: tenant_isolation | n_plus_one | missing_index | kafka_sync | early_return. " +
2676
2676
  "Review tiers: ai_review (Tier 2) | context_review (Tier 3). " +
2677
2677
  "Use sparingly — only for acknowledged legacy files or CI environments where context is unavailable."),
2678
- }, async ({ work_item_id, skip_checks = [] }) => {
2679
- const inputChars = JSON.stringify({ work_item_id, skip_checks }).length;
2678
+ suppressions: z.array(z.object({
2679
+ check: z.enum(["TENANT_ISOLATION", "N_PLUS_ONE", "MISSING_INDEX", "KAFKA_SYNC", "EARLY_RETURN"]),
2680
+ method: z.string().describe("Method name to suppress, e.g. 'countOrphanedByDeviceUserId'"),
2681
+ reason: z.string().describe("Mandatory justification for suppression")
2682
+ })).optional().default([]).describe("Method-scoped suppressions with mandatory reasons.")
2683
+ }, async ({ work_item_id, skip_checks = [], suppressions = [] }) => {
2684
+ const inputChars = JSON.stringify({ work_item_id, skip_checks, suppressions }).length;
2680
2685
  const wsRoot = getWorkspaceRoot();
2681
2686
  const startTime = Date.now();
2682
2687
  // ══════════════════════════════════════════════════════════════════════
@@ -2806,10 +2811,15 @@ server.tool("run_pre_pr_checks_with_reviewer_agent", "Unified, single-call PR re
2806
2811
  const dm = line.match(/(?:find|delete|count|exists)\w+By(\w+)/);
2807
2812
  if (dm && !dm[1].toLowerCase().includes("tenant")) {
2808
2813
  let suppressionReason = "";
2809
- for (let i = Math.max(0, idx - 5); i < idx; i++) {
2810
- const match = f.lines[i].match(/@TenantScopeException\s*\(\s*"([^"]+)"\s*\)/i);
2811
- if (match)
2812
- suppressionReason = match[1];
2814
+ const explicitSup = suppressions.find(s => s.check === "TENANT_ISOLATION" && (s.method === dm[0] || s.method.endsWith(`.${dm[0]}`)));
2815
+ if (explicitSup)
2816
+ suppressionReason = explicitSup.reason;
2817
+ else {
2818
+ for (let i = Math.max(0, idx - 5); i < idx; i++) {
2819
+ const match = f.lines[i].match(/@TenantScopeException\s*\(\s*"([^"]+)"\s*\)/i);
2820
+ if (match)
2821
+ suppressionReason = match[1];
2822
+ }
2813
2823
  }
2814
2824
  if (suppressionReason) {
2815
2825
  p1Suppressed.push({ file: f.file_path, line: idx + 1, reason: suppressionReason, method: dm[0] });
@@ -2825,13 +2835,24 @@ server.tool("run_pre_pr_checks_with_reviewer_agent", "Unified, single-call PR re
2825
2835
  const block = f.lines.slice(ai, ai + 5).join(" ");
2826
2836
  if (!/tenantId|tenant_id|:tenantId/i.test(block)) {
2827
2837
  let suppressionReason = "";
2828
- for (let i = Math.max(0, ai - 5); i < ai; i++) {
2829
- const match = f.lines[i].match(/@TenantScopeException\s*\(\s*"([^"]+)"\s*\)/i);
2830
- if (match)
2831
- suppressionReason = match[1];
2838
+ let queryMethodName = "";
2839
+ const methodDeclMatch = block.match(/(?:List|Optional|Page|long|int|boolean|void|[A-Z]\w+)\s+(\w+)\s*\(/);
2840
+ if (methodDeclMatch)
2841
+ queryMethodName = methodDeclMatch[1];
2842
+ if (queryMethodName) {
2843
+ const explicitSup = suppressions.find(s => s.check === "TENANT_ISOLATION" && (s.method === queryMethodName || s.method.endsWith(`.${queryMethodName}`)));
2844
+ if (explicitSup)
2845
+ suppressionReason = explicitSup.reason;
2846
+ }
2847
+ if (!suppressionReason) {
2848
+ for (let i = Math.max(0, ai - 5); i < ai; i++) {
2849
+ const match = f.lines[i].match(/@TenantScopeException\s*\(\s*"([^"]+)"\s*\)/i);
2850
+ if (match)
2851
+ suppressionReason = match[1];
2852
+ }
2832
2853
  }
2833
2854
  if (suppressionReason) {
2834
- p1Suppressed.push({ file: f.file_path, line: ai + 1, reason: suppressionReason, method: "@Query method" });
2855
+ p1Suppressed.push({ file: f.file_path, line: ai + 1, reason: suppressionReason, method: queryMethodName || "@Query method" });
2835
2856
  }
2836
2857
  else {
2837
2858
  p1.push({ check: "TENANT_ISOLATION", severity: "BLOCKING", file: f.file_path, line: ai + 1,
@@ -3480,8 +3501,13 @@ server.tool("run_pre_pr_checks", "Full-spectrum PR review system — runs BEFORE
3480
3501
  work_item_id: z.string().describe("Active work item ID, e.g. 'WI-1234'."),
3481
3502
  skip_checks: z.array(z.enum(["tenant_isolation", "n_plus_one", "missing_index", "kafka_sync", "early_return", "ai_review"])).optional().default([]).describe("Checks to skip. Options: tenant_isolation | n_plus_one | missing_index | kafka_sync | early_return | ai_review. " +
3482
3503
  "Use sparingly — only for acknowledged legacy files that pre-date the rule."),
3483
- }, async ({ work_item_id, skip_checks = [] }) => {
3484
- const inputChars = JSON.stringify({ work_item_id, skip_checks }).length;
3504
+ suppressions: z.array(z.object({
3505
+ check: z.enum(["TENANT_ISOLATION", "N_PLUS_ONE", "MISSING_INDEX", "KAFKA_SYNC", "EARLY_RETURN"]),
3506
+ method: z.string().describe("Method name to suppress, e.g. 'countOrphanedByDeviceUserId'"),
3507
+ reason: z.string().describe("Mandatory justification for suppression")
3508
+ })).optional().default([]).describe("Method-scoped suppressions with mandatory reasons.")
3509
+ }, async ({ work_item_id, skip_checks = [], suppressions = [] }) => {
3510
+ const inputChars = JSON.stringify({ work_item_id, skip_checks, suppressions }).length;
3485
3511
  const wsRoot = getWorkspaceRoot();
3486
3512
  // ── Resolve task folder ────────────────────────────────────────────────
3487
3513
  const registry = readRegistry();
@@ -3559,10 +3585,15 @@ server.tool("run_pre_pr_checks", "Full-spectrum PR review system — runs BEFORE
3559
3585
  const byClause = derivedMatch[1].toLowerCase();
3560
3586
  if (!byClause.includes("tenantid") && !byClause.includes("tenant")) {
3561
3587
  let suppressionReason = "";
3562
- for (let i = Math.max(0, idx - 5); i < idx; i++) {
3563
- const match = f.lines[i].match(/@TenantScopeException\s*\(\s*"([^"]+)"\s*\)/i);
3564
- if (match)
3565
- suppressionReason = match[1];
3588
+ const explicitSup = suppressions.find(s => s.check === "TENANT_ISOLATION" && (s.method === derivedMatch[0] || s.method.endsWith(`.${derivedMatch[0]}`)));
3589
+ if (explicitSup)
3590
+ suppressionReason = explicitSup.reason;
3591
+ else {
3592
+ for (let i = Math.max(0, idx - 5); i < idx; i++) {
3593
+ const match = f.lines[i].match(/@TenantScopeException\s*\(\s*"([^"]+)"\s*\)/i);
3594
+ if (match)
3595
+ suppressionReason = match[1];
3596
+ }
3566
3597
  }
3567
3598
  if (suppressionReason) {
3568
3599
  p1Suppressed.push({ file: f.file_path, line: idx + 1, reason: suppressionReason, method: derivedMatch[0] });
@@ -3586,13 +3617,24 @@ server.tool("run_pre_pr_checks", "Full-spectrum PR review system — runs BEFORE
3586
3617
  const queryBlock = f.lines.slice(annotIdx, annotIdx + 5).join(" ");
3587
3618
  if (!/tenantId|tenant_id|:tenantId/i.test(queryBlock)) {
3588
3619
  let suppressionReason = "";
3589
- for (let i = Math.max(0, annotIdx - 5); i < annotIdx; i++) {
3590
- const match = f.lines[i].match(/@TenantScopeException\s*\(\s*"([^"]+)"\s*\)/i);
3591
- if (match)
3592
- suppressionReason = match[1];
3620
+ let queryMethodName = "";
3621
+ const methodDeclMatch = queryBlock.match(/(?:List|Optional|Page|long|int|boolean|void|[A-Z]\w+)\s+(\w+)\s*\(/);
3622
+ if (methodDeclMatch)
3623
+ queryMethodName = methodDeclMatch[1];
3624
+ if (queryMethodName) {
3625
+ const explicitSup = suppressions.find(s => s.check === "TENANT_ISOLATION" && (s.method === queryMethodName || s.method.endsWith(`.${queryMethodName}`)));
3626
+ if (explicitSup)
3627
+ suppressionReason = explicitSup.reason;
3628
+ }
3629
+ if (!suppressionReason) {
3630
+ for (let i = Math.max(0, annotIdx - 5); i < annotIdx; i++) {
3631
+ const match = f.lines[i].match(/@TenantScopeException\s*\(\s*"([^"]+)"\s*\)/i);
3632
+ if (match)
3633
+ suppressionReason = match[1];
3634
+ }
3593
3635
  }
3594
3636
  if (suppressionReason) {
3595
- p1Suppressed.push({ file: f.file_path, line: annotIdx + 1, reason: suppressionReason, method: "@Query method" });
3637
+ p1Suppressed.push({ file: f.file_path, line: annotIdx + 1, reason: suppressionReason, method: queryMethodName || "@Query method" });
3596
3638
  }
3597
3639
  else {
3598
3640
  p1Findings.push({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "secufusion-mcp",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "description": "SecuFusion MCP server - developer workflow tooling with guardrails",
6
6
  "main": "index.js",