secufusion-mcp 3.0.0 → 3.1.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/README.md CHANGED
@@ -1016,3 +1016,9 @@ PR Ready to Merge 🚀
1016
1016
  ## License
1017
1017
 
1018
1018
  ISC © SecuFusion
1019
+
1020
+ ## Phase 9 Features Added
1021
+ - Config-driven deterministic Workspace Discovery (`.secufusion/workspace.json`)
1022
+ - Dynamic Core vs Optional Repository Classification
1023
+ - Evidence Completeness Engine (`get_evidence_completeness`)
1024
+ - Portable Workspace Paths (No hardcoded paths)
package/agents/planner.md CHANGED
@@ -390,3 +390,11 @@ STEP 5: call manage_task(action: "log_decision",
390
390
 
391
391
  6. **Learning Loop (Phase 6)**: Assess if a new lesson was learned. If so, call `record_engineering_experience` (Status: PROPOSED), then `validate_experience` with evidence, and `link_experience` to tie it to traceability.
392
392
 
393
+
394
+
395
+ ## Phase 9 — Workspace & Evidence Completeness
396
+ (TRIGGER: During repository investigation or implementation planning)
397
+
398
+ 1. **Verify Evidence Completeness**: Call `get_evidence_completeness` to ensure all relevant CORE and OPTIONAL repositories are available in the current workspace.
399
+ 2. **Missing Evidence Rule**: If a relevant repository is missing (e.g. `sfn-policy-api` is missing and the task modifies policy), you MUST report this as an explicit blocker or limitation rather than assuming the code does not exist.
400
+ 3. **Status Check**: If the workspace status is `BLOCKED` or `PARTIAL` concerning the specific feature, notify the user. Never claim unsupported evidence as a negative fact.
@@ -432,3 +432,13 @@ Flag the following if found:
432
432
  - `PRODUCTION_UI_CHANGED_WITHOUT_PLAN`
433
433
  - `CONFIRMED_DESIGN_WITHOUT_TRACEABILITY`
434
434
  - `IMPLEMENTATION_DEVIATES_FROM_CONFIRMED_DESIGN`
435
+
436
+
437
+ ### Phase 9 Workspace & Evidence Verification
438
+ When reviewing an implementation plan or code changes, verify:
439
+ - The implementation does not assume facts based on missing repositories.
440
+ - `get_evidence_completeness` was considered if cross-service impacts are suspected.
441
+
442
+ Flag the following if found:
443
+ - `EVIDENCE_TREATED_AS_FACT_WHEN_MISSING`
444
+ - `WORKSPACE_PARTIAL_RESTRICTION_IGNORED`
@@ -0,0 +1,69 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { inspectWorkspaceRepositories, getEvidenceCompleteness } from "./workspace_intelligence.js";
4
+ import { performFullExtraction } from "./server.js";
5
+ function reset(ecosystemRoot) {
6
+ if (fs.existsSync(ecosystemRoot)) {
7
+ fs.rmSync(ecosystemRoot, { recursive: true, force: true });
8
+ }
9
+ fs.mkdirSync(ecosystemRoot, { recursive: true });
10
+ }
11
+ function runBenchmark() {
12
+ console.log("=== RUNNING PHASE 9 WORKSPACE INTELLIGENCE BENCHMARK ===\n");
13
+ const ecosystemRoot = path.join(process.cwd(), '.benchmark_tmp_ph9');
14
+ reset(ecosystemRoot);
15
+ try {
16
+ // --- Setup Mock Workspace ---
17
+ const coreRepos = ["sfn-iam-api", "sfn-events-api", "sfn-tenants-api", "sfn-web-ui"];
18
+ const optionalRepos = ["sfn-auth-api", "sfn-browser-ext"];
19
+ [...coreRepos, ...optionalRepos].forEach(repo => {
20
+ fs.mkdirSync(path.join(ecosystemRoot, repo), { recursive: true });
21
+ fs.writeFileSync(path.join(ecosystemRoot, repo, "index.ts"), "console.log('test');");
22
+ });
23
+ // --- Benchmark 1: All Available ---
24
+ console.log("--- 1. FULL WORKSPACE BENCHMARK ---");
25
+ let inspection = inspectWorkspaceRepositories(ecosystemRoot);
26
+ console.log(`Status: ${inspection.status}`);
27
+ if (inspection.status !== "READY")
28
+ throw new Error("Status should be READY");
29
+ if (inspection.core_found.length !== 4)
30
+ throw new Error("Should find 4 core repos");
31
+ let completeness = getEvidenceCompleteness(ecosystemRoot);
32
+ console.log(`Completeness Status: ${completeness.completeness_status}`);
33
+ // --- Benchmark 2: Missing Optional Repository ---
34
+ console.log("\n--- 2. MISSING OPTIONAL BENCHMARK ---");
35
+ fs.rmSync(path.join(ecosystemRoot, "sfn-browser-ext"), { recursive: true, force: true });
36
+ inspection = inspectWorkspaceRepositories(ecosystemRoot);
37
+ console.log(`Status after missing optional: ${inspection.status}`);
38
+ if (inspection.status !== "READY")
39
+ throw new Error("Status should still be READY");
40
+ if (inspection.optional_missing.includes("sfn-browser-ext") === false)
41
+ throw new Error("Missing optional should be tracked");
42
+ // --- Benchmark 3: Missing Core Repository ---
43
+ console.log("\n--- 3. MISSING CORE BENCHMARK ---");
44
+ fs.rmSync(path.join(ecosystemRoot, "sfn-iam-api"), { recursive: true, force: true });
45
+ inspection = inspectWorkspaceRepositories(ecosystemRoot);
46
+ console.log(`Status after missing core: ${inspection.status}`);
47
+ if (inspection.status !== "PARTIAL")
48
+ throw new Error("Status should be PARTIAL (some core found, some missing)");
49
+ if (inspection.core_missing.includes("sfn-iam-api") === false)
50
+ throw new Error("Missing core should be tracked");
51
+ completeness = getEvidenceCompleteness(ecosystemRoot);
52
+ console.log(`Completeness Message: ${completeness.message}`);
53
+ // --- Benchmark 4: Extraction Respects Boundaries ---
54
+ console.log("\n--- 4. EXTRACTION BOUNDARY BENCHMARK ---");
55
+ const stats = performFullExtraction(ecosystemRoot);
56
+ console.log(`Extraction Status: ${stats.inspectionStatus}`);
57
+ if (stats.inspectionStatus !== "PARTIAL")
58
+ throw new Error("Extraction should bubble up PARTIAL status");
59
+ console.log("\n=== BENCHMARK COMPLETE (ALL PASS) ===");
60
+ }
61
+ catch (e) {
62
+ console.error("Benchmark Failed:", e.message);
63
+ process.exit(1);
64
+ }
65
+ finally {
66
+ fs.rmSync(ecosystemRoot, { recursive: true, force: true });
67
+ }
68
+ }
69
+ runBenchmark();
@@ -24,7 +24,7 @@ import { loadRequirement, saveRequirement, validateStateTransition, generateConf
24
24
  import { discoverTests, executeTests, ingestTestResults, loadTestResults, correlateTestFailure } from "./test_engine.js";
25
25
  import { loadPlan, savePlan, validatePlan } from "./implementation_plan.js";
26
26
  import { generateTestObligations, aggregateValidation } from "./validation_engine.js";
27
- import { recordExperience, loadExperience, getEngineeringExperience, validateExperience, getExperienceContext, findExperienceConflicts, findRepeatedOutcomes, markExperienceStale, retireExperience, linkExperience, getLearningFeedback } from "./experience_manager.js";
27
+ import { recordExperience, getEngineeringExperience, validateExperience, getExperienceContext, findExperienceConflicts, findRepeatedOutcomes, markExperienceStale, retireExperience, linkExperience, getLearningFeedback } from "./experience_manager.js";
28
28
  import { loadTraceability, addTraceabilityLink, traceForward, traceReverse, findTraceabilityGaps } from "./traceability_engine.js";
29
29
  import { investigateEntity, investigateRelationship } from "./investigation_engine.js";
30
30
  import { buildGraph } from "./graph_builder.js";
@@ -33,6 +33,7 @@ import { parseApiEndpoints } from "./parsers/api.js";
33
33
  import { parseEventTopics } from "./parsers/events.js";
34
34
  import { inferPatterns } from "./parsers/patterns.js";
35
35
  import { parseFrontend } from "./parsers/frontend.js";
36
+ import { inspectWorkspaceRepositories, getWorkspaceConfig, getEvidenceCompleteness } from "./workspace_intelligence.js";
36
37
  import { parseExtension } from "./parsers/extension.js";
37
38
  import { parseNodeApis } from "./parsers/backend-node.js";
38
39
  import { parseInfra } from "./parsers/infra.js";
@@ -3185,19 +3186,14 @@ export function performFullExtraction(ecosystemRoot) {
3185
3186
  catch (e) { }
3186
3187
  dna.graph = buildGraph(dna, spec);
3187
3188
  writeDna(ecosystemRoot, dna);
3188
- const hasBackend = (models.length > 0 || apis.length > 0 || nodeApis.length > 0);
3189
3189
  const hasFrontend = frontend.length > 0;
3190
3190
  const hasExtension = extension.length > 0;
3191
- const missing = [];
3192
- if (!hasBackend)
3193
- missing.push("backend");
3194
- if (!hasFrontend)
3195
- missing.push("frontend");
3196
- if (!hasExtension)
3197
- missing.push("snf-browser-extn");
3198
- const missingWarning = missing.length > 0 ? `\n\n[CRITICAL CORE CHECK] The following core components are missing from the DNA map: ${missing.join(', ')}. Please verify if this is expected.` : "";
3191
+ const inspection = inspectWorkspaceRepositories(ecosystemRoot);
3192
+ const missing = inspection.core_missing;
3193
+ const missingWarning = missing.length > 0 ? `\n\n[CRITICAL CORE CHECK] The following core repositories are missing from the workspace: ${missing.join(', ')}. Evidence logic and planning will be blocked or degraded.` : "";
3199
3194
  return {
3200
3195
  missingWarning,
3196
+ inspectionStatus: inspection.status,
3201
3197
  extensionCount: extension.length,
3202
3198
  modelsCount: models.length,
3203
3199
  apisCount: apis.length + nodeApis.length,
@@ -3862,18 +3858,6 @@ server.tool("get_learning_feedback", "Return bounded historical feedback relevan
3862
3858
  return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
3863
3859
  }
3864
3860
  });
3865
- server.tool("get_engineering_experience", "Retrieves past engineering experiences and decisions.", {
3866
- workspace_root: z.string().describe("Root directory of the workspace")
3867
- }, async ({ workspace_root }) => {
3868
- try {
3869
- const ecosystemRoot = getEcosystemRoot(workspace_root);
3870
- const exp = loadExperience(ecosystemRoot);
3871
- return { content: [{ type: "text", text: JSON.stringify(exp, null, 2) }] };
3872
- }
3873
- catch (e) {
3874
- return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
3875
- }
3876
- });
3877
3861
  server.tool("discover_tests", "Discover relevant tests for a given set of artifact IDs.", {
3878
3862
  workspace_root: z.string().describe("Root directory of the workspace"),
3879
3863
  artifacts: z.array(z.string()).describe("List of artifact IDs (e.g. CLASS:UserService)")
@@ -4158,5 +4142,30 @@ Confirm to the user when the watcher is active and which directory is being watc
4158
4142
  }]
4159
4143
  }));
