secufusion-mcp 1.0.26 → 1.0.28

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/AGENTS.md +8 -10
  2. package/index.js +167 -84
  3. package/package.json +1 -1
package/AGENTS.md CHANGED
@@ -7,25 +7,23 @@ You are an elite Senior Developer and Architect working on the SecuFusion worksp
7
7
  ## Phase 00 — Task Classification (The Absolute First Step)
8
8
  (TRIGGER: the moment any task, bug, user story, feature, or work item is received)
9
9
 
10
- ### THE RULE — no exceptions, no shortcuts
10
+ ### THE RULE — Read, Examine, Then Classify
11
11
 
12
- **The VERY FIRST thing you do when any task arrives is call the `classify_task` tool.**
12
+ When any task arrives, you must NOT blindly run the classification tool. You must achieve 99% accuracy on the problem statement first.
13
13
 
14
- Not after reading the codebase.
15
- Not after thinking about the approach.
16
- Not after asking clarifying questions.
17
- Not after saying "let me analyze this".
14
+ **STEP 1:** Read and carefully examine the problem statement.
15
+ **STEP 2:** Once you fully understand it, call the `classify_task` tool.
16
+ **STEP 3:** The tool will run validation and analysis. Read the classification and proposed resolution.
17
+ **STEP 4:** If everything is correct and approved, proceed to plan and code. Before this is complete, NO code is allowed.
18
18
 
19
- **FIRST. THING. `classify_task`. Always.**
20
-
21
- If you find yourself about to read a file, write a plan, or type any code before calling
19
+ If you find yourself about to write a plan or type any code before calling
22
20
  `classify_task` — **STOP**. You are doing it wrong. Call `classify_task` first.
23
21
 
24
22
  ---
25
23
 
26
24
  ### When to trigger
27
25
 
28
- Every single one of these MUST call `classify_task` BEFORE anything else:
26
+ Every single one of these MUST trigger this analysis and classification flow BEFORE anything else:
29
27
 
30
28
  - User pastes a work item: `"WI-2847: Add MFA enforcement..."` / `"BUG-1140: Tenant deletion reports failure..."`
31
29
  - User describes a bug: `"There is a null pointer in the events service"` / `"The dashboard is showing wrong device count"`
