secufusion-mcp 1.2.7 → 2.0.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.
@@ -0,0 +1,121 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { resolve, readFileSafe, getWorkspaceRoot } from './utils.js';
4
+
5
+ function parseArgs() {
6
+ try {
7
+ return JSON.parse(process.argv[2] || '{}');
8
+ } catch {
9
+ return { work_item_id: 'UNKNOWN', skip_checks: [], suppressions: [] };
10
+ }
11
+ }
12
+
13
+ async function runTier1Checks(workItemId: string, skipChecks: string[], suppressions: any[]) {
14
+ const wsRoot = getWorkspaceRoot();
15
+
16
+ // Find task folder
17
+ const tasksBase = resolve(".secufusion/tasks");
18
+ let taskDir: string | null = null;
19
+ if (fs.existsSync(tasksBase)) {
20
+ const folders = fs.readdirSync(tasksBase);
21
+ const match = folders.find(f => f.startsWith(workItemId));
22
+ if (match) taskDir = path.join(tasksBase, match);
23
+ }
24
+
25
+ if (!taskDir) {
26
+ return `❌ CHANGES_REQUESTED: Task folder not found for ${workItemId}`;
27
+ }
28
+
29
+ // Read files-touched.json
30
+ let filesTouched: string[] = [];
31
+ try {
32
+ const ftContent = readFileSafe(path.join(taskDir, 'files-touched.json'));
33
+ if (ftContent) filesTouched = JSON.parse(ftContent);
34
+ } catch { }
35
+
36
+ let out = `## run_pre_pr_checks_with_reviewer_agent — ${workItemId}\n\n`;
37
+ out += `Found ${filesTouched.length} modified files.\n\n`;
38
+
39
+ let allPassed = true;
40
+ let failedChecks: string[] = [];
41
+
42
+ for (const file of filesTouched) {
43
+ const fullPath = path.resolve(wsRoot, file);
44
+ const content = readFileSafe(fullPath);
45
+ if (!content) continue;
46
+
47
+ const isJava = file.endsWith('.java');
48
+ const isTs = file.endsWith('.ts');
49
+ const isSql = file.endsWith('.sql');
50
+
51
+ // P1 TENANT_ISOLATION
52
+ if (!skipChecks.includes('tenant_isolation') && isJava && file.includes('Repository')) {
53
+ const hasTenantFilter = /tenantid|tenant_id|\.tenant/i.test(content);
54
+ if (!hasTenantFilter) {
55
+ const suppressed = suppressions.find(s => s.check === 'TENANT_ISOLATION');
56
+ if (!suppressed) {
57
+ allPassed = false;
58
+ failedChecks.push(`❌ P1 TENANT_ISOLATION: ${file} lacks tenant filtering.`);
59
+ }
60
+ }
61
+ }
62
+
63
+ // P2 N_PLUS_ONE
64
+ if (!skipChecks.includes('n_plus_one') && (isJava || isTs)) {
65
+ // Very naive check for loop containing repository/dao calls
66
+ if (/(for|while)\s*\(.*\)[\s\S]*?(repository\.|dao\.)/i.test(content)) {
67
+ allPassed = false;
68
+ failedChecks.push(`❌ P2 N_PLUS_ONE: Potential DB call inside loop in ${file}.`);
69
+ }
70
+ }
71
+
72
+ // P3 MISSING_INDEX
73
+ if (!skipChecks.includes('missing_index') && isSql) {
74
+ if (/create table|alter table.*add column/i.test(content) && !/create index/i.test(content)) {
75
+ allPassed = false;
76
+ failedChecks.push(`❌ P3 MISSING_INDEX: Schema modification without an index in ${file}.`);
77
+ }
78
+ }
79
+
80
+ // P4 KAFKA_SYNC
81
+ if (!skipChecks.includes('kafka_sync') && isJava && content.includes('@KafkaListener')) {
82
+ if (/resttemplate|webclient|httpclient/i.test(content)) {
83
+ allPassed = false;
84
+ failedChecks.push(`❌ P4 KAFKA_SYNC: Synchronous HTTP call inside @KafkaListener in ${file}.`);
85
+ }
86
+ }
87
+
88
+ // P5 EARLY_RETURN
89
+ if (!skipChecks.includes('early_return') && (isJava || isTs)) {
90
+ // Count number of return statements. If > 1 in a method block, flag it (naive implementation)
91
+ const returns = (content.match(/\breturn\b/g) || []).length;
92
+ if (returns > 1 && !file.includes('Test')) {
93
+ allPassed = false;
94
+ failedChecks.push(`❌ P5 EARLY_RETURN: Multiple exit points detected in ${file}.`);
95
+ }
96
+ }
97
+ }
98
+
99
+ if (!allPassed) {
100
+ out += `### Tier 1: Mechanical Checks Failed\n`;
101
+ out += failedChecks.join('\n') + '\n\n';
102
+ out += `Verdict: ❌ CHANGES_REQUESTED\n`;
103
+ } else {
104
+ out += `### Tier 1: Mechanical Checks Passed\n`;
105
+ out += `All files passed zero-tolerance P1-P5 validation.\n\n`;
106
+
107
+ if (!skipChecks.includes('ai_review') || !skipChecks.includes('context_review')) {
108
+ out += `### Tier 2 & 3: AI Reviewer\n`;
109
+ out += `(AI logic would execute here using Claude context)\n\n`;
110
+ }
111
+
112
+ out += `Verdict: ✅ APPROVED\n`;
113
+ }
114
+
115
+ return out;
116
+ }
117
+
118
+ const args = parseArgs();
119
+ runTier1Checks(args.work_item_id, args.skip_checks, args.suppressions).then(out => {
120
+ console.log(out);
121
+ });
@@ -0,0 +1,29 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ export function getWorkspaceRoot() {
4
+ let currentDir = process.cwd();
5
+ const rootMarkers = [".git", "package.json", "pom.xml", ".secufusion-project-spec.json", ".agents"];
6
+ for (let i = 0; i < 10; i++) {
7
+ for (const marker of rootMarkers) {
8
+ if (fs.existsSync(path.join(currentDir, marker))) {
9
+ return currentDir;
10
+ }
11
+ }
12
+ const parent = path.dirname(currentDir);
13
+ if (parent === currentDir)
14
+ break;
15
+ currentDir = parent;
16
+ }
17
+ return process.cwd();
18
+ }
19
+ export function resolve(file) {
20
+ return path.resolve(getWorkspaceRoot(), file);
21
+ }
22
+ export function readFileSafe(filePath) {
23
+ try {
24
+ return fs.readFileSync(filePath, "utf-8");
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ }
@@ -0,0 +1,31 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+
4
+ export function getWorkspaceRoot(): string {
5
+ let currentDir = process.cwd();
6
+ const rootMarkers = [".git", "package.json", "pom.xml", ".secufusion-project-spec.json", ".agents"];
7
+
8
+ for (let i = 0; i < 10; i++) {
9
+ for (const marker of rootMarkers) {
10
+ if (fs.existsSync(path.join(currentDir, marker))) {
11
+ return currentDir;
12
+ }
13
+ }
14
+ const parent = path.dirname(currentDir);
15
+ if (parent === currentDir) break;
16
+ currentDir = parent;
17
+ }
18
+ return process.cwd();
19
+ }
20
+
21
+ export function resolve(file: string): string {
22
+ return path.resolve(getWorkspaceRoot(), file);
23
+ }
24
+
25
+ export function readFileSafe(filePath: string): string | null {
26
+ try {
27
+ return fs.readFileSync(filePath, "utf-8");
28
+ } catch {
29
+ return null;
30
+ }
31
+ }