4160
4144
  // ─────────────────────────────────────────────────────────────────────────────
4145
+ server.tool("discover_secufusion_workspace", "Determine the current SecuFusion workspace and return its configuration (core vs optional repositories).", { workspace_root: z.string().optional() }, async ({ workspace_root }) => {
4146
+ const root = workspace_root || getWorkspaceRoot();
4147
+ const config = getWorkspaceConfig(root);
4148
+ return { content: [{ type: "text", text: JSON.stringify(config, null, 2) }] };
4149
+ });
4150
+ server.tool("inspect_workspace_repositories", "Inspect which repositories are actually present in the workspace, classifying them as core/optional.", { workspace_root: z.string().optional() }, async ({ workspace_root }) => {
4151
+ const root = workspace_root || getWorkspaceRoot();
4152
+ const result = inspectWorkspaceRepositories(root);
4153
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
4154
+ });
4155
+ server.tool("refresh_repository_knowledge", "Refresh the deterministic knowledge graph from the current repository state.", { workspace_root: z.string().optional() }, async ({ workspace_root }) => {
4156
+ const root = workspace_root || getWorkspaceRoot();
4157
+ const stats = performFullExtraction(root);
4158
+ return { content: [{ type: "text", text: JSON.stringify({ status: "REFRESHED", stats }, null, 2) }] };
4159
+ });
4160
+ server.tool("get_workspace_status", "Get the overall status of the workspace (READY, PARTIAL, BLOCKED, FAILED).", { workspace_root: z.string().optional() }, async ({ workspace_root }) => {
4161
+ const root = workspace_root || getWorkspaceRoot();
4162
+ const result = inspectWorkspaceRepositories(root);
4163
+ return { content: [{ type: "text", text: result.status }] };
4164
+ });
4165
+ server.tool("get_evidence_completeness", "Retrieve evidence completeness context, exposing which core/optional repositories are missing.", { workspace_root: z.string().optional() }, async ({ workspace_root }) => {
4166
+ const root = workspace_root || getWorkspaceRoot();
4167
+ const completeness = getEvidenceCompleteness(root);
4168
+ return { content: [{ type: "text", text: JSON.stringify(completeness, null, 2) }] };
4169
+ });
4161
4170
  const transport = new StdioServerTransport();
