secufusion-mcp 2.5.0 → 3.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,196 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { loadExperience } from "./experience_manager.js";
4
+ // --- Snapshot Management ---
5
+ export function loadSnapshot(ecosystemRoot, name) {
6
+ const p = path.join(ecosystemRoot, '.secufusion', `graph.${name}.json`);
7
+ if (!fs.existsSync(p))
8
+ return null;
9
+ return JSON.parse(fs.readFileSync(p, "utf-8"));
10
+ }
11
+ export function saveSnapshot(ecosystemRoot, name, snap) {
12
+ const p = path.join(ecosystemRoot, '.secufusion', `graph.${name}.json`);
13
+ fs.mkdirSync(path.dirname(p), { recursive: true });
14
+ fs.writeFileSync(p, JSON.stringify(snap, null, 2), "utf-8");
15
+ }
16
+ export function compareRepositorySnapshots(base, current) {
17
+ const changes = [];
18
+ const baseNodes = new Map(base.nodes.map(n => [n.id, n]));
19
+ const currNodes = new Map(current.nodes.map(n => [n.id, n]));
20
+ for (const [id, curr] of currNodes.entries()) {
21
+ if (!baseNodes.has(id)) {
22
+ changes.push({ category: `${curr.type}_ADDED`, artifact_id: id, current_evidence: curr });
23
+ }
24
+ else {
25
+ // Assume modified if it exists. A real diff would check metadata.
26
+ const b = baseNodes.get(id);
27
+ if (JSON.stringify(curr.metadata) !== JSON.stringify(b?.metadata)) {
28
+ changes.push({ category: `${curr.type}_MODIFIED`, artifact_id: id, base_evidence: b, current_evidence: curr });
29
+ }
30
+ }
31
+ }
32
+ for (const [id, b] of baseNodes.entries()) {
33
+ if (!currNodes.has(id)) {
34
+ changes.push({ category: `${b.type}_REMOVED`, artifact_id: id, base_evidence: b });
35
+ }
36
+ }
37
+ // Edges
38
+ const baseEdges = new Map(base.edges.map(e => [e.id, e]));
39
+ const currEdges = new Map(current.edges.map(e => [e.id, e]));
40
+ for (const [id, curr] of currEdges.entries()) {
41
+ if (!baseEdges.has(id)) {
42
+ changes.push({ category: `RELATIONSHIP_ADDED`, artifact_id: id, current_evidence: curr });
43
+ }
44
+ }
45
+ for (const [id, b] of baseEdges.entries()) {
46
+ if (!currEdges.has(id)) {
47
+ changes.push({ category: `RELATIONSHIP_REMOVED`, artifact_id: id, base_evidence: b });
48
+ }
49
+ }
50
+ return changes;
51
+ }
52
+ // --- Architecture Drift ---
53
+ export function detectArchitectureDrift(base, current) {
54
+ const drift = [];
55
+ const changes = compareRepositorySnapshots(base, current);
56
+ for (const ch of changes) {
57
+ if (ch.category === "RELATIONSHIP_REMOVED" || ch.category === "RELATIONSHIP_ADDED") {
58
+ const edge = ch.current_evidence || ch.base_evidence;
59
+ if (edge.type === "CONSUMES" || edge.type === "PRODUCES") {
60
+ drift.push({ category: "EVENT_DRIFT", affected_component: edge.source, base_evidence: ch.base_evidence, current_evidence: ch.current_evidence, status: "DRIFT_DETECTED" });
61
+ }
62
+ else if (edge.type === "CALLS") {
63
+ drift.push({ category: "DEPENDENCY_DRIFT", affected_component: edge.source, base_evidence: ch.base_evidence, current_evidence: ch.current_evidence, status: "DRIFT_DETECTED" });
64
+ }
65
+ else if (edge.type === "OWNS") {
66
+ drift.push({ category: "DATABASE_DRIFT", affected_component: edge.source, base_evidence: ch.base_evidence, current_evidence: ch.current_evidence, status: "DRIFT_DETECTED" });
67
+ }
68
+ else if (edge.type === "EXPOSES") {
69
+ drift.push({ category: "API_DRIFT", affected_component: edge.source, base_evidence: ch.base_evidence, current_evidence: ch.current_evidence, status: "DRIFT_DETECTED" });
70
+ }
71
+ }
72
+ }
73
+ return drift;
74
+ }
75
+ // --- Requirement Drift ---
76
+ export function detectRequirementDrift(reqs, current) {
77
+ const drift = [];
78
+ const currNodes = new Map(current.nodes.map(n => [n.id, n]));
79
+ for (const req of reqs) {
80
+ if (req.status !== "CONFIRMED")
81
+ continue;
82
+ if (req.apis) {
83
+ for (const api of req.apis) {
84
+ if (!currNodes.has(`API:${api}`)) {
85
+ drift.push({ state: "DRIFT_DETECTED", condition: "API_NOT_IN_IMPLEMENTATION", requirement_id: req.id, expected_artifact: api });
86
+ }
87
+ }
88
+ }
89
+ }
90
+ return drift;
91
+ }
92
+ // --- Traceability Drift ---
93
+ export function detectTraceabilityDrift(links, current) {
94
+ const drift = [];
95
+ const currNodes = new Map(current.nodes.map(n => [n.id, n]));
96
+ for (const link of links) {
97
+ if (link.target && ["API", "CLASS", "SERVICE", "DATABASE_TABLE", "TOPIC"].includes(link.type) || link.target.startsWith("API:") || link.target.startsWith("CLASS:")) {
98
+ if (!currNodes.has(link.target)) {
99
+ drift.push({ condition: "CODE_WITHOUT_TRACEABILITY", source_id: link.source, target_id: link.target }); // Target disappeared
100
+ }
101
+ }
102
+ }
103
+ return drift;
104
+ }
105
+ // --- Historical Matching ---
106
+ export function getChangeHistoryContext(ecosystemRoot, changes) {
107
+ const list = loadExperience(ecosystemRoot);
108
+ const affected = new Set(changes.map(c => {
109
+ if (c.current_evidence && c.current_evidence.source)
110
+ return c.current_evidence.source;
111
+ if (c.base_evidence && c.base_evidence.source)
112
+ return c.base_evidence.source;
113
+ return c.artifact_id;
114
+ }));
115
+ const results = [];
116
+ for (const exp of list) {
117
+ if (exp.status === "RETIRED")
118
+ continue;
119
+ if (exp.affected_components && exp.affected_components.some(c => affected.has(c))) {
120
+ results.push(exp);
121
+ }
122
+ if (results.length >= 10)
123
+ break; // Bounded
124
+ }
125
+ return results;
126
+ }
127
+ export function findRegressionTests(ecosystemRoot, changes, tests) {
128
+ const affected = new Set(changes.map(c => c.artifact_id));
129
+ return tests.filter(t => t.target_components && t.target_components.some((c) => affected.has(c)));
130
+ }
131
+ // --- Risk Indicators ---
132
+ export function analyzeRegressionRisk(changes, history, drift, reqDrift, traceDrift, regressionTests) {
133
+ const risks = [];
134
+ if (history.length > 0) {
135
+ const failures = history.filter(h => h.outcome === "FAILURE" || h.outcome === "BLOCKED");
136
+ if (failures.length > 0) {
137
+ risks.push({ indicator: "HISTORICAL_FAILURE_MATCH", evidence: { matching_history: failures.map(f => f.id) }, risk_level: "HIGH_EVIDENCE_RISK" });
138
+ }
139
+ const experiences = history.filter(h => h.status === "VALIDATED");
140
+ if (experiences.length > 0) {
141
+ risks.push({ indicator: "HISTORICAL_VALIDATED_EXPERIENCE_MATCH", evidence: { matching_experience: experiences.map(e => e.id) }, risk_level: "MODERATE_EVIDENCE_RISK" });
142
+ }
143
+ }
144
+ if (drift.length > 0) {
145
+ risks.push({ indicator: "ARCHITECTURE_DRIFT", evidence: { drift_count: drift.length }, risk_level: "MODERATE_EVIDENCE_RISK" });
146
+ }
147
+ if (traceDrift.length > 0) {
148
+ risks.push({ indicator: "TRACEABILITY_GAP", evidence: { gaps: traceDrift }, risk_level: "MODERATE_EVIDENCE_RISK" });
149
+ }
150
+ // Cross-service
151
+ const services = new Set();
152
+ changes.forEach(c => {
153
+ const node = c.current_evidence || c.base_evidence;
154
+ if (node?.repository)
155
+ services.add(node.repository);
156
+ if (node?.id?.startsWith("SERVICE:"))
157
+ services.add(node.id);
158
+ });
159
+ if (services.size > 1) {
160
+ risks.push({ indicator: "CROSS_SERVICE_CHANGE", evidence: { services: Array.from(services) }, risk_level: "HIGH_EVIDENCE_RISK" });
161
+ }
162
+ return risks;
163
+ }
164
+ export function getRegressionContext(ecosystemRoot, base, current, reqs, links, allTests) {
165
+ const changes = compareRepositorySnapshots(base, current);
166
+ const archDrift = detectArchitectureDrift(base, current);
167
+ const reqDrift = detectRequirementDrift(reqs, current);
168
+ const traceDrift = detectTraceabilityDrift(links, current);
169
+ const history = getChangeHistoryContext(ecosystemRoot, changes);
170
+ const regTests = findRegressionTests(ecosystemRoot, changes, allTests);
171
+ // Check regression test gaps
172
+ if (history.some(h => h.outcome === "FAILURE") && regTests.length === 0) {
173
+ traceDrift.push({ condition: "REQUIRED_REGRESSION_TEST_MISSING", source_id: "MULTIPLE" });
174
+ }
175
+ const risks = analyzeRegressionRisk(changes, history, archDrift, reqDrift, traceDrift, regTests);
176
+ return {
177
+ current_changes: changes.slice(0, 50), // Bounded
178
+ architecture_drift: archDrift.slice(0, 20),
179
+ requirement_drift: reqDrift.slice(0, 20),
180
+ traceability_drift: traceDrift.slice(0, 20),
181
+ historical_experience: history.filter(h => h.status === "VALIDATED"),
182
+ historical_failures: history.filter(h => h.outcome === "FAILURE" || h.outcome === "BLOCKED"),
183
+ regression_tests: regTests,
184
+ risk_indicators: risks
185
+ };
186
+ }
187
+ export function recordRegressionObservation(ecosystemRoot, observation) {
188
+ const p = path.join(ecosystemRoot, '.secufusion', 'regressions.json');
189
+ let list = [];
190
+ if (fs.existsSync(p)) {
191
+ list = JSON.parse(fs.readFileSync(p, "utf-8"));
192
+ }
193
+ list.push({ ...observation, id: `OBS-${Date.now()}` });
194
+ fs.mkdirSync(path.dirname(p), { recursive: true });
195
+ fs.writeFileSync(p, JSON.stringify(list, null, 2), "utf-8");
196
+ }
@@ -0,0 +1,84 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ export function getRequirementPath(ecosystemRoot, reqId) {
4
+ const reqDir = path.join(ecosystemRoot, ".secufusion", "requirements");
5
+ if (!fs.existsSync(reqDir)) {
6
+ fs.mkdirSync(reqDir, { recursive: true });
7
+ }
8
+ return path.join(reqDir, `${reqId}.json`);
9
+ }
10
+ export function loadRequirement(ecosystemRoot, reqId) {
11
+ const p = getRequirementPath(ecosystemRoot, reqId);
12
+ if (!fs.existsSync(p))
13
+ return null;
14
+ return JSON.parse(fs.readFileSync(p, "utf-8"));
15
+ }
16
+ export function saveRequirement(ecosystemRoot, req) {
17
+ const p = getRequirementPath(ecosystemRoot, req.id);
18
+ fs.writeFileSync(p, JSON.stringify(req, null, 2));
19
+ }
20
+ export function validateStateTransition(current, next, req) {
21
+ if (next === "BLOCKED" || next === "SUPERSEDED")
22
+ return true;
23
+ const validFlows = {
24
+ "RAW": ["UNDERSTOOD", "INVESTIGATING", "BLOCKED", "SUPERSEDED"],
25
+ "UNDERSTOOD": ["INVESTIGATING", "BLOCKED", "SUPERSEDED"],
26
+ "INVESTIGATING": ["CONFIRMED", "CLARIFICATION_REQUIRED", "BLOCKED", "SUPERSEDED"],
27
+ "CLARIFICATION_REQUIRED": ["INVESTIGATING", "BLOCKED", "SUPERSEDED"],
28
+ "CONFIRMED": ["INVESTIGATING", "BLOCKED", "SUPERSEDED"],
29
+ "BLOCKED": ["INVESTIGATING", "SUPERSEDED"],
30
+ "SUPERSEDED": []
31
+ };
32
+ if (!validFlows[current].includes(next)) {
33
+ throw new Error(`Invalid transition from ${current} to ${next}`);
34
+ }
35
+ if (next === "CONFIRMED") {
36
+ if (req.blockers && req.blockers.length > 0) {
37
+ throw new Error("Cannot transition to CONFIRMED with active blockers.");
38
+ }
39
+ const openClarifications = req.clarification_questions.filter(c => c.state === "OPEN" && c.blocking);
40
+ if (openClarifications.length > 0) {
41
+ throw new Error("Cannot transition to CONFIRMED with OPEN blocking clarification questions.");
42
+ }
43
+ }
44
+ return true;
45
+ }
46
+ export function generateConfirmedSpecification(req) {
47
+ let spec = `---
48
+ Requirement ID: ${req.id}
49
+ Title: ${req.title || "Untitled"}
50
+ Status: ${req.status}
51
+ ---
52
+
53
+ # CONFIRMED SPECIFICATION
54
+
55
+ ## WHAT THE USER REQUESTED
56
+ ${req.raw_request || "Not provided."}
57
+
58
+ ## WHAT THE REPOSITORY CURRENTLY DOES
59
+ (Based on gathered facts)
60
+ ${req.facts ? req.facts.map(f => "- " + f).join("\\n") : "No facts recorded."}
61
+
62
+ ## WHAT WAS CONFIRMED BY THE USER
63
+ ${req.decisions ? req.decisions.map(d => "- " + d).join("\\n") : "No specific decisions recorded."}
64
+
65
+ ## WHAT REMAINS UNKNOWN
66
+ ${req.unknowns ? req.unknowns.map(u => "- " + u).join("\\n") : "None."}
67
+
68
+ ## SCOPE & DETAILS
69
+ - Scope: ${req.scope || "Unknown"}
70
+ - Tenancy: ${req.tenancy || "Unknown"}
71
+ - Authorization: ${req.authorization || "Unknown"}
72
+ - Data Lifecycle: ${req.data_lifecycle || "Unknown"}
73
+
74
+ ### Technical Implications
75
+ - APIs: ${req.apis ? req.apis.join(", ") : "None"}
76
+ - Events: ${req.events ? req.events.join(", ") : "None"}
77
+ - Persistence: ${req.persistence ? req.persistence.join(", ") : "None"}
78
+ - Frontend: ${req.frontend ? req.frontend.join(", ") : "None"}
79
+
80
+ ## ACCEPTANCE CRITERIA
81
+ ${req.acceptance_criteria ? req.acceptance_criteria.map(a => "- [ ] " + a).join("\\n") : "None specified."}
82
+ `;
83
+ return spec;
84
+ }
@@ -0,0 +1,62 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ function runQuery(graph, query, depth) {
4
+ const queryLower = query.toLowerCase();
5
+ let startNodes = graph.nodes.filter((n) => (n.id.toLowerCase().includes(queryLower) || n.name.toLowerCase().includes(queryLower)));
6
+ const resultNodes = new Map();
7
+ const resultEdges = new Map();
8
+ let currentLayer = startNodes;
9
+ for (let d = 0; d <= depth; d++) {
10
+ const nextLayer = [];
11
+ for (const n of currentLayer) {
12
+ if (!resultNodes.has(n.id))
13
+ resultNodes.set(n.id, n);
14
+ }
15
+ if (d < depth) {
16
+ for (const edge of graph.edges) {
17
+ if (resultNodes.has(edge.source) || resultNodes.has(edge.target)) {
18
+ if (!resultEdges.has(edge.id)) {
19
+ resultEdges.set(edge.id, edge);
20
+ const sourceNode = graph.nodes.find((n) => n.id === edge.source);
21
+ const targetNode = graph.nodes.find((n) => n.id === edge.target);
22
+ if (sourceNode && !resultNodes.has(sourceNode.id))
23
+ nextLayer.push(sourceNode);
24
+ if (targetNode && !resultNodes.has(targetNode.id))
25
+ nextLayer.push(targetNode);
26
+ }
27
+ }
28
+ }
29
+ }
30
+ currentLayer = nextLayer;
31
+ }
32
+ return {
33
+ query,
34
+ nodes: Array.from(resultNodes.values()),
35
+ edges: Array.from(resultEdges.values())
36
+ };
37
+ }
38
+ const ecosystemRoot = "c:\\Users\\Yash\\Desktop\\mcp";
39
+ const dnaPath = path.join(ecosystemRoot, ".secufusion", "dna.json");
40
+ if (!fs.existsSync(dnaPath)) {
41
+ console.error("No dna.json found at", dnaPath);
42
+ process.exit(1);
43
+ }
44
+ const dna = JSON.parse(fs.readFileSync(dnaPath, 'utf8'));
45
+ const graph = dna.graph;
46
+ if (!graph) {
47
+ console.error("No graph in dna.json");
48
+ process.exit(1);
49
+ }
50
+ console.log("--- User Deletion Benchmark ---");
51
+ const userRes = runQuery(graph, "User", 2);
52
+ console.log(`Found ${userRes.nodes.length} nodes and ${userRes.edges.length} edges for 'User'.`);
53
+ // Print paths
54
+ userRes.edges.slice(0, 10).forEach(e => {
55
+ console.log(` ${e.source} --[${e.type}]--> ${e.target}`);
56
+ });
57
+ console.log("\n--- Retention Policy Benchmark ---");
58
+ const retRes = runQuery(graph, "retention", 2);
59
+ console.log(`Found ${retRes.nodes.length} nodes and ${retRes.edges.length} edges for 'retention'.`);
60
+ retRes.edges.slice(0, 10).forEach(e => {
61
+ console.log(` ${e.source} --[${e.type}]--> ${e.target}`);
62
+ });