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.
- package/.claude/settings.json +9 -0
- package/.claude-plugin/plugin.json +5 -6
- package/.mcp.json +19 -0
- package/{AGENTS.md → agents/AGENTS.md} +3 -3
- package/agents/claude.md +50 -0
- package/agents/coder.md +80 -0
- package/agents/planner.md +354 -0
- package/agents/reviewer.md +384 -0
- package/commands/analyze.md +15 -0
- package/commands/blast-radius.md +14 -0
- package/commands/map-architecture.md +10 -0
- package/commands/map-services.md +3 -0
- package/commands/watch-dna.md +9 -0
- package/hooks/hooks.json +17 -0
- package/hooks/knowledge-drift.sh +48 -0
- package/mcp/dist/parsers/api.js +30 -0
- package/mcp/dist/parsers/backend-node.js +26 -0
- package/mcp/dist/parsers/config.js +74 -0
- package/mcp/dist/parsers/database.js +40 -0
- package/mcp/dist/parsers/domain.js +21 -0
- package/mcp/dist/parsers/events.js +36 -0
- package/mcp/dist/parsers/frontend.js +34 -0
- package/mcp/dist/parsers/infra.js +42 -0
- package/mcp/dist/parsers/patterns.js +49 -0
- package/mcp/dist/parsers/security.js +16 -0
- package/{index.js → mcp/dist/server.js} +408 -4
- package/package.json +19 -15
- package/scripts/sfn-pr-check.js +108 -0
- package/scripts/sfn-pr-check.ts +121 -0
- package/scripts/utils.js +29 -0
- package/scripts/utils.ts +31 -0
- package/.secufusion-project-spec.json +0 -1068
- /package/commands/{code.md → sfn-code.md} +0 -0
- /package/commands/{init.md → sfn-init.md} +0 -0
- /package/commands/{plan.md → sfn-plan.md} +0 -0
- /package/commands/{review.md → sfn-review.md} +0 -0
|
@@ -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
|
+
});
|
package/scripts/utils.js
ADDED
|
@@ -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
|
+
}
|
package/scripts/utils.ts
ADDED
|
@@ -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
|
+
}
|