secufusion-mcp 1.1.0 → 1.2.1

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 (3) hide show
  1. package/AGENTS.md +26 -26
  2. package/index.js +64 -22
  3. package/package.json +1 -1
package/AGENTS.md CHANGED
@@ -468,6 +468,7 @@ STEP 1: call run_pre_pr_checks_with_reviewer_agent(work_item_id: <id>)
468
468
  P3 MISSING_INDEX — new query columns have migration index
469
469
  P4 KAFKA_SYNC — no sync work inside @KafkaListener
470
470
  P5 EARLY_RETURN — no early returns (single-exit rule)
471
+ (Note: If a mechanical check is a false positive, pass the `suppressions` parameter to the tool with `{ check, method, reason }` instead of ignoring it.)
471
472
  — Tier 2: AI File Reviewer (only after P1–P5 pass)
472
473
  Checks: hardcoded URLs, missing @Transactional(readOnly),
473
474
  missing @PreAuthorize, debug statements, exception swallowing,
@@ -727,37 +728,37 @@ Before Phase 0.7 plan presentation:
727
728
  **Audience:** Technical and Non-Technical stakeholders on the Azure DevOps board.
728
729
  **Format rules:**
729
730
  - Layman summary: NO class names, NO method names, NO technical terms. Pure English.
730
- - Technical deep-dive: BE specific. Exact class names, exact method names, exact file paths. Source from `files-touched.json`.
731
+ - Execution Flow: BE specific. Exact class names, exact method names, exact file paths. Source from `files-touched.json`.
732
+ - Sequential flow ONLY. Do not use before/after framing (e.g., avoid "not X directly," "exactly as before," "hardened after review," etc.). Just state what happens and in what order.
731
733
  - Tables must have content in every cell. If unknown → "not recorded".
732
734
  - Max 2 sentences in any single prose paragraph.
733
735
  - Max 600 words.
734
736
 
735
737
  ```markdown
736
738
  ## Layman Summary
737
- {layman_summary — 2-3 sentences max}
739
+ {layman_summary — 2-3 sentences max. Sequential flow only.}
738
740
 
739
- ## Technical Deep-Dive
741
+ ## Execution Flow
740
742
  {If auth/permission gating exists:}
741
- > Both endpoints gated by `{permission}` authority.
743
+ > 1. Request gated by `{permission}` authority.
742
744
 
743
- ### Scenarios Handled
744
- **Happy path:**
745
- {happy_path from scenarios.json — one sentence}
745
+ {Describe the straight sequence of what happens, in order, step-by-step. E.g.:}
746
+ 2. **Trigger:** {How the flow starts}
747
+ 3. **Validation:** {What checks occur}
748
+ 4. **Processing:** {What data is transformed or queried}
749
+ 5. **Result:** {What is returned or persisted}
746
750
 
747
- **Sad paths handled:**
748
- {for each sad_path in scenarios.json:}
749
- - {sad_path description} → {how handled}
750
-
751
- **Security cases:**
751
+ ### Security & Error Handling
752
752
  {for each security_case in scenarios.json:}
753
753
  - {security concern} → {enforcement}
754
+ {for each sad_path in scenarios.json:}
755
+ - {sad_path description} → {how handled}
754
756
 
755
- ### What did NOT change
757
+ ### Infrastructure Impact
756
758
  | Area | Status |
757
759
  |---|---|
758
760
  | DB migration | {✅ Not required / ⚠️ Created: V{n}__...sql} |
759
761
  | Kafka topics | {✅ No new topics / ⚠️ New topic: {name}} |
760
- | {other_service}-api | {✅ No changes / ⚠️ Changes required} |
761
762
  | API contract | {✅ No breaking changes / ⚠️ See decisions} |
762
763
 
763
764
  ---
@@ -767,7 +768,8 @@ Before Phase 0.7 plan presentation:
767
768
  ### PR Comment Template (`pr-comment.md`)
768
769
  **Audience:** Busy Developer Reviewers reading the PR description.
769
770
  **Format rules:**
770
- - Group changes by WHAT changed, not by file name (e.g., "New endpoint: GET /path", "Test coverage: N scenarios").
771
+ - Group changes by the sequence of execution, not by file name.
772
+ - Sequential flow ONLY. No before/after comparisons. Just state what the code does now.
771
773
  - ✅ for things NOT changing is equally important.
772
774
  - Guardrails section is mandatory.
773
775
  - Max 400 words. Condense changes table if longer.
@@ -779,13 +781,14 @@ Before Phase 0.7 plan presentation:
779
781
 
780
782
  ---
781
783
  ## Summary
782
- {2-3 sentences MAX. What changed and why.}
784
+ {2-3 sentences MAX. What the code does. Sequential flow only.}
783
785
 
784
786
  ---
785
- ## Changes
786
- | Area | What changed |
787
- |---|---|
788
- | {logical area} | {what changed in plain terms} |
787
+ ## Implementation Sequence
788
+ 1. {Step 1 of the new flow}
789
+ 2. {Step 2 of the new flow}
790
+ 3. {Step 3 of the new flow}
791
+ 4. {Step 4 of the new flow}
789
792
 
790
793
  ---
791
794
  ## Affected Service
@@ -793,16 +796,13 @@ Before Phase 0.7 plan presentation:
793
796
 
794
797
  ---
795
798
  ## Impact
796
- - ✅ No functional or behavioral changes {OR describe actual behavioral change}
799
+ - ✅ No functional or behavioral changes {OR describe actual behavioral change sequentially}
797
800
  - ✅ No DB migration required {OR: ⚠️ Flyway migration V{n} included}
798
801
  - ✅ No Kafka topic changes {OR: ⚠️ New topic: {name}}
799
- - ✅ No API contract changes {OR: ⚠️ New endpoints: see Changes above}
802
+ - ✅ No API contract changes {OR: ⚠️ New endpoints: see Implementation Sequence above}
800
803
 
801
804
  ---
802
- ## Scenarios
803
- **Happy path:** {happy_path one line}
804
-
805
- **Sad paths handled:**
805
+ ## Error Handling
806
806
  - {what fails} → {how handled}
807
807
 
808
808
  ---
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.1",
4
4
  "type": "module",
5
5
  "description": "SecuFusion MCP server - developer workflow tooling with guardrails",
6
6
  "main": "index.js",