package/index.js CHANGED
@@ -1605,72 +1605,67 @@ server.tool("classify_task", "MANDATORY FIRST STEP for every task without except
1605
1605
  if (corpus.includes("pure ui") || corpus.includes("no backend changes") || corpus.includes("frontend only")) {
1606
1606
  backendScore = 0;
1607
1607
  }
1608
- // Determine classification
1609
- let classification;
1610
- let confidence = "HIGH";
1611
- if (backendScore > 0 && frontendScore === 0 && extensionScore === 0) {
1612
- classification = "BACKEND_ONLY";
1613
- }
1614
- else if (frontendScore > 0 && backendScore === 0 && extensionScore === 0) {
1615
- classification = "FRONTEND_ONLY";
1616
- }
1617
- else if (extensionScore > 0 && backendScore === 0 && frontendScore === 0) {
1618
- classification = "EXTENSION_ONLY";
1619
- }
1620
- else if (backendScore > 0 && (frontendScore > 0 || extensionScore > 0)) {
1621
- classification = "FULL_STACK";
1622
- }
1623
- else if (frontendScore > 0 && extensionScore > 0 && backendScore === 0) {
1624
- // Both are non-backend — treat as frontend
1625
- classification = "FRONTEND_ONLY";
1626
- }
1627
- else {
1628
- // All scores zero — ambiguous, default to backend
1629
- classification = "BACKEND_ONLY";
1630
- confidence = "LOW";
1631
- }
1632
- // Bug special rule: bugs default to backend unless explicitly UI
1633
- if (task_type === "bug" && confidence === "LOW") {
1634
- classification = "BACKEND_ONLY";
1635
- confidence = "LOW";
1636
- }
1608
+ // Determine classification will be done after Pass 4.5
1637
1609
  // ── Step 4.5: Problem statement validation ───────────────────────────────────
1638
1610
  const descLower = description.toLowerCase();
1639
1611
  let titleAccurate = true;
1640
1612
  let suggestedTitle = "";
1641
1613
  let suggestedTitleReason = "";
1642
1614
  // CHECK 1: Title accuracy
1643
- const verbs = ["delete", "create", "fetch", "update", "add", "remove", "fix", "implement"];
1644
- const nouns = ["tenant", "admin", "device", "policy", "gateway", "notification", "audit", "role", "permission", "user", "group", "mapping", "api", "endpoint", "dashboard", "db", "database"];
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
- }
1657
- const foundConcepts = [];
1658
- [...verbs, ...nouns, ...services].forEach(word => {
1659
- if (descLower.includes(word))
1660
- foundConcepts.push(word);
1661
- });
1662
- const titleLower = title.toLowerCase();
1663
- const matchedConcepts = foundConcepts.filter(c => titleLower.includes(c));
1664
- if (foundConcepts.length > 0 && matchedConcepts.length < (foundConcepts.length * 0.5)) {
1665
- titleAccurate = false;
1666
- const mainAction = verbs.find(v => descLower.includes(v)) || "Update";
1667
- const mainEntity = nouns.find(n => descLower.includes(n)) || "component";
1668
- const serviceName = services.find(s => descLower.includes(s)) || "service";
1669
- suggestedTitle = `${task_type.toUpperCase()}: ${mainAction} ${mainEntity} in ${serviceName}`;
1670
- suggestedTitleReason = "Title misses major concept from description";
1671
- if (title === "Tenant dashboard not loading" && descLower.includes("500") && descLower.includes("deletion")) {
1672
- suggestedTitle = "BUG: Fix false HTTP 500 response on successful tenant deletion in sfn-tenants-api";
1673
- suggestedTitleReason = "Title says dashboard loading issue but description is about API response after successful DB deletion";
1615
+ const STOP_WORDS = new Set([
1616
+ "the", "a", "an", "is", "are", "was", "were",
1617
+ "be", "been", "being", "have", "has", "had",
1618
+ "do", "does", "did", "will", "would", "could",
1619
+ "should", "may", "might", "shall", "can",
1620
+ "to", "of", "in", "on", "at", "by", "for",
1621
+ "with", "about", "against", "between", "into",
1622
+ "through", "during", "before", "after", "above",
1623
+ "below", "from", "up", "down", "out", "off",
1624
+ "over", "under", "again", "further", "then",
1625
+ "once", "and", "but", "or", "nor", "so", "yet",
1626
+ "both", "either", "neither", "not", "only",
1627
+ "same", "than", "too", "very", "just", "that",
1628
+ "this", "these", "those", "it", "its", "itself",
1629
+ "which", "who", "whom", "what", "where", "when",
1630
+ "why", "how", "all", "each", "every",
1631
+ "few", "more", "most", "other", "some", "such",
1632
+ "no", "own", "also", "instead", "rather",
1633
+ "shows", "show", "display", "displays", "showing", "shown",
1634
+ "currently", "now"
1635
+ ]);
1636
+ function extractSymptomWords(text) {
1637
+ return text
1638
+ .toLowerCase()
1639
+ .replace(/[^a-z0-9\s]/g, ' ')
1640
+ .split(/\s+/)
1641
+ .filter(w => w.length > 3)
1642
+ .filter(w => !STOP_WORDS.has(w))
1643
+ .filter(w => !w.match(/^\d+$/));
1644
+ }
1645
+ const titleSymptoms = extractSymptomWords(title);
1646
+ const descSymptoms = extractSymptomWords(description);
1647
+ const firstSentence = description.split('.')[0] || "";
1648
+ const coreSymptoms = extractSymptomWords(firstSentence).slice(0, 5);
1649
+ const titleCoverage = coreSymptoms.filter(sym => titleSymptoms.some(ts => ts.includes(sym) || sym.includes(ts))).length / Math.max(coreSymptoms.length, 1);
1650
+ titleAccurate = titleCoverage >= 0.4;
1651
+ function detectDomain(text) {
1652
+ const t = text.toLowerCase();
1653
+ if (t.includes("modal") || t.includes("display") || t.includes("shows") || t.includes("screen"))
1654
+ return "display/UI";
1655
+ if (t.includes("database") || t.includes("query") || t.includes("table"))
1656
+ return "database";
1657
+ return "unknown";
1658
+ }
1659
+ if (!titleAccurate) {
1660
+ const titleDomain = detectDomain(title);
1661
+ const descDomain = detectDomain(description);
1662
+ if (titleDomain === descDomain && titleDomain !== "unknown") {
1663
+ titleAccurate = true;
1664
+ // Will add to advisories later
1665
+ }
1666
+ else {
1667
+ suggestedTitle = "BUG: " + firstSentence.slice(0, 50) + "...";
1668
+ suggestedTitleReason = "Title symptoms do not match description core symptoms";
1674
1669
  }
1675
1670
  }
1676
1671
  // CHECK 2: Problem statement completeness
@@ -1744,25 +1739,24 @@ server.tool("classify_task", "MANDATORY FIRST STEP for every task without except
1744
1739
  let taskTypeCorrect = true;
1745
1740
  let suggestedTaskType = "";
1746
1741
  let taskTypeReason = "";
1747
- if (task_type === "bug" && (descLower.includes("add new feature") || descLower.includes("implement a new") || descLower.includes("create"))) {
1742
+ const DEFECT_SIGNALS = [
1743
+ "instead of", "rather than", "shows",
1744
+ "displays", "not showing", "not displaying",
1745
+ "incorrect", "wrong", "broken", "failing",
1746
+ "error", "exception", "crash", "null",
1747
+ "missing", "unexpected", "should show",
1748
+ "should display", "expected", "actual",
1749
+ "bug", "fix", "issue", "problem",
1750
+ "not working", "doesn't work", "fails"
1751
+ ];
1752
+ const hasDefectSignal = DEFECT_SIGNALS.some(s => (title + " " + description).toLowerCase().includes(s));
1753
+ if (task_type === "bug" && !hasDefectSignal) {
1748
1754
  taskTypeCorrect = false;
1749
1755
  suggestedTaskType = "feature";
1750
- taskTypeReason = "Description implements new functionality, not fixing a defect";
1756
+ taskTypeReason = "No defect signals found in description";
1751
1757
  }
1752
- else if (task_type === "user_story" && (descLower.includes("fix") || descLower.includes("broken") || descLower.includes("error") || descLower.includes("exception") || descLower.includes("failing"))) {
1753
- taskTypeCorrect = false;
1754
- suggestedTaskType = "bug";
1755
- taskTypeReason = "Description describes fixing a defect, not a user story";
1756
- }
1757
- else if (task_type === "refactor" && (descLower.includes("users are reporting") || descLower.includes("production issue"))) {
1758
- taskTypeCorrect = false;
1759
- suggestedTaskType = "hotfix";
1760
- taskTypeReason = "Description implies a production issue, not a refactor";
1761
- }
1762
- else if (task_type === "chore" && (descLower.includes("core business logic") || descLower.includes("endpoint"))) {
1763
- taskTypeCorrect = false;
1764
- suggestedTaskType = "feature";
1765
- taskTypeReason = "Description involves functional changes, not a chore";
1758
+ else {
1759
+ taskTypeCorrect = true;
1766
1760
  }
1767
1761
  // CHECK 6: SecuFusion-specific validation
1768
1762
  const secufusionFlags = [];
@@ -1771,15 +1765,88 @@ server.tool("classify_task", "MANDATORY FIRST STEP for every task without except
1771
1765
  secufusionFlags.push(`This may re-introduce rejected pattern #${r.id}: ${r.pattern}`);
1772
1766
  }
1773
1767
  });
1768
+ const architectureOwnershipSignals = [];
1769
+ let ownershipNotes = [];
1774
1770
  // Dynamically inject coding patterns from project spec
1775
1771
  if (spec && spec.coding_patterns) {
1776
- Object.entries(spec.coding_patterns).forEach(([key, rule]) => {
1777
- const keyTokens = key.toLowerCase().split('_');
1778
- // if any token from the key is in the corpus, add the rule
1779
- if (keyTokens.some(token => corpus.includes(token))) {
1780
- secufusionFlags.push(`Architecture Rule (${key}): ${rule}`);
1772
+ const FRONTEND_OWNERSHIP_PHRASES = [
1773
+ "frontend must", "frontend should", "handled by the frontend",
1774
+ "must be handled by the frontend", "frontend handles", "ui must",
1775
+ "client must", "client side", "never returns pre-formatted",
1776
+ "frontend converts", "browser must", "only produces", "backend only produces"
1777
+ ];
1778
+ const BACKEND_OWNERSHIP_PHRASES = [
1779
+ "backend must", "backend should", "server must", "service must",
1780
+ "always scoped with", "must filter by", "never expose",
1781
+ "controller resolves", "service layer must"
1782
+ ];
1783
+ const extractKw = (text) => text.toLowerCase().replace(/[^a-z0-9\s]/g, ' ').split(/\s+/).filter(w => w.length > 3);
1784
+ for (const [ruleName, ruleText] of Object.entries(spec.coding_patterns)) {
1785
+ const rText = ruleText;
1786
+ const ruleTextLower = rText.toLowerCase();
1787
+ const descLowerCombined = (title + " " + description).toLowerCase();
1788
+ const ruleKeywords = extractKw(rText);
1789
+ const taskMentionsRule = ruleKeywords.some(kw => descLowerCombined.includes(kw));
1790
+ if (!taskMentionsRule)
1791
+ continue;
1792
+ const isFrontendOwnership = FRONTEND_OWNERSHIP_PHRASES.some(phrase => ruleTextLower.includes(phrase));
1793
+ const isBackendOwnership = BACKEND_OWNERSHIP_PHRASES.some(phrase => ruleTextLower.includes(phrase));
1794
+ if (isFrontendOwnership) {
1795
+ frontendScore += 8;
1796
+ ownershipNotes.push(`Architecture rule '${ruleName}' confirms frontend ownership: ${rText.slice(0, 80)}...`);
1797
+ architectureOwnershipSignals.push({
1798
+ rule: ruleName,
1799
+ direction: "FRONTEND",
1800
+ note: rText
1801
+ });
1781
1802
  }
1782
- });
1803
+ else if (isBackendOwnership) {
1804
+ backendScore += 8;
1805
+ ownershipNotes.push(`Architecture rule '${ruleName}' confirms backend ownership: ${rText.slice(0, 80)}...`);
1806
+ architectureOwnershipSignals.push({
1807
+ rule: ruleName,
1808
+ direction: "BACKEND",
1809
+ note: rText
1810
+ });
1811
+ }
1812
+ else {
1813
+ secufusionFlags.push(`Note — Architecture Rule (${ruleName}): ${rText.slice(0, 100)}... (verify this applies to your implementation)`);
1814
+ }
1815
+ }
1816
+ }
1817
+ // Pass 5 — Confidence gate & Classification assignment
1818
+ let classification;
1819
+ let confidence = "HIGH";
1820
+ if (backendScore > 0 && frontendScore === 0 && extensionScore === 0) {
1821
+ classification = "BACKEND_ONLY";
1822
+ }
1823
+ else if (frontendScore > 0 && backendScore === 0 && extensionScore === 0) {
1824
+ classification = "FRONTEND_ONLY";
1825
+ }
1826
+ else if (extensionScore > 0 && backendScore === 0 && frontendScore === 0) {
1827
+ classification = "EXTENSION_ONLY";
1828
+ }
1829
+ else if (backendScore > 0 && (frontendScore > 0 || extensionScore > 0)) {
1830
+ if (frontendScore > backendScore * 2.5) {
1831
+ classification = "FRONTEND_ONLY";
1832
+ }
1833
+ else if (backendScore > frontendScore * 2.5) {
1834
+ classification = "BACKEND_ONLY";
1835
+ }
1836
+ else {
1837
+ classification = "FULL_STACK";
1838
+ }
1839
+ }
1840
+ else if (frontendScore > 0 && extensionScore > 0 && backendScore === 0) {
1841
+ classification = "FRONTEND_ONLY";
1842
+ }
1843
+ else {
1844
+ classification = "BACKEND_ONLY";
1845
+ confidence = "LOW";
1846
+ }
1847
+ if (task_type === "bug" && confidence === "LOW") {
1848
+ classification = "BACKEND_ONLY";
1849
+ confidence = "LOW";
1783
1850
  }
1784
1851
  let validationVerdict = "CLEAN";
1785
1852
  if (!titleAccurate || !taskTypeCorrect || (task_type === "bug" && rootCauseAssumed)) {
@@ -1893,10 +1960,22 @@ server.tool("classify_task", "MANDATORY FIRST STEP for every task without except
1893
1960
  else if (classification === "FULL_STACK") {
1894
1961
  allowedNextAction = "CONFIRM";
1895
1962
  nextPhase = "Confirm backend scope, then plan API contract first";
1963
+ const backendScopeLines = [];
1964
+ const matchedServiceNames = backendFound.filter(s => s.startsWith("sfn-"));
1965
+ const matchedTechConstructs = backendFound.filter(s => !s.startsWith("sfn-"));
1966
+ if (matchedServiceNames.length > 0) {
1967
+ backendScopeLines.push(`Services: ${[...new Set(matchedServiceNames)].join(", ")}`);
1968
+ }
1969
+ if (matchedTechConstructs.length > 0) {
1970
+ backendScopeLines.push(`Changes: ${[...new Set(matchedTechConstructs)].join(", ")}`);
1971
+ }
1972
+ const backendScopeText = backendScopeLines.length > 0
1973
+ ? backendScopeLines.join("\n → ")
1974
+ : "Backend logic changes required";
1896
1975
  developerMessage =
1897
1976
  `⚠️ FULL STACK TASK\n\n` +
1898
- `Backend scope (yours): ${backendFound.slice(0, 8).join(", ")}\n` +
1899
- `Frontend scope (not yours): ${frontendFound.slice(0, 8).join(", ")}\n\n` +
1977
+ `Backend scope (yours):\n → ${backendScopeText}\n` +
1978
+ `Frontend scope (not yours): ${[...new Set(frontendFound)].slice(0, 8).join(", ")}\n\n` +
1900
1979
  `I will implement the backend only and define the API contract first.\n` +
1901
1980
  `Confirm and I will proceed to plan.`;
1902
1981
  }
@@ -1942,6 +2021,10 @@ server.tool("classify_task", "MANDATORY FIRST STEP for every task without except
1942
2021
  finalDeveloperMessage += "\n────────────────────────────────────────\n";
1943
2022
  }
1944
2023
  }
2024
+ if (architectureOwnershipSignals.length > 0) {
2025
+ finalDeveloperMessage += "📐 Architecture ownership signals:\n" +
2026
+ architectureOwnershipSignals.map(s => ` → [${s.direction}] Rule '${s.rule}': ${s.note.slice(0, 80)}...`).join("\n") + "\n\n";
2027
+ }
1945
2028
  if (learnedLogs.length > 0) {
1946
2029
  finalDeveloperMessage += `🧠 RETROSPECTIVE INTELLIGENCE APPLIED\n`;
1947
2030
  finalDeveloperMessage += learnedLogs.join("\n") + "\n\n";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "secufusion-mcp",
3
- "version": "1.0.26",
3
+ "version": "1.0.28",
4
4
  "type": "module",
5
5
  "description": "SecuFusion MCP server - developer workflow tooling with guardrails",
6
6
  "main": "index.js",