4162
4171
  await server.connect(transport);
@@ -0,0 +1,99 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ export const DEFAULT_WORKSPACE_CONFIG = {
4
+ workspace: "SecuFusion",
5
+ coreRepositories: ["sfn-iam-api", "sfn-events-api", "sfn-tenants-api", "sfn-web-ui"],
6
+ optionalRepositories: [
7
+ "sfn-auth-api", "sfn-policy-api", "sfn-notification-api",
8
+ "sfn-packages-api", "sfn-rules", "sfn-browser-ext",
9
+ "sfn-edge-ai", "sfn-gateway-api", "sfn-eureka-api",
10
+ "sfn-devops-terraform", "sfn-k8s-apps-config", "sfn-seed-sql"
11
+ ]
12
+ };
13
+ export function getWorkspaceConfig(ecosystemRoot) {
14
+ const configPath = path.join(ecosystemRoot, ".secufusion", "workspace.json");
15
+ if (fs.existsSync(configPath)) {
16
+ try {
17
+ return JSON.parse(fs.readFileSync(configPath, "utf-8"));
18
+ }
19
+ catch (e) {
20
+ console.error("Failed to parse workspace.json", e);
21
+ }
22
+ }
23
+ // Auto-generate if missing
24
+ fs.mkdirSync(path.join(ecosystemRoot, ".secufusion"), { recursive: true });
25
+ fs.writeFileSync(configPath, JSON.stringify(DEFAULT_WORKSPACE_CONFIG, null, 2), "utf-8");
26
+ return DEFAULT_WORKSPACE_CONFIG;
27
+ }
28
+ export function inspectWorkspaceRepositories(ecosystemRoot) {
29
+ const config = getWorkspaceConfig(ecosystemRoot);
30
+ const result = {
31
+ workspace_root: ecosystemRoot,
32
+ workspace_name: config.workspace,
33
+ core_found: [],
34
+ core_missing: [],
35
+ optional_found: [],
36
+ optional_missing: [],
37
+ unreadable: [],
38
+ status: "UNKNOWN",
39
+ snapshot_identity: new Date().toISOString()
40
+ };
41
+ const checkRepo = (repo, isCore) => {
42
+ const repoPath = path.join(ecosystemRoot, repo);
43
+ if (fs.existsSync(repoPath)) {
44
+ try {
45
+ fs.accessSync(repoPath, fs.constants.R_OK);
46
+ if (fs.statSync(repoPath).isDirectory()) {
47
+ if (isCore)
48
+ result.core_found.push(repo);
49
+ else
50
+ result.optional_found.push(repo);
51
+ }
52
+ else {
53
+ result.unreadable.push(repo);
54
+ if (isCore)
55
+ result.core_missing.push(repo);
56
+ else
57
+ result.optional_missing.push(repo);
58
+ }
59
+ }
60
+ catch (e) {
61
+ result.unreadable.push(repo);
62
+ if (isCore)
63
+ result.core_missing.push(repo);
64
+ else
65
+ result.optional_missing.push(repo);
66
+ }
67
+ }
68
+ else {
69
+ if (isCore)
70
+ result.core_missing.push(repo);
71
+ else
72
+ result.optional_missing.push(repo);
73
+ }
74
+ };
75
+ config.coreRepositories.forEach(r => checkRepo(r, true));
76
+ config.optionalRepositories.forEach(r => checkRepo(r, false));
77
+ if (result.core_missing.length === 0) {
78
+ result.status = "READY";
79
+ }
80
+ else if (result.core_found.length > 0) {
81
+ result.status = "PARTIAL";
82
+ }
83
+ else {
84
+ result.status = "BLOCKED";
85
+ }
86
+ return result;
87
+ }
88
+ export function getEvidenceCompleteness(ecosystemRoot) {
89
+ const inspection = inspectWorkspaceRepositories(ecosystemRoot);
90
+ return {
91
+ completeness_status: inspection.status,
92
+ missing_core: inspection.core_missing,
93
+ missing_optional: inspection.optional_missing,
94
+ unreadable: inspection.unreadable,
95
+ message: inspection.status === "READY"
96
+ ? "Core repository evidence is complete."
97
+ : `Missing core evidence for: ${inspection.core_missing.join(', ')}. Reasoning may be blocked.`
98
+ };
99
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "secufusion-mcp",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "type": "module",
5
5
  "description": "Project-level spec driven AI workflow tooling for SecuFusion, MCP server - developer workflow tooling with guardrails",
6
6
  "main": "mcp/dist/server.js",