secufusion-mcp 2.4.1 → 2.5.1

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.
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "secufusion-mcp",
3
3
  "description": "SecuFusion MSSP platform dev agent — project-level spec driven, tenant-isolation enforced, zero-trust guardrails. 5 Core Slash Commands: /sfn-init (dynamic DNA + architecture diagram + watcher), /sfn-plan (philosophy check + intent WHY + classify + plan), /sfn-code (spec-driven implementation), /sfn-review (zero-tolerance 3-tier PR gate), /sfn-explore (on-demand service graph or blast-radius analysis).",
4
- "version": "2.4.1",
4
+ "version": "2.5.1",
5
5
  "author": {
6
6
  "name": "Motivity Labs"
7
7
  },
8
8
  "homepage": "https://github.com/MLMCPS/secufusion-mcp",
9
9
  "license": "ISC"
10
- }
10
+ }
package/agents/AGENTS.md CHANGED
@@ -45,15 +45,15 @@ Call `philosophy_check` AFTER DNA is loaded — never before. The engine us
45
45
  - Which Kafka topics fan out to how many consumers
46
46
  - Whether your description violates any golden rules
47
47
 
48
- **This check is completely invisible to the user.** You do not announce it. You do not show scores, verdicts, or "philosophy" terminology. You surface a concern to the user only if the verdict is FAIL or BLOCKED — and you translate it into plain developer language.
48
+ **This check is completely invisible to the user.** You do not announce it. You do not show verdicts or "philosophy" terminology. You surface a concern to the user only if the architectural risk is high or the intent is missing — and you translate it into plain developer language.
49
49
 
50
- For `bug` and `hotfix`: philosophy check **fully runs** — no skip. The scoring engine applies relaxed thresholds: WHY gets a +20 head-start (something is broken = implicit justification), WHO gets a +15 head-start (reporter = implicit authority), and `NO_JUSTIFICATION` is not a blocking verdict. However, ADVISORY and FAIL still fire normally — a high-risk bug fix (auth regression, data-loss, multi-tenant blast) will still be challenged.
50
+ For `bug` and `hotfix`: philosophy check **fully runs** — no skip. The agent acts as the reasoning engine against the Project DNA. Bugs inherently have a "WHY" (something is broken) and an implicit authority (the reporter), but the engine evaluates the **blast radius and architectural risk**. A high-risk bug fix (auth regression, data-loss, multi-tenant blast) will still be challenged if the rollback plan or scope isn't clear.
51
51
 
52
52
  **Verdicts:**
53
- - `PHILOSOPHY_PASS` → Silent. Proceed immediately.
54
- - `PHILOSOPHY_ADVISORY` → Note concerns in `decisions.json`. Tell the user briefly what to watch out for. Continue.
55
- - `PHILOSOPHY_FAIL` → STOP. Ask the user what is missing (no jargon). Wait for answers. Re-run philosophy_check.
56
- - `PHILOSOPHY_BLOCKED` → STOP completely. Tell the user the risk and who must sign off. Do not plan. Do not code.
53
+ - **Silent Pass** → Bug is isolated or safe. Proceed immediately.
54
+ - **Advisory** → Note concerns in `decisions.json`. Tell the user briefly what to watch out for. Continue.
55
+ - **Fail** → STOP. Ask the user what is missing (no jargon). Wait for answers. Re-run philosophy_check.
56
+ - **Blocked** → STOP completely. Tell the user the risk and who must sign off. Do not plan. Do not code.
57
57
 
58
58
  ---
59
59
 
@@ -323,4 +323,4 @@ Java Microservices (all behind gateway):
323
323
  Services WITHOUT migrations: `sfn-auth-api`, `sfn-eureka-api`, `sfn-gateway-api`, `sfn-policy-api`, `sfn-web-ui`, `snf-browser-extn`
324
324
 
325
325
  NEVER guess migration version numbers. Always read `.secufusion-migrations.json` first.
326
-
326
+
@@ -1547,40 +1547,6 @@ server.tool("philosophy_check", "MANDATORY PHASE -1 STEP — runs BEFORE classif
1547
1547
  client_confirmed: z.boolean().optional().describe("Has the decision maker explicitly confirmed this is needed?"),
1548
1548
  }, async ({ work_item_id, title, description, task_type, reported_by, client_confirmed }) => {
1549
1549
  const inputChars = JSON.stringify({ work_item_id, title, description, task_type }).length;
1550
- // Bugs and hotfixes run through the full philosophy engine with relaxed scoring:
1551
- // WHY → +20 head-start (defects have an inherent "WHY": something is broken)
1552
- // WHO → +15 head-start (reporter is implicit authority for a defect)
1553
- // RISK → NO_JUSTIFICATION block exempted for bug/hotfix (see Pass 5)
1554
- // This ensures high-risk bug fixes (auth regressions, data-loss fixes, multi-tenant
1555
- // defects) still surface ADVISORY or FAIL when the blast radius warrants it.
1556
- // Check cache
1557
- const philosophyDir = resolve(".secufusion/philosophy");
1558
- const philosophyFile = path.join(philosophyDir, `${work_item_id}.json`);
1559
- let cached = null;
1560
- if (fs.existsSync(philosophyFile)) {
1561
- try {
1562
- cached = JSON.parse(readFileSafe(philosophyFile) || "null");
1563
- if (cached && (cached.philosophy_verdict === "PHILOSOPHY_PASS" || cached.philosophy_verdict === "PHILOSOPHY_ADVISORY")) {
1564
- return appendTelemetry({ content: [{ type: "text", text: `[PHILOSOPHY CACHED] ${work_item_id} — verdict: ${cached.philosophy_verdict}\n\n${cached.developer_message}` }] }, inputChars);
1565
- }
1566
- }
1567
- catch { }
1568
- }
1569
- // Mandatory interview for ALL work items (features, bugs, chores, etc.)
1570
- if (!cached || !cached.interview_completed) {
1571
- const interviewText = `[PHILOSOPHY FAIL] ${work_item_id}\n\nBefore I help you build this, talk to me.\n\nWhat problem is this solving?\nWalk me through what the user is experiencing today\nwithout this feature. What's broken? What's painful?\nWhat happens if we don't build this at all?\n\nWho will actually use this and how often?\nIs this solving a problem for 1 client or all tenants?\n\nI see you said WHO: CONFIRMED —\nbut confirmed by whom exactly?\nIs that a verbal yes in a meeting or\na written requirement from the client?\nThose are very different things.\n\nDo NOT call classify_task. Re-run philosophy_check after resolving.`;
1572
- if (!fs.existsSync(philosophyDir))
1573
- fs.mkdirSync(philosophyDir, { recursive: true });
1574
- try {
1575
- writeFile(philosophyFile, JSON.stringify({
1576
- work_item_id,
1577
- philosophy_verdict: "PHILOSOPHY_FAIL",
1578
- interview_completed: true
1579
- }, null, 2));
1580
- }
1581
- catch { }
1582
- return appendTelemetry({ content: [{ type: "text", text: interviewText }] }, inputChars);
1583
- }
1584
1550
  const wsRoot = getWorkspaceRoot();
1585
1551
  let rejectedPatterns = [];
1586
1552
  const rejRaw = readFileSafe(resolve(REJECTED_FILE));
@@ -1590,12 +1556,8 @@ server.tool("philosophy_check", "MANDATORY PHASE -1 STEP — runs BEFORE classif
1590
1556
  }
1591
1557
  catch { }
1592
1558
  }
1593
- // ── Load Architecture DNA (MANDATORY for intelligent risk scoring) ──────
1594
- // philosophy_check reasons against real architecture — not just keywords.
1595
- // DNA provides: DB entities, Kafka topics, services, extension, auth model.
1596
1559
  let dna = null;
1597
1560
  let spec = null;
1598
- // Load project spec (golden rules, service map, security model)
1599
1561
  const specCands2 = [
1600
1562
  path.resolve(path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, "$1")), ".secufusion-project-spec.json"),
1601
1563
  path.resolve(wsRoot, ".secufusion-project-spec.json"),
@@ -1609,7 +1571,6 @@ server.tool("philosophy_check", "MANDATORY PHASE -1 STEP — runs BEFORE classif
1609
1571
  catch { }
1610
1572
  }
1611
1573
  }
1612
- // Load DNA — full architecture graph
1613
1574
  const dnaCandidates = [
1614
1575
  path.resolve(wsRoot, ".secufusion", "dna.json"),
1615
1576
  path.resolve(path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, "$1")), ".secufusion", "dna.json"),
@@ -1623,327 +1584,26 @@ server.tool("philosophy_check", "MANDATORY PHASE -1 STEP — runs BEFORE classif
1623
1584
  catch { }
1624
1585
  }
1625
1586
  }
1626
- // Extract architectural context
1627
1587
  const dnaServices = dna?.services?.map((s) => (s.name || "").toLowerCase()).filter((s) => s.length > 4) || [];
1628
1588
  const dnaEntities = dna?.db_entities?.map((e) => (e.name || e.table || "").toLowerCase()).filter((e) => e.length > 3) || [];
1629
1589
  const dnaTopics = dna?.kafka_topics || dna?.events || [];
1630
1590
  const dnaExtension = dna?.chrome_extension || dna?.extension || dna?.["snf-browser-extn"] || null;
1631
1591
  const dnaSecurity = dna?.security_context || spec?.security || null;
1632
- const dnaGoldenRules = spec?.golden_rules || [];
1633
- // DNA-aware risk bonus (stacks on top of keyword risk scores)
1634
- let dna_risk_bonus = 0;
1635
- const dnaRiskNotes = [];
1636
- const text = `${title} ${description}`.toLowerCase();
1637
- const wordCount = description.trim().split(/\s+/).length;
1638
- const mentionedServices = dnaServices.filter((s) => text.includes(s));
1639
- // [DNA-1] DB entities mentioned in story — each known entity = real schema blast
1640
- const mentionedEntities = dnaEntities.filter((e) => text.includes(e));
1641
- if (mentionedEntities.length > 0) {
1642
- const entityRisk = Math.min(mentionedEntities.length * 10, 40);
1643
- dna_risk_bonus += entityRisk;
1644
- dnaRiskNotes.push(`DB entities: ${mentionedEntities.join(", ")} (+${entityRisk})`);
1645
- }
1646
- // [DNA-1b] Migration Spec Check
1647
- if (mentionedEntities.length > 0 || /\bmigration\b/i.test(text) || /\btable\b/i.test(text) || /\bcolumn\b/i.test(text)) {
1648
- const migSpec = loadMigrationSpec(wsRoot);
1649
- if (migSpec && mentionedServices.length > 0) {
1650
- mentionedServices.forEach((s) => {
1651
- const ctx = getMigrationContextForService(migSpec, s);
1652
- if (ctx && ctx.has_migrations) {
1653
- dna_risk_bonus += 15;
1654
- dnaRiskNotes.push(`[MIGRATION SPEC] Manual migration script required for ${s} (Next: ${ctx.next_suggested}) (+15)`);
1655
- }
1656
- });
1657
- }
1658
- else if (migSpec && mentionedEntities.length > 0) {
1659
- // If service isn't mentioned by name but entities are, we still want to warn globally
1660
- dna_risk_bonus += 10;
1661
- dnaRiskNotes.push(`[MIGRATION SPEC] DB touched. Ensure manual migration scripts are applied to PostgreSQL before deploy. (+10)`);
1662
- }
1663
- }
1664
- // [DNA-2] Kafka topics — fan-out cascades to all consumers
1665
- const mentionedTopics = dnaTopics.filter((t) => {
1666
- const tn = (t.name || t.topic || "").toLowerCase();
1667
- return tn.length > 3 && text.includes(tn);
1668
- });
1669
- if (mentionedTopics.length > 0) {
1670
- const topicConsumers = mentionedTopics.reduce((sum, t) => sum + (t.consumers?.length || t.consumer_count || 0), 0);
1671
- const topicRisk = Math.min(mentionedTopics.length * 15 + topicConsumers * 5, 35);
1672
- dna_risk_bonus += topicRisk;
1673
- dnaRiskNotes.push(`Kafka topics: ${mentionedTopics.map((t) => t.name || t.topic).join(", ")} (${topicConsumers} consumers) (+${topicRisk})`);
1674
- }
1675
- // [DNA-3] Chrome extension changes require store deployment
1676
- if (dnaExtension && (text.includes("extension") || text.includes("chrome") || text.includes("background script") || text.includes("content script") || text.includes("popup"))) {
1677
- dna_risk_bonus += 20;
1678
- dnaRiskNotes.push("snf-browser-extn affected — manual store publish (+20)");
1679
- }
1680
- // [DNA-4] Shared multi-tenant auth service — changes blast all tenants
1681
- const isSharedAuth = dnaSecurity && (dnaSecurity.type === "shared" || dnaSecurity.multi_tenant === true || dnaSecurity.multi_tenant === "true");
1682
- if (isSharedAuth && (text.includes("auth") || text.includes("login") || text.includes("permission") || text.includes("role") || text.includes("tenant") || text.includes("token"))) {
1683
- dna_risk_bonus += 25;
1684
- dnaRiskNotes.push("Shared multi-tenant auth — cross-tenant blast (+25)");
1685
- }
1686
- // [DNA-5] Polyglot service coordination — 3+ services = integration risk
1687
- if (mentionedServices.length >= 3) {
1688
- dna_risk_bonus += 15;
1689
- dnaRiskNotes.push(`Multi-service: ${mentionedServices.slice(0, 4).join(", ")} (+15)`);
1690
- }
1691
- // [DNA-6] Golden rule violation signals
1692
- const antiPatterns = ["bypass", "skip migration", "no migration", "hardcode", "raw sql", "direct db", "without migration"];
1693
- const hitAntipatterns = antiPatterns.filter(ap => text.includes(ap));
1694
- if (hitAntipatterns.length > 0) {
1695
- dna_risk_bonus += 20;
1696
- dnaRiskNotes.push(`Antipattern signal: ${hitAntipatterns.join(", ")} (+20)`);
1697
- }
1698
- const dnaAvailable = dna !== null;
1699
- const dnaWarning = dnaAvailable ? "" : "\n[NOTE] DNA not loaded — risk is keyword-only. Run /sfn-init for architecture-aware scoring.";
1700
- // PASS 1 - WHY CHECK
1701
- let why_score = 0;
1702
- if (task_type === "bug" || task_type === "hotfix") {
1703
- why_score += 20; // Relaxed WHY sensing for defects
1704
- }
1705
- const STRONG_WHY = ["users spend", "current process causes", "compliance requires", "audit requires", "production issue", "users are reporting", "data shows", "confirmed by", "ticket from client", "business requirement", "regulatory", "security risk", "revenue impact"];
1706
- const WEAK_WHY = [
1707
- "would be nice", "nice feature", "nice addition", "could be useful", "other platforms have",
1708
- "other saas", "other products have", "similar products", "competitors have",
1709
- "client might want", "we should probably", "nice to have", "maybe useful",
1710
- "might improve", "could improve", "would improve", "would be helpful",
1711
- "suggestion", "idea", "asked for it"
1712
- ];
1713
- for (const s of STRONG_WHY)
1714
- if (text.includes(s))
1715
- why_score += 10;
1716
- for (const s of WEAK_WHY)
1717
- if (text.includes(s))
1718
- why_score -= 10;
1719
- if (text.includes("client asked") && !text.includes("because") && !text.includes("so that"))
1720
- why_score -= 10;
1721
- // Penalize descriptions under 30 words with no strong why — they can't justify themselves
1722
- if (wordCount < 30 && why_score <= 0)
1723
- why_score -= 10;
1724
- let purpose_stated = false;
1725
- let purpose_text = "";
1726
- for (const m of ["so that", "in order to", "because", "which means", "this allows"]) {
1727
- const idx = description.toLowerCase().indexOf(m);
1728
- if (idx !== -1) {
1729
- purpose_stated = true;
1730
- purpose_text = description.substring(idx, idx + 120).trim();
1731
- break;
1732
- }
1733
- }
1734
- const why_verdict = why_score >= 10 ? "STRONG" : why_score >= 0 ? "ACCEPTABLE" : why_score >= -19 ? "WEAK" : "NO_JUSTIFICATION";
1735
- // PASS 2 - WHO CHECK
1736
- let who_score = 0;
1737
- if (task_type === "bug" || task_type === "hotfix") {
1738
- who_score += 15; // Relaxed WHO sensing for defects
1739
- }
1740
- const AUTH_SIG = ["client confirmed", "client approved", "stakeholder sign-off", "decision made by", "approved by", "confirmed in meeting", "email from client", "client requirement", "explicit request from", "signed off"];
1741
- const UNCERT_SIG = [
1742
- "assumed", "we think", "probably", "should want", "might need", "could need",
1743
- "i think the client", "i think clients", "pm suggestion", "internal idea", "we decided",
1744
- "i think", "don't think", "shouldn't need", "won't need"
1745
- ];
1746
- for (const s of AUTH_SIG)
1747
- if (text.includes(s))
1748
- who_score += 10;
1749
- for (const s of UNCERT_SIG)
1750
- if (text.includes(s))
1751
- who_score -= 10;
1752
- if (reported_by) {
1753
- const rb = reported_by.toLowerCase();
1754
- if (rb === "client")
1755
- who_score += 15;
1756
- else if (rb === "pm")
1757
- who_score += 5;
1758
- else if (rb === "developer")
1759
- who_score -= 5;
1760
- }
1761
- if (client_confirmed === true)
1762
- who_score += 20;
1763
- else if (client_confirmed === false)
1764
- who_score -= 20;
1765
- const who_verdict = who_score >= 15 ? "CONFIRMED" : who_score >= 0 ? "PROBABLE" : who_score >= -14 ? "UNCERTAIN" : "UNCONFIRMED";
1766
- // PASS 3 - WHAT CHECK
1767
- let what_score = 0;
1768
- if (text.includes("acceptance criteria"))
1769
- what_score += 20;
1770
- const hasGWT = text.includes("given") && text.includes("when") && text.includes("then");
1771
- if (hasGWT)
1772
- what_score += 15;
1773
- const numberedACs = (description.match(/\b(ac-?\d+|\d+\.\s+(?:given|when|then|the user|the system))/gi) || []).length;
1774
- if (numberedACs >= 2)
1775
- what_score += 10;
1776
- if ((description.match(/\bmust\b/gi) || []).length >= 1)
1777
- what_score += 5;
1778
- if (text.includes("as discussed"))
1779
- what_score -= 15;
1780
- if (text.includes("as agreed"))
1781
- what_score -= 15;
1782
- if (text.includes("tbd"))
1783
- what_score -= 20;
1784
- if (text.includes("to be defined"))
1785
- what_score -= 20;
1786
- if (wordCount < 50)
1787
- what_score -= 10;
1788
- if (!/\b(\d+%|within \d+|less than \d+|at least \d+)\b/.test(description) && wordCount < 80)
1789
- what_score -= 10;
1790
- const hasOutOfScope = text.includes("out of scope") || text.includes("not included");
1791
- if (hasOutOfScope)
1792
- what_score += 10;
1793
- if (text.includes("edge cases"))
1794
- what_score += 10;
1795
- if (text.includes("error handling"))
1796
- what_score += 5;
1797
- const what_verdict = what_score >= 20 ? "CLEAR" : what_score >= 0 ? "ACCEPTABLE" : what_score >= -19 ? "VAGUE" : "UNDEFINED";
1798
- const ac_count = Math.max(numberedACs, (description.match(/\b(ac-?\d+|given\b)/gi) || []).length);
1799
- // PASS 4 - RISK CHECK
1800
- let risk_score = 0;
1801
- const high_risk_factors = [];
1802
- const riskSignals = [
1803
- ["db migration", 20, "DB migration"], ["database migration", 20, "DB migration"],
1804
- ["drop column", 30, "Drop column"], ["rename column", 30, "Rename column"],
1805
- ["authentication change", 25, "Auth change"], ["auth change", 25, "Auth change"],
1806
- ["authorization change", 20, "Authz change"], ["permission change", 20, "Permission change"],
1807
- ["all tenants", 20, "Affects all tenants"], ["kafka schema", 25, "Kafka schema change"],
1808
- ["chrome extension", 15, "Chrome extension"], ["irreversible", 30, "Irreversible"],
1809
- ["data migration", 20, "Data migration"], ["api contract", 25, "API contract change"],
1810
- ["breaking change", 25, "Breaking change"], ["affects existing", 15, "Affects existing"],
1811
- ];
1812
- for (const [kw, score, label] of riskSignals) {
1813
- if (text.includes(kw)) {
1814
- risk_score += score;
1815
- if (!high_risk_factors.includes(label))
1816
- high_risk_factors.push(label);
1817
- }
1818
- }
1819
- // Regex-based: catch "drop the old X columns", "Drop legacy device columns", etc.
1820
- // Use 'text' (title+desc combined) since title often has key verb, description has the noun.
1821
- if (/\bdrop\b.{0,60}\bcolumns?/i.test(text) && !high_risk_factors.includes("Drop column")) {
1822
- risk_score += 30;
1823
- high_risk_factors.push("Drop column (pattern)");
1824
- }
1825
- if (/\bdelete\b.{0,30}\bcolumns?/i.test(text) && !high_risk_factors.includes("Drop column")) {
1826
- risk_score += 30;
1827
- high_risk_factors.push("Delete column (pattern)");
1828
- }
1829
- if (text.includes("delete") && (text.includes(" data") || text.includes("record"))) {
1830
- risk_score += 25;
1831
- high_risk_factors.push("Data deletion");
1832
- }
1833
- if (text.includes("new endpoint"))
1834
- risk_score += 10;
1835
- if (text.includes("new table"))
1836
- risk_score += 10;
1837
- if (text.includes("new kafka topic"))
1838
- risk_score += 15;
1839
- if (text.includes("new service"))
1840
- risk_score += 20;
1841
- if (text.includes("read only") || text.includes("display only"))
1842
- risk_score -= 10;
1843
- if (text.includes("no db change") || text.includes("no migration"))
1844
- risk_score -= 15;
1845
- if (text.includes("isolated change"))
1846
- risk_score -= 10;
1847
- if (text.includes("config change only"))
1848
- risk_score -= 10;
1849
- let rejected_pattern_risk = false;
1850
- for (const rp of rejectedPatterns) {
1851
- const rpKeywords = rp.keywords || (rp.pattern ? [rp.pattern] : []);
1852
- for (const kw of rpKeywords) {
1853
- if (kw && text.includes(kw.toLowerCase())) {
1854
- rejected_pattern_risk = true;
1855
- risk_score += 20;
1856
- high_risk_factors.push(`Rejected pattern: ${rp.id || rp.name}`);
1857
- break;
1858
- }
1859
- }
1860
- }
1861
- // ── Apply DNA-aware risk bonus ──────────────────────────────────────
1862
- // Keyword scoring is blind to actual architecture. DNA bonus reflects real blast radius.
1863
- risk_score += Math.min(dna_risk_bonus, 50); // cap at 50 to prevent double-stacking
1864
- for (const note of dnaRiskNotes) {
1865
- if (!high_risk_factors.includes(note))
1866
- high_risk_factors.push(`[DNA] ${note}`);
1867
- }
1868
- const risk_verdict = risk_score <= 10 ? "LOW" : risk_score <= 30 ? "MEDIUM" : risk_score <= 50 ? "HIGH" : "CRITICAL";
1869
- const reversibility = risk_score <= 20 ? "REVERSIBLE" : risk_score <= 40 ? "REVERSIBLE_WITH_EFFORT" : "DIFFICULT_TO_REVERSE";
1870
- // PASS 5 - PHILOSOPHY VERDICT
1871
- let philosophy_verdict = "PHILOSOPHY_PASS";
1872
- const blocking_questions = [];
1873
- const advisory_notes = [];
1874
- if ((who_verdict === "UNCONFIRMED" && risk_verdict === "CRITICAL") ||
1875
- (who_verdict === "UNCONFIRMED" && risk_verdict === "HIGH" && reversibility === "DIFFICULT_TO_REVERSE") ||
1876
- (why_verdict === "NO_JUSTIFICATION" && (task_type === "feature" || task_type === "user_story")) ||
1877
- (rejected_pattern_risk && risk_verdict === "CRITICAL")) {
1878
- philosophy_verdict = "PHILOSOPHY_BLOCKED";
1879
- }
1880
- else if (who_verdict === "UNCONFIRMED" ||
1881
- why_verdict === "NO_JUSTIFICATION" ||
1882
- what_verdict === "UNDEFINED" ||
1883
- (risk_verdict === "CRITICAL" && reversibility === "DIFFICULT_TO_REVERSE")) {
1884
- philosophy_verdict = "PHILOSOPHY_FAIL";
1885
- }
1886
- else if (who_verdict === "UNCERTAIN" || why_verdict === "WEAK" || what_verdict === "VAGUE" || risk_verdict === "HIGH") {
1887
- philosophy_verdict = "PHILOSOPHY_ADVISORY";
1888
- }
1889
- const allowed_next_action = philosophy_verdict === "PHILOSOPHY_PASS" ? "PROCEED" :
1890
- philosophy_verdict === "PHILOSOPHY_ADVISORY" ? "ADVISORY_PROCEED" :
1891
- philosophy_verdict === "PHILOSOPHY_FAIL" ? "STOP" : "ESCALATE";
1892
- if (why_verdict === "NO_JUSTIFICATION" || why_verdict === "WEAK")
1893
- blocking_questions.push({ category: "WHY", question: `What specific problem does "${title}" solve?`, impact: "Without a clear problem statement, the solution may be wrong.", action: "Add a problem statement with data, user pain, or regulatory requirement." });
1894
- if (who_verdict === "UNCONFIRMED" || who_verdict === "UNCERTAIN")
1895
- blocking_questions.push({ category: "WHO", question: "Who has explicitly confirmed this is needed?", impact: "Building unconfirmed features wastes sprint capacity.", action: "Get written confirmation from client or PM." });
1896
- if (what_verdict === "UNDEFINED" || what_verdict === "VAGUE")
1897
- blocking_questions.push({ category: "WHAT", question: "What are the acceptance criteria?", impact: "Vague stories produce vague software.", action: "Add Given/When/Then ACs." });
1898
- if (risk_verdict === "CRITICAL" && reversibility === "DIFFICULT_TO_REVERSE")
1899
- blocking_questions.push({ category: "RISK", question: `CRITICAL risk: ${high_risk_factors.join(", ")}. What is the rollback plan?`, impact: "This change may be impossible to revert.", action: "Define rollback plan and get explicit sign-off." });
1900
- if (who_verdict === "UNCERTAIN")
1901
- advisory_notes.push({ category: "WHO", note: "Decision maker not fully confirmed", suggestion: "Confirm with client or PM." });
1902
- if (why_verdict === "WEAK")
1903
- advisory_notes.push({ category: "WHY", note: "Weak justification", suggestion: "Strengthen with data or regulatory context." });
1904
- if (what_verdict === "VAGUE")
1905
- advisory_notes.push({ category: "WHAT", note: "ACs incomplete or vague", suggestion: "Add measurable ACs before coding." });
1906
- if (risk_verdict === "HIGH")
1907
- advisory_notes.push({ category: "RISK", note: `High risk: ${high_risk_factors.join(", ") || "multiple signals"}`, suggestion: "Review blast radius. Consider staged rollout." });
1908
- let developer_message = "";
1909
- const scoresSummary = `\nScores | WHY:${why_score} WHO:${who_score} WHAT:${what_score} RISK:${risk_score}${dna_risk_bonus > 0 ? ` (DNA+${dna_risk_bonus})` : ""}${dnaWarning}`;
1910
- if (philosophy_verdict === "PHILOSOPHY_PASS") {
1911
- developer_message = `[PHILOSOPHY PASS] ${work_item_id}\n\nWHY : ${why_verdict}${purpose_text ? " - " + purpose_text : ""}\nWHO : ${who_verdict}\nWHAT : ${what_verdict} — ${ac_count} AC(s)\nRISK : ${risk_verdict} (${reversibility})${high_risk_factors.length > 0 ? "\nRisk factors: " + high_risk_factors.join(", ") : ""}\n\nProceed to classify_task.${scoresSummary}`;
1912
- }
1913
- else if (philosophy_verdict === "PHILOSOPHY_ADVISORY") {
1914
- developer_message = `[PHILOSOPHY ADVISORY] ${work_item_id}\n\nStory may proceed with caution. Note these concerns:\n\n` +
1915
- advisory_notes.map((n) => ` [${n.category}] ${n.note}\n Suggestion: ${n.suggestion}`).join("\n\n") +
1916
- `\n\nWHY:${why_verdict} | WHO:${who_verdict} | WHAT:${what_verdict} | RISK:${risk_verdict}\nProceed to classify_task. Record advisory notes in decisions.json.${scoresSummary}`;
1917
- }
1918
- else if (philosophy_verdict === "PHILOSOPHY_FAIL") {
1919
- developer_message = `[PHILOSOPHY FAIL] ${work_item_id}\n\nImplementation BLOCKED. The following must be resolved before planning can begin:\n\n` +
1920
- blocking_questions.map((q) => ` [${q.category}] ${q.question}\n Why: ${q.impact}\n Action: ${q.action}`).join("\n\n") +
1921
- `\n\nDo NOT call classify_task. Re-run philosophy_check after resolving.${scoresSummary}`;
1922
- }
1923
- else {
1924
- developer_message = `[PHILOSOPHY BLOCKED] ${work_item_id}\n\nESCALATION REQUIRED. ${risk_verdict} risk + ${who_verdict} authority.\n\n` +
1925
- blocking_questions.map((q) => ` [${q.category}] ${q.question}\n Risk: ${q.impact}\n Required: ${q.action}`).join("\n\n") +
1926
- `\n\nNO implementation or planning until explicit written sign-off from the decision maker.${scoresSummary}`;
1927
- }
1928
- const result = {
1929
- work_item_id, title, task_type, philosophy_verdict, allowed_next_action,
1930
- dna_assessment: {
1931
- dna_available: dnaAvailable,
1932
- dna_risk_bonus,
1933
- dna_risk_notes: dnaRiskNotes,
1934
- },
1935
- passes: {
1936
- why: { score: why_score, verdict: why_verdict, purpose_stated, purpose_text },
1937
- who: { score: who_score, verdict: who_verdict },
1938
- what: { score: what_score, verdict: what_verdict, ac_count, has_given_when_then: hasGWT, has_out_of_scope: hasOutOfScope, word_count: wordCount },
1939
- risk: { score: risk_score, verdict: risk_verdict, reversibility, rejected_pattern_risk, high_risk_factors }
1940
- },
1941
- blocking_questions, advisory_notes, developer_message, scored_at: new Date().toISOString()
1942
- };
1943
- try {
1944
- writeFile(philosophyFile, JSON.stringify(result, null, 2));
1945
- }
1946
- catch { }
1592
+ let developer_message = `[PHILOSOPHY CONTEXT GATHERED] ${work_item_id}\n\n` +
1593
+ `INSTRUCTIONS FOR THE AGENT:\n` +
1594
+ `You are now acting as the Philosophy Check Engine defined in your AGENTS.md persona.\n\n` +
1595
+ `1. Review the following gathered Project DNA against the task (${task_type}): "${title}".\n` +
1596
+ `2. Determine if the business intent is clear, if the decision maker is explicit, and if the architectural impact is fully understood.\n` +
1597
+ `3. If clarification is needed, STOP. Do not proceed. Ask the user the hard questions directly (What problem is this solving? Who confirmed this? What breaks if we do not build it?).\n` +
1598
+ `4. If everything is clear and the architectural risk is acceptable, output your "System Impact Analysis" based on this DNA, and then call classify_task.\n\n` +
1599
+ `── PROJECT DNA CONTEXT ──\n` +
1600
+ `Services: ` + (dnaServices.length > 0 ? dnaServices.join(", ") : "None detected") + `\n` +
1601
+ `Entities: ` + (dnaEntities.length > 0 ? dnaEntities.join(", ") : "None detected") + `\n` +
1602
+ `Kafka Topics: ` + (dnaTopics.length > 0 ? dnaTopics.map((t) => t.name || t.topic).join(", ") : "None detected") + `\n` +
1603
+ `Extension: ` + (dnaExtension ? "Present" : "None detected") + `\n` +
1604
+ `Security: ` + (dnaSecurity ? JSON.stringify(dnaSecurity) : "None detected") + `\n` +
1605
+ `Rejected Patterns: ` + (rejectedPatterns.map(p => p.id || p.name).join(", ") || "None") + `\n\n` +
1606
+ `Do not run the old static scoring. Use your LLM reasoning.`;
1947
1607
  return appendTelemetry({ content: [{ type: "text", text: developer_message }] }, inputChars);
1948
1608
  });
1949
1609
  // --- Tool 10: classify_task --------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "secufusion-mcp",
3
- "version": "2.4.1",
3
+ "version": "2.5.1",
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",
@@ -45,4 +45,4 @@
45
45
  "@types/node": "^22.0.0",
46
46
  "typescript": "^5.0.0"
47
47
  }
48
- }
48
+ }