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.
- package/AGENTS.md +26 -26
- package/index.js +64 -22
- 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
|
-
-
|
|
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
|
-
##
|
|
741
|
+
## Execution Flow
|
|
740
742
|
{If auth/permission gating exists:}
|
|
741
|
-
>
|
|
743
|
+
> 1. Request gated by `{permission}` authority.
|
|
742
744
|
|
|
743
|
-
|
|
744
|
-
**
|
|
745
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
|
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
|
|
784
|
+
{2-3 sentences MAX. What the code does. Sequential flow only.}
|
|
783
785
|
|
|
784
786
|
---
|
|
785
|
-
##
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
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
|
|
802
|
+
- ✅ No API contract changes {OR: ⚠️ New endpoints: see Implementation Sequence above}
|
|
800
803
|
|
|
801
804
|
---
|
|
802
|
-
##
|
|
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
|
-
|
|
2679
|
-
|
|
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
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
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
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
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
|
-
|
|
3484
|
-
|
|
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
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
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
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
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({
|