@kairos-sdk/core 0.3.2 → 0.4.0
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/README.md +37 -5
- package/dist/{chunk-KQSNT3HZ.js → chunk-N6LRD2FN.js} +107 -15
- package/dist/chunk-N6LRD2FN.js.map +1 -0
- package/dist/{chunk-RYGYNOR6.js → chunk-NJ6QZBIC.js} +624 -43
- package/dist/chunk-NJ6QZBIC.js.map +1 -0
- package/dist/cli.cjs +820 -66
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +87 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +729 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -18
- package/dist/index.d.ts +23 -18
- package/dist/index.js +2 -2
- package/dist/mcp-server.cjs +636 -35
- package/dist/mcp-server.cjs.map +1 -1
- package/dist/mcp-server.js +23 -1
- package/dist/mcp-server.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-KQSNT3HZ.js.map +0 -1
- package/dist/chunk-RYGYNOR6.js.map +0 -1
package/dist/mcp-server.cjs
CHANGED
|
@@ -577,17 +577,28 @@ var N8nValidator = class {
|
|
|
577
577
|
this.checkRule21(workflow, issues);
|
|
578
578
|
this.checkRule22(workflow, issues);
|
|
579
579
|
this.checkRule23(workflow, issues);
|
|
580
|
+
if (Array.isArray(workflow.nodes)) {
|
|
581
|
+
const nodeById = new Map(workflow.nodes.map((n) => [n.id, n.type]));
|
|
582
|
+
for (const issue of issues) {
|
|
583
|
+
if (issue.nodeId && !issue.nodeType) {
|
|
584
|
+
const nt = nodeById.get(issue.nodeId);
|
|
585
|
+
if (nt) issue.nodeType = nt;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
580
589
|
const errors = issues.filter((i) => i.severity === "error");
|
|
581
590
|
return { valid: errors.length === 0, issues };
|
|
582
591
|
}
|
|
583
|
-
err(issues, rule, message, nodeId) {
|
|
592
|
+
err(issues, rule, message, nodeId, nodeType) {
|
|
584
593
|
const issue = { rule, severity: "error", message };
|
|
585
594
|
if (nodeId !== void 0) issue.nodeId = nodeId;
|
|
595
|
+
if (nodeType !== void 0) issue.nodeType = nodeType;
|
|
586
596
|
issues.push(issue);
|
|
587
597
|
}
|
|
588
|
-
warn(issues, rule, message, nodeId) {
|
|
598
|
+
warn(issues, rule, message, nodeId, nodeType) {
|
|
589
599
|
const issue = { rule, severity: "warn", message };
|
|
590
600
|
if (nodeId !== void 0) issue.nodeId = nodeId;
|
|
601
|
+
if (nodeType !== void 0) issue.nodeType = nodeType;
|
|
591
602
|
issues.push(issue);
|
|
592
603
|
}
|
|
593
604
|
isTriggerNode(node) {
|
|
@@ -1164,6 +1175,11 @@ var N8nApiClient = class {
|
|
|
1164
1175
|
}
|
|
1165
1176
|
};
|
|
1166
1177
|
|
|
1178
|
+
// src/generation/prompt-builder.ts
|
|
1179
|
+
var import_node_fs = require("fs");
|
|
1180
|
+
var import_node_path2 = require("path");
|
|
1181
|
+
var import_node_os2 = require("os");
|
|
1182
|
+
|
|
1167
1183
|
// src/generation/prompts/v1.ts
|
|
1168
1184
|
var SYSTEM_PROMPT_V1 = `You are a workflow generation engine for n8n. Your only output is a generate_workflow tool call containing valid n8n workflow JSON. You never respond with prose, explanations, or markdown. If you cannot fulfill the request, set the error field in the tool call.
|
|
1169
1185
|
|
|
@@ -1347,8 +1363,34 @@ Cron: { "rule": { "interval": [{ "field": "cronExpression", "expression": "0 9 *
|
|
|
1347
1363
|
Respond ONLY with a generate_workflow tool call. No prose. No markdown outside the tool call.
|
|
1348
1364
|
If the request is impossible or unclear, set the error field instead of generating a workflow.`;
|
|
1349
1365
|
|
|
1350
|
-
// src/
|
|
1351
|
-
var
|
|
1366
|
+
// src/validation/rule-metadata.ts
|
|
1367
|
+
var VALIDATOR_RULE_IDS = Array.from({ length: 23 }, (_, i) => i + 1);
|
|
1368
|
+
var RULE_PIPELINE_STAGES = {
|
|
1369
|
+
1: "node_generation",
|
|
1370
|
+
2: "node_generation",
|
|
1371
|
+
3: "node_generation",
|
|
1372
|
+
4: "node_generation",
|
|
1373
|
+
5: "node_generation",
|
|
1374
|
+
6: "node_generation",
|
|
1375
|
+
7: "node_generation",
|
|
1376
|
+
8: "node_generation",
|
|
1377
|
+
9: "connection_wiring",
|
|
1378
|
+
10: "connection_wiring",
|
|
1379
|
+
11: "connection_wiring",
|
|
1380
|
+
12: "workflow_structure",
|
|
1381
|
+
13: "node_generation",
|
|
1382
|
+
14: "workflow_structure",
|
|
1383
|
+
15: "node_generation",
|
|
1384
|
+
16: "node_generation",
|
|
1385
|
+
17: "credential_injection",
|
|
1386
|
+
18: "connection_wiring",
|
|
1387
|
+
19: "node_generation",
|
|
1388
|
+
20: "connection_wiring",
|
|
1389
|
+
21: "workflow_structure",
|
|
1390
|
+
22: "workflow_structure",
|
|
1391
|
+
23: "node_generation"
|
|
1392
|
+
};
|
|
1393
|
+
var RULE_MITIGATIONS = {
|
|
1352
1394
|
1: "Provide a non-empty workflow name string",
|
|
1353
1395
|
2: "Include at least one node in the nodes array",
|
|
1354
1396
|
3: "Every node must have a unique UUID v4 string as its id field",
|
|
@@ -1359,8 +1401,10 @@ var RULE_REMEDIES = {
|
|
|
1359
1401
|
8: "Every node must have a non-empty name string",
|
|
1360
1402
|
9: "connections must be a plain object (use {} if no connections)",
|
|
1361
1403
|
10: "Every node name in connections (source and target) must exactly match a name in the nodes array",
|
|
1404
|
+
11: "Every non-trigger node should have at least one incoming connection",
|
|
1362
1405
|
12: "Remove forbidden fields: id, active, createdAt, updatedAt, versionId, meta, tags \u2014 these are server-assigned",
|
|
1363
|
-
|
|
1406
|
+
13: "workflow.settings must be a plain object if present",
|
|
1407
|
+
14: "Include at least one trigger node (e.g. scheduleTrigger, webhookTrigger, manualTrigger, or service-specific)",
|
|
1364
1408
|
15: 'Node type strings must be fully qualified: "n8n-nodes-base.httpRequest" not just "httpRequest"',
|
|
1365
1409
|
16: "All node names must be unique within the workflow",
|
|
1366
1410
|
17: 'Credentials must be an object with non-empty string id and name fields: { id: "placeholder-id", name: "My Credential" }',
|
|
@@ -1368,9 +1412,17 @@ var RULE_REMEDIES = {
|
|
|
1368
1412
|
19: "Use known safe typeVersion values for each node type",
|
|
1369
1413
|
20: "Remove connection cycles \u2014 ensure no node can reach itself through the connection graph",
|
|
1370
1414
|
21: 'When using webhook with responseMode "responseNode", include a respondToWebhook node in the flow',
|
|
1371
|
-
22: "Ensure all required parameters are set for each node type (e.g. webhook needs httpMethod and path)"
|
|
1415
|
+
22: "Ensure all required parameters are set for each node type (e.g. webhook needs httpMethod and path)",
|
|
1416
|
+
23: "Use node types that exist in the n8n registry \u2014 check with kairos_sync"
|
|
1372
1417
|
};
|
|
1418
|
+
|
|
1419
|
+
// src/generation/prompt-builder.ts
|
|
1420
|
+
var CRITICAL_SCORE_THRESHOLD = 0.15;
|
|
1373
1421
|
var PromptBuilder = class {
|
|
1422
|
+
patternsPath;
|
|
1423
|
+
constructor(patternsPath) {
|
|
1424
|
+
this.patternsPath = patternsPath ?? (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".kairos", "patterns.json");
|
|
1425
|
+
}
|
|
1374
1426
|
build(request, matches, globalFailureRates = [], dynamicCatalog) {
|
|
1375
1427
|
const mode = this.resolveMode(matches);
|
|
1376
1428
|
const system = this.buildSystem(matches, mode, globalFailureRates, dynamicCatalog);
|
|
@@ -1456,20 +1508,114 @@ A loosely similar workflow (score: ${hint.score.toFixed(2)}) used these node typ
|
|
|
1456
1508
|
}
|
|
1457
1509
|
return blocks;
|
|
1458
1510
|
}
|
|
1511
|
+
loadPatterns() {
|
|
1512
|
+
try {
|
|
1513
|
+
const raw = (0, import_node_fs.readFileSync)(this.patternsPath, "utf-8");
|
|
1514
|
+
const analysis = JSON.parse(raw);
|
|
1515
|
+
const patterns = analysis.topFailureRules ?? [];
|
|
1516
|
+
return patterns.filter((p) => typeof p.pipelineStage === "string" && typeof p.state === "string");
|
|
1517
|
+
} catch {
|
|
1518
|
+
return [];
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
getWarnedRules() {
|
|
1522
|
+
return this.getActivePatterns().map((p) => p.rule);
|
|
1523
|
+
}
|
|
1524
|
+
getActivePatterns() {
|
|
1525
|
+
const MAX_WARNED = 10;
|
|
1526
|
+
const all = this.loadPatterns().filter((p) => p.state !== "resolved" && p.confidence > 0);
|
|
1527
|
+
const regressed = all.filter((p) => p.regressed).sort((a, b) => b.compositeScore - a.compositeScore);
|
|
1528
|
+
const confirmed = all.filter((p) => !p.regressed && p.state === "confirmed").sort((a, b) => b.compositeScore - a.compositeScore);
|
|
1529
|
+
const drafts = all.filter((p) => !p.regressed && p.state !== "confirmed").sort((a, b) => b.compositeScore - a.compositeScore);
|
|
1530
|
+
return [...regressed, ...confirmed, ...drafts].slice(0, MAX_WARNED);
|
|
1531
|
+
}
|
|
1459
1532
|
buildFailureWarnings(matches, globalFailureRates) {
|
|
1533
|
+
const richPatterns = this.getActivePatterns();
|
|
1534
|
+
if (richPatterns.length > 0) {
|
|
1535
|
+
return this.buildStageGroupedWarnings(richPatterns, matches);
|
|
1536
|
+
}
|
|
1537
|
+
return this.buildLegacyWarnings(matches, globalFailureRates);
|
|
1538
|
+
}
|
|
1539
|
+
buildStageGroupedWarnings(patterns, matches) {
|
|
1540
|
+
const stageLabels = {
|
|
1541
|
+
credential_injection: "CREDENTIAL FORMATTING",
|
|
1542
|
+
connection_wiring: "CONNECTION WIRING",
|
|
1543
|
+
node_generation: "NODE GENERATION",
|
|
1544
|
+
workflow_structure: "WORKFLOW STRUCTURE"
|
|
1545
|
+
};
|
|
1546
|
+
const byStage = /* @__PURE__ */ new Map();
|
|
1547
|
+
for (const p of patterns) {
|
|
1548
|
+
const list = byStage.get(p.pipelineStage) ?? [];
|
|
1549
|
+
list.push(p);
|
|
1550
|
+
byStage.set(p.pipelineStage, list);
|
|
1551
|
+
}
|
|
1552
|
+
const sections = [];
|
|
1553
|
+
for (const [stage, stagePatterns] of byStage) {
|
|
1554
|
+
const label = stageLabels[stage] ?? stage;
|
|
1555
|
+
const byMitigation = /* @__PURE__ */ new Map();
|
|
1556
|
+
for (const p of stagePatterns) {
|
|
1557
|
+
const key = p.mitigation ?? `rule_${p.rule}`;
|
|
1558
|
+
const list = byMitigation.get(key) ?? [];
|
|
1559
|
+
list.push(p);
|
|
1560
|
+
byMitigation.set(key, list);
|
|
1561
|
+
}
|
|
1562
|
+
const lines = [];
|
|
1563
|
+
for (const group of byMitigation.values()) {
|
|
1564
|
+
if (group.length === 1) {
|
|
1565
|
+
const p = group[0];
|
|
1566
|
+
const urgency = p.regressed ? "CRITICAL REGRESSION: " : (p.compositeScore ?? 0) >= CRITICAL_SCORE_THRESHOLD ? "CRITICAL: " : "";
|
|
1567
|
+
const statePrefix = p.state === "confirmed" ? "[CONFIRMED] " : "";
|
|
1568
|
+
const trendSuffix = p.trend === "worsening" ? " (GETTING WORSE)" : p.trend === "improving" ? " (improving)" : "";
|
|
1569
|
+
const remedy = p.mitigation ?? RULE_MITIGATIONS[p.rule];
|
|
1570
|
+
const remedyStr = remedy ? `
|
|
1571
|
+
Fix: ${remedy}` : "";
|
|
1572
|
+
lines.push(`- ${urgency}${statePrefix}Rule ${p.rule}${trendSuffix}: ${p.exampleMessages[0] ?? "No example"}${remedyStr}`);
|
|
1573
|
+
} else {
|
|
1574
|
+
const ruleNums = group.map((p) => p.rule).join(", ");
|
|
1575
|
+
const totalFailures = group.reduce((s, p) => s + p.failureCount, 0);
|
|
1576
|
+
const hasConfirmed = group.some((p) => p.state === "confirmed");
|
|
1577
|
+
const statePrefix = hasConfirmed ? "[CONFIRMED] " : "";
|
|
1578
|
+
const remedy = group[0].mitigation;
|
|
1579
|
+
const remedyStr = remedy ? `
|
|
1580
|
+
Fix: ${remedy}` : "";
|
|
1581
|
+
lines.push(`- ${statePrefix}Rules ${ruleNums} (${totalFailures} failures combined): same root cause${remedyStr}`);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
sections.push(`### ${label}
|
|
1585
|
+
${lines.join("\n")}`);
|
|
1586
|
+
}
|
|
1587
|
+
for (const match of matches) {
|
|
1588
|
+
const fps = match.workflow.failurePatterns;
|
|
1589
|
+
if (!fps?.length) continue;
|
|
1590
|
+
const coveredRules = new Set(patterns.map((p) => p.rule));
|
|
1591
|
+
const extra = fps.filter((fp) => !coveredRules.has(fp.rule));
|
|
1592
|
+
for (const fp of extra) {
|
|
1593
|
+
const remedy = RULE_MITIGATIONS[fp.rule];
|
|
1594
|
+
const remedyStr = remedy ? ` \u2014 Fix: ${remedy}` : "";
|
|
1595
|
+
sections.push(`- Rule ${fp.rule}: "${fp.message}"${remedyStr} (seen in similar workflows)`);
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
if (sections.length === 0) return null;
|
|
1599
|
+
return `## Known Failure Patterns \u2014 AVOID THESE
|
|
1600
|
+
|
|
1601
|
+
Grouped by generation stage. Fix these BEFORE outputting your response:
|
|
1602
|
+
|
|
1603
|
+
${sections.join("\n\n")}`;
|
|
1604
|
+
}
|
|
1605
|
+
buildLegacyWarnings(matches, globalFailureRates) {
|
|
1460
1606
|
const lines = [];
|
|
1461
1607
|
for (const match of matches) {
|
|
1462
1608
|
const patterns = match.workflow.failurePatterns;
|
|
1463
1609
|
if (!patterns?.length) continue;
|
|
1464
1610
|
for (const fp of patterns) {
|
|
1465
|
-
const remedy =
|
|
1611
|
+
const remedy = RULE_MITIGATIONS[fp.rule];
|
|
1466
1612
|
const remedyStr = remedy ? ` \u2014 Fix: ${remedy}` : "";
|
|
1467
1613
|
lines.push(`- Rule ${fp.rule}: "${fp.message}"${remedyStr} (seen ${fp.occurrences}x in similar workflows)`);
|
|
1468
1614
|
}
|
|
1469
1615
|
}
|
|
1470
1616
|
const highFreqRules = globalFailureRates.filter((r) => r.rate >= 0.15);
|
|
1471
1617
|
for (const rule of highFreqRules) {
|
|
1472
|
-
const remedy =
|
|
1618
|
+
const remedy = RULE_MITIGATIONS[rule.rule];
|
|
1473
1619
|
const remedyStr = remedy ? ` \u2014 Fix: ${remedy}` : "";
|
|
1474
1620
|
lines.push(`- Rule ${rule.rule}: "${rule.commonMessage}"${remedyStr} (fails in ${Math.round(rule.rate * 100)}% of all builds)`);
|
|
1475
1621
|
}
|
|
@@ -1488,15 +1634,55 @@ Workflow name: "${request.name}"` : "";
|
|
|
1488
1634
|
};
|
|
1489
1635
|
|
|
1490
1636
|
// src/telemetry/reader.ts
|
|
1637
|
+
var import_node_os3 = require("os");
|
|
1638
|
+
var import_node_path4 = require("path");
|
|
1639
|
+
|
|
1640
|
+
// src/telemetry/event-reader.ts
|
|
1491
1641
|
var import_promises2 = require("fs/promises");
|
|
1492
|
-
var
|
|
1493
|
-
var
|
|
1642
|
+
var import_node_fs2 = require("fs");
|
|
1643
|
+
var import_node_path3 = require("path");
|
|
1644
|
+
var import_node_readline = require("readline");
|
|
1645
|
+
async function readTelemetryEvents(dir, days) {
|
|
1646
|
+
let files;
|
|
1647
|
+
try {
|
|
1648
|
+
files = await (0, import_promises2.readdir)(dir);
|
|
1649
|
+
} catch {
|
|
1650
|
+
return [];
|
|
1651
|
+
}
|
|
1652
|
+
const cutoff = /* @__PURE__ */ new Date();
|
|
1653
|
+
cutoff.setDate(cutoff.getDate() - days);
|
|
1654
|
+
const cutoffStr = cutoff.toISOString().slice(0, 10);
|
|
1655
|
+
const todayStr = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1656
|
+
const datePattern = /^\d{4}-\d{2}-\d{2}\.jsonl$/;
|
|
1657
|
+
const recentFiles = files.filter((f) => datePattern.test(f) && f >= cutoffStr && f <= `${todayStr}.jsonl`).sort();
|
|
1658
|
+
const events = [];
|
|
1659
|
+
for (const file of recentFiles) {
|
|
1660
|
+
const fileDate = file.replace(".jsonl", "");
|
|
1661
|
+
try {
|
|
1662
|
+
const rl = (0, import_node_readline.createInterface)({
|
|
1663
|
+
input: (0, import_node_fs2.createReadStream)((0, import_node_path3.join)(dir, file), "utf-8"),
|
|
1664
|
+
crlfDelay: Infinity
|
|
1665
|
+
});
|
|
1666
|
+
for await (const line of rl) {
|
|
1667
|
+
if (!line.trim()) continue;
|
|
1668
|
+
try {
|
|
1669
|
+
events.push({ ...JSON.parse(line), fileDate });
|
|
1670
|
+
} catch {
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
} catch {
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
return events;
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
// src/telemetry/reader.ts
|
|
1494
1680
|
var TelemetryReader = class {
|
|
1495
1681
|
dir;
|
|
1496
1682
|
cache = null;
|
|
1497
1683
|
cacheTime = 0;
|
|
1498
1684
|
constructor(dir) {
|
|
1499
|
-
this.dir = dir ?? (0,
|
|
1685
|
+
this.dir = dir ?? (0, import_node_path4.join)((0, import_node_os3.homedir)(), ".kairos", "telemetry");
|
|
1500
1686
|
}
|
|
1501
1687
|
async getFailureRates(days = 30) {
|
|
1502
1688
|
const now = Date.now();
|
|
@@ -1505,9 +1691,10 @@ var TelemetryReader = class {
|
|
|
1505
1691
|
}
|
|
1506
1692
|
const events = await this.readRecentEvents(days);
|
|
1507
1693
|
const buildSessions = new Set(
|
|
1508
|
-
events.filter((e) => e.eventType === "build_complete"
|
|
1694
|
+
events.filter((e) => e.eventType === "build_complete").map((e) => e.sessionId)
|
|
1509
1695
|
);
|
|
1510
|
-
|
|
1696
|
+
const MIN_BUILDS_FOR_RATES = 3;
|
|
1697
|
+
if (buildSessions.size < MIN_BUILDS_FOR_RATES) return [];
|
|
1511
1698
|
const ruleSessions = /* @__PURE__ */ new Map();
|
|
1512
1699
|
for (const event of events) {
|
|
1513
1700
|
if (event.eventType !== "generation_attempt") continue;
|
|
@@ -1545,32 +1732,425 @@ var TelemetryReader = class {
|
|
|
1545
1732
|
return rates;
|
|
1546
1733
|
}
|
|
1547
1734
|
async readRecentEvents(days) {
|
|
1548
|
-
|
|
1735
|
+
return readTelemetryEvents(this.dir, days);
|
|
1736
|
+
}
|
|
1737
|
+
};
|
|
1738
|
+
|
|
1739
|
+
// src/telemetry/pattern-analyzer.ts
|
|
1740
|
+
var import_promises3 = require("fs/promises");
|
|
1741
|
+
var import_node_path5 = require("path");
|
|
1742
|
+
var import_node_os4 = require("os");
|
|
1743
|
+
var PATTERN_SCHEMA_VERSION = 2;
|
|
1744
|
+
var PatternAnalyzer = class _PatternAnalyzer {
|
|
1745
|
+
telemetryDir;
|
|
1746
|
+
outputDir;
|
|
1747
|
+
constructor(telemetryDir) {
|
|
1748
|
+
const defaultDir = (0, import_node_path5.join)((0, import_node_os4.homedir)(), ".kairos", "telemetry");
|
|
1749
|
+
this.telemetryDir = telemetryDir ?? defaultDir;
|
|
1750
|
+
this.outputDir = telemetryDir ? (0, import_node_path5.join)(telemetryDir, "..") : (0, import_node_path5.join)((0, import_node_os4.homedir)(), ".kairos");
|
|
1751
|
+
}
|
|
1752
|
+
async loadPreviousPatterns() {
|
|
1549
1753
|
try {
|
|
1550
|
-
|
|
1754
|
+
const raw = await (0, import_promises3.readFile)((0, import_node_path5.join)(this.outputDir, "patterns.json"), "utf-8");
|
|
1755
|
+
const prev = JSON.parse(raw);
|
|
1756
|
+
const version = prev.schemaVersion ?? 0;
|
|
1757
|
+
const patterns = prev.topFailureRules ?? [];
|
|
1758
|
+
if (version === PATTERN_SCHEMA_VERSION) return patterns;
|
|
1759
|
+
return this.migratePatterns(patterns, version);
|
|
1551
1760
|
} catch {
|
|
1552
1761
|
return [];
|
|
1553
1762
|
}
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1763
|
+
}
|
|
1764
|
+
migratePatterns(patterns, fromVersion) {
|
|
1765
|
+
let migrated = patterns;
|
|
1766
|
+
if (fromVersion < 1) {
|
|
1767
|
+
migrated = migrated.map((p) => ({
|
|
1768
|
+
...p,
|
|
1769
|
+
compositeScore: p.compositeScore ?? 0,
|
|
1770
|
+
scoringFactors: p.scoringFactors ?? { rawConfidence: 0, impact: 0, recency: 0, stickinessBoost: 0 },
|
|
1771
|
+
pipelineStage: p.pipelineStage ?? "node_generation"
|
|
1772
|
+
}));
|
|
1773
|
+
}
|
|
1774
|
+
if (fromVersion < 2) {
|
|
1775
|
+
migrated = migrated.map((p) => ({
|
|
1776
|
+
...p,
|
|
1777
|
+
scoringFactors: {
|
|
1778
|
+
...p.scoringFactors,
|
|
1779
|
+
stickinessBoost: p.scoringFactors.stickinessBoost ?? p.scoringFactors["validationBoost"] ?? 0
|
|
1780
|
+
}
|
|
1781
|
+
}));
|
|
1782
|
+
}
|
|
1783
|
+
return migrated;
|
|
1784
|
+
}
|
|
1785
|
+
async analyze(days = 30) {
|
|
1786
|
+
const previousPatterns = await this.loadPreviousPatterns();
|
|
1787
|
+
const events = await this.readAllEvents(days);
|
|
1788
|
+
const starts = events.filter((e) => e.eventType === "build_start");
|
|
1789
|
+
const attempts = events.filter((e) => e.eventType === "generation_attempt");
|
|
1790
|
+
const passed = attempts.filter(
|
|
1791
|
+
(a) => a.data.validationPassed === true
|
|
1792
|
+
);
|
|
1793
|
+
const failed = attempts.filter(
|
|
1794
|
+
(a) => a.data.validationPassed === false
|
|
1795
|
+
);
|
|
1796
|
+
const ruleFailures = /* @__PURE__ */ new Map();
|
|
1797
|
+
const credentialFailures = /* @__PURE__ */ new Map();
|
|
1798
|
+
for (const a of failed) {
|
|
1799
|
+
const weight = this.recencyWeight(a.fileDate);
|
|
1800
|
+
const data = a.data;
|
|
1801
|
+
for (const issue of data.issues ?? []) {
|
|
1802
|
+
const entry = ruleFailures.get(issue.rule) ?? { count: 0, sessions: /* @__PURE__ */ new Set(), recencyWeights: [], allMessages: [] };
|
|
1803
|
+
entry.count++;
|
|
1804
|
+
entry.sessions.add(a.sessionId);
|
|
1805
|
+
entry.recencyWeights.push(weight);
|
|
1806
|
+
entry.allMessages.push(issue.message);
|
|
1807
|
+
ruleFailures.set(issue.rule, entry);
|
|
1808
|
+
if (issue.rule === 17) {
|
|
1809
|
+
const credPatterns = [
|
|
1810
|
+
/credential\s+"([^"]+)"/,
|
|
1811
|
+
/credentialType[:\s]+"?([^"'\s]+)"?/,
|
|
1812
|
+
/missing\s+credential\s+(?:for\s+)?["']?([^"'\s]+)/i
|
|
1813
|
+
];
|
|
1814
|
+
let credType = "unknown";
|
|
1815
|
+
for (const re of credPatterns) {
|
|
1816
|
+
const m = issue.message.match(re);
|
|
1817
|
+
if (m?.[1]) {
|
|
1818
|
+
credType = m[1];
|
|
1819
|
+
break;
|
|
1820
|
+
}
|
|
1568
1821
|
}
|
|
1822
|
+
credentialFailures.set(credType, (credentialFailures.get(credType) ?? 0) + 1);
|
|
1569
1823
|
}
|
|
1570
|
-
} catch {
|
|
1571
1824
|
}
|
|
1572
1825
|
}
|
|
1573
|
-
|
|
1826
|
+
const failedByDate = /* @__PURE__ */ new Map();
|
|
1827
|
+
for (const a of failed) {
|
|
1828
|
+
failedByDate.set(a.fileDate, (failedByDate.get(a.fileDate) ?? 0) + 1);
|
|
1829
|
+
}
|
|
1830
|
+
const sortedFailDates = [...failedByDate.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
1831
|
+
const hasTrendData = sortedFailDates.length >= 3;
|
|
1832
|
+
let midDate = "";
|
|
1833
|
+
if (hasTrendData) {
|
|
1834
|
+
const halfTotal = failed.length / 2;
|
|
1835
|
+
let cumulative = 0;
|
|
1836
|
+
for (const [date, count] of sortedFailDates) {
|
|
1837
|
+
cumulative += count;
|
|
1838
|
+
if (cumulative >= halfTotal) {
|
|
1839
|
+
midDate = date;
|
|
1840
|
+
break;
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
const ruleTrends = /* @__PURE__ */ new Map();
|
|
1845
|
+
if (hasTrendData) {
|
|
1846
|
+
for (const a of failed) {
|
|
1847
|
+
const data = a.data;
|
|
1848
|
+
const isNewer = a.fileDate > midDate;
|
|
1849
|
+
for (const issue of data.issues ?? []) {
|
|
1850
|
+
const entry = ruleTrends.get(issue.rule) ?? { older: 0, newer: 0 };
|
|
1851
|
+
if (isNewer) entry.newer++;
|
|
1852
|
+
else entry.older++;
|
|
1853
|
+
ruleTrends.set(issue.rule, entry);
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
1858
|
+
for (const a of attempts) {
|
|
1859
|
+
const list = sessions.get(a.sessionId) ?? [];
|
|
1860
|
+
list.push(a);
|
|
1861
|
+
sessions.set(a.sessionId, list);
|
|
1862
|
+
}
|
|
1863
|
+
let firstTryPass = 0;
|
|
1864
|
+
let correctionNeeded = 0;
|
|
1865
|
+
let singleAttemptFail = 0;
|
|
1866
|
+
for (const sessionAttempts of sessions.values()) {
|
|
1867
|
+
const lastAttempt = sessionAttempts[sessionAttempts.length - 1];
|
|
1868
|
+
const lastPassed = lastAttempt.data.validationPassed === true;
|
|
1869
|
+
if (sessionAttempts.length === 1 && lastPassed) {
|
|
1870
|
+
firstTryPass++;
|
|
1871
|
+
} else if (sessionAttempts.length > 1 && lastPassed) {
|
|
1872
|
+
correctionNeeded++;
|
|
1873
|
+
} else {
|
|
1874
|
+
singleAttemptFail++;
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
const durations = attempts.map((a) => a.data.durationMs).filter((d) => typeof d === "number" && d > 0);
|
|
1878
|
+
const avgDuration = durations.length > 0 ? durations.reduce((s, d) => s + d, 0) / durations.length : 0;
|
|
1879
|
+
const totalInput = attempts.reduce((s, a) => s + (a.data.tokensInput ?? 0), 0);
|
|
1880
|
+
const totalOutput = attempts.reduce((s, a) => s + (a.data.tokensOutput ?? 0), 0);
|
|
1881
|
+
const totalSessions = Math.max(sessions.size, 1);
|
|
1882
|
+
const stickinessCount = /* @__PURE__ */ new Map();
|
|
1883
|
+
for (const sessionAttempts of sessions.values()) {
|
|
1884
|
+
if (sessionAttempts.length < 2) continue;
|
|
1885
|
+
for (let i = 0; i < sessionAttempts.length - 1; i++) {
|
|
1886
|
+
const curr = sessionAttempts[i].data;
|
|
1887
|
+
const next = sessionAttempts[i + 1].data;
|
|
1888
|
+
if (curr.validationPassed !== false || next.validationPassed !== false) continue;
|
|
1889
|
+
const currRules = new Set((curr.issues ?? []).map((iss) => iss.rule));
|
|
1890
|
+
const nextRules = new Set((next.issues ?? []).map((iss) => iss.rule));
|
|
1891
|
+
for (const rule of currRules) {
|
|
1892
|
+
if (nextRules.has(rule)) {
|
|
1893
|
+
stickinessCount.set(rule, (stickinessCount.get(rule) ?? 0) + 1);
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
const CONFIRMED_THRESHOLD = 3;
|
|
1899
|
+
const BUILDS_SINCE_LAST_FAILURE_THRESHOLD = 5;
|
|
1900
|
+
const RESOLVED_TTL_DAYS = 90;
|
|
1901
|
+
const activePatterns = [...ruleFailures.entries()].map(([rule, entry]) => {
|
|
1902
|
+
const t = ruleTrends.get(rule) ?? { older: 0, newer: 0 };
|
|
1903
|
+
const rawConfidence = Math.min(entry.sessions.size / totalSessions, 1);
|
|
1904
|
+
const state = entry.count >= CONFIRMED_THRESHOLD ? "confirmed" : "draft";
|
|
1905
|
+
const avgRecency = entry.recencyWeights.length > 0 ? entry.recencyWeights.reduce((s, w) => s + w, 0) / entry.recencyWeights.length : 1;
|
|
1906
|
+
const stickiness = stickinessCount.get(rule) ?? 0;
|
|
1907
|
+
const { compositeScore, factors } = this.computeCompositeScore(rawConfidence, entry.count, state, avgRecency, stickiness);
|
|
1908
|
+
return {
|
|
1909
|
+
rule,
|
|
1910
|
+
failureCount: entry.count,
|
|
1911
|
+
confidence: Math.round(rawConfidence * 1e3) / 1e3,
|
|
1912
|
+
compositeScore,
|
|
1913
|
+
scoringFactors: factors,
|
|
1914
|
+
state,
|
|
1915
|
+
trend: this.classifyTrend(t.older, t.newer),
|
|
1916
|
+
pipelineStage: RULE_PIPELINE_STAGES[rule] ?? "node_generation",
|
|
1917
|
+
exampleMessages: this.deduplicateMessages(entry.allMessages),
|
|
1918
|
+
mitigation: RULE_MITIGATIONS[rule] ?? null
|
|
1919
|
+
};
|
|
1920
|
+
}).sort((a, b) => b.compositeScore - a.compositeScore);
|
|
1921
|
+
const activeRules = new Set(activePatterns.map((p) => p.rule));
|
|
1922
|
+
for (const p of activePatterns) {
|
|
1923
|
+
const prev = previousPatterns.find((pp) => pp.rule === p.rule);
|
|
1924
|
+
if (prev?.state === "resolved") {
|
|
1925
|
+
p.trend = "worsening";
|
|
1926
|
+
p.regressed = true;
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
const ruleLastFailureDate = /* @__PURE__ */ new Map();
|
|
1930
|
+
for (const a of failed) {
|
|
1931
|
+
const data = a.data;
|
|
1932
|
+
for (const issue of data.issues ?? []) {
|
|
1933
|
+
const existing = ruleLastFailureDate.get(issue.rule);
|
|
1934
|
+
if (!existing || a.fileDate > existing) {
|
|
1935
|
+
ruleLastFailureDate.set(issue.rule, a.fileDate);
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
const newlyResolved = previousPatterns.filter((p) => {
|
|
1940
|
+
if (p.state !== "confirmed" || activeRules.has(p.rule)) return false;
|
|
1941
|
+
const lastFailDate = ruleLastFailureDate.get(p.rule) ?? "";
|
|
1942
|
+
const buildsSince = starts.filter((s) => s.fileDate > lastFailDate).length;
|
|
1943
|
+
return buildsSince >= BUILDS_SINCE_LAST_FAILURE_THRESHOLD;
|
|
1944
|
+
}).map((p) => ({
|
|
1945
|
+
...p,
|
|
1946
|
+
state: "resolved",
|
|
1947
|
+
trend: "improving",
|
|
1948
|
+
pipelineStage: p.pipelineStage ?? RULE_PIPELINE_STAGES[p.rule] ?? "node_generation",
|
|
1949
|
+
confidence: 0,
|
|
1950
|
+
compositeScore: 0,
|
|
1951
|
+
scoringFactors: { rawConfidence: 0, impact: 0, recency: 0, stickinessBoost: 0 },
|
|
1952
|
+
failureCount: 0,
|
|
1953
|
+
resolvedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1954
|
+
}));
|
|
1955
|
+
const ttlCutoff = /* @__PURE__ */ new Date();
|
|
1956
|
+
ttlCutoff.setDate(ttlCutoff.getDate() - RESOLVED_TTL_DAYS);
|
|
1957
|
+
const ttlCutoffStr = ttlCutoff.toISOString();
|
|
1958
|
+
const carriedResolved = previousPatterns.filter((p) => p.state === "resolved" && !activeRules.has(p.rule) && (!p.resolvedAt || p.resolvedAt >= ttlCutoffStr)).map((p) => ({ ...p }));
|
|
1959
|
+
const newlyResolvedRules = new Set(newlyResolved.map((p) => p.rule));
|
|
1960
|
+
const pendingResolution = previousPatterns.filter((p) => p.state === "confirmed" && !activeRules.has(p.rule) && !newlyResolvedRules.has(p.rule)).map((p) => ({ ...p }));
|
|
1961
|
+
const deduped = [
|
|
1962
|
+
...newlyResolved,
|
|
1963
|
+
...carriedResolved.filter((p) => !newlyResolvedRules.has(p.rule)),
|
|
1964
|
+
...pendingResolution
|
|
1965
|
+
];
|
|
1966
|
+
const patterns = [...activePatterns, ...deduped];
|
|
1967
|
+
const credTypes = [...credentialFailures.entries()].sort((a, b) => b[1] - a[1]).map(([type, count]) => ({ type, count }));
|
|
1968
|
+
const drift = this.detectDrift(patterns);
|
|
1969
|
+
const warnEffMap = /* @__PURE__ */ new Map();
|
|
1970
|
+
const buildCompletes = events.filter((e) => e.eventType === "build_complete");
|
|
1971
|
+
for (const bc of buildCompletes) {
|
|
1972
|
+
const bcData = bc.data;
|
|
1973
|
+
const warned = bcData.warnedRules ?? [];
|
|
1974
|
+
if (warned.length === 0) continue;
|
|
1975
|
+
const sessionFailedRules = /* @__PURE__ */ new Set();
|
|
1976
|
+
const sessionAttempts = sessions.get(bc.sessionId) ?? [];
|
|
1977
|
+
for (const a of sessionAttempts) {
|
|
1978
|
+
const ad = a.data;
|
|
1979
|
+
if (ad.validationPassed === false) {
|
|
1980
|
+
for (const issue of ad.issues ?? []) {
|
|
1981
|
+
sessionFailedRules.add(issue.rule);
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
for (const rule of warned) {
|
|
1986
|
+
const entry = warnEffMap.get(rule) ?? { warned: 0, passed: 0, failed: 0 };
|
|
1987
|
+
entry.warned++;
|
|
1988
|
+
if (sessionFailedRules.has(rule)) entry.failed++;
|
|
1989
|
+
else entry.passed++;
|
|
1990
|
+
warnEffMap.set(rule, entry);
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
const warningEffectiveness = [...warnEffMap.entries()].map(([rule, e]) => ({
|
|
1994
|
+
rule,
|
|
1995
|
+
timesWarned: e.warned,
|
|
1996
|
+
timesWarnedAndPassed: e.passed,
|
|
1997
|
+
timesWarnedAndFailed: e.failed,
|
|
1998
|
+
effectivenessRate: e.warned > 0 ? Math.round(e.passed / e.warned * 1e3) / 1e3 : 0
|
|
1999
|
+
})).sort((a, b) => b.timesWarned - a.timesWarned);
|
|
2000
|
+
const coOccurrenceMap = /* @__PURE__ */ new Map();
|
|
2001
|
+
for (const a of failed) {
|
|
2002
|
+
const data = a.data;
|
|
2003
|
+
const rules = [...new Set((data.issues ?? []).map((i) => i.rule))].sort((x, y) => x - y);
|
|
2004
|
+
for (let i = 0; i < rules.length; i++) {
|
|
2005
|
+
for (let j = i + 1; j < rules.length; j++) {
|
|
2006
|
+
const key = `${rules[i]},${rules[j]}`;
|
|
2007
|
+
coOccurrenceMap.set(key, (coOccurrenceMap.get(key) ?? 0) + 1);
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
const ruleCoOccurrence = [...coOccurrenceMap.entries()].filter(([, count]) => count >= 3).map(([key, count]) => {
|
|
2012
|
+
const [a, b] = key.split(",").map(Number);
|
|
2013
|
+
return { rules: [a, b], count };
|
|
2014
|
+
}).sort((a, b) => b.count - a.count);
|
|
2015
|
+
const attemptDistribution = {};
|
|
2016
|
+
for (const sessionAttempts of sessions.values()) {
|
|
2017
|
+
const depth = sessionAttempts.length;
|
|
2018
|
+
attemptDistribution[depth] = (attemptDistribution[depth] ?? 0) + 1;
|
|
2019
|
+
}
|
|
2020
|
+
return {
|
|
2021
|
+
schemaVersion: PATTERN_SCHEMA_VERSION,
|
|
2022
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2023
|
+
summary: {
|
|
2024
|
+
totalBuilds: starts.length,
|
|
2025
|
+
totalAttempts: attempts.length,
|
|
2026
|
+
firstTryPassRate: Math.round(firstTryPass / totalSessions * 1e3) / 1e3,
|
|
2027
|
+
correctionRate: Math.round(correctionNeeded / totalSessions * 1e3) / 1e3,
|
|
2028
|
+
singleAttemptFailRate: Math.round(singleAttemptFail / totalSessions * 1e3) / 1e3,
|
|
2029
|
+
avgDurationMs: Math.round(avgDuration),
|
|
2030
|
+
totalTokensInput: totalInput,
|
|
2031
|
+
totalTokensOutput: totalOutput,
|
|
2032
|
+
attemptDistribution
|
|
2033
|
+
},
|
|
2034
|
+
topFailureRules: patterns,
|
|
2035
|
+
failingCredentialTypes: credTypes,
|
|
2036
|
+
drift,
|
|
2037
|
+
warningEffectiveness,
|
|
2038
|
+
ruleCoOccurrence
|
|
2039
|
+
};
|
|
2040
|
+
}
|
|
2041
|
+
async analyzeAndSave(days = 30) {
|
|
2042
|
+
const analysis = await this.analyze(days);
|
|
2043
|
+
await (0, import_promises3.mkdir)(this.outputDir, { recursive: true });
|
|
2044
|
+
const outputPath = (0, import_node_path5.join)(this.outputDir, "patterns.json");
|
|
2045
|
+
const tmpPath = `${outputPath}.tmp`;
|
|
2046
|
+
await (0, import_promises3.writeFile)(tmpPath, JSON.stringify(analysis, null, 2), "utf-8");
|
|
2047
|
+
await (0, import_promises3.rename)(tmpPath, outputPath);
|
|
2048
|
+
const historySummary = {
|
|
2049
|
+
timestamp: analysis.generatedAt,
|
|
2050
|
+
totalBuilds: analysis.summary.totalBuilds,
|
|
2051
|
+
firstTryPassRate: analysis.summary.firstTryPassRate,
|
|
2052
|
+
correctionRate: analysis.summary.correctionRate,
|
|
2053
|
+
singleAttemptFailRate: analysis.summary.singleAttemptFailRate,
|
|
2054
|
+
activePatternCount: analysis.topFailureRules.filter((p) => p.state !== "resolved").length,
|
|
2055
|
+
topRules: analysis.topFailureRules.filter((p) => p.state !== "resolved").slice(0, 5).map((p) => ({ rule: p.rule, compositeScore: p.compositeScore, state: p.state }))
|
|
2056
|
+
};
|
|
2057
|
+
const historyPath = (0, import_node_path5.join)(this.outputDir, "pattern-history.jsonl");
|
|
2058
|
+
await (0, import_promises3.appendFile)(historyPath, JSON.stringify(historySummary) + "\n", "utf-8");
|
|
2059
|
+
return analysis;
|
|
2060
|
+
}
|
|
2061
|
+
async getHistory(limit = 20) {
|
|
2062
|
+
try {
|
|
2063
|
+
const raw = await (0, import_promises3.readFile)((0, import_node_path5.join)(this.outputDir, "pattern-history.jsonl"), "utf-8");
|
|
2064
|
+
return raw.trim().split("\n").filter(Boolean).map((l) => JSON.parse(l)).slice(-limit);
|
|
2065
|
+
} catch {
|
|
2066
|
+
return [];
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
static fromEnv() {
|
|
2070
|
+
const dir = process.env["KAIROS_TELEMETRY"];
|
|
2071
|
+
return dir && dir !== "true" && dir !== "false" ? new _PatternAnalyzer(dir) : new _PatternAnalyzer();
|
|
2072
|
+
}
|
|
2073
|
+
detectDrift(patterns) {
|
|
2074
|
+
const VALIDATOR_RULES = VALIDATOR_RULE_IDS;
|
|
2075
|
+
const validatorRuleSet = new Set(VALIDATOR_RULES);
|
|
2076
|
+
const alerts = [];
|
|
2077
|
+
for (const p of patterns) {
|
|
2078
|
+
if (p.state !== "resolved" && !validatorRuleSet.has(p.rule)) {
|
|
2079
|
+
alerts.push({
|
|
2080
|
+
type: "stale_pattern",
|
|
2081
|
+
rule: p.rule,
|
|
2082
|
+
message: `Pattern references Rule ${p.rule} which does not exist in the current validator (rules 1-23)`
|
|
2083
|
+
});
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
for (const rule of VALIDATOR_RULES) {
|
|
2087
|
+
if (!(rule in RULE_MITIGATIONS)) {
|
|
2088
|
+
alerts.push({
|
|
2089
|
+
type: "missing_mitigation",
|
|
2090
|
+
rule,
|
|
2091
|
+
message: `Rule ${rule} has no mitigation text \u2014 if it fails, the system can't advise the LLM how to fix it`
|
|
2092
|
+
});
|
|
2093
|
+
}
|
|
2094
|
+
if (!(rule in RULE_PIPELINE_STAGES)) {
|
|
2095
|
+
alerts.push({
|
|
2096
|
+
type: "missing_stage_mapping",
|
|
2097
|
+
rule,
|
|
2098
|
+
message: `Rule ${rule} has no pipeline stage mapping \u2014 failures won't be grouped correctly`
|
|
2099
|
+
});
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
const coveredRules = VALIDATOR_RULES.filter((r) => r in RULE_MITIGATIONS && r in RULE_PIPELINE_STAGES).length;
|
|
2103
|
+
return {
|
|
2104
|
+
healthy: alerts.length === 0,
|
|
2105
|
+
alerts,
|
|
2106
|
+
coveredRules,
|
|
2107
|
+
totalRules: VALIDATOR_RULES.length
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
computeCompositeScore(rawConfidence, sampleSize, state, avgRecency, stickiness) {
|
|
2111
|
+
const stateWeights = { draft: 0.3, confirmed: 0.8, resolved: 0.1 };
|
|
2112
|
+
const stateWeight = stateWeights[state];
|
|
2113
|
+
const impact = (1 - Math.exp(-sampleSize / 5)) * stateWeight;
|
|
2114
|
+
const stickinessBoost = Math.min(0.15, stickiness * 0.05);
|
|
2115
|
+
const compositeScore = Math.min(Math.round(rawConfidence * impact * avgRecency * (1 + stickinessBoost) * 1e3) / 1e3, 1);
|
|
2116
|
+
return {
|
|
2117
|
+
compositeScore,
|
|
2118
|
+
factors: {
|
|
2119
|
+
rawConfidence: Math.round(rawConfidence * 1e3) / 1e3,
|
|
2120
|
+
impact: Math.round(impact * 1e3) / 1e3,
|
|
2121
|
+
recency: Math.round(avgRecency * 1e3) / 1e3,
|
|
2122
|
+
stickinessBoost: Math.round(stickinessBoost * 1e3) / 1e3
|
|
2123
|
+
}
|
|
2124
|
+
};
|
|
2125
|
+
}
|
|
2126
|
+
classifyTrend(older, newer) {
|
|
2127
|
+
const total = older + newer;
|
|
2128
|
+
if (total === 0) return "stable";
|
|
2129
|
+
if (older === 0) return "new";
|
|
2130
|
+
const newerRatio = newer / total;
|
|
2131
|
+
if (newerRatio >= 0.65) return "worsening";
|
|
2132
|
+
if (newerRatio <= 0.35) return "improving";
|
|
2133
|
+
return "stable";
|
|
2134
|
+
}
|
|
2135
|
+
deduplicateMessages(messages, maxCount = 3) {
|
|
2136
|
+
const normalize = (msg) => msg.replace(/[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}/gi, "...").replace(/\bnode\s+"[^"]+"/g, 'node "..."').replace(/\s+/g, " ").trim();
|
|
2137
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2138
|
+
const unique = [];
|
|
2139
|
+
for (const msg of messages) {
|
|
2140
|
+
const key = normalize(msg);
|
|
2141
|
+
if (!seen.has(key) && unique.length < maxCount) {
|
|
2142
|
+
seen.add(key);
|
|
2143
|
+
unique.push(msg);
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
return unique;
|
|
2147
|
+
}
|
|
2148
|
+
recencyWeight(fileDate, halfLifeDays = 30) {
|
|
2149
|
+
const daysAgo = Math.max(0, (Date.now() - (/* @__PURE__ */ new Date(fileDate + "T12:00:00Z")).getTime()) / (1e3 * 60 * 60 * 24));
|
|
2150
|
+
return Math.max(0.1, Math.exp(-Math.LN2 * daysAgo / halfLifeDays));
|
|
2151
|
+
}
|
|
2152
|
+
async readAllEvents(days) {
|
|
2153
|
+
return readTelemetryEvents(this.telemetryDir, days);
|
|
1574
2154
|
}
|
|
1575
2155
|
};
|
|
1576
2156
|
|
|
@@ -1644,12 +2224,12 @@ var nullLogger = {
|
|
|
1644
2224
|
};
|
|
1645
2225
|
|
|
1646
2226
|
// src/mcp-server.ts
|
|
1647
|
-
var
|
|
1648
|
-
var
|
|
2227
|
+
var import_node_fs3 = require("fs");
|
|
2228
|
+
var import_node_path6 = require("path");
|
|
1649
2229
|
var import_node_url = require("url");
|
|
1650
2230
|
var import_meta = {};
|
|
1651
|
-
var __dirname = (0,
|
|
1652
|
-
var pkg = JSON.parse((0,
|
|
2231
|
+
var __dirname = (0, import_node_path6.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
|
|
2232
|
+
var pkg = JSON.parse((0, import_node_fs3.readFileSync)((0, import_node_path6.join)(__dirname, "..", "package.json"), "utf-8"));
|
|
1653
2233
|
var library = new FileLibrary();
|
|
1654
2234
|
var validator = new N8nValidator();
|
|
1655
2235
|
var nodeSyncer = new NodeSyncer();
|
|
@@ -1951,6 +2531,27 @@ server.tool(
|
|
|
1951
2531
|
};
|
|
1952
2532
|
}
|
|
1953
2533
|
);
|
|
2534
|
+
server.tool(
|
|
2535
|
+
"kairos_patterns",
|
|
2536
|
+
"Analyze telemetry data and return failure patterns, build stats, and credential breakdowns. Useful for understanding what goes wrong most often and how to prevent it.",
|
|
2537
|
+
{
|
|
2538
|
+
days: import_zod.z.number().default(30).describe("Number of days of telemetry to analyze"),
|
|
2539
|
+
limit: import_zod.z.number().optional().describe("Maximum number of failure patterns to return")
|
|
2540
|
+
},
|
|
2541
|
+
async ({ days, limit }) => {
|
|
2542
|
+
const analyzer = PatternAnalyzer.fromEnv();
|
|
2543
|
+
const analysis = await analyzer.analyzeAndSave(days);
|
|
2544
|
+
if (limit !== void 0 && limit > 0) {
|
|
2545
|
+
analysis.topFailureRules = analysis.topFailureRules.slice(0, limit);
|
|
2546
|
+
}
|
|
2547
|
+
return {
|
|
2548
|
+
content: [{
|
|
2549
|
+
type: "text",
|
|
2550
|
+
text: JSON.stringify(analysis, null, 2)
|
|
2551
|
+
}]
|
|
2552
|
+
};
|
|
2553
|
+
}
|
|
2554
|
+
);
|
|
1954
2555
|
server.tool(
|
|
1955
2556
|
"kairos_list",
|
|
1956
2557
|
"List all workflows deployed on the connected n8n instance.",
|