secufusion-mcp 1.0.27 → 1.0.29
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/.secufusion-project-spec.json +3 -0
- package/index.js +244 -81
- package/package.json +1 -1
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
"branch_naming_convention": "Mixed, not strictly enforced. Common patterns: feature/<kebab-description>, fix/<desc> or bugfix/<ticket-or-desc>, ticket-only (TASK-####, TASK-####-<desc>, BUG-####-<desc>), release-<major>.<minor>.<patch> (dash, not slash) for release branches. Trunk branches are 'main' and 'develop' in every repo, but which one is checked out as the working default varies per repo (sfn-events-api and sfn-web-ui default to develop; snf-browser-extn defaults to main). Many ad hoc/free-text branch names also exist.",
|
|
13
13
|
"commit_message_pattern": "No single enforced convention. Recurring mixture: (1) Azure Repos auto-generated 'Merged PR <number>: <title>' on PR completion (dominant pattern in all 3 sampled repos), (2) ticket-prefixed 'TASK-<number>: <desc>' or 'TASK-<number>-<desc>' (common in sfn-events-api), (3) conventional-commit style 'feat(<scope>): ...' / 'fix(<scope>): ...' (used consistently within recent 'ai-ops' feature work in sfn-web-ui, not elsewhere), (4) plain imperative descriptive messages with no prefix at all. Work items referenced via https://dev.azure.com/secufusion/SFCloud-MSSP/_workitems/edit/<id>."
|
|
14
14
|
},
|
|
15
|
+
"coding_patterns": {
|
|
16
|
+
"timestamp_handling": "backend ONLY produces UTC timestamp strings. All time formatting and local timezone conversion MUST be handled by the frontend."
|
|
17
|
+
},
|
|
15
18
|
"microservices": {
|
|
16
19
|
"sfn-auth-api": {
|
|
17
20
|
"artifact_id": "sfn-auth-api",
|
package/index.js
CHANGED
|
@@ -1462,6 +1462,73 @@ server.tool("record_retrospective", "Records a structured retrospective after ta
|
|
|
1462
1462
|
}
|
|
1463
1463
|
return appendTelemetry({ content: [{ type: "text", text: out }] }, inputChars);
|
|
1464
1464
|
});
|
|
1465
|
+
const STOP_WORDS = new Set([
|
|
1466
|
+
"the", "a", "an", "is", "are", "was", "were", "be",
|
|
1467
|
+
"been", "being", "have", "has", "had", "do", "does",
|
|
1468
|
+
"did", "will", "would", "could", "should", "may",
|
|
1469
|
+
"might", "shall", "can", "to", "of", "in", "on", "at",
|
|
1470
|
+
"by", "for", "with", "about", "against", "between",
|
|
1471
|
+
"into", "through", "during", "before", "after",
|
|
1472
|
+
"from", "up", "down", "out", "off", "over", "under",
|
|
1473
|
+
"and", "but", "or", "nor", "so", "yet", "both",
|
|
1474
|
+
"either", "neither", "not", "only", "same", "than",
|
|
1475
|
+
"too", "very", "just", "that", "this", "these",
|
|
1476
|
+
"those", "it", "its", "itself", "which", "who",
|
|
1477
|
+
"what", "where", "when", "why", "how", "all", "each",
|
|
1478
|
+
"every", "few", "more", "most", "other", "some",
|
|
1479
|
+
"such", "no", "own", "also", "instead", "rather",
|
|
1480
|
+
"shows", "show", "display", "displays", "showing",
|
|
1481
|
+
"shown", "currently", "now", "then", "also", "there",
|
|
1482
|
+
"their", "they", "them", "we", "our", "your", "my",
|
|
1483
|
+
"his", "her", "its", "page", "modal", "screen"
|
|
1484
|
+
]);
|
|
1485
|
+
function extractSymptomWords(text) {
|
|
1486
|
+
return text
|
|
1487
|
+
.toLowerCase()
|
|
1488
|
+
.replace(/[^a-z0-9\s]/g, " ")
|
|
1489
|
+
.split(/\s+/)
|
|
1490
|
+
.filter(w => w.length > 3)
|
|
1491
|
+
.filter(w => !STOP_WORDS.has(w))
|
|
1492
|
+
.filter(w => !/^\d+$/.test(w));
|
|
1493
|
+
}
|
|
1494
|
+
function levenshteinClose(a, b, threshold = 2) {
|
|
1495
|
+
if (Math.abs(a.length - b.length) > threshold)
|
|
1496
|
+
return false;
|
|
1497
|
+
let matrix = Array.from({ length: a.length + 1 }, (_, i) => [i, ...Array(b.length).fill(0)]);
|
|
1498
|
+
for (let j = 0; j <= b.length; j++)
|
|
1499
|
+
matrix[0][j] = j;
|
|
1500
|
+
for (let i = 1; i <= a.length; i++) {
|
|
1501
|
+
for (let j = 1; j <= b.length; j++) {
|
|
1502
|
+
matrix[i][j] = a[i - 1] === b[j - 1]
|
|
1503
|
+
? matrix[i - 1][j - 1]
|
|
1504
|
+
: 1 + Math.min(matrix[i - 1][j], matrix[i][j - 1], matrix[i - 1][j - 1]);
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
return matrix[a.length][b.length] <= threshold;
|
|
1508
|
+
}
|
|
1509
|
+
function buildScopeText(signals, domain) {
|
|
1510
|
+
if (signals.length === 0) {
|
|
1511
|
+
return `${domain} changes required`;
|
|
1512
|
+
}
|
|
1513
|
+
const serviceNames = signals.filter(s => s.includes("-api") || s.includes("-service") ||
|
|
1514
|
+
s.includes("sfn-"));
|
|
1515
|
+
const techTerms = signals.filter(s => !s.includes("-api") && !s.includes("-service") &&
|
|
1516
|
+
!s.includes("sfn-"));
|
|
1517
|
+
const parts = [];
|
|
1518
|
+
if (serviceNames.length > 0) {
|
|
1519
|
+
parts.push(`Services: ${serviceNames.join(", ")}`);
|
|
1520
|
+
}
|
|
1521
|
+
if (techTerms.length > 0 && techTerms.length <= 4) {
|
|
1522
|
+
parts.push(`Changes: ${techTerms.join(", ")}`);
|
|
1523
|
+
}
|
|
1524
|
+
else if (techTerms.length > 4) {
|
|
1525
|
+
parts.push(`Changes: ${techTerms.slice(0, 3).join(", ")}` +
|
|
1526
|
+
` (+${techTerms.length - 3} more)`);
|
|
1527
|
+
}
|
|
1528
|
+
return parts.length > 0
|
|
1529
|
+
? parts.join("\n ")
|
|
1530
|
+
: `${domain} implementation required`;
|
|
1531
|
+
}
|
|
1465
1532
|
// ─── Tool 9: classify_task ────────────────────────────────────────────────────
|
|
1466
1533
|
server.tool("classify_task", "MANDATORY FIRST STEP for every task without exception. " +
|
|
1467
1534
|
"Classifies a task as BACKEND_ONLY, FRONTEND_ONLY, FULL_STACK, or EXTENSION_ONLY. " +
|
|
@@ -1605,43 +1672,13 @@ server.tool("classify_task", "MANDATORY FIRST STEP for every task without except
|
|
|
1605
1672
|
if (corpus.includes("pure ui") || corpus.includes("no backend changes") || corpus.includes("frontend only")) {
|
|
1606
1673
|
backendScore = 0;
|
|
1607
1674
|
}
|
|
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
|
-
}
|
|
1675
|
+
// Determine classification will be done after Pass 4.5
|
|
1637
1676
|
// ── Step 4.5: Problem statement validation ───────────────────────────────────
|
|
1638
1677
|
const descLower = description.toLowerCase();
|
|
1639
1678
|
let titleAccurate = true;
|
|
1640
1679
|
let suggestedTitle = "";
|
|
1641
1680
|
let suggestedTitleReason = "";
|
|
1642
1681
|
// 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
1682
|
const services = [];
|
|
1646
1683
|
if (spec) {
|
|
1647
1684
|
if (spec.microservices)
|
|
@@ -1654,23 +1691,45 @@ server.tool("classify_task", "MANDATORY FIRST STEP for every task without except
|
|
|
1654
1691
|
if (services.length === 0) {
|
|
1655
1692
|
services.push("sfn-iam-api", "sfn-events-api", "sfn-tenants-api", "sfn-policy-api", "sfn-gateway-api", "sfn-web-ui");
|
|
1656
1693
|
}
|
|
1657
|
-
const
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
const
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1694
|
+
const firstSentence = description.split(/[.!?]/)[0] || description;
|
|
1695
|
+
const coreSymptoms = extractSymptomWords(firstSentence).slice(0, 6);
|
|
1696
|
+
const titleWords = extractSymptomWords(title);
|
|
1697
|
+
// Title is accurate if covers >= 35% of
|
|
1698
|
+
// core symptom words from first sentence
|
|
1699
|
+
const matchCount = coreSymptoms.filter(sym => titleWords.some(tw => tw.includes(sym) || sym.includes(tw) ||
|
|
1700
|
+
levenshteinClose(tw, sym))).length;
|
|
1701
|
+
const titleCoverage = coreSymptoms.length > 0
|
|
1702
|
+
? matchCount / coreSymptoms.length
|
|
1703
|
+
: 1.0;
|
|
1704
|
+
titleAccurate = titleCoverage >= 0.35;
|
|
1705
|
+
// Secondary check: if title and desc are
|
|
1706
|
+
// about same domain → not misleading
|
|
1707
|
+
// even if coverage is low
|
|
1708
|
+
if (!titleAccurate) {
|
|
1709
|
+
const DISPLAY_WORDS = [
|
|
1710
|
+
"show", "display", "render", "modal", "page",
|
|
1711
|
+
"screen", "ui", "view", "format", "time", "date",
|
|
1712
|
+
"timezone", "utc", "local", "color", "layout",
|
|
1713
|
+
"button", "icon", "text", "label"
|
|
1714
|
+
];
|
|
1715
|
+
const BACKEND_WORDS = [
|
|
1716
|
+
"api", "service", "endpoint", "db", "database",
|
|
1717
|
+
"query", "kafka", "migration", "entity", "null",
|
|
1718
|
+
"exception", "500", "error", "timeout", "token"
|
|
1719
|
+
];
|
|
1720
|
+
const titleIsDisplay = DISPLAY_WORDS.some(w => title.toLowerCase().includes(w));
|
|
1721
|
+
const descIsDisplay = DISPLAY_WORDS.some(w => description.toLowerCase().includes(w));
|
|
1722
|
+
const titleIsBackend = BACKEND_WORDS.some(w => title.toLowerCase().includes(w));
|
|
1723
|
+
const descIsBackend = BACKEND_WORDS.some(w => description.toLowerCase().includes(w));
|
|
1724
|
+
// Same domain = not misleading
|
|
1725
|
+
if ((titleIsDisplay && descIsDisplay) ||
|
|
1726
|
+
(titleIsBackend && descIsBackend)) {
|
|
1727
|
+
titleAccurate = true;
|
|
1728
|
+
// Optional advisory — not a blocking flag
|
|
1729
|
+
}
|
|
1730
|
+
else {
|
|
1731
|
+
suggestedTitle = "BUG: " + firstSentence.slice(0, 50) + "...";
|
|
1732
|
+
suggestedTitleReason = "Title symptoms do not match description core symptoms";
|
|
1674
1733
|
}
|
|
1675
1734
|
}
|
|
1676
1735
|
// CHECK 2: Problem statement completeness
|
|
@@ -1740,30 +1799,26 @@ server.tool("classify_task", "MANDATORY FIRST STEP for every task without except
|
|
|
1740
1799
|
scopeQuestions.push("2. Which component or service is affected?");
|
|
1741
1800
|
}
|
|
1742
1801
|
}
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
taskTypeCorrect = false;
|
|
1764
|
-
suggestedTaskType = "feature";
|
|
1765
|
-
taskTypeReason = "Description involves functional changes, not a chore";
|
|
1766
|
-
}
|
|
1802
|
+
const DEFECT_SIGNALS = [
|
|
1803
|
+
"instead of", "rather than", "incorrect", "wrong",
|
|
1804
|
+
"broken", "failing", "error", "exception", "crash",
|
|
1805
|
+
"null", "missing", "unexpected", "should show",
|
|
1806
|
+
"should display", "expected", "actual", "not working",
|
|
1807
|
+
"doesn't work", "fails", "not showing", "bug",
|
|
1808
|
+
"fix", "issue", "problem", "invalid", "400", "500",
|
|
1809
|
+
"404", "403", "401", "timeout", "slow", "blank",
|
|
1810
|
+
"empty", "duplicate", "stale", "outdated"
|
|
1811
|
+
];
|
|
1812
|
+
const hasDefectSignal = DEFECT_SIGNALS.some(s => (title + " " + description)
|
|
1813
|
+
.toLowerCase()
|
|
1814
|
+
.includes(s));
|
|
1815
|
+
// Only flag wrong task type when NO defect
|
|
1816
|
+
// signals exist at all
|
|
1817
|
+
let taskTypeCorrect = task_type !== "bug" || hasDefectSignal;
|
|
1818
|
+
let suggestedTaskType = !taskTypeCorrect ? "feature" : task_type;
|
|
1819
|
+
let taskTypeReason = !taskTypeCorrect
|
|
1820
|
+
? "No defect signals found in title or description. Description appears to implement new functionality."
|
|
1821
|
+
: "";
|
|
1767
1822
|
// CHECK 6: SecuFusion-specific validation
|
|
1768
1823
|
const secufusionFlags = [];
|
|
1769
1824
|
rejectedPatterns.forEach(r => {
|
|
@@ -1771,15 +1826,107 @@ server.tool("classify_task", "MANDATORY FIRST STEP for every task without except
|
|
|
1771
1826
|
secufusionFlags.push(`This may re-introduce rejected pattern #${r.id}: ${r.pattern}`);
|
|
1772
1827
|
}
|
|
1773
1828
|
});
|
|
1774
|
-
|
|
1829
|
+
const architectureOwnershipSignals = [];
|
|
1830
|
+
const FRONTEND_OWNERSHIP_PHRASES = [
|
|
1831
|
+
"frontend must",
|
|
1832
|
+
"frontend should",
|
|
1833
|
+
"handled by the frontend",
|
|
1834
|
+
"must be handled by the frontend",
|
|
1835
|
+
"frontend handles",
|
|
1836
|
+
"frontend converts",
|
|
1837
|
+
"ui must",
|
|
1838
|
+
"client must",
|
|
1839
|
+
"client side",
|
|
1840
|
+
"never returns pre-formatted",
|
|
1841
|
+
"only produces",
|
|
1842
|
+
"backend only produces",
|
|
1843
|
+
"browser must",
|
|
1844
|
+
"all time formatting",
|
|
1845
|
+
"timezone conversion must"
|
|
1846
|
+
];
|
|
1847
|
+
const BACKEND_OWNERSHIP_PHRASES = [
|
|
1848
|
+
"backend must",
|
|
1849
|
+
"backend should",
|
|
1850
|
+
"server must",
|
|
1851
|
+
"service must",
|
|
1852
|
+
"always scoped with",
|
|
1853
|
+
"must filter by",
|
|
1854
|
+
"never expose",
|
|
1855
|
+
"controller resolves",
|
|
1856
|
+
"service layer must",
|
|
1857
|
+
"repository must",
|
|
1858
|
+
"always include tenantid"
|
|
1859
|
+
];
|
|
1775
1860
|
if (spec && spec.coding_patterns) {
|
|
1776
|
-
Object.entries(spec.coding_patterns)
|
|
1777
|
-
const
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1861
|
+
for (const [ruleName, ruleText] of Object.entries(spec.coding_patterns)) {
|
|
1862
|
+
const ruleStr = String(ruleText).toLowerCase();
|
|
1863
|
+
const taskText = (title + " " + description).toLowerCase();
|
|
1864
|
+
const ruleWords = ruleName
|
|
1865
|
+
.toLowerCase()
|
|
1866
|
+
.replace(/_/g, " ")
|
|
1867
|
+
.split(" ")
|
|
1868
|
+
.filter(w => w.length > 3);
|
|
1869
|
+
const ruleApplies = ruleWords.some(w => taskText.includes(w)) || Object.keys(spec.coding_patterns).some(k => taskText.includes(k.toLowerCase().replace(/_/g, " ")));
|
|
1870
|
+
if (!ruleApplies)
|
|
1871
|
+
continue;
|
|
1872
|
+
const isFrontendOwnership = FRONTEND_OWNERSHIP_PHRASES.some(phrase => ruleStr.includes(phrase));
|
|
1873
|
+
const isBackendOwnership = BACKEND_OWNERSHIP_PHRASES.some(phrase => ruleStr.includes(phrase));
|
|
1874
|
+
if (isFrontendOwnership) {
|
|
1875
|
+
frontendScore = (frontendScore || 0) + 8;
|
|
1876
|
+
architectureOwnershipSignals.push({
|
|
1877
|
+
rule: ruleName,
|
|
1878
|
+
direction: "FRONTEND",
|
|
1879
|
+
note: String(ruleText).slice(0, 120)
|
|
1880
|
+
});
|
|
1781
1881
|
}
|
|
1782
|
-
|
|
1882
|
+
else if (isBackendOwnership) {
|
|
1883
|
+
backendScore = (backendScore || 0) + 8;
|
|
1884
|
+
architectureOwnershipSignals.push({
|
|
1885
|
+
rule: ruleName,
|
|
1886
|
+
direction: "BACKEND",
|
|
1887
|
+
note: String(ruleText).slice(0, 120)
|
|
1888
|
+
});
|
|
1889
|
+
}
|
|
1890
|
+
else {
|
|
1891
|
+
secufusionFlags.push(`Note — Architecture Rule (${ruleName}): ` +
|
|
1892
|
+
`${String(ruleText).slice(0, 100)}` +
|
|
1893
|
+
` (verify this applies to your implementation)`);
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
// Pass 5 — Confidence gate & Classification assignment
|
|
1898
|
+
let classification;
|
|
1899
|
+
let confidence = "HIGH";
|
|
1900
|
+
if (backendScore > 0 && frontendScore === 0 && extensionScore === 0) {
|
|
1901
|
+
classification = "BACKEND_ONLY";
|
|
1902
|
+
}
|
|
1903
|
+
else if (frontendScore > 0 && backendScore === 0 && extensionScore === 0) {
|
|
1904
|
+
classification = "FRONTEND_ONLY";
|
|
1905
|
+
}
|
|
1906
|
+
else if (extensionScore > 0 && backendScore === 0 && frontendScore === 0) {
|
|
1907
|
+
classification = "EXTENSION_ONLY";
|
|
1908
|
+
}
|
|
1909
|
+
else if (backendScore > 0 && (frontendScore > 0 || extensionScore > 0)) {
|
|
1910
|
+
if (frontendScore > backendScore * 2.5) {
|
|
1911
|
+
classification = "FRONTEND_ONLY";
|
|
1912
|
+
}
|
|
1913
|
+
else if (backendScore > frontendScore * 2.5) {
|
|
1914
|
+
classification = "BACKEND_ONLY";
|
|
1915
|
+
}
|
|
1916
|
+
else {
|
|
1917
|
+
classification = "FULL_STACK";
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
else if (frontendScore > 0 && extensionScore > 0 && backendScore === 0) {
|
|
1921
|
+
classification = "FRONTEND_ONLY";
|
|
1922
|
+
}
|
|
1923
|
+
else {
|
|
1924
|
+
classification = "BACKEND_ONLY";
|
|
1925
|
+
confidence = "LOW";
|
|
1926
|
+
}
|
|
1927
|
+
if (task_type === "bug" && confidence === "LOW") {
|
|
1928
|
+
classification = "BACKEND_ONLY";
|
|
1929
|
+
confidence = "LOW";
|
|
1783
1930
|
}
|
|
1784
1931
|
let validationVerdict = "CLEAN";
|
|
1785
1932
|
if (!titleAccurate || !taskTypeCorrect || (task_type === "bug" && rootCauseAssumed)) {
|
|
@@ -1893,10 +2040,18 @@ server.tool("classify_task", "MANDATORY FIRST STEP for every task without except
|
|
|
1893
2040
|
else if (classification === "FULL_STACK") {
|
|
1894
2041
|
allowedNextAction = "CONFIRM";
|
|
1895
2042
|
nextPhase = "Confirm backend scope, then plan API contract first";
|
|
2043
|
+
const uniqueBackendSignals = [
|
|
2044
|
+
...new Set(backendFound.filter(Boolean))
|
|
2045
|
+
];
|
|
2046
|
+
const uniqueFrontendSignals = [
|
|
2047
|
+
...new Set(frontendFound.filter(Boolean))
|
|
2048
|
+
];
|
|
2049
|
+
const backendScopeText = buildScopeText(uniqueBackendSignals, "backend");
|
|
2050
|
+
const frontendScopeText = buildScopeText(uniqueFrontendSignals, "frontend");
|
|
1896
2051
|
developerMessage =
|
|
1897
2052
|
`⚠️ FULL STACK TASK\n\n` +
|
|
1898
|
-
`Backend scope (yours)
|
|
1899
|
-
`Frontend scope (not yours)
|
|
2053
|
+
`Backend scope (yours):\n → ${backendScopeText}\n` +
|
|
2054
|
+
`Frontend scope (not yours):\n → ${frontendScopeText}\n\n` +
|
|
1900
2055
|
`I will implement the backend only and define the API contract first.\n` +
|
|
1901
2056
|
`Confirm and I will proceed to plan.`;
|
|
1902
2057
|
}
|
|
@@ -1942,6 +2097,14 @@ server.tool("classify_task", "MANDATORY FIRST STEP for every task without except
|
|
|
1942
2097
|
finalDeveloperMessage += "\n────────────────────────────────────────\n";
|
|
1943
2098
|
}
|
|
1944
2099
|
}
|
|
2100
|
+
if (architectureOwnershipSignals.length > 0) {
|
|
2101
|
+
finalDeveloperMessage += `\n📐 Architecture ownership signals:\n`;
|
|
2102
|
+
for (const sig of architectureOwnershipSignals) {
|
|
2103
|
+
finalDeveloperMessage +=
|
|
2104
|
+
` → [${sig.direction}] Rule '${sig.rule}':\n` +
|
|
2105
|
+
` ${sig.note}\n`;
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
1945
2108
|
if (learnedLogs.length > 0) {
|
|
1946
2109
|
finalDeveloperMessage += `🧠 RETROSPECTIVE INTELLIGENCE APPLIED\n`;
|
|
1947
2110
|
finalDeveloperMessage += learnedLogs.join("\n") + "\n\n";
|