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.
- package/AGENTS.md +8 -10
- package/index.js +167 -84
- 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 —
|
|
10
|
+
### THE RULE — Read, Examine, Then Classify
|
|
11
11
|
|
|
12
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
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
|
-
|
|
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 = "
|
|
1756
|
+
taskTypeReason = "No defect signals found in description";
|
|
1751
1757
|
}
|
|
1752
|
-
else
|
|
1753
|
-
taskTypeCorrect =
|
|
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
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
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)
|
|
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";
|