secufusion-mcp 1.0.22 → 1.0.24

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.
Files changed (3) hide show
  1. package/README.md +22 -6
  2. package/index.js +410 -5
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -705,6 +705,21 @@ When you pass a title like `"BUG-1140: Tenant deletion reports failure"` to `man
705
705
  - **Backward Compatible:** The AI only ever needs to supply the `work_item_id` (e.g. `BUG-1140`) for subsequent updates. The server instantly finds the correct folder via `registry.json` (O(1) lookup) or falls back to a prefix scan for legacy `WI-{id}` folders.
706
706
  - **OS Safe:** Automatically trims trailing dashes and clamps lengths to prevent Windows `MAX_PATH` errors.
707
707
 
708
+ ### Dynamic Architecture Validation (v1.0.23+)
709
+
710
+ As of version **1.0.23**, the deep analysis engine in `classify_task` is fully dynamic and driven entirely by your `.secufusion-project-spec.json`:
711
+ - Validation dynamically cross-references explicit microservices, frontend repos, and Chrome extensions.
712
+ - Explicit frontend overrides (e.g., `"pure ui"`, `"no backend changes"`) can bypass false-positive `FULL_STACK` labels.
713
+ - The Breaking Change Pre-Scan safely checks for exact table names and Kafka topics derived from your architecture.
714
+ - `coding_patterns` defined in the spec are injected seamlessly into `secufusionFlags` validation.
715
+
716
+ ### Retrospective Intelligence (v1.0.24+)
717
+
718
+ As of version **1.0.24**, the MCP server introduces a fully automated **Retrospective Layer**:
719
+ - **Auto-Retrospective Trigger**: `manage_task complete` now automatically generates a partial retrospective and asks the developer 7 targeted questions.
720
+ - **record_retrospective**: A new tool that saves retrospective insights, tracking plan accuracy, classification accuracy, and pre-PR check attempts.
721
+ - **Dynamic Learning (Pass 0)**: `classify_task` now includes a Pass 0 that injects learned signals from past retrospectives into the active classification logic.
722
+
708
723
  ---
709
724
 
710
725
  ## Talking to the AI — What You'll Ever Say
@@ -760,12 +775,12 @@ The **mandatory first step** for every task without exception. Classifies a task
760
775
 
761
776
  | Pass | What it does |
762
777
  |---|---|
763
- | **Pass 1 — Weighted signal tiers** | Tier 1: service names = 10pts each. Tier 2: tech constructs = 5pts. Tier 3: domain terms = 2-3pts. Tier 4: generic words = 1pt. |
764
- | **Pass 2 — Negation detection** | Scans each sentence. `"not a UI issue"` → frontend penalty. Each negated sentence subtracts 8pts from the relevant domain. |
778
+ | **Pass 1 — Weighted signal tiers** | Tier 1: service names = 10pts each (dynamically populated from `project-spec.json`). Tier 2: tech constructs = 5pts. Tier 3: domain terms = 2-3pts. Tier 4: generic words = 1pt. |
779
+ | **Pass 2 — Negation detection** | Scans each sentence. `"not a UI issue"` → frontend penalty. Explicit overrides (e.g. `"pure ui"`, `"no backend changes"`) zero out backend scores to prevent `FULL_STACK` misclassifications. |
765
780
  | **Pass 3 — Root-cause phrase extraction** | 25 backend patterns + 9 frontend patterns matched via regex. |
766
781
  | **Pass 4 — Bug disambiguation matrix** | For `task_type: bug`: data-correctness → +15 backend, exception/crash → +15 backend, auth/permission → +12 backend. |
767
- | **Pass 4.5 — Problem Statement Validation** | **NEW (v1.0.19)**: Validates Title Accuracy, Completeness, Root Cause Assumptions, Scope Clarity, Task Type Correctness, and SecuFusion constraints. May halt the AI with a `MISLEADING` or `NEEDS_CLARIFICATION` verdict if the task is poorly defined. |
768
- | **Pass 5 — Confidence gate** | `HIGH` only when dominant score ≥ 1.8× second-place **AND** at least one Tier 1/2 signal matched. |
782
+ | **Pass 4.5 — Problem Statement Validation** | **NEW (v1.0.19+)**: Validates Title, Scope, and Task Type. Dynamically extracts `coding_patterns` from the project spec and injects them as active validations if relevant keywords are found. |
783
+ | **Pass 5 — Confidence & Breaking Change Gate** | `HIGH` only when dominant score ≥ 1.8× second-place **AND** at least one Tier 1/2 signal matched. Dynamically cross-references explicitly mentioned endpoints, Kafka topics, and DB tables against `project-spec.json` to accurately flag breaking change risks. |
769
784
 
770
785
  **Output — `allowed_next_action`:**
771
786
 
@@ -800,12 +815,13 @@ Proceeding to plan presentation. No developer confirmation needed.
800
815
 
801
816
  | Before | After |
802
817
  |---|---|
803
- | Flat keyword counting — every word scored equally | 4-tier weighted scoring — service names = 10× generic words |
818
+ | Flat keyword counting — every word scored equally | 4-tier weighted scoring — service names = 10× generic words (dynamically loaded from `project-spec.json`) |
804
819
  | `"dashboard"` → scored as frontend | Root-cause phrases — `"shows wrong count on dashboard"` → backend +12 |
805
- | No negation awareness | Sentence-level negation — `"not a UI issue"` removes frontend weight |
820
+ | No negation awareness | Sentence-level negation — `"not a UI issue"` removes frontend weight. Explicit overrides (e.g. `"pure ui"`) safely force `FRONTEND_ONLY`. |
806
821
  | Bug heuristic: default to backend only on LOW confidence | 5-category bug disambiguation matrix (+12–15pts per category) |
807
822
  | HIGH confidence even on equal scores | HIGH only when ratio ≥ 1.8× AND Tier 1/2 signal matched |
808
823
  | Mixed signals → always FULL_STACK | Backend dominates at 2.5× → classified BACKEND_ONLY, frontend treated as noise |
824
+ | Validation / Breaking Changes hardcoded | Validation rules, endpoints, DB tables, and Kafka topics dynamically extracted from `project-spec.json` |
809
825
 
810
826
  ### `AGENTS.md` — All phases rewritten to strict enforcement
811
827
 
package/index.js CHANGED
@@ -1044,7 +1044,98 @@ server.tool("manage_task", "Manages structured task records in .secufusion/tasks
1044
1044
  ...scenarios.map(s => `- [${s["type"]}] ${s["scenario"]}`),
1045
1045
  ].join("\n");
1046
1046
  writeFile(prSummaryPath, prSummary);
1047
- return appendTelemetry({ content: [{ type: "text", text: `✅ Task WI-${work_item_id} marked complete.\n\n**PR summary generated:** ${prSummaryPath}\n\n${prSummary}` }] }, inputChars);
1047
+ let retroMessage = "";
1048
+ try {
1049
+ const classificationFile = path.join(resolve(CLASSIFICATIONS_DIR), `${work_item_id}.json`);
1050
+ const classificationData = fs.existsSync(classificationFile) ? readJson(classificationFile) : {};
1051
+ let migration_risk_predicted = "NONE";
1052
+ let breaking_changes_predicted = 0;
1053
+ const rollbackDecision = decisions.find(d => (d["decision"] || "").toLowerCase().includes("rollback strategy"));
1054
+ if (rollbackDecision) {
1055
+ const match = rollbackDecision["decision"].match(/\[(SAFE|RISKY|DANGEROUS)\]/i);
1056
+ if (match)
1057
+ migration_risk_predicted = match[1].toUpperCase();
1058
+ }
1059
+ const breakingDecision = decisions.find(d => (d["decision"] || "").toLowerCase().includes("breaking changes predicted"));
1060
+ if (breakingDecision) {
1061
+ const match = breakingDecision["decision"].match(/(\d+)/);
1062
+ if (match)
1063
+ breaking_changes_predicted = parseInt(match[1], 10);
1064
+ }
1065
+ const sad_paths = scenarios.filter(s => s["type"] === "e2e" || s["scenario"]?.toString().toLowerCase().includes("sad")).length;
1066
+ const edge_cases = scenarios.filter(s => s["scenario"]?.toString().toLowerCase().includes("edge")).length;
1067
+ const security_cases = scenarios.filter(s => s["scenario"]?.toString().toLowerCase().includes("security")).length;
1068
+ const partialRetro = {
1069
+ status: "partial — awaiting developer input",
1070
+ work_item_id,
1071
+ task_title: spec["title"] || "",
1072
+ branch: "",
1073
+ completed_at: progress["completed_at"],
1074
+ classification: {
1075
+ predicted: classificationData["dominant_tier"] || "",
1076
+ predicted_confidence: classificationData["confidence"] || "",
1077
+ predicted_score_backend: classificationData["backend_score"] || 0,
1078
+ predicted_score_frontend: classificationData["frontend_score"] || 0,
1079
+ was_correct: null,
1080
+ correction_notes: null
1081
+ },
1082
+ plan_vs_actual: {
1083
+ steps_planned: files.length,
1084
+ steps_actual: null,
1085
+ steps_accuracy_pct: null,
1086
+ files_planned: files.map(f => f["file_path"]),
1087
+ files_actual: [],
1088
+ unplanned_files: null,
1089
+ missed_files: null,
1090
+ migration_risk_predicted,
1091
+ migration_risk_actual: null,
1092
+ migration_prediction_correct: null,
1093
+ breaking_changes_predicted,
1094
+ breaking_changes_actual: null,
1095
+ breaking_prediction_correct: null,
1096
+ performance_risk_predicted: "",
1097
+ performance_issues_found: null,
1098
+ performance_prediction_correct: null
1099
+ },
1100
+ scenarios: {
1101
+ sad_paths_planned: sad_paths,
1102
+ sad_paths_covered_in_tests: null,
1103
+ edge_cases_planned: edge_cases,
1104
+ edge_cases_covered_in_tests: null,
1105
+ security_cases_planned: security_cases,
1106
+ security_cases_covered_in_tests: null,
1107
+ scenario_coverage_pct: null
1108
+ },
1109
+ effort: {
1110
+ acs_planned: (spec["acceptance_criteria"] || []).length,
1111
+ acs_completed: (progress["completed_acs"] || []).length,
1112
+ acs_added_during_work: null,
1113
+ acs_removed_during_work: null,
1114
+ scope_creep_detected: null,
1115
+ scope_creep_description: null
1116
+ },
1117
+ quality: {
1118
+ pre_pr_checks_passed_first_attempt: null,
1119
+ pre_pr_attempts_needed: null,
1120
+ checks_that_failed: null,
1121
+ rejected_patterns_triggered: null,
1122
+ linter_errors_found: null
1123
+ },
1124
+ learnings: {
1125
+ what_went_well: null,
1126
+ what_was_harder_than_expected: null,
1127
+ signals_that_should_have_been_caught: null,
1128
+ plan_sections_that_were_wrong: null
1129
+ },
1130
+ classifier_feedback: null
1131
+ };
1132
+ writeFile(path.join(dir, "retrospective.json"), JSON.stringify(partialRetro, null, 2));
1133
+ retroMessage = `\n─────────────────────────────────────────\n📊 RETROSPECTIVE STARTED\n\nA partial retrospective has been saved.\nTo complete it and improve future plans,\nanswer these questions:\n\nQ1: Was the classification correct?\n (predicted: ${classificationData["dominant_tier"] || "UNKNOWN"} — ${classificationData["confidence"] || "UNKNOWN"} confidence)\n Reply: retro correct / retro incorrect: {reason}\n\nQ2: How many implementation steps did it take?\n (planned: ${files.length})\n Reply: retro steps {actual_number}\n\nQ3: Any unplanned files modified?\n Reply: retro unplanned {filename1, filename2}\n or: retro unplanned none\n\nQ4: Actual migration risk?\n (predicted: ${migration_risk_predicted})\n Reply: retro migration {SAFE/RISKY/DANGEROUS/NONE}\n\nQ5: How many pre-PR attempts needed?\n Reply: retro attempts {number}\n\nQ6: Any scope creep?\n Reply: retro scope yes: {description}\n or: retro scope no\n\nQ7: Any classifier feedback?\n Reply: retro signal missing {term}\n or: retro signal noise {term}\n or: retro phrase {phrase that would help}\n or: retro skip\n\nYou can answer all at once or skip with: retro skip\n─────────────────────────────────────────`;
1134
+ }
1135
+ catch (err) {
1136
+ console.warn("Auto-retrospective failed:", err);
1137
+ }
1138
+ return appendTelemetry({ content: [{ type: "text", text: `✅ Task WI-${work_item_id} marked complete.\n\n**PR summary generated:** ${prSummaryPath}\n\n${prSummary}\n${retroMessage}` }] }, inputChars);
1048
1139
  }
1049
1140
  return appendTelemetry({ content: [{ type: "text", text: "Invalid action." }] }, inputChars);
1050
1141
  });
@@ -1122,6 +1213,255 @@ server.tool("get_pattern_from_task", "Extract reusable implementation patterns f
1122
1213
  };
1123
1214
  return appendTelemetry({ content: [{ type: "text", text: `**Patterns from WI-${work_item_id}: ${spec["title"] || ""}**\n\n${JSON.stringify(patterns, null, 2)}` }] }, inputChars);
1124
1215
  });
1216
+ // ─── Tool 8.5: record_retrospective ───────────────────────────────────────────
1217
+ server.tool("record_retrospective", "Records a structured retrospective after task completion. Compares planned vs actual: steps, files, migration risk, breaking changes, scenario coverage, scope creep, and pre-PR check results. Accumulates insights in .secufusion/retrospective-insights.json to make classify_task smarter over time. Called automatically by manage_task action=complete — can also be called manually at any point during a task.", {
1218
+ work_item_id: z.string().describe("Azure DevOps work item ID."),
1219
+ classification_correct: z.boolean().describe("Was the classify_task classification correct? true = matched actual work done, false = wrong or misleading"),
1220
+ classification_correction_notes: z.string().optional().describe("Only if classification_correct=false. What was wrong and what the correct classification should have been."),
1221
+ steps_actual: z.number().describe("How many implementation steps were actually needed."),
1222
+ unplanned_files: z.array(z.string()).optional().describe("Files that were modified but NOT in the plan."),
1223
+ missed_files: z.array(z.string()).optional().describe("Files that were in the plan but NOT touched."),
1224
+ migration_risk_actual: z.string().optional().describe("Actual Flyway migration risk encountered. 'SAFE' | 'RISKY' | 'DANGEROUS' | 'NONE'"),
1225
+ breaking_changes_actual: z.number().optional().describe("How many actual breaking changes were found. 0 if none."),
1226
+ performance_issues_found: z.boolean().optional().describe("Were any performance issues found during implementation or PR review?"),
1227
+ pre_pr_attempts: z.number().describe("How many times run_pre_pr_checks was called before all checks passed. 1 = passed first attempt (perfect), 2+ = needed fixes"),
1228
+ checks_that_failed: z.array(z.string()).optional().describe("List of check names that failed at least once."),
1229
+ scope_creep_detected: z.boolean().describe("Were ACs added or significantly changed during work that were not in the original plan?"),
1230
+ scope_creep_description: z.string().optional().describe("Only if scope_creep_detected=true. What changed and why."),
1231
+ what_went_well: z.array(z.string()).optional().describe("Things that worked exactly as planned."),
1232
+ what_was_harder: z.array(z.string()).optional().describe("Things that took more effort than expected."),
1233
+ classifier_feedback: z.object({
1234
+ signal_missing: z.string().optional(),
1235
+ signal_noise: z.string().optional(),
1236
+ root_cause_phrase: z.string().optional(),
1237
+ bug_category: z.string().optional(),
1238
+ tier_upgrade: z.string().optional()
1239
+ }).optional().describe("Feedback to improve classify_task for future tasks.")
1240
+ }, async (params) => {
1241
+ const inputChars = JSON.stringify(params).length;
1242
+ const { work_item_id, classification_correct, classification_correction_notes, steps_actual, unplanned_files = [], missed_files = [], migration_risk_actual, breaking_changes_actual, performance_issues_found, pre_pr_attempts, checks_that_failed = [], scope_creep_detected, scope_creep_description, what_went_well = [], what_was_harder = [], classifier_feedback } = params;
1243
+ const tasksRoot = resolve(TASKS_DIR);
1244
+ const dir = resolveTaskFolder(work_item_id, tasksRoot);
1245
+ if (!dir)
1246
+ return appendTelemetry({ isError: true, content: [{ type: "text", text: `Task WI-${work_item_id} not found.` }] }, inputChars);
1247
+ const spec = readJson(path.join(dir, "spec.json")) || {};
1248
+ const progress = readJson(path.join(dir, "progress.json")) || {};
1249
+ const decisions = readJson(path.join(dir, "decisions.json")) || [];
1250
+ const files = readJson(path.join(dir, "files-touched.json")) || [];
1251
+ const scenarios = readJson(path.join(dir, "scenarios.json")) || [];
1252
+ const classificationFile = path.join(resolve(CLASSIFICATIONS_DIR), `${work_item_id}.json`);
1253
+ const classificationData = readJson(classificationFile) || {};
1254
+ const acs_planned = (spec["acceptance_criteria"] || []).length;
1255
+ const acs_completed = (progress["completed_acs"] || []).length;
1256
+ const acs_added_during_work = acs_completed - acs_planned;
1257
+ let migration_risk_predicted = "NONE";
1258
+ let breaking_changes_predicted = 0;
1259
+ const rollbackDecision = decisions.find(d => (d["decision"] || "").toLowerCase().includes("rollback strategy"));
1260
+ if (rollbackDecision) {
1261
+ const match = rollbackDecision["decision"].match(/\[(SAFE|RISKY|DANGEROUS)\]/i);
1262
+ if (match)
1263
+ migration_risk_predicted = match[1].toUpperCase();
1264
+ }
1265
+ const breakingDecision = decisions.find(d => (d["decision"] || "").toLowerCase().includes("breaking changes predicted"));
1266
+ if (breakingDecision) {
1267
+ const match = breakingDecision["decision"].match(/(\d+)/);
1268
+ if (match)
1269
+ breaking_changes_predicted = parseInt(match[1], 10);
1270
+ }
1271
+ const sad_paths = scenarios.filter(s => s["type"] === "e2e" || s["scenario"]?.toString().toLowerCase().includes("sad")).length;
1272
+ const edge_cases = scenarios.filter(s => s["scenario"]?.toString().toLowerCase().includes("edge")).length;
1273
+ const security_cases = scenarios.filter(s => s["scenario"]?.toString().toLowerCase().includes("security")).length;
1274
+ const files_planned = files.map(f => f["file_path"]);
1275
+ const steps_planned = files.length;
1276
+ const steps_accuracy_pct = steps_planned === 0 && steps_actual === 0 ? 100 : Math.round((Math.min(steps_planned, steps_actual) / Math.max(steps_planned, steps_actual)) * 100);
1277
+ const scenario_coverage_pct = scenarios.length === 0 ? 100 : Math.round((files.length / scenarios.length) * 100);
1278
+ const migration_prediction_correct = migration_risk_actual === migration_risk_predicted || (!migration_risk_actual && migration_risk_predicted === "NONE");
1279
+ const breaking_prediction_correct = breaking_changes_actual === breaking_changes_predicted || (!breaking_changes_actual && breaking_changes_predicted === 0);
1280
+ const retro = {
1281
+ work_item_id,
1282
+ task_title: spec["title"] || "",
1283
+ branch: "",
1284
+ completed_at: new Date().toISOString(),
1285
+ classification: {
1286
+ predicted: classificationData["dominant_tier"] || "",
1287
+ predicted_confidence: classificationData["confidence"] || "",
1288
+ predicted_score_backend: classificationData["backend_score"] || 0,
1289
+ predicted_score_frontend: classificationData["frontend_score"] || 0,
1290
+ was_correct: classification_correct,
1291
+ correction_notes: classification_correction_notes || ""
1292
+ },
1293
+ plan_vs_actual: {
1294
+ steps_planned,
1295
+ steps_actual,
1296
+ steps_accuracy_pct,
1297
+ files_planned,
1298
+ files_actual: files.map(f => f["file_path"]),
1299
+ unplanned_files,
1300
+ missed_files,
1301
+ migration_risk_predicted,
1302
+ migration_risk_actual: migration_risk_actual || "",
1303
+ migration_prediction_correct,
1304
+ breaking_changes_predicted,
1305
+ breaking_changes_actual: breaking_changes_actual || 0,
1306
+ breaking_prediction_correct,
1307
+ performance_risk_predicted: "",
1308
+ performance_issues_found: performance_issues_found || false,
1309
+ performance_prediction_correct: !performance_issues_found
1310
+ },
1311
+ scenarios: {
1312
+ sad_paths_planned: sad_paths,
1313
+ sad_paths_covered_in_tests: sad_paths,
1314
+ edge_cases_planned: edge_cases,
1315
+ edge_cases_covered_in_tests: edge_cases,
1316
+ security_cases_planned: security_cases,
1317
+ security_cases_covered_in_tests: security_cases,
1318
+ scenario_coverage_pct
1319
+ },
1320
+ effort: {
1321
+ acs_planned,
1322
+ acs_completed,
1323
+ acs_added_during_work: Math.max(0, acs_added_during_work),
1324
+ acs_removed_during_work: Math.max(0, -acs_added_during_work),
1325
+ scope_creep_detected,
1326
+ scope_creep_description: scope_creep_description || ""
1327
+ },
1328
+ quality: {
1329
+ pre_pr_checks_passed_first_attempt: pre_pr_attempts === 1,
1330
+ pre_pr_attempts_needed: pre_pr_attempts,
1331
+ checks_that_failed,
1332
+ rejected_patterns_triggered: [],
1333
+ linter_errors_found: 0
1334
+ },
1335
+ learnings: {
1336
+ what_went_well,
1337
+ what_was_harder_than_expected: what_was_harder,
1338
+ signals_that_should_have_been_caught: [],
1339
+ plan_sections_that_were_wrong: []
1340
+ },
1341
+ classifier_feedback: classifier_feedback || {}
1342
+ };
1343
+ writeFile(path.join(dir, "retrospective.json"), JSON.stringify(retro, null, 2));
1344
+ const insightsFile = resolve(".secufusion/retrospective-insights.json");
1345
+ let insights = {
1346
+ total_tasks_completed: 0,
1347
+ last_updated: "",
1348
+ classification_accuracy: { total_classified: 0, correct: 0, incorrect: 0, accuracy_pct: 0, most_common_misclassification: "" },
1349
+ plan_accuracy: { avg_steps_planned: 0, avg_steps_actual: 0, avg_step_accuracy_pct: 0, avg_files_unplanned: 0, migration_risk_accuracy_pct: 0, breaking_change_accuracy_pct: 0 },
1350
+ quality_trends: { avg_pre_pr_attempts: 0, most_common_failed_check: "", most_triggered_rejected_pattern: "" },
1351
+ scenario_coverage: { avg_sad_path_coverage_pct: 0, avg_edge_case_coverage_pct: 0, most_missed_scenario_type: "" },
1352
+ scope_creep: { tasks_with_scope_creep: 0, avg_acs_added_during_work: 0 },
1353
+ classifier_learning_queue: [],
1354
+ task_history: []
1355
+ };
1356
+ if (fs.existsSync(insightsFile)) {
1357
+ insights = readJson(insightsFile) || insights;
1358
+ }
1359
+ const n = insights.total_tasks_completed + 1;
1360
+ insights.total_tasks_completed = n;
1361
+ insights.last_updated = new Date().toISOString();
1362
+ insights.classification_accuracy.total_classified += 1;
1363
+ if (classification_correct) {
1364
+ insights.classification_accuracy.correct += 1;
1365
+ }
1366
+ else {
1367
+ insights.classification_accuracy.incorrect += 1;
1368
+ }
1369
+ insights.classification_accuracy.accuracy_pct = Math.round((insights.classification_accuracy.correct / insights.classification_accuracy.total_classified) * 100);
1370
+ insights.plan_accuracy.avg_steps_planned = Math.round(((insights.plan_accuracy.avg_steps_planned * (n - 1)) + steps_planned) / n);
1371
+ insights.plan_accuracy.avg_steps_actual = Math.round(((insights.plan_accuracy.avg_steps_actual * (n - 1)) + steps_actual) / n);
1372
+ insights.plan_accuracy.avg_step_accuracy_pct = Math.round(((insights.plan_accuracy.avg_step_accuracy_pct * (n - 1)) + steps_accuracy_pct) / n);
1373
+ insights.plan_accuracy.avg_files_unplanned = Math.round(((insights.plan_accuracy.avg_files_unplanned * (n - 1)) + unplanned_files.length) / n);
1374
+ const migRiskCorrect = migration_prediction_correct ? 100 : 0;
1375
+ insights.plan_accuracy.migration_risk_accuracy_pct = Math.round(((insights.plan_accuracy.migration_risk_accuracy_pct * (n - 1)) + migRiskCorrect) / n);
1376
+ const breakRiskCorrect = breaking_prediction_correct ? 100 : 0;
1377
+ insights.plan_accuracy.breaking_change_accuracy_pct = Math.round(((insights.plan_accuracy.breaking_change_accuracy_pct * (n - 1)) + breakRiskCorrect) / n);
1378
+ insights.quality_trends.avg_pre_pr_attempts = Math.round(((insights.quality_trends.avg_pre_pr_attempts * (n - 1)) + pre_pr_attempts) / n);
1379
+ if (scope_creep_detected)
1380
+ insights.scope_creep.tasks_with_scope_creep += 1;
1381
+ insights.scope_creep.avg_acs_added_during_work = Math.round(((insights.scope_creep.avg_acs_added_during_work * (n - 1)) + Math.max(0, acs_added_during_work)) / n);
1382
+ insights.task_history.push({
1383
+ work_item_id,
1384
+ title: spec["title"] || "",
1385
+ completed_at: retro.completed_at,
1386
+ classification_correct,
1387
+ step_accuracy_pct: steps_accuracy_pct,
1388
+ pre_pr_attempts,
1389
+ scope_creep: scope_creep_detected
1390
+ });
1391
+ if (insights.task_history.length > 50)
1392
+ insights.task_history.shift();
1393
+ if (classifier_feedback) {
1394
+ for (const [key, value] of Object.entries(classifier_feedback)) {
1395
+ if (value) {
1396
+ let signalType = key.replace('signal_', '');
1397
+ if (key === 'root_cause_phrase')
1398
+ signalType = 'phrase';
1399
+ else if (key === 'bug_category')
1400
+ signalType = 'bug_category';
1401
+ else if (key === 'tier_upgrade')
1402
+ signalType = 'tier_upgrade';
1403
+ const existing = insights.classifier_learning_queue.find((q) => q.signal_type === signalType && q.signal_value === value);
1404
+ if (existing) {
1405
+ existing.frequency += 1;
1406
+ }
1407
+ else {
1408
+ insights.classifier_learning_queue.push({
1409
+ from_task: work_item_id,
1410
+ signal_type: signalType,
1411
+ signal_value: value,
1412
+ frequency: 1,
1413
+ applied: false
1414
+ });
1415
+ }
1416
+ }
1417
+ }
1418
+ }
1419
+ if (!fs.existsSync(resolve(".secufusion")))
1420
+ fs.mkdirSync(resolve(".secufusion"), { recursive: true });
1421
+ writeFile(insightsFile, JSON.stringify(insights, null, 2));
1422
+ let out = `📊 RETROSPECTIVE — ${work_item_id}: ${spec["title"] || ""}\n─────────────────────────────────────────\n\n`;
1423
+ out += `🎯 Classification\nPredicted : ${retro.classification.predicted} (${retro.classification.predicted_confidence} confidence)\n`;
1424
+ out += `Correct : ${classification_correct ? "yes" : "no"}\n`;
1425
+ if (!classification_correct)
1426
+ out += `Should have been: ${classification_correction_notes}\n`;
1427
+ out += `\n📋 Plan Accuracy\nSteps : Planned ${steps_planned} → Actual ${steps_actual} (${steps_accuracy_pct}% accurate)\n`;
1428
+ out += `Files : ${files_planned.length} planned, ${unplanned_files.length} unplanned, ${missed_files.length} missed\n`;
1429
+ out += `Migration : Predicted ${migration_risk_predicted} → Actual ${migration_risk_actual || "NONE"} ${migration_prediction_correct ? "✅" : "❌"}\n`;
1430
+ out += `Breaking : Predicted ${breaking_changes_predicted} → Actual ${breaking_changes_actual || 0} ${breaking_prediction_correct ? "✅" : "❌"}\n`;
1431
+ out += `Performance: ${performance_issues_found ? "Issues found ❌" : "No issues ✅"}\n`;
1432
+ out += `\n✅ Quality\nPre-PR attempts : ${pre_pr_attempts} (${pre_pr_attempts === 1 ? "perfect" : "needed fixes"})\n`;
1433
+ out += `Checks failed : ${checks_that_failed.length > 0 ? checks_that_failed.join(", ") : "none"}\n`;
1434
+ out += `Scope creep : ${scope_creep_detected ? "yes" : "no"}\n`;
1435
+ if (scope_creep_detected)
1436
+ out += `${scope_creep_description}\n`;
1437
+ out += `\n🧪 Scenario Coverage\nSad paths : ${sad_paths} planned → ${retro.scenarios.sad_paths_covered_in_tests} covered (${scenario_coverage_pct}%)\n`;
1438
+ out += `Edge cases : ${edge_cases} planned → ${retro.scenarios.edge_cases_covered_in_tests} covered (${scenario_coverage_pct}%)\n`;
1439
+ out += `Security : ${security_cases} planned → ${retro.scenarios.security_cases_covered_in_tests} covered (${scenario_coverage_pct}%)\n`;
1440
+ out += `\n💡 Learnings\nWent well : ${what_went_well.length > 0 ? what_went_well.join(", ") : "none"}\n`;
1441
+ out += `Harder than expected: ${what_was_harder.length > 0 ? what_was_harder.join(", ") : "none"}\n`;
1442
+ out += `\n📈 Session Totals (from retrospective-insights.json)\nTasks completed : ${insights.total_tasks_completed}\n`;
1443
+ out += `Avg plan accuracy : ${insights.plan_accuracy.avg_step_accuracy_pct}%\n`;
1444
+ out += `Avg pre-PR attempts: ${insights.quality_trends.avg_pre_pr_attempts}\n`;
1445
+ out += `Classification accuracy: ${insights.classification_accuracy.accuracy_pct}%\n`;
1446
+ out += `─────────────────────────────────────────\n`;
1447
+ if (classifier_feedback && Object.keys(classifier_feedback).length > 0) {
1448
+ out += `\n🧠 Classifier feedback queued:\n`;
1449
+ for (const [key, value] of Object.entries(classifier_feedback)) {
1450
+ if (value) {
1451
+ let signalType = key.replace('signal_', '');
1452
+ if (key === 'root_cause_phrase')
1453
+ signalType = 'phrase';
1454
+ else if (key === 'bug_category')
1455
+ signalType = 'bug_category';
1456
+ else if (key === 'tier_upgrade')
1457
+ signalType = 'tier_upgrade';
1458
+ const queued = insights.classifier_learning_queue.find((q) => q.signal_type === signalType && q.signal_value === value);
1459
+ out += `${signalType}: '${value}'\nThis will improve future classify_task results.\n${queued?.frequency || 1} task(s) have reported this signal.\n`;
1460
+ }
1461
+ }
1462
+ }
1463
+ return appendTelemetry({ content: [{ type: "text", text: out }] }, inputChars);
1464
+ });
1125
1465
  // ─── Tool 9: classify_task ────────────────────────────────────────────────────
1126
1466
  server.tool("classify_task", "MANDATORY FIRST STEP for every task without exception. " +
1127
1467
  "Classifies a task as BACKEND_ONLY, FRONTEND_ONLY, FULL_STACK, or EXTENSION_ONLY. " +
@@ -1186,20 +1526,60 @@ server.tool("classify_task", "MANDATORY FIRST STEP for every task without except
1186
1526
  const inRegistry = registry.some(r => r["work_item_id"] === work_item_id);
1187
1527
  if (inRegistry)
1188
1528
  resumingExistingTask = true;
1529
+ // ── Pass 0: Retrospective Intelligence ────────────────────────────────
1530
+ const insightsFile = resolve(".secufusion/retrospective-insights.json");
1531
+ let learnedLogs = [];
1532
+ let learnedSignals = { backend: [], frontend: [], extension: [], phrase: [], bug_category: [] };
1533
+ if (fs.existsSync(insightsFile)) {
1534
+ try {
1535
+ const insights = readJson(insightsFile);
1536
+ if (insights && Array.isArray(insights.classifier_learning_queue)) {
1537
+ let updated = false;
1538
+ insights.classifier_learning_queue.forEach((q) => {
1539
+ if (q.frequency >= 2) {
1540
+ if (q.signal_type === "backend")
1541
+ learnedSignals.backend.push(q.signal_value);
1542
+ else if (q.signal_type === "frontend")
1543
+ learnedSignals.frontend.push(q.signal_value);
1544
+ else if (q.signal_type === "extension")
1545
+ learnedSignals.extension.push(q.signal_value);
1546
+ else if (q.signal_type === "phrase")
1547
+ learnedSignals.phrase.push(q.signal_value);
1548
+ else if (q.signal_type === "bug_category")
1549
+ learnedSignals.bug_category.push(q.signal_value);
1550
+ if (!q.applied) {
1551
+ learnedLogs.push(`[LEARNED] applying signal '${q.signal_value}' from ${q.frequency} past retrospectives`);
1552
+ q.applied = true;
1553
+ updated = true;
1554
+ }
1555
+ }
1556
+ });
1557
+ if (updated) {
1558
+ writeFile(insightsFile, JSON.stringify(insights, null, 2));
1559
+ }
1560
+ }
1561
+ }
1562
+ catch (e) {
1563
+ /* ignore */
1564
+ }
1565
+ }
1189
1566
  // ── Step 4: Signal scoring ────────────────────────────────────────────
1190
1567
  const corpus = (title + " " + description).toLowerCase();
1191
1568
  const BACKEND_SIGNALS = [
1192
1569
  "api", "endpoint", "service", "repository", "database", "db", "query",
1193
1570
  "migration", "flyway", "kafka", "consumer", "producer", "topic",
1194
- "backend", "server", "rest", "json response", "payload"
1571
+ "backend", "server", "rest", "json response", "payload",
1572
+ ...learnedSignals.backend, ...learnedSignals.phrase, ...learnedSignals.bug_category
1195
1573
  ];
1196
1574
  const FRONTEND_SIGNALS = [
1197
1575
  "ui", "frontend", "react", "component", "page", "screen", "button",
1198
- "modal", "layout", "style", "css", "web ui", "browser ui", "render"
1576
+ "modal", "layout", "style", "css", "web ui", "browser ui", "render",
1577
+ ...learnedSignals.frontend
1199
1578
  ];
1200
1579
  const EXTENSION_SIGNALS = [
1201
1580
  "chrome extension", "extension", "content script", "background script",
1202
- "browser extension", "popup"
1581
+ "browser extension", "popup",
1582
+ ...learnedSignals.extension
1203
1583
  ];
1204
1584
  if (spec) {
1205
1585
  if (spec.microservices) {
@@ -1262,7 +1642,18 @@ server.tool("classify_task", "MANDATORY FIRST STEP for every task without except
1262
1642
  // CHECK 1: Title accuracy
1263
1643
  const verbs = ["delete", "create", "fetch", "update", "add", "remove", "fix", "implement"];
1264
1644
  const nouns = ["tenant", "admin", "device", "policy", "gateway", "notification", "audit", "role", "permission", "user", "group", "mapping", "api", "endpoint", "dashboard", "db", "database"];
1265
- const services = ["sfn-iam-api", "sfn-events-api", "sfn-tenants-api", "sfn-policy-api", "sfn-gateway-api", "sfn-web-ui"];
1645
+ const services = [];
1646
+ if (spec) {
1647
+ if (spec.microservices)
1648
+ Object.keys(spec.microservices).forEach(k => services.push(k.toLowerCase()));
1649
+ if (spec.frontend && spec.frontend.repo)
1650
+ services.push(spec.frontend.repo.toLowerCase());
1651
+ if (spec.chrome_extension && spec.chrome_extension.repo)
1652
+ services.push(spec.chrome_extension.repo.toLowerCase());
1653
+ }
1654
+ if (services.length === 0) {
1655
+ services.push("sfn-iam-api", "sfn-events-api", "sfn-tenants-api", "sfn-policy-api", "sfn-gateway-api", "sfn-web-ui");
1656
+ }
1266
1657
  const foundConcepts = [];
1267
1658
  [...verbs, ...nouns, ...services].forEach(word => {
1268
1659
  if (descLower.includes(word))
@@ -1427,6 +1818,16 @@ server.tool("classify_task", "MANDATORY FIRST STEP for every task without except
1427
1818
  "column", "table", "migration", "flyway", "entity", "@entity",
1428
1819
  "schema", "alter", "add column", "drop column", "rename column",
1429
1820
  ];
1821
+ if (spec && spec.microservices) {
1822
+ Object.values(spec.microservices).forEach(svc => {
1823
+ if (Array.isArray(svc.owns_tables)) {
1824
+ svc.owns_tables.forEach(t => DB_CHANGE_SIGNALS.push(t.toLowerCase()));
1825
+ }
1826
+ if (Array.isArray(svc.kafka_consumes)) {
1827
+ svc.kafka_consumes.forEach(topic => KAFKA_CHANGE_SIGNALS.push(topic.toLowerCase()));
1828
+ }
1829
+ });
1830
+ }
1430
1831
  const endpointRisk = ENDPOINT_CHANGE_SIGNALS.some(s => corpus.includes(s));
1431
1832
  const kafkaRisk = KAFKA_CHANGE_SIGNALS.some(s => corpus.includes(s));
1432
1833
  const dbRisk = DB_CHANGE_SIGNALS.some(s => corpus.includes(s));
@@ -1541,6 +1942,10 @@ server.tool("classify_task", "MANDATORY FIRST STEP for every task without except
1541
1942
  finalDeveloperMessage += "\n────────────────────────────────────────\n";
1542
1943
  }
1543
1944
  }
1945
+ if (learnedLogs.length > 0) {
1946
+ finalDeveloperMessage += `🧠 RETROSPECTIVE INTELLIGENCE APPLIED\n`;
1947
+ finalDeveloperMessage += learnedLogs.join("\n") + "\n\n";
1948
+ }
1544
1949
  finalDeveloperMessage += developerMessage; // The original classification message
1545
1950
  developerMessage = finalDeveloperMessage;
1546
1951
  // Collect relevant rejected patterns (keyword match against corpus)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "secufusion-mcp",
3
- "version": "1.0.22",
3
+ "version": "1.0.24",
4
4
  "type": "module",
5
5
  "description": "SecuFusion MCP server - developer workflow tooling with guardrails",
6
6
  "main": "index.js",