@kairos-sdk/core 0.3.1 → 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 +39 -7
- 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 +637 -35
- package/dist/mcp-server.cjs.map +1 -1
- package/dist/mcp-server.js +24 -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/index.cjs
CHANGED
|
@@ -559,17 +559,28 @@ var N8nValidator = class {
|
|
|
559
559
|
this.checkRule21(workflow, issues);
|
|
560
560
|
this.checkRule22(workflow, issues);
|
|
561
561
|
this.checkRule23(workflow, issues);
|
|
562
|
+
if (Array.isArray(workflow.nodes)) {
|
|
563
|
+
const nodeById = new Map(workflow.nodes.map((n) => [n.id, n.type]));
|
|
564
|
+
for (const issue of issues) {
|
|
565
|
+
if (issue.nodeId && !issue.nodeType) {
|
|
566
|
+
const nt = nodeById.get(issue.nodeId);
|
|
567
|
+
if (nt) issue.nodeType = nt;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
562
571
|
const errors = issues.filter((i) => i.severity === "error");
|
|
563
572
|
return { valid: errors.length === 0, issues };
|
|
564
573
|
}
|
|
565
|
-
err(issues, rule, message, nodeId) {
|
|
574
|
+
err(issues, rule, message, nodeId, nodeType) {
|
|
566
575
|
const issue = { rule, severity: "error", message };
|
|
567
576
|
if (nodeId !== void 0) issue.nodeId = nodeId;
|
|
577
|
+
if (nodeType !== void 0) issue.nodeType = nodeType;
|
|
568
578
|
issues.push(issue);
|
|
569
579
|
}
|
|
570
|
-
warn(issues, rule, message, nodeId) {
|
|
580
|
+
warn(issues, rule, message, nodeId, nodeType) {
|
|
571
581
|
const issue = { rule, severity: "warn", message };
|
|
572
582
|
if (nodeId !== void 0) issue.nodeId = nodeId;
|
|
583
|
+
if (nodeType !== void 0) issue.nodeType = nodeType;
|
|
573
584
|
issues.push(issue);
|
|
574
585
|
}
|
|
575
586
|
isTriggerNode(node) {
|
|
@@ -932,14 +943,23 @@ var ResponseParseError = class extends KairosError {
|
|
|
932
943
|
|
|
933
944
|
// src/errors/validation-error.ts
|
|
934
945
|
var ValidationError = class extends KairosError {
|
|
935
|
-
constructor(message, issues) {
|
|
946
|
+
constructor(message, issues, attemptMetadata, warnedRules) {
|
|
936
947
|
super(message);
|
|
937
948
|
this.issues = issues;
|
|
949
|
+
this.attemptMetadata = attemptMetadata;
|
|
950
|
+
this.warnedRules = warnedRules;
|
|
938
951
|
this.name = "ValidationError";
|
|
939
952
|
}
|
|
940
953
|
issues;
|
|
954
|
+
attemptMetadata;
|
|
955
|
+
warnedRules;
|
|
941
956
|
};
|
|
942
957
|
|
|
958
|
+
// src/generation/prompt-builder.ts
|
|
959
|
+
var import_node_fs = require("fs");
|
|
960
|
+
var import_node_path = require("path");
|
|
961
|
+
var import_node_os = require("os");
|
|
962
|
+
|
|
943
963
|
// src/generation/prompts/v1.ts
|
|
944
964
|
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.
|
|
945
965
|
|
|
@@ -1132,8 +1152,34 @@ function scoreToMode(score) {
|
|
|
1132
1152
|
return "scratch";
|
|
1133
1153
|
}
|
|
1134
1154
|
|
|
1135
|
-
// src/
|
|
1136
|
-
var
|
|
1155
|
+
// src/validation/rule-metadata.ts
|
|
1156
|
+
var VALIDATOR_RULE_IDS = Array.from({ length: 23 }, (_, i) => i + 1);
|
|
1157
|
+
var RULE_PIPELINE_STAGES = {
|
|
1158
|
+
1: "node_generation",
|
|
1159
|
+
2: "node_generation",
|
|
1160
|
+
3: "node_generation",
|
|
1161
|
+
4: "node_generation",
|
|
1162
|
+
5: "node_generation",
|
|
1163
|
+
6: "node_generation",
|
|
1164
|
+
7: "node_generation",
|
|
1165
|
+
8: "node_generation",
|
|
1166
|
+
9: "connection_wiring",
|
|
1167
|
+
10: "connection_wiring",
|
|
1168
|
+
11: "connection_wiring",
|
|
1169
|
+
12: "workflow_structure",
|
|
1170
|
+
13: "node_generation",
|
|
1171
|
+
14: "workflow_structure",
|
|
1172
|
+
15: "node_generation",
|
|
1173
|
+
16: "node_generation",
|
|
1174
|
+
17: "credential_injection",
|
|
1175
|
+
18: "connection_wiring",
|
|
1176
|
+
19: "node_generation",
|
|
1177
|
+
20: "connection_wiring",
|
|
1178
|
+
21: "workflow_structure",
|
|
1179
|
+
22: "workflow_structure",
|
|
1180
|
+
23: "node_generation"
|
|
1181
|
+
};
|
|
1182
|
+
var RULE_MITIGATIONS = {
|
|
1137
1183
|
1: "Provide a non-empty workflow name string",
|
|
1138
1184
|
2: "Include at least one node in the nodes array",
|
|
1139
1185
|
3: "Every node must have a unique UUID v4 string as its id field",
|
|
@@ -1144,8 +1190,10 @@ var RULE_REMEDIES = {
|
|
|
1144
1190
|
8: "Every node must have a non-empty name string",
|
|
1145
1191
|
9: "connections must be a plain object (use {} if no connections)",
|
|
1146
1192
|
10: "Every node name in connections (source and target) must exactly match a name in the nodes array",
|
|
1193
|
+
11: "Every non-trigger node should have at least one incoming connection",
|
|
1147
1194
|
12: "Remove forbidden fields: id, active, createdAt, updatedAt, versionId, meta, tags \u2014 these are server-assigned",
|
|
1148
|
-
|
|
1195
|
+
13: "workflow.settings must be a plain object if present",
|
|
1196
|
+
14: "Include at least one trigger node (e.g. scheduleTrigger, webhookTrigger, manualTrigger, or service-specific)",
|
|
1149
1197
|
15: 'Node type strings must be fully qualified: "n8n-nodes-base.httpRequest" not just "httpRequest"',
|
|
1150
1198
|
16: "All node names must be unique within the workflow",
|
|
1151
1199
|
17: 'Credentials must be an object with non-empty string id and name fields: { id: "placeholder-id", name: "My Credential" }',
|
|
@@ -1153,9 +1201,17 @@ var RULE_REMEDIES = {
|
|
|
1153
1201
|
19: "Use known safe typeVersion values for each node type",
|
|
1154
1202
|
20: "Remove connection cycles \u2014 ensure no node can reach itself through the connection graph",
|
|
1155
1203
|
21: 'When using webhook with responseMode "responseNode", include a respondToWebhook node in the flow',
|
|
1156
|
-
22: "Ensure all required parameters are set for each node type (e.g. webhook needs httpMethod and path)"
|
|
1204
|
+
22: "Ensure all required parameters are set for each node type (e.g. webhook needs httpMethod and path)",
|
|
1205
|
+
23: "Use node types that exist in the n8n registry \u2014 check with kairos_sync"
|
|
1157
1206
|
};
|
|
1207
|
+
|
|
1208
|
+
// src/generation/prompt-builder.ts
|
|
1209
|
+
var CRITICAL_SCORE_THRESHOLD = 0.15;
|
|
1158
1210
|
var PromptBuilder = class {
|
|
1211
|
+
patternsPath;
|
|
1212
|
+
constructor(patternsPath) {
|
|
1213
|
+
this.patternsPath = patternsPath ?? (0, import_node_path.join)((0, import_node_os.homedir)(), ".kairos", "patterns.json");
|
|
1214
|
+
}
|
|
1159
1215
|
build(request, matches, globalFailureRates = [], dynamicCatalog) {
|
|
1160
1216
|
const mode = this.resolveMode(matches);
|
|
1161
1217
|
const system = this.buildSystem(matches, mode, globalFailureRates, dynamicCatalog);
|
|
@@ -1241,20 +1297,114 @@ A loosely similar workflow (score: ${hint.score.toFixed(2)}) used these node typ
|
|
|
1241
1297
|
}
|
|
1242
1298
|
return blocks;
|
|
1243
1299
|
}
|
|
1300
|
+
loadPatterns() {
|
|
1301
|
+
try {
|
|
1302
|
+
const raw = (0, import_node_fs.readFileSync)(this.patternsPath, "utf-8");
|
|
1303
|
+
const analysis = JSON.parse(raw);
|
|
1304
|
+
const patterns = analysis.topFailureRules ?? [];
|
|
1305
|
+
return patterns.filter((p) => typeof p.pipelineStage === "string" && typeof p.state === "string");
|
|
1306
|
+
} catch {
|
|
1307
|
+
return [];
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
getWarnedRules() {
|
|
1311
|
+
return this.getActivePatterns().map((p) => p.rule);
|
|
1312
|
+
}
|
|
1313
|
+
getActivePatterns() {
|
|
1314
|
+
const MAX_WARNED = 10;
|
|
1315
|
+
const all = this.loadPatterns().filter((p) => p.state !== "resolved" && p.confidence > 0);
|
|
1316
|
+
const regressed = all.filter((p) => p.regressed).sort((a, b) => b.compositeScore - a.compositeScore);
|
|
1317
|
+
const confirmed = all.filter((p) => !p.regressed && p.state === "confirmed").sort((a, b) => b.compositeScore - a.compositeScore);
|
|
1318
|
+
const drafts = all.filter((p) => !p.regressed && p.state !== "confirmed").sort((a, b) => b.compositeScore - a.compositeScore);
|
|
1319
|
+
return [...regressed, ...confirmed, ...drafts].slice(0, MAX_WARNED);
|
|
1320
|
+
}
|
|
1244
1321
|
buildFailureWarnings(matches, globalFailureRates) {
|
|
1322
|
+
const richPatterns = this.getActivePatterns();
|
|
1323
|
+
if (richPatterns.length > 0) {
|
|
1324
|
+
return this.buildStageGroupedWarnings(richPatterns, matches);
|
|
1325
|
+
}
|
|
1326
|
+
return this.buildLegacyWarnings(matches, globalFailureRates);
|
|
1327
|
+
}
|
|
1328
|
+
buildStageGroupedWarnings(patterns, matches) {
|
|
1329
|
+
const stageLabels = {
|
|
1330
|
+
credential_injection: "CREDENTIAL FORMATTING",
|
|
1331
|
+
connection_wiring: "CONNECTION WIRING",
|
|
1332
|
+
node_generation: "NODE GENERATION",
|
|
1333
|
+
workflow_structure: "WORKFLOW STRUCTURE"
|
|
1334
|
+
};
|
|
1335
|
+
const byStage = /* @__PURE__ */ new Map();
|
|
1336
|
+
for (const p of patterns) {
|
|
1337
|
+
const list = byStage.get(p.pipelineStage) ?? [];
|
|
1338
|
+
list.push(p);
|
|
1339
|
+
byStage.set(p.pipelineStage, list);
|
|
1340
|
+
}
|
|
1341
|
+
const sections = [];
|
|
1342
|
+
for (const [stage, stagePatterns] of byStage) {
|
|
1343
|
+
const label = stageLabels[stage] ?? stage;
|
|
1344
|
+
const byMitigation = /* @__PURE__ */ new Map();
|
|
1345
|
+
for (const p of stagePatterns) {
|
|
1346
|
+
const key = p.mitigation ?? `rule_${p.rule}`;
|
|
1347
|
+
const list = byMitigation.get(key) ?? [];
|
|
1348
|
+
list.push(p);
|
|
1349
|
+
byMitigation.set(key, list);
|
|
1350
|
+
}
|
|
1351
|
+
const lines = [];
|
|
1352
|
+
for (const group of byMitigation.values()) {
|
|
1353
|
+
if (group.length === 1) {
|
|
1354
|
+
const p = group[0];
|
|
1355
|
+
const urgency = p.regressed ? "CRITICAL REGRESSION: " : (p.compositeScore ?? 0) >= CRITICAL_SCORE_THRESHOLD ? "CRITICAL: " : "";
|
|
1356
|
+
const statePrefix = p.state === "confirmed" ? "[CONFIRMED] " : "";
|
|
1357
|
+
const trendSuffix = p.trend === "worsening" ? " (GETTING WORSE)" : p.trend === "improving" ? " (improving)" : "";
|
|
1358
|
+
const remedy = p.mitigation ?? RULE_MITIGATIONS[p.rule];
|
|
1359
|
+
const remedyStr = remedy ? `
|
|
1360
|
+
Fix: ${remedy}` : "";
|
|
1361
|
+
lines.push(`- ${urgency}${statePrefix}Rule ${p.rule}${trendSuffix}: ${p.exampleMessages[0] ?? "No example"}${remedyStr}`);
|
|
1362
|
+
} else {
|
|
1363
|
+
const ruleNums = group.map((p) => p.rule).join(", ");
|
|
1364
|
+
const totalFailures = group.reduce((s, p) => s + p.failureCount, 0);
|
|
1365
|
+
const hasConfirmed = group.some((p) => p.state === "confirmed");
|
|
1366
|
+
const statePrefix = hasConfirmed ? "[CONFIRMED] " : "";
|
|
1367
|
+
const remedy = group[0].mitigation;
|
|
1368
|
+
const remedyStr = remedy ? `
|
|
1369
|
+
Fix: ${remedy}` : "";
|
|
1370
|
+
lines.push(`- ${statePrefix}Rules ${ruleNums} (${totalFailures} failures combined): same root cause${remedyStr}`);
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
sections.push(`### ${label}
|
|
1374
|
+
${lines.join("\n")}`);
|
|
1375
|
+
}
|
|
1376
|
+
for (const match of matches) {
|
|
1377
|
+
const fps = match.workflow.failurePatterns;
|
|
1378
|
+
if (!fps?.length) continue;
|
|
1379
|
+
const coveredRules = new Set(patterns.map((p) => p.rule));
|
|
1380
|
+
const extra = fps.filter((fp) => !coveredRules.has(fp.rule));
|
|
1381
|
+
for (const fp of extra) {
|
|
1382
|
+
const remedy = RULE_MITIGATIONS[fp.rule];
|
|
1383
|
+
const remedyStr = remedy ? ` \u2014 Fix: ${remedy}` : "";
|
|
1384
|
+
sections.push(`- Rule ${fp.rule}: "${fp.message}"${remedyStr} (seen in similar workflows)`);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
if (sections.length === 0) return null;
|
|
1388
|
+
return `## Known Failure Patterns \u2014 AVOID THESE
|
|
1389
|
+
|
|
1390
|
+
Grouped by generation stage. Fix these BEFORE outputting your response:
|
|
1391
|
+
|
|
1392
|
+
${sections.join("\n\n")}`;
|
|
1393
|
+
}
|
|
1394
|
+
buildLegacyWarnings(matches, globalFailureRates) {
|
|
1245
1395
|
const lines = [];
|
|
1246
1396
|
for (const match of matches) {
|
|
1247
1397
|
const patterns = match.workflow.failurePatterns;
|
|
1248
1398
|
if (!patterns?.length) continue;
|
|
1249
1399
|
for (const fp of patterns) {
|
|
1250
|
-
const remedy =
|
|
1400
|
+
const remedy = RULE_MITIGATIONS[fp.rule];
|
|
1251
1401
|
const remedyStr = remedy ? ` \u2014 Fix: ${remedy}` : "";
|
|
1252
1402
|
lines.push(`- Rule ${fp.rule}: "${fp.message}"${remedyStr} (seen ${fp.occurrences}x in similar workflows)`);
|
|
1253
1403
|
}
|
|
1254
1404
|
}
|
|
1255
1405
|
const highFreqRules = globalFailureRates.filter((r) => r.rate >= 0.15);
|
|
1256
1406
|
for (const rule of highFreqRules) {
|
|
1257
|
-
const remedy =
|
|
1407
|
+
const remedy = RULE_MITIGATIONS[rule.rule];
|
|
1258
1408
|
const remedyStr = remedy ? ` \u2014 Fix: ${remedy}` : "";
|
|
1259
1409
|
lines.push(`- Rule ${rule.rule}: "${rule.commonMessage}"${remedyStr} (fails in ${Math.round(rule.rate * 100)}% of all builds)`);
|
|
1260
1410
|
}
|
|
@@ -1365,7 +1515,7 @@ var WorkflowDesigner = class {
|
|
|
1365
1515
|
issues: validation.issues
|
|
1366
1516
|
});
|
|
1367
1517
|
if (validation.valid) {
|
|
1368
|
-
return { workflow: parsed.workflow, credentialsNeeded: parsed.credentialsNeeded, attempts, attemptMetadata };
|
|
1518
|
+
return { workflow: parsed.workflow, credentialsNeeded: parsed.credentialsNeeded, attempts, attemptMetadata, warnedRules: this.promptBuilder.getWarnedRules() };
|
|
1369
1519
|
}
|
|
1370
1520
|
lastErrors = errors;
|
|
1371
1521
|
this.logger.warn(`WorkflowDesigner: validation failed on attempt ${attempt}`, {
|
|
@@ -1375,7 +1525,9 @@ var WorkflowDesigner = class {
|
|
|
1375
1525
|
const finalIssues = attemptMetadata.at(-1)?.issues ?? lastErrors;
|
|
1376
1526
|
throw new ValidationError(
|
|
1377
1527
|
`Workflow failed validation after ${MAX_ATTEMPTS} attempts`,
|
|
1378
|
-
finalIssues
|
|
1528
|
+
finalIssues,
|
|
1529
|
+
attemptMetadata,
|
|
1530
|
+
this.promptBuilder.getWarnedRules()
|
|
1379
1531
|
);
|
|
1380
1532
|
}
|
|
1381
1533
|
async callClaude(system, userMessage, temperature) {
|
|
@@ -1429,8 +1581,8 @@ var WorkflowDesigner = class {
|
|
|
1429
1581
|
|
|
1430
1582
|
// src/telemetry/collector.ts
|
|
1431
1583
|
var import_promises = require("fs/promises");
|
|
1432
|
-
var
|
|
1433
|
-
var
|
|
1584
|
+
var import_node_path2 = require("path");
|
|
1585
|
+
var import_node_os2 = require("os");
|
|
1434
1586
|
|
|
1435
1587
|
// src/telemetry/types.ts
|
|
1436
1588
|
var TELEMETRY_SCHEMA_VERSION = 2;
|
|
@@ -1441,7 +1593,7 @@ var TelemetryCollector = class {
|
|
|
1441
1593
|
sessionId;
|
|
1442
1594
|
dirReady = null;
|
|
1443
1595
|
constructor(dir) {
|
|
1444
|
-
this.dir = dir ?? (0,
|
|
1596
|
+
this.dir = dir ?? (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".kairos", "telemetry");
|
|
1445
1597
|
this.sessionId = generateUUID();
|
|
1446
1598
|
}
|
|
1447
1599
|
async emit(eventType, data) {
|
|
@@ -1458,21 +1610,61 @@ var TelemetryCollector = class {
|
|
|
1458
1610
|
}
|
|
1459
1611
|
await this.dirReady;
|
|
1460
1612
|
const filename = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10) + ".jsonl";
|
|
1461
|
-
const filepath = (0,
|
|
1613
|
+
const filepath = (0, import_node_path2.join)(this.dir, filename);
|
|
1462
1614
|
await (0, import_promises.appendFile)(filepath, JSON.stringify(event) + "\n", "utf-8");
|
|
1463
1615
|
}
|
|
1464
1616
|
};
|
|
1465
1617
|
|
|
1466
1618
|
// src/telemetry/reader.ts
|
|
1619
|
+
var import_node_os3 = require("os");
|
|
1620
|
+
var import_node_path4 = require("path");
|
|
1621
|
+
|
|
1622
|
+
// src/telemetry/event-reader.ts
|
|
1467
1623
|
var import_promises2 = require("fs/promises");
|
|
1468
|
-
var
|
|
1469
|
-
var
|
|
1624
|
+
var import_node_fs2 = require("fs");
|
|
1625
|
+
var import_node_path3 = require("path");
|
|
1626
|
+
var import_node_readline = require("readline");
|
|
1627
|
+
async function readTelemetryEvents(dir, days) {
|
|
1628
|
+
let files;
|
|
1629
|
+
try {
|
|
1630
|
+
files = await (0, import_promises2.readdir)(dir);
|
|
1631
|
+
} catch {
|
|
1632
|
+
return [];
|
|
1633
|
+
}
|
|
1634
|
+
const cutoff = /* @__PURE__ */ new Date();
|
|
1635
|
+
cutoff.setDate(cutoff.getDate() - days);
|
|
1636
|
+
const cutoffStr = cutoff.toISOString().slice(0, 10);
|
|
1637
|
+
const todayStr = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1638
|
+
const datePattern = /^\d{4}-\d{2}-\d{2}\.jsonl$/;
|
|
1639
|
+
const recentFiles = files.filter((f) => datePattern.test(f) && f >= cutoffStr && f <= `${todayStr}.jsonl`).sort();
|
|
1640
|
+
const events = [];
|
|
1641
|
+
for (const file of recentFiles) {
|
|
1642
|
+
const fileDate = file.replace(".jsonl", "");
|
|
1643
|
+
try {
|
|
1644
|
+
const rl = (0, import_node_readline.createInterface)({
|
|
1645
|
+
input: (0, import_node_fs2.createReadStream)((0, import_node_path3.join)(dir, file), "utf-8"),
|
|
1646
|
+
crlfDelay: Infinity
|
|
1647
|
+
});
|
|
1648
|
+
for await (const line of rl) {
|
|
1649
|
+
if (!line.trim()) continue;
|
|
1650
|
+
try {
|
|
1651
|
+
events.push({ ...JSON.parse(line), fileDate });
|
|
1652
|
+
} catch {
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
} catch {
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
return events;
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
// src/telemetry/reader.ts
|
|
1470
1662
|
var TelemetryReader = class {
|
|
1471
1663
|
dir;
|
|
1472
1664
|
cache = null;
|
|
1473
1665
|
cacheTime = 0;
|
|
1474
1666
|
constructor(dir) {
|
|
1475
|
-
this.dir = dir ?? (0,
|
|
1667
|
+
this.dir = dir ?? (0, import_node_path4.join)((0, import_node_os3.homedir)(), ".kairos", "telemetry");
|
|
1476
1668
|
}
|
|
1477
1669
|
async getFailureRates(days = 30) {
|
|
1478
1670
|
const now = Date.now();
|
|
@@ -1481,9 +1673,10 @@ var TelemetryReader = class {
|
|
|
1481
1673
|
}
|
|
1482
1674
|
const events = await this.readRecentEvents(days);
|
|
1483
1675
|
const buildSessions = new Set(
|
|
1484
|
-
events.filter((e) => e.eventType === "build_complete"
|
|
1676
|
+
events.filter((e) => e.eventType === "build_complete").map((e) => e.sessionId)
|
|
1485
1677
|
);
|
|
1486
|
-
|
|
1678
|
+
const MIN_BUILDS_FOR_RATES = 3;
|
|
1679
|
+
if (buildSessions.size < MIN_BUILDS_FOR_RATES) return [];
|
|
1487
1680
|
const ruleSessions = /* @__PURE__ */ new Map();
|
|
1488
1681
|
for (const event of events) {
|
|
1489
1682
|
if (event.eventType !== "generation_attempt") continue;
|
|
@@ -1521,32 +1714,425 @@ var TelemetryReader = class {
|
|
|
1521
1714
|
return rates;
|
|
1522
1715
|
}
|
|
1523
1716
|
async readRecentEvents(days) {
|
|
1524
|
-
|
|
1717
|
+
return readTelemetryEvents(this.dir, days);
|
|
1718
|
+
}
|
|
1719
|
+
};
|
|
1720
|
+
|
|
1721
|
+
// src/telemetry/pattern-analyzer.ts
|
|
1722
|
+
var import_promises3 = require("fs/promises");
|
|
1723
|
+
var import_node_path5 = require("path");
|
|
1724
|
+
var import_node_os4 = require("os");
|
|
1725
|
+
var PATTERN_SCHEMA_VERSION = 2;
|
|
1726
|
+
var PatternAnalyzer = class _PatternAnalyzer {
|
|
1727
|
+
telemetryDir;
|
|
1728
|
+
outputDir;
|
|
1729
|
+
constructor(telemetryDir) {
|
|
1730
|
+
const defaultDir = (0, import_node_path5.join)((0, import_node_os4.homedir)(), ".kairos", "telemetry");
|
|
1731
|
+
this.telemetryDir = telemetryDir ?? defaultDir;
|
|
1732
|
+
this.outputDir = telemetryDir ? (0, import_node_path5.join)(telemetryDir, "..") : (0, import_node_path5.join)((0, import_node_os4.homedir)(), ".kairos");
|
|
1733
|
+
}
|
|
1734
|
+
async loadPreviousPatterns() {
|
|
1525
1735
|
try {
|
|
1526
|
-
|
|
1736
|
+
const raw = await (0, import_promises3.readFile)((0, import_node_path5.join)(this.outputDir, "patterns.json"), "utf-8");
|
|
1737
|
+
const prev = JSON.parse(raw);
|
|
1738
|
+
const version = prev.schemaVersion ?? 0;
|
|
1739
|
+
const patterns = prev.topFailureRules ?? [];
|
|
1740
|
+
if (version === PATTERN_SCHEMA_VERSION) return patterns;
|
|
1741
|
+
return this.migratePatterns(patterns, version);
|
|
1527
1742
|
} catch {
|
|
1528
1743
|
return [];
|
|
1529
1744
|
}
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1745
|
+
}
|
|
1746
|
+
migratePatterns(patterns, fromVersion) {
|
|
1747
|
+
let migrated = patterns;
|
|
1748
|
+
if (fromVersion < 1) {
|
|
1749
|
+
migrated = migrated.map((p) => ({
|
|
1750
|
+
...p,
|
|
1751
|
+
compositeScore: p.compositeScore ?? 0,
|
|
1752
|
+
scoringFactors: p.scoringFactors ?? { rawConfidence: 0, impact: 0, recency: 0, stickinessBoost: 0 },
|
|
1753
|
+
pipelineStage: p.pipelineStage ?? "node_generation"
|
|
1754
|
+
}));
|
|
1755
|
+
}
|
|
1756
|
+
if (fromVersion < 2) {
|
|
1757
|
+
migrated = migrated.map((p) => ({
|
|
1758
|
+
...p,
|
|
1759
|
+
scoringFactors: {
|
|
1760
|
+
...p.scoringFactors,
|
|
1761
|
+
stickinessBoost: p.scoringFactors.stickinessBoost ?? p.scoringFactors["validationBoost"] ?? 0
|
|
1762
|
+
}
|
|
1763
|
+
}));
|
|
1764
|
+
}
|
|
1765
|
+
return migrated;
|
|
1766
|
+
}
|
|
1767
|
+
async analyze(days = 30) {
|
|
1768
|
+
const previousPatterns = await this.loadPreviousPatterns();
|
|
1769
|
+
const events = await this.readAllEvents(days);
|
|
1770
|
+
const starts = events.filter((e) => e.eventType === "build_start");
|
|
1771
|
+
const attempts = events.filter((e) => e.eventType === "generation_attempt");
|
|
1772
|
+
const passed = attempts.filter(
|
|
1773
|
+
(a) => a.data.validationPassed === true
|
|
1774
|
+
);
|
|
1775
|
+
const failed = attempts.filter(
|
|
1776
|
+
(a) => a.data.validationPassed === false
|
|
1777
|
+
);
|
|
1778
|
+
const ruleFailures = /* @__PURE__ */ new Map();
|
|
1779
|
+
const credentialFailures = /* @__PURE__ */ new Map();
|
|
1780
|
+
for (const a of failed) {
|
|
1781
|
+
const weight = this.recencyWeight(a.fileDate);
|
|
1782
|
+
const data = a.data;
|
|
1783
|
+
for (const issue of data.issues ?? []) {
|
|
1784
|
+
const entry = ruleFailures.get(issue.rule) ?? { count: 0, sessions: /* @__PURE__ */ new Set(), recencyWeights: [], allMessages: [] };
|
|
1785
|
+
entry.count++;
|
|
1786
|
+
entry.sessions.add(a.sessionId);
|
|
1787
|
+
entry.recencyWeights.push(weight);
|
|
1788
|
+
entry.allMessages.push(issue.message);
|
|
1789
|
+
ruleFailures.set(issue.rule, entry);
|
|
1790
|
+
if (issue.rule === 17) {
|
|
1791
|
+
const credPatterns = [
|
|
1792
|
+
/credential\s+"([^"]+)"/,
|
|
1793
|
+
/credentialType[:\s]+"?([^"'\s]+)"?/,
|
|
1794
|
+
/missing\s+credential\s+(?:for\s+)?["']?([^"'\s]+)/i
|
|
1795
|
+
];
|
|
1796
|
+
let credType = "unknown";
|
|
1797
|
+
for (const re of credPatterns) {
|
|
1798
|
+
const m = issue.message.match(re);
|
|
1799
|
+
if (m?.[1]) {
|
|
1800
|
+
credType = m[1];
|
|
1801
|
+
break;
|
|
1802
|
+
}
|
|
1544
1803
|
}
|
|
1804
|
+
credentialFailures.set(credType, (credentialFailures.get(credType) ?? 0) + 1);
|
|
1545
1805
|
}
|
|
1546
|
-
} catch {
|
|
1547
1806
|
}
|
|
1548
1807
|
}
|
|
1549
|
-
|
|
1808
|
+
const failedByDate = /* @__PURE__ */ new Map();
|
|
1809
|
+
for (const a of failed) {
|
|
1810
|
+
failedByDate.set(a.fileDate, (failedByDate.get(a.fileDate) ?? 0) + 1);
|
|
1811
|
+
}
|
|
1812
|
+
const sortedFailDates = [...failedByDate.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
1813
|
+
const hasTrendData = sortedFailDates.length >= 3;
|
|
1814
|
+
let midDate = "";
|
|
1815
|
+
if (hasTrendData) {
|
|
1816
|
+
const halfTotal = failed.length / 2;
|
|
1817
|
+
let cumulative = 0;
|
|
1818
|
+
for (const [date, count] of sortedFailDates) {
|
|
1819
|
+
cumulative += count;
|
|
1820
|
+
if (cumulative >= halfTotal) {
|
|
1821
|
+
midDate = date;
|
|
1822
|
+
break;
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
const ruleTrends = /* @__PURE__ */ new Map();
|
|
1827
|
+
if (hasTrendData) {
|
|
1828
|
+
for (const a of failed) {
|
|
1829
|
+
const data = a.data;
|
|
1830
|
+
const isNewer = a.fileDate > midDate;
|
|
1831
|
+
for (const issue of data.issues ?? []) {
|
|
1832
|
+
const entry = ruleTrends.get(issue.rule) ?? { older: 0, newer: 0 };
|
|
1833
|
+
if (isNewer) entry.newer++;
|
|
1834
|
+
else entry.older++;
|
|
1835
|
+
ruleTrends.set(issue.rule, entry);
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
1840
|
+
for (const a of attempts) {
|
|
1841
|
+
const list = sessions.get(a.sessionId) ?? [];
|
|
1842
|
+
list.push(a);
|
|
1843
|
+
sessions.set(a.sessionId, list);
|
|
1844
|
+
}
|
|
1845
|
+
let firstTryPass = 0;
|
|
1846
|
+
let correctionNeeded = 0;
|
|
1847
|
+
let singleAttemptFail = 0;
|
|
1848
|
+
for (const sessionAttempts of sessions.values()) {
|
|
1849
|
+
const lastAttempt = sessionAttempts[sessionAttempts.length - 1];
|
|
1850
|
+
const lastPassed = lastAttempt.data.validationPassed === true;
|
|
1851
|
+
if (sessionAttempts.length === 1 && lastPassed) {
|
|
1852
|
+
firstTryPass++;
|
|
1853
|
+
} else if (sessionAttempts.length > 1 && lastPassed) {
|
|
1854
|
+
correctionNeeded++;
|
|
1855
|
+
} else {
|
|
1856
|
+
singleAttemptFail++;
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
const durations = attempts.map((a) => a.data.durationMs).filter((d) => typeof d === "number" && d > 0);
|
|
1860
|
+
const avgDuration = durations.length > 0 ? durations.reduce((s, d) => s + d, 0) / durations.length : 0;
|
|
1861
|
+
const totalInput = attempts.reduce((s, a) => s + (a.data.tokensInput ?? 0), 0);
|
|
1862
|
+
const totalOutput = attempts.reduce((s, a) => s + (a.data.tokensOutput ?? 0), 0);
|
|
1863
|
+
const totalSessions = Math.max(sessions.size, 1);
|
|
1864
|
+
const stickinessCount = /* @__PURE__ */ new Map();
|
|
1865
|
+
for (const sessionAttempts of sessions.values()) {
|
|
1866
|
+
if (sessionAttempts.length < 2) continue;
|
|
1867
|
+
for (let i = 0; i < sessionAttempts.length - 1; i++) {
|
|
1868
|
+
const curr = sessionAttempts[i].data;
|
|
1869
|
+
const next = sessionAttempts[i + 1].data;
|
|
1870
|
+
if (curr.validationPassed !== false || next.validationPassed !== false) continue;
|
|
1871
|
+
const currRules = new Set((curr.issues ?? []).map((iss) => iss.rule));
|
|
1872
|
+
const nextRules = new Set((next.issues ?? []).map((iss) => iss.rule));
|
|
1873
|
+
for (const rule of currRules) {
|
|
1874
|
+
if (nextRules.has(rule)) {
|
|
1875
|
+
stickinessCount.set(rule, (stickinessCount.get(rule) ?? 0) + 1);
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
const CONFIRMED_THRESHOLD = 3;
|
|
1881
|
+
const BUILDS_SINCE_LAST_FAILURE_THRESHOLD = 5;
|
|
1882
|
+
const RESOLVED_TTL_DAYS = 90;
|
|
1883
|
+
const activePatterns = [...ruleFailures.entries()].map(([rule, entry]) => {
|
|
1884
|
+
const t = ruleTrends.get(rule) ?? { older: 0, newer: 0 };
|
|
1885
|
+
const rawConfidence = Math.min(entry.sessions.size / totalSessions, 1);
|
|
1886
|
+
const state = entry.count >= CONFIRMED_THRESHOLD ? "confirmed" : "draft";
|
|
1887
|
+
const avgRecency = entry.recencyWeights.length > 0 ? entry.recencyWeights.reduce((s, w) => s + w, 0) / entry.recencyWeights.length : 1;
|
|
1888
|
+
const stickiness = stickinessCount.get(rule) ?? 0;
|
|
1889
|
+
const { compositeScore, factors } = this.computeCompositeScore(rawConfidence, entry.count, state, avgRecency, stickiness);
|
|
1890
|
+
return {
|
|
1891
|
+
rule,
|
|
1892
|
+
failureCount: entry.count,
|
|
1893
|
+
confidence: Math.round(rawConfidence * 1e3) / 1e3,
|
|
1894
|
+
compositeScore,
|
|
1895
|
+
scoringFactors: factors,
|
|
1896
|
+
state,
|
|
1897
|
+
trend: this.classifyTrend(t.older, t.newer),
|
|
1898
|
+
pipelineStage: RULE_PIPELINE_STAGES[rule] ?? "node_generation",
|
|
1899
|
+
exampleMessages: this.deduplicateMessages(entry.allMessages),
|
|
1900
|
+
mitigation: RULE_MITIGATIONS[rule] ?? null
|
|
1901
|
+
};
|
|
1902
|
+
}).sort((a, b) => b.compositeScore - a.compositeScore);
|
|
1903
|
+
const activeRules = new Set(activePatterns.map((p) => p.rule));
|
|
1904
|
+
for (const p of activePatterns) {
|
|
1905
|
+
const prev = previousPatterns.find((pp) => pp.rule === p.rule);
|
|
1906
|
+
if (prev?.state === "resolved") {
|
|
1907
|
+
p.trend = "worsening";
|
|
1908
|
+
p.regressed = true;
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
const ruleLastFailureDate = /* @__PURE__ */ new Map();
|
|
1912
|
+
for (const a of failed) {
|
|
1913
|
+
const data = a.data;
|
|
1914
|
+
for (const issue of data.issues ?? []) {
|
|
1915
|
+
const existing = ruleLastFailureDate.get(issue.rule);
|
|
1916
|
+
if (!existing || a.fileDate > existing) {
|
|
1917
|
+
ruleLastFailureDate.set(issue.rule, a.fileDate);
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
const newlyResolved = previousPatterns.filter((p) => {
|
|
1922
|
+
if (p.state !== "confirmed" || activeRules.has(p.rule)) return false;
|
|
1923
|
+
const lastFailDate = ruleLastFailureDate.get(p.rule) ?? "";
|
|
1924
|
+
const buildsSince = starts.filter((s) => s.fileDate > lastFailDate).length;
|
|
1925
|
+
return buildsSince >= BUILDS_SINCE_LAST_FAILURE_THRESHOLD;
|
|
1926
|
+
}).map((p) => ({
|
|
1927
|
+
...p,
|
|
1928
|
+
state: "resolved",
|
|
1929
|
+
trend: "improving",
|
|
1930
|
+
pipelineStage: p.pipelineStage ?? RULE_PIPELINE_STAGES[p.rule] ?? "node_generation",
|
|
1931
|
+
confidence: 0,
|
|
1932
|
+
compositeScore: 0,
|
|
1933
|
+
scoringFactors: { rawConfidence: 0, impact: 0, recency: 0, stickinessBoost: 0 },
|
|
1934
|
+
failureCount: 0,
|
|
1935
|
+
resolvedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1936
|
+
}));
|
|
1937
|
+
const ttlCutoff = /* @__PURE__ */ new Date();
|
|
1938
|
+
ttlCutoff.setDate(ttlCutoff.getDate() - RESOLVED_TTL_DAYS);
|
|
1939
|
+
const ttlCutoffStr = ttlCutoff.toISOString();
|
|
1940
|
+
const carriedResolved = previousPatterns.filter((p) => p.state === "resolved" && !activeRules.has(p.rule) && (!p.resolvedAt || p.resolvedAt >= ttlCutoffStr)).map((p) => ({ ...p }));
|
|
1941
|
+
const newlyResolvedRules = new Set(newlyResolved.map((p) => p.rule));
|
|
1942
|
+
const pendingResolution = previousPatterns.filter((p) => p.state === "confirmed" && !activeRules.has(p.rule) && !newlyResolvedRules.has(p.rule)).map((p) => ({ ...p }));
|
|
1943
|
+
const deduped = [
|
|
1944
|
+
...newlyResolved,
|
|
1945
|
+
...carriedResolved.filter((p) => !newlyResolvedRules.has(p.rule)),
|
|
1946
|
+
...pendingResolution
|
|
1947
|
+
];
|
|
1948
|
+
const patterns = [...activePatterns, ...deduped];
|
|
1949
|
+
const credTypes = [...credentialFailures.entries()].sort((a, b) => b[1] - a[1]).map(([type, count]) => ({ type, count }));
|
|
1950
|
+
const drift = this.detectDrift(patterns);
|
|
1951
|
+
const warnEffMap = /* @__PURE__ */ new Map();
|
|
1952
|
+
const buildCompletes = events.filter((e) => e.eventType === "build_complete");
|
|
1953
|
+
for (const bc of buildCompletes) {
|
|
1954
|
+
const bcData = bc.data;
|
|
1955
|
+
const warned = bcData.warnedRules ?? [];
|
|
1956
|
+
if (warned.length === 0) continue;
|
|
1957
|
+
const sessionFailedRules = /* @__PURE__ */ new Set();
|
|
1958
|
+
const sessionAttempts = sessions.get(bc.sessionId) ?? [];
|
|
1959
|
+
for (const a of sessionAttempts) {
|
|
1960
|
+
const ad = a.data;
|
|
1961
|
+
if (ad.validationPassed === false) {
|
|
1962
|
+
for (const issue of ad.issues ?? []) {
|
|
1963
|
+
sessionFailedRules.add(issue.rule);
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
for (const rule of warned) {
|
|
1968
|
+
const entry = warnEffMap.get(rule) ?? { warned: 0, passed: 0, failed: 0 };
|
|
1969
|
+
entry.warned++;
|
|
1970
|
+
if (sessionFailedRules.has(rule)) entry.failed++;
|
|
1971
|
+
else entry.passed++;
|
|
1972
|
+
warnEffMap.set(rule, entry);
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
const warningEffectiveness = [...warnEffMap.entries()].map(([rule, e]) => ({
|
|
1976
|
+
rule,
|
|
1977
|
+
timesWarned: e.warned,
|
|
1978
|
+
timesWarnedAndPassed: e.passed,
|
|
1979
|
+
timesWarnedAndFailed: e.failed,
|
|
1980
|
+
effectivenessRate: e.warned > 0 ? Math.round(e.passed / e.warned * 1e3) / 1e3 : 0
|
|
1981
|
+
})).sort((a, b) => b.timesWarned - a.timesWarned);
|
|
1982
|
+
const coOccurrenceMap = /* @__PURE__ */ new Map();
|
|
1983
|
+
for (const a of failed) {
|
|
1984
|
+
const data = a.data;
|
|
1985
|
+
const rules = [...new Set((data.issues ?? []).map((i) => i.rule))].sort((x, y) => x - y);
|
|
1986
|
+
for (let i = 0; i < rules.length; i++) {
|
|
1987
|
+
for (let j = i + 1; j < rules.length; j++) {
|
|
1988
|
+
const key = `${rules[i]},${rules[j]}`;
|
|
1989
|
+
coOccurrenceMap.set(key, (coOccurrenceMap.get(key) ?? 0) + 1);
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
const ruleCoOccurrence = [...coOccurrenceMap.entries()].filter(([, count]) => count >= 3).map(([key, count]) => {
|
|
1994
|
+
const [a, b] = key.split(",").map(Number);
|
|
1995
|
+
return { rules: [a, b], count };
|
|
1996
|
+
}).sort((a, b) => b.count - a.count);
|
|
1997
|
+
const attemptDistribution = {};
|
|
1998
|
+
for (const sessionAttempts of sessions.values()) {
|
|
1999
|
+
const depth = sessionAttempts.length;
|
|
2000
|
+
attemptDistribution[depth] = (attemptDistribution[depth] ?? 0) + 1;
|
|
2001
|
+
}
|
|
2002
|
+
return {
|
|
2003
|
+
schemaVersion: PATTERN_SCHEMA_VERSION,
|
|
2004
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2005
|
+
summary: {
|
|
2006
|
+
totalBuilds: starts.length,
|
|
2007
|
+
totalAttempts: attempts.length,
|
|
2008
|
+
firstTryPassRate: Math.round(firstTryPass / totalSessions * 1e3) / 1e3,
|
|
2009
|
+
correctionRate: Math.round(correctionNeeded / totalSessions * 1e3) / 1e3,
|
|
2010
|
+
singleAttemptFailRate: Math.round(singleAttemptFail / totalSessions * 1e3) / 1e3,
|
|
2011
|
+
avgDurationMs: Math.round(avgDuration),
|
|
2012
|
+
totalTokensInput: totalInput,
|
|
2013
|
+
totalTokensOutput: totalOutput,
|
|
2014
|
+
attemptDistribution
|
|
2015
|
+
},
|
|
2016
|
+
topFailureRules: patterns,
|
|
2017
|
+
failingCredentialTypes: credTypes,
|
|
2018
|
+
drift,
|
|
2019
|
+
warningEffectiveness,
|
|
2020
|
+
ruleCoOccurrence
|
|
2021
|
+
};
|
|
2022
|
+
}
|
|
2023
|
+
async analyzeAndSave(days = 30) {
|
|
2024
|
+
const analysis = await this.analyze(days);
|
|
2025
|
+
await (0, import_promises3.mkdir)(this.outputDir, { recursive: true });
|
|
2026
|
+
const outputPath = (0, import_node_path5.join)(this.outputDir, "patterns.json");
|
|
2027
|
+
const tmpPath = `${outputPath}.tmp`;
|
|
2028
|
+
await (0, import_promises3.writeFile)(tmpPath, JSON.stringify(analysis, null, 2), "utf-8");
|
|
2029
|
+
await (0, import_promises3.rename)(tmpPath, outputPath);
|
|
2030
|
+
const historySummary = {
|
|
2031
|
+
timestamp: analysis.generatedAt,
|
|
2032
|
+
totalBuilds: analysis.summary.totalBuilds,
|
|
2033
|
+
firstTryPassRate: analysis.summary.firstTryPassRate,
|
|
2034
|
+
correctionRate: analysis.summary.correctionRate,
|
|
2035
|
+
singleAttemptFailRate: analysis.summary.singleAttemptFailRate,
|
|
2036
|
+
activePatternCount: analysis.topFailureRules.filter((p) => p.state !== "resolved").length,
|
|
2037
|
+
topRules: analysis.topFailureRules.filter((p) => p.state !== "resolved").slice(0, 5).map((p) => ({ rule: p.rule, compositeScore: p.compositeScore, state: p.state }))
|
|
2038
|
+
};
|
|
2039
|
+
const historyPath = (0, import_node_path5.join)(this.outputDir, "pattern-history.jsonl");
|
|
2040
|
+
await (0, import_promises3.appendFile)(historyPath, JSON.stringify(historySummary) + "\n", "utf-8");
|
|
2041
|
+
return analysis;
|
|
2042
|
+
}
|
|
2043
|
+
async getHistory(limit = 20) {
|
|
2044
|
+
try {
|
|
2045
|
+
const raw = await (0, import_promises3.readFile)((0, import_node_path5.join)(this.outputDir, "pattern-history.jsonl"), "utf-8");
|
|
2046
|
+
return raw.trim().split("\n").filter(Boolean).map((l) => JSON.parse(l)).slice(-limit);
|
|
2047
|
+
} catch {
|
|
2048
|
+
return [];
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
static fromEnv() {
|
|
2052
|
+
const dir = process.env["KAIROS_TELEMETRY"];
|
|
2053
|
+
return dir && dir !== "true" && dir !== "false" ? new _PatternAnalyzer(dir) : new _PatternAnalyzer();
|
|
2054
|
+
}
|
|
2055
|
+
detectDrift(patterns) {
|
|
2056
|
+
const VALIDATOR_RULES = VALIDATOR_RULE_IDS;
|
|
2057
|
+
const validatorRuleSet = new Set(VALIDATOR_RULES);
|
|
2058
|
+
const alerts = [];
|
|
2059
|
+
for (const p of patterns) {
|
|
2060
|
+
if (p.state !== "resolved" && !validatorRuleSet.has(p.rule)) {
|
|
2061
|
+
alerts.push({
|
|
2062
|
+
type: "stale_pattern",
|
|
2063
|
+
rule: p.rule,
|
|
2064
|
+
message: `Pattern references Rule ${p.rule} which does not exist in the current validator (rules 1-23)`
|
|
2065
|
+
});
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
for (const rule of VALIDATOR_RULES) {
|
|
2069
|
+
if (!(rule in RULE_MITIGATIONS)) {
|
|
2070
|
+
alerts.push({
|
|
2071
|
+
type: "missing_mitigation",
|
|
2072
|
+
rule,
|
|
2073
|
+
message: `Rule ${rule} has no mitigation text \u2014 if it fails, the system can't advise the LLM how to fix it`
|
|
2074
|
+
});
|
|
2075
|
+
}
|
|
2076
|
+
if (!(rule in RULE_PIPELINE_STAGES)) {
|
|
2077
|
+
alerts.push({
|
|
2078
|
+
type: "missing_stage_mapping",
|
|
2079
|
+
rule,
|
|
2080
|
+
message: `Rule ${rule} has no pipeline stage mapping \u2014 failures won't be grouped correctly`
|
|
2081
|
+
});
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
const coveredRules = VALIDATOR_RULES.filter((r) => r in RULE_MITIGATIONS && r in RULE_PIPELINE_STAGES).length;
|
|
2085
|
+
return {
|
|
2086
|
+
healthy: alerts.length === 0,
|
|
2087
|
+
alerts,
|
|
2088
|
+
coveredRules,
|
|
2089
|
+
totalRules: VALIDATOR_RULES.length
|
|
2090
|
+
};
|
|
2091
|
+
}
|
|
2092
|
+
computeCompositeScore(rawConfidence, sampleSize, state, avgRecency, stickiness) {
|
|
2093
|
+
const stateWeights = { draft: 0.3, confirmed: 0.8, resolved: 0.1 };
|
|
2094
|
+
const stateWeight = stateWeights[state];
|
|
2095
|
+
const impact = (1 - Math.exp(-sampleSize / 5)) * stateWeight;
|
|
2096
|
+
const stickinessBoost = Math.min(0.15, stickiness * 0.05);
|
|
2097
|
+
const compositeScore = Math.min(Math.round(rawConfidence * impact * avgRecency * (1 + stickinessBoost) * 1e3) / 1e3, 1);
|
|
2098
|
+
return {
|
|
2099
|
+
compositeScore,
|
|
2100
|
+
factors: {
|
|
2101
|
+
rawConfidence: Math.round(rawConfidence * 1e3) / 1e3,
|
|
2102
|
+
impact: Math.round(impact * 1e3) / 1e3,
|
|
2103
|
+
recency: Math.round(avgRecency * 1e3) / 1e3,
|
|
2104
|
+
stickinessBoost: Math.round(stickinessBoost * 1e3) / 1e3
|
|
2105
|
+
}
|
|
2106
|
+
};
|
|
2107
|
+
}
|
|
2108
|
+
classifyTrend(older, newer) {
|
|
2109
|
+
const total = older + newer;
|
|
2110
|
+
if (total === 0) return "stable";
|
|
2111
|
+
if (older === 0) return "new";
|
|
2112
|
+
const newerRatio = newer / total;
|
|
2113
|
+
if (newerRatio >= 0.65) return "worsening";
|
|
2114
|
+
if (newerRatio <= 0.35) return "improving";
|
|
2115
|
+
return "stable";
|
|
2116
|
+
}
|
|
2117
|
+
deduplicateMessages(messages, maxCount = 3) {
|
|
2118
|
+
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();
|
|
2119
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2120
|
+
const unique = [];
|
|
2121
|
+
for (const msg of messages) {
|
|
2122
|
+
const key = normalize(msg);
|
|
2123
|
+
if (!seen.has(key) && unique.length < maxCount) {
|
|
2124
|
+
seen.add(key);
|
|
2125
|
+
unique.push(msg);
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
return unique;
|
|
2129
|
+
}
|
|
2130
|
+
recencyWeight(fileDate, halfLifeDays = 30) {
|
|
2131
|
+
const daysAgo = Math.max(0, (Date.now() - (/* @__PURE__ */ new Date(fileDate + "T12:00:00Z")).getTime()) / (1e3 * 60 * 60 * 24));
|
|
2132
|
+
return Math.max(0.1, Math.exp(-Math.LN2 * daysAgo / halfLifeDays));
|
|
2133
|
+
}
|
|
2134
|
+
async readAllEvents(days) {
|
|
2135
|
+
return readTelemetryEvents(this.telemetryDir, days);
|
|
1550
2136
|
}
|
|
1551
2137
|
};
|
|
1552
2138
|
|
|
@@ -1572,6 +2158,7 @@ var Kairos = class {
|
|
|
1572
2158
|
logger;
|
|
1573
2159
|
telemetry;
|
|
1574
2160
|
telemetryReader;
|
|
2161
|
+
patternAnalyzer;
|
|
1575
2162
|
model;
|
|
1576
2163
|
saveQueue = Promise.resolve(null);
|
|
1577
2164
|
constructor(options) {
|
|
@@ -1597,12 +2184,15 @@ var Kairos = class {
|
|
|
1597
2184
|
if (options.telemetry === true) {
|
|
1598
2185
|
this.telemetry = new TelemetryCollector();
|
|
1599
2186
|
this.telemetryReader = new TelemetryReader();
|
|
2187
|
+
this.patternAnalyzer = new PatternAnalyzer();
|
|
1600
2188
|
} else if (typeof options.telemetry === "string") {
|
|
1601
2189
|
this.telemetry = new TelemetryCollector(options.telemetry);
|
|
1602
2190
|
this.telemetryReader = new TelemetryReader(options.telemetry);
|
|
2191
|
+
this.patternAnalyzer = new PatternAnalyzer(options.telemetry);
|
|
1603
2192
|
} else {
|
|
1604
2193
|
this.telemetry = null;
|
|
1605
2194
|
this.telemetryReader = null;
|
|
2195
|
+
this.patternAnalyzer = null;
|
|
1606
2196
|
}
|
|
1607
2197
|
}
|
|
1608
2198
|
requireProvider() {
|
|
@@ -1640,11 +2230,45 @@ var Kairos = class {
|
|
|
1640
2230
|
this.logger.info(`Telemetry: ${highFreq.length} high-frequency failure rule(s) will be warned about`);
|
|
1641
2231
|
}
|
|
1642
2232
|
}
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
2233
|
+
let designResult;
|
|
2234
|
+
try {
|
|
2235
|
+
designResult = await this.designer.design(
|
|
2236
|
+
{ description, ...options?.name ? { name: options.name } : {} },
|
|
2237
|
+
matches,
|
|
2238
|
+
globalFailureRates
|
|
2239
|
+
);
|
|
2240
|
+
} catch (err) {
|
|
2241
|
+
if (err instanceof ValidationError && err.attemptMetadata) {
|
|
2242
|
+
for (const meta of err.attemptMetadata) {
|
|
2243
|
+
await this.telemetry?.emit("generation_attempt", {
|
|
2244
|
+
description,
|
|
2245
|
+
attempt: meta.attempt,
|
|
2246
|
+
temperature: meta.temperature,
|
|
2247
|
+
durationMs: meta.durationMs,
|
|
2248
|
+
tokensInput: meta.tokensInput,
|
|
2249
|
+
tokensOutput: meta.tokensOutput,
|
|
2250
|
+
validationPassed: meta.validationPassed,
|
|
2251
|
+
issueCount: meta.issues.length,
|
|
2252
|
+
issues: meta.issues.map((i) => ({ rule: i.rule, message: i.message, nodeId: i.nodeId ?? null, nodeType: i.nodeType ?? null }))
|
|
2253
|
+
});
|
|
2254
|
+
}
|
|
2255
|
+
await this.telemetry?.emit("build_complete", {
|
|
2256
|
+
description,
|
|
2257
|
+
success: false,
|
|
2258
|
+
totalAttempts: err.attemptMetadata.length,
|
|
2259
|
+
totalDurationMs: Date.now() - buildStart,
|
|
2260
|
+
totalTokensInput: err.attemptMetadata.reduce((s, m) => s + m.tokensInput, 0),
|
|
2261
|
+
totalTokensOutput: err.attemptMetadata.reduce((s, m) => s + m.tokensOutput, 0),
|
|
2262
|
+
workflowName: null,
|
|
2263
|
+
workflowId: null,
|
|
2264
|
+
dryRun: options?.dryRun ?? false,
|
|
2265
|
+
credentialsNeeded: 0,
|
|
2266
|
+
warnedRules: err.warnedRules ?? []
|
|
2267
|
+
});
|
|
2268
|
+
this.updatePatterns();
|
|
2269
|
+
}
|
|
2270
|
+
throw err;
|
|
2271
|
+
}
|
|
1648
2272
|
await this.emitAttemptTelemetry(description, designResult);
|
|
1649
2273
|
const workflow = options?.name ? { ...designResult.workflow, name: options.name } : designResult.workflow;
|
|
1650
2274
|
this.saveToLibrary(workflow, description, designResult, matches);
|
|
@@ -1661,8 +2285,10 @@ var Kairos = class {
|
|
|
1661
2285
|
workflowName: workflow.name,
|
|
1662
2286
|
workflowId: null,
|
|
1663
2287
|
dryRun: true,
|
|
1664
|
-
credentialsNeeded: designResult.credentialsNeeded.length
|
|
2288
|
+
credentialsNeeded: designResult.credentialsNeeded.length,
|
|
2289
|
+
warnedRules: designResult.warnedRules
|
|
1665
2290
|
});
|
|
2291
|
+
this.updatePatterns();
|
|
1666
2292
|
return {
|
|
1667
2293
|
workflowId: null,
|
|
1668
2294
|
name: workflow.name,
|
|
@@ -1691,8 +2317,10 @@ var Kairos = class {
|
|
|
1691
2317
|
workflowName: deployed.name,
|
|
1692
2318
|
workflowId: deployed.workflowId,
|
|
1693
2319
|
dryRun: false,
|
|
1694
|
-
credentialsNeeded: designResult.credentialsNeeded.length
|
|
2320
|
+
credentialsNeeded: designResult.credentialsNeeded.length,
|
|
2321
|
+
warnedRules: designResult.warnedRules
|
|
1695
2322
|
});
|
|
2323
|
+
this.updatePatterns();
|
|
1696
2324
|
return {
|
|
1697
2325
|
workflowId: deployed.workflowId,
|
|
1698
2326
|
name: deployed.name,
|
|
@@ -1715,7 +2343,41 @@ var Kairos = class {
|
|
|
1715
2343
|
await this.library.initialize();
|
|
1716
2344
|
const matches = await this.library.search(description);
|
|
1717
2345
|
const globalFailureRates = await this.telemetryReader?.getFailureRates() ?? [];
|
|
1718
|
-
|
|
2346
|
+
let designResult;
|
|
2347
|
+
try {
|
|
2348
|
+
designResult = await this.designer.design({ description }, matches, globalFailureRates);
|
|
2349
|
+
} catch (err) {
|
|
2350
|
+
if (err instanceof ValidationError && err.attemptMetadata) {
|
|
2351
|
+
for (const meta of err.attemptMetadata) {
|
|
2352
|
+
await this.telemetry?.emit("generation_attempt", {
|
|
2353
|
+
description,
|
|
2354
|
+
attempt: meta.attempt,
|
|
2355
|
+
temperature: meta.temperature,
|
|
2356
|
+
durationMs: meta.durationMs,
|
|
2357
|
+
tokensInput: meta.tokensInput,
|
|
2358
|
+
tokensOutput: meta.tokensOutput,
|
|
2359
|
+
validationPassed: meta.validationPassed,
|
|
2360
|
+
issueCount: meta.issues.length,
|
|
2361
|
+
issues: meta.issues.map((i) => ({ rule: i.rule, message: i.message, nodeId: i.nodeId ?? null, nodeType: i.nodeType ?? null }))
|
|
2362
|
+
});
|
|
2363
|
+
}
|
|
2364
|
+
await this.telemetry?.emit("build_complete", {
|
|
2365
|
+
description,
|
|
2366
|
+
success: false,
|
|
2367
|
+
totalAttempts: err.attemptMetadata.length,
|
|
2368
|
+
totalDurationMs: Date.now() - buildStart,
|
|
2369
|
+
totalTokensInput: err.attemptMetadata.reduce((s, m) => s + m.tokensInput, 0),
|
|
2370
|
+
totalTokensOutput: err.attemptMetadata.reduce((s, m) => s + m.tokensOutput, 0),
|
|
2371
|
+
workflowName: null,
|
|
2372
|
+
workflowId: null,
|
|
2373
|
+
dryRun: false,
|
|
2374
|
+
credentialsNeeded: 0,
|
|
2375
|
+
warnedRules: err.warnedRules ?? []
|
|
2376
|
+
});
|
|
2377
|
+
this.updatePatterns();
|
|
2378
|
+
}
|
|
2379
|
+
throw err;
|
|
2380
|
+
}
|
|
1719
2381
|
await this.emitAttemptTelemetry(description, designResult);
|
|
1720
2382
|
const provider = this.requireProvider();
|
|
1721
2383
|
const deployed = await provider.update(id, designResult.workflow);
|
|
@@ -1733,8 +2395,10 @@ var Kairos = class {
|
|
|
1733
2395
|
workflowName: deployed.name,
|
|
1734
2396
|
workflowId: deployed.workflowId,
|
|
1735
2397
|
dryRun: false,
|
|
1736
|
-
credentialsNeeded: designResult.credentialsNeeded.length
|
|
2398
|
+
credentialsNeeded: designResult.credentialsNeeded.length,
|
|
2399
|
+
warnedRules: designResult.warnedRules
|
|
1737
2400
|
});
|
|
2401
|
+
this.updatePatterns();
|
|
1738
2402
|
return {
|
|
1739
2403
|
workflowId: deployed.workflowId,
|
|
1740
2404
|
name: deployed.name,
|
|
@@ -1749,6 +2413,13 @@ var Kairos = class {
|
|
|
1749
2413
|
await this.saveQueue.catch(() => {
|
|
1750
2414
|
});
|
|
1751
2415
|
}
|
|
2416
|
+
updatePatterns() {
|
|
2417
|
+
if (!this.patternAnalyzer) return;
|
|
2418
|
+
this.saveQueue = this.saveQueue.then(() => this.patternAnalyzer.analyzeAndSave()).then(() => null).catch((err) => {
|
|
2419
|
+
this.logger.warn("Pattern analysis failed (non-fatal)", { err: String(err) });
|
|
2420
|
+
return null;
|
|
2421
|
+
});
|
|
2422
|
+
}
|
|
1752
2423
|
async emitAttemptTelemetry(description, designResult) {
|
|
1753
2424
|
for (const meta of designResult.attemptMetadata) {
|
|
1754
2425
|
await this.telemetry?.emit("generation_attempt", {
|
|
@@ -1760,7 +2431,7 @@ var Kairos = class {
|
|
|
1760
2431
|
tokensOutput: meta.tokensOutput,
|
|
1761
2432
|
validationPassed: meta.validationPassed,
|
|
1762
2433
|
issueCount: meta.issues.length,
|
|
1763
|
-
issues: meta.issues.map((i) => ({ rule: i.rule, message: i.message }))
|
|
2434
|
+
issues: meta.issues.map((i) => ({ rule: i.rule, message: i.message, nodeId: i.nodeId ?? null, nodeType: i.nodeType ?? null }))
|
|
1764
2435
|
});
|
|
1765
2436
|
}
|
|
1766
2437
|
}
|
|
@@ -1859,9 +2530,9 @@ var Kairos = class {
|
|
|
1859
2530
|
};
|
|
1860
2531
|
|
|
1861
2532
|
// src/library/file-library.ts
|
|
1862
|
-
var
|
|
1863
|
-
var
|
|
1864
|
-
var
|
|
2533
|
+
var import_promises4 = require("fs/promises");
|
|
2534
|
+
var import_node_path6 = require("path");
|
|
2535
|
+
var import_node_os5 = require("os");
|
|
1865
2536
|
|
|
1866
2537
|
// src/library/scorer.ts
|
|
1867
2538
|
var WEIGHTS = {
|
|
@@ -2097,7 +2768,7 @@ var FileLibrary = class {
|
|
|
2097
2768
|
initPromise = null;
|
|
2098
2769
|
writeQueue = Promise.resolve();
|
|
2099
2770
|
constructor(dir) {
|
|
2100
|
-
this.dir = dir ?? (0,
|
|
2771
|
+
this.dir = dir ?? (0, import_node_path6.join)((0, import_node_os5.homedir)(), ".kairos", "library");
|
|
2101
2772
|
}
|
|
2102
2773
|
async initialize() {
|
|
2103
2774
|
if (!this.initPromise) {
|
|
@@ -2106,10 +2777,10 @@ var FileLibrary = class {
|
|
|
2106
2777
|
return this.initPromise;
|
|
2107
2778
|
}
|
|
2108
2779
|
async doInitialize() {
|
|
2109
|
-
await (0,
|
|
2110
|
-
const indexPath = (0,
|
|
2780
|
+
await (0, import_promises4.mkdir)(this.dir, { recursive: true });
|
|
2781
|
+
const indexPath = (0, import_node_path6.join)(this.dir, "index.json");
|
|
2111
2782
|
try {
|
|
2112
|
-
const raw = await (0,
|
|
2783
|
+
const raw = await (0, import_promises4.readFile)(indexPath, "utf-8");
|
|
2113
2784
|
const parsed = JSON.parse(raw);
|
|
2114
2785
|
if (!Array.isArray(parsed)) {
|
|
2115
2786
|
this.workflows = [];
|
|
@@ -2239,10 +2910,10 @@ var FileLibrary = class {
|
|
|
2239
2910
|
}
|
|
2240
2911
|
persist() {
|
|
2241
2912
|
this.writeQueue = this.writeQueue.then(async () => {
|
|
2242
|
-
const indexPath = (0,
|
|
2913
|
+
const indexPath = (0, import_node_path6.join)(this.dir, "index.json");
|
|
2243
2914
|
const tmpPath = `${indexPath}.tmp`;
|
|
2244
|
-
await (0,
|
|
2245
|
-
await (0,
|
|
2915
|
+
await (0, import_promises4.writeFile)(tmpPath, JSON.stringify(this.workflows, null, 2), "utf-8");
|
|
2916
|
+
await (0, import_promises4.rename)(tmpPath, indexPath);
|
|
2246
2917
|
});
|
|
2247
2918
|
return this.writeQueue;
|
|
2248
2919
|
}
|