@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/cli.cjs
CHANGED
|
@@ -524,17 +524,28 @@ var N8nValidator = class {
|
|
|
524
524
|
this.checkRule21(workflow, issues);
|
|
525
525
|
this.checkRule22(workflow, issues);
|
|
526
526
|
this.checkRule23(workflow, issues);
|
|
527
|
+
if (Array.isArray(workflow.nodes)) {
|
|
528
|
+
const nodeById = new Map(workflow.nodes.map((n) => [n.id, n.type]));
|
|
529
|
+
for (const issue of issues) {
|
|
530
|
+
if (issue.nodeId && !issue.nodeType) {
|
|
531
|
+
const nt = nodeById.get(issue.nodeId);
|
|
532
|
+
if (nt) issue.nodeType = nt;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
527
536
|
const errors = issues.filter((i) => i.severity === "error");
|
|
528
537
|
return { valid: errors.length === 0, issues };
|
|
529
538
|
}
|
|
530
|
-
err(issues, rule, message, nodeId) {
|
|
539
|
+
err(issues, rule, message, nodeId, nodeType) {
|
|
531
540
|
const issue = { rule, severity: "error", message };
|
|
532
541
|
if (nodeId !== void 0) issue.nodeId = nodeId;
|
|
542
|
+
if (nodeType !== void 0) issue.nodeType = nodeType;
|
|
533
543
|
issues.push(issue);
|
|
534
544
|
}
|
|
535
|
-
warn(issues, rule, message, nodeId) {
|
|
545
|
+
warn(issues, rule, message, nodeId, nodeType) {
|
|
536
546
|
const issue = { rule, severity: "warn", message };
|
|
537
547
|
if (nodeId !== void 0) issue.nodeId = nodeId;
|
|
548
|
+
if (nodeType !== void 0) issue.nodeType = nodeType;
|
|
538
549
|
issues.push(issue);
|
|
539
550
|
}
|
|
540
551
|
isTriggerNode(node) {
|
|
@@ -897,14 +908,23 @@ var ResponseParseError = class extends KairosError {
|
|
|
897
908
|
|
|
898
909
|
// src/errors/validation-error.ts
|
|
899
910
|
var ValidationError = class extends KairosError {
|
|
900
|
-
constructor(message, issues) {
|
|
911
|
+
constructor(message, issues, attemptMetadata, warnedRules) {
|
|
901
912
|
super(message);
|
|
902
913
|
this.issues = issues;
|
|
914
|
+
this.attemptMetadata = attemptMetadata;
|
|
915
|
+
this.warnedRules = warnedRules;
|
|
903
916
|
this.name = "ValidationError";
|
|
904
917
|
}
|
|
905
918
|
issues;
|
|
919
|
+
attemptMetadata;
|
|
920
|
+
warnedRules;
|
|
906
921
|
};
|
|
907
922
|
|
|
923
|
+
// src/generation/prompt-builder.ts
|
|
924
|
+
var import_node_fs = require("fs");
|
|
925
|
+
var import_node_path = require("path");
|
|
926
|
+
var import_node_os = require("os");
|
|
927
|
+
|
|
908
928
|
// src/generation/prompts/v1.ts
|
|
909
929
|
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.
|
|
910
930
|
|
|
@@ -1097,8 +1117,34 @@ function scoreToMode(score) {
|
|
|
1097
1117
|
return "scratch";
|
|
1098
1118
|
}
|
|
1099
1119
|
|
|
1100
|
-
// src/
|
|
1101
|
-
var
|
|
1120
|
+
// src/validation/rule-metadata.ts
|
|
1121
|
+
var VALIDATOR_RULE_IDS = Array.from({ length: 23 }, (_, i) => i + 1);
|
|
1122
|
+
var RULE_PIPELINE_STAGES = {
|
|
1123
|
+
1: "node_generation",
|
|
1124
|
+
2: "node_generation",
|
|
1125
|
+
3: "node_generation",
|
|
1126
|
+
4: "node_generation",
|
|
1127
|
+
5: "node_generation",
|
|
1128
|
+
6: "node_generation",
|
|
1129
|
+
7: "node_generation",
|
|
1130
|
+
8: "node_generation",
|
|
1131
|
+
9: "connection_wiring",
|
|
1132
|
+
10: "connection_wiring",
|
|
1133
|
+
11: "connection_wiring",
|
|
1134
|
+
12: "workflow_structure",
|
|
1135
|
+
13: "node_generation",
|
|
1136
|
+
14: "workflow_structure",
|
|
1137
|
+
15: "node_generation",
|
|
1138
|
+
16: "node_generation",
|
|
1139
|
+
17: "credential_injection",
|
|
1140
|
+
18: "connection_wiring",
|
|
1141
|
+
19: "node_generation",
|
|
1142
|
+
20: "connection_wiring",
|
|
1143
|
+
21: "workflow_structure",
|
|
1144
|
+
22: "workflow_structure",
|
|
1145
|
+
23: "node_generation"
|
|
1146
|
+
};
|
|
1147
|
+
var RULE_MITIGATIONS = {
|
|
1102
1148
|
1: "Provide a non-empty workflow name string",
|
|
1103
1149
|
2: "Include at least one node in the nodes array",
|
|
1104
1150
|
3: "Every node must have a unique UUID v4 string as its id field",
|
|
@@ -1109,8 +1155,10 @@ var RULE_REMEDIES = {
|
|
|
1109
1155
|
8: "Every node must have a non-empty name string",
|
|
1110
1156
|
9: "connections must be a plain object (use {} if no connections)",
|
|
1111
1157
|
10: "Every node name in connections (source and target) must exactly match a name in the nodes array",
|
|
1158
|
+
11: "Every non-trigger node should have at least one incoming connection",
|
|
1112
1159
|
12: "Remove forbidden fields: id, active, createdAt, updatedAt, versionId, meta, tags \u2014 these are server-assigned",
|
|
1113
|
-
|
|
1160
|
+
13: "workflow.settings must be a plain object if present",
|
|
1161
|
+
14: "Include at least one trigger node (e.g. scheduleTrigger, webhookTrigger, manualTrigger, or service-specific)",
|
|
1114
1162
|
15: 'Node type strings must be fully qualified: "n8n-nodes-base.httpRequest" not just "httpRequest"',
|
|
1115
1163
|
16: "All node names must be unique within the workflow",
|
|
1116
1164
|
17: 'Credentials must be an object with non-empty string id and name fields: { id: "placeholder-id", name: "My Credential" }',
|
|
@@ -1118,9 +1166,17 @@ var RULE_REMEDIES = {
|
|
|
1118
1166
|
19: "Use known safe typeVersion values for each node type",
|
|
1119
1167
|
20: "Remove connection cycles \u2014 ensure no node can reach itself through the connection graph",
|
|
1120
1168
|
21: 'When using webhook with responseMode "responseNode", include a respondToWebhook node in the flow',
|
|
1121
|
-
22: "Ensure all required parameters are set for each node type (e.g. webhook needs httpMethod and path)"
|
|
1169
|
+
22: "Ensure all required parameters are set for each node type (e.g. webhook needs httpMethod and path)",
|
|
1170
|
+
23: "Use node types that exist in the n8n registry \u2014 check with kairos_sync"
|
|
1122
1171
|
};
|
|
1172
|
+
|
|
1173
|
+
// src/generation/prompt-builder.ts
|
|
1174
|
+
var CRITICAL_SCORE_THRESHOLD = 0.15;
|
|
1123
1175
|
var PromptBuilder = class {
|
|
1176
|
+
patternsPath;
|
|
1177
|
+
constructor(patternsPath) {
|
|
1178
|
+
this.patternsPath = patternsPath ?? (0, import_node_path.join)((0, import_node_os.homedir)(), ".kairos", "patterns.json");
|
|
1179
|
+
}
|
|
1124
1180
|
build(request, matches, globalFailureRates = [], dynamicCatalog) {
|
|
1125
1181
|
const mode = this.resolveMode(matches);
|
|
1126
1182
|
const system = this.buildSystem(matches, mode, globalFailureRates, dynamicCatalog);
|
|
@@ -1206,20 +1262,114 @@ A loosely similar workflow (score: ${hint.score.toFixed(2)}) used these node typ
|
|
|
1206
1262
|
}
|
|
1207
1263
|
return blocks;
|
|
1208
1264
|
}
|
|
1265
|
+
loadPatterns() {
|
|
1266
|
+
try {
|
|
1267
|
+
const raw = (0, import_node_fs.readFileSync)(this.patternsPath, "utf-8");
|
|
1268
|
+
const analysis = JSON.parse(raw);
|
|
1269
|
+
const patterns = analysis.topFailureRules ?? [];
|
|
1270
|
+
return patterns.filter((p) => typeof p.pipelineStage === "string" && typeof p.state === "string");
|
|
1271
|
+
} catch {
|
|
1272
|
+
return [];
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
getWarnedRules() {
|
|
1276
|
+
return this.getActivePatterns().map((p) => p.rule);
|
|
1277
|
+
}
|
|
1278
|
+
getActivePatterns() {
|
|
1279
|
+
const MAX_WARNED = 10;
|
|
1280
|
+
const all = this.loadPatterns().filter((p) => p.state !== "resolved" && p.confidence > 0);
|
|
1281
|
+
const regressed = all.filter((p) => p.regressed).sort((a, b) => b.compositeScore - a.compositeScore);
|
|
1282
|
+
const confirmed = all.filter((p) => !p.regressed && p.state === "confirmed").sort((a, b) => b.compositeScore - a.compositeScore);
|
|
1283
|
+
const drafts = all.filter((p) => !p.regressed && p.state !== "confirmed").sort((a, b) => b.compositeScore - a.compositeScore);
|
|
1284
|
+
return [...regressed, ...confirmed, ...drafts].slice(0, MAX_WARNED);
|
|
1285
|
+
}
|
|
1209
1286
|
buildFailureWarnings(matches, globalFailureRates) {
|
|
1287
|
+
const richPatterns = this.getActivePatterns();
|
|
1288
|
+
if (richPatterns.length > 0) {
|
|
1289
|
+
return this.buildStageGroupedWarnings(richPatterns, matches);
|
|
1290
|
+
}
|
|
1291
|
+
return this.buildLegacyWarnings(matches, globalFailureRates);
|
|
1292
|
+
}
|
|
1293
|
+
buildStageGroupedWarnings(patterns, matches) {
|
|
1294
|
+
const stageLabels = {
|
|
1295
|
+
credential_injection: "CREDENTIAL FORMATTING",
|
|
1296
|
+
connection_wiring: "CONNECTION WIRING",
|
|
1297
|
+
node_generation: "NODE GENERATION",
|
|
1298
|
+
workflow_structure: "WORKFLOW STRUCTURE"
|
|
1299
|
+
};
|
|
1300
|
+
const byStage = /* @__PURE__ */ new Map();
|
|
1301
|
+
for (const p of patterns) {
|
|
1302
|
+
const list = byStage.get(p.pipelineStage) ?? [];
|
|
1303
|
+
list.push(p);
|
|
1304
|
+
byStage.set(p.pipelineStage, list);
|
|
1305
|
+
}
|
|
1306
|
+
const sections = [];
|
|
1307
|
+
for (const [stage, stagePatterns] of byStage) {
|
|
1308
|
+
const label = stageLabels[stage] ?? stage;
|
|
1309
|
+
const byMitigation = /* @__PURE__ */ new Map();
|
|
1310
|
+
for (const p of stagePatterns) {
|
|
1311
|
+
const key = p.mitigation ?? `rule_${p.rule}`;
|
|
1312
|
+
const list = byMitigation.get(key) ?? [];
|
|
1313
|
+
list.push(p);
|
|
1314
|
+
byMitigation.set(key, list);
|
|
1315
|
+
}
|
|
1316
|
+
const lines = [];
|
|
1317
|
+
for (const group of byMitigation.values()) {
|
|
1318
|
+
if (group.length === 1) {
|
|
1319
|
+
const p = group[0];
|
|
1320
|
+
const urgency = p.regressed ? "CRITICAL REGRESSION: " : (p.compositeScore ?? 0) >= CRITICAL_SCORE_THRESHOLD ? "CRITICAL: " : "";
|
|
1321
|
+
const statePrefix = p.state === "confirmed" ? "[CONFIRMED] " : "";
|
|
1322
|
+
const trendSuffix = p.trend === "worsening" ? " (GETTING WORSE)" : p.trend === "improving" ? " (improving)" : "";
|
|
1323
|
+
const remedy = p.mitigation ?? RULE_MITIGATIONS[p.rule];
|
|
1324
|
+
const remedyStr = remedy ? `
|
|
1325
|
+
Fix: ${remedy}` : "";
|
|
1326
|
+
lines.push(`- ${urgency}${statePrefix}Rule ${p.rule}${trendSuffix}: ${p.exampleMessages[0] ?? "No example"}${remedyStr}`);
|
|
1327
|
+
} else {
|
|
1328
|
+
const ruleNums = group.map((p) => p.rule).join(", ");
|
|
1329
|
+
const totalFailures = group.reduce((s, p) => s + p.failureCount, 0);
|
|
1330
|
+
const hasConfirmed = group.some((p) => p.state === "confirmed");
|
|
1331
|
+
const statePrefix = hasConfirmed ? "[CONFIRMED] " : "";
|
|
1332
|
+
const remedy = group[0].mitigation;
|
|
1333
|
+
const remedyStr = remedy ? `
|
|
1334
|
+
Fix: ${remedy}` : "";
|
|
1335
|
+
lines.push(`- ${statePrefix}Rules ${ruleNums} (${totalFailures} failures combined): same root cause${remedyStr}`);
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
sections.push(`### ${label}
|
|
1339
|
+
${lines.join("\n")}`);
|
|
1340
|
+
}
|
|
1341
|
+
for (const match of matches) {
|
|
1342
|
+
const fps = match.workflow.failurePatterns;
|
|
1343
|
+
if (!fps?.length) continue;
|
|
1344
|
+
const coveredRules = new Set(patterns.map((p) => p.rule));
|
|
1345
|
+
const extra = fps.filter((fp) => !coveredRules.has(fp.rule));
|
|
1346
|
+
for (const fp of extra) {
|
|
1347
|
+
const remedy = RULE_MITIGATIONS[fp.rule];
|
|
1348
|
+
const remedyStr = remedy ? ` \u2014 Fix: ${remedy}` : "";
|
|
1349
|
+
sections.push(`- Rule ${fp.rule}: "${fp.message}"${remedyStr} (seen in similar workflows)`);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
if (sections.length === 0) return null;
|
|
1353
|
+
return `## Known Failure Patterns \u2014 AVOID THESE
|
|
1354
|
+
|
|
1355
|
+
Grouped by generation stage. Fix these BEFORE outputting your response:
|
|
1356
|
+
|
|
1357
|
+
${sections.join("\n\n")}`;
|
|
1358
|
+
}
|
|
1359
|
+
buildLegacyWarnings(matches, globalFailureRates) {
|
|
1210
1360
|
const lines = [];
|
|
1211
1361
|
for (const match of matches) {
|
|
1212
1362
|
const patterns = match.workflow.failurePatterns;
|
|
1213
1363
|
if (!patterns?.length) continue;
|
|
1214
1364
|
for (const fp of patterns) {
|
|
1215
|
-
const remedy =
|
|
1365
|
+
const remedy = RULE_MITIGATIONS[fp.rule];
|
|
1216
1366
|
const remedyStr = remedy ? ` \u2014 Fix: ${remedy}` : "";
|
|
1217
1367
|
lines.push(`- Rule ${fp.rule}: "${fp.message}"${remedyStr} (seen ${fp.occurrences}x in similar workflows)`);
|
|
1218
1368
|
}
|
|
1219
1369
|
}
|
|
1220
1370
|
const highFreqRules = globalFailureRates.filter((r) => r.rate >= 0.15);
|
|
1221
1371
|
for (const rule of highFreqRules) {
|
|
1222
|
-
const remedy =
|
|
1372
|
+
const remedy = RULE_MITIGATIONS[rule.rule];
|
|
1223
1373
|
const remedyStr = remedy ? ` \u2014 Fix: ${remedy}` : "";
|
|
1224
1374
|
lines.push(`- Rule ${rule.rule}: "${rule.commonMessage}"${remedyStr} (fails in ${Math.round(rule.rate * 100)}% of all builds)`);
|
|
1225
1375
|
}
|
|
@@ -1330,7 +1480,7 @@ var WorkflowDesigner = class {
|
|
|
1330
1480
|
issues: validation.issues
|
|
1331
1481
|
});
|
|
1332
1482
|
if (validation.valid) {
|
|
1333
|
-
return { workflow: parsed.workflow, credentialsNeeded: parsed.credentialsNeeded, attempts, attemptMetadata };
|
|
1483
|
+
return { workflow: parsed.workflow, credentialsNeeded: parsed.credentialsNeeded, attempts, attemptMetadata, warnedRules: this.promptBuilder.getWarnedRules() };
|
|
1334
1484
|
}
|
|
1335
1485
|
lastErrors = errors;
|
|
1336
1486
|
this.logger.warn(`WorkflowDesigner: validation failed on attempt ${attempt}`, {
|
|
@@ -1340,7 +1490,9 @@ var WorkflowDesigner = class {
|
|
|
1340
1490
|
const finalIssues = attemptMetadata.at(-1)?.issues ?? lastErrors;
|
|
1341
1491
|
throw new ValidationError(
|
|
1342
1492
|
`Workflow failed validation after ${MAX_ATTEMPTS} attempts`,
|
|
1343
|
-
finalIssues
|
|
1493
|
+
finalIssues,
|
|
1494
|
+
attemptMetadata,
|
|
1495
|
+
this.promptBuilder.getWarnedRules()
|
|
1344
1496
|
);
|
|
1345
1497
|
}
|
|
1346
1498
|
async callClaude(system, userMessage, temperature) {
|
|
@@ -1394,8 +1546,8 @@ var WorkflowDesigner = class {
|
|
|
1394
1546
|
|
|
1395
1547
|
// src/telemetry/collector.ts
|
|
1396
1548
|
var import_promises = require("fs/promises");
|
|
1397
|
-
var
|
|
1398
|
-
var
|
|
1549
|
+
var import_node_path2 = require("path");
|
|
1550
|
+
var import_node_os2 = require("os");
|
|
1399
1551
|
|
|
1400
1552
|
// src/telemetry/types.ts
|
|
1401
1553
|
var TELEMETRY_SCHEMA_VERSION = 2;
|
|
@@ -1406,7 +1558,7 @@ var TelemetryCollector = class {
|
|
|
1406
1558
|
sessionId;
|
|
1407
1559
|
dirReady = null;
|
|
1408
1560
|
constructor(dir) {
|
|
1409
|
-
this.dir = dir ?? (0,
|
|
1561
|
+
this.dir = dir ?? (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".kairos", "telemetry");
|
|
1410
1562
|
this.sessionId = generateUUID();
|
|
1411
1563
|
}
|
|
1412
1564
|
async emit(eventType, data) {
|
|
@@ -1423,21 +1575,61 @@ var TelemetryCollector = class {
|
|
|
1423
1575
|
}
|
|
1424
1576
|
await this.dirReady;
|
|
1425
1577
|
const filename = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10) + ".jsonl";
|
|
1426
|
-
const filepath = (0,
|
|
1578
|
+
const filepath = (0, import_node_path2.join)(this.dir, filename);
|
|
1427
1579
|
await (0, import_promises.appendFile)(filepath, JSON.stringify(event) + "\n", "utf-8");
|
|
1428
1580
|
}
|
|
1429
1581
|
};
|
|
1430
1582
|
|
|
1431
1583
|
// src/telemetry/reader.ts
|
|
1584
|
+
var import_node_os3 = require("os");
|
|
1585
|
+
var import_node_path4 = require("path");
|
|
1586
|
+
|
|
1587
|
+
// src/telemetry/event-reader.ts
|
|
1432
1588
|
var import_promises2 = require("fs/promises");
|
|
1433
|
-
var
|
|
1434
|
-
var
|
|
1589
|
+
var import_node_fs2 = require("fs");
|
|
1590
|
+
var import_node_path3 = require("path");
|
|
1591
|
+
var import_node_readline = require("readline");
|
|
1592
|
+
async function readTelemetryEvents(dir, days) {
|
|
1593
|
+
let files;
|
|
1594
|
+
try {
|
|
1595
|
+
files = await (0, import_promises2.readdir)(dir);
|
|
1596
|
+
} catch {
|
|
1597
|
+
return [];
|
|
1598
|
+
}
|
|
1599
|
+
const cutoff = /* @__PURE__ */ new Date();
|
|
1600
|
+
cutoff.setDate(cutoff.getDate() - days);
|
|
1601
|
+
const cutoffStr = cutoff.toISOString().slice(0, 10);
|
|
1602
|
+
const todayStr = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1603
|
+
const datePattern = /^\d{4}-\d{2}-\d{2}\.jsonl$/;
|
|
1604
|
+
const recentFiles = files.filter((f) => datePattern.test(f) && f >= cutoffStr && f <= `${todayStr}.jsonl`).sort();
|
|
1605
|
+
const events = [];
|
|
1606
|
+
for (const file of recentFiles) {
|
|
1607
|
+
const fileDate = file.replace(".jsonl", "");
|
|
1608
|
+
try {
|
|
1609
|
+
const rl = (0, import_node_readline.createInterface)({
|
|
1610
|
+
input: (0, import_node_fs2.createReadStream)((0, import_node_path3.join)(dir, file), "utf-8"),
|
|
1611
|
+
crlfDelay: Infinity
|
|
1612
|
+
});
|
|
1613
|
+
for await (const line of rl) {
|
|
1614
|
+
if (!line.trim()) continue;
|
|
1615
|
+
try {
|
|
1616
|
+
events.push({ ...JSON.parse(line), fileDate });
|
|
1617
|
+
} catch {
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
} catch {
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
return events;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
// src/telemetry/reader.ts
|
|
1435
1627
|
var TelemetryReader = class {
|
|
1436
1628
|
dir;
|
|
1437
1629
|
cache = null;
|
|
1438
1630
|
cacheTime = 0;
|
|
1439
1631
|
constructor(dir) {
|
|
1440
|
-
this.dir = dir ?? (0,
|
|
1632
|
+
this.dir = dir ?? (0, import_node_path4.join)((0, import_node_os3.homedir)(), ".kairos", "telemetry");
|
|
1441
1633
|
}
|
|
1442
1634
|
async getFailureRates(days = 30) {
|
|
1443
1635
|
const now = Date.now();
|
|
@@ -1446,9 +1638,10 @@ var TelemetryReader = class {
|
|
|
1446
1638
|
}
|
|
1447
1639
|
const events = await this.readRecentEvents(days);
|
|
1448
1640
|
const buildSessions = new Set(
|
|
1449
|
-
events.filter((e) => e.eventType === "build_complete"
|
|
1641
|
+
events.filter((e) => e.eventType === "build_complete").map((e) => e.sessionId)
|
|
1450
1642
|
);
|
|
1451
|
-
|
|
1643
|
+
const MIN_BUILDS_FOR_RATES = 3;
|
|
1644
|
+
if (buildSessions.size < MIN_BUILDS_FOR_RATES) return [];
|
|
1452
1645
|
const ruleSessions = /* @__PURE__ */ new Map();
|
|
1453
1646
|
for (const event of events) {
|
|
1454
1647
|
if (event.eventType !== "generation_attempt") continue;
|
|
@@ -1486,32 +1679,425 @@ var TelemetryReader = class {
|
|
|
1486
1679
|
return rates;
|
|
1487
1680
|
}
|
|
1488
1681
|
async readRecentEvents(days) {
|
|
1489
|
-
|
|
1682
|
+
return readTelemetryEvents(this.dir, days);
|
|
1683
|
+
}
|
|
1684
|
+
};
|
|
1685
|
+
|
|
1686
|
+
// src/telemetry/pattern-analyzer.ts
|
|
1687
|
+
var import_promises3 = require("fs/promises");
|
|
1688
|
+
var import_node_path5 = require("path");
|
|
1689
|
+
var import_node_os4 = require("os");
|
|
1690
|
+
var PATTERN_SCHEMA_VERSION = 2;
|
|
1691
|
+
var PatternAnalyzer = class _PatternAnalyzer {
|
|
1692
|
+
telemetryDir;
|
|
1693
|
+
outputDir;
|
|
1694
|
+
constructor(telemetryDir) {
|
|
1695
|
+
const defaultDir = (0, import_node_path5.join)((0, import_node_os4.homedir)(), ".kairos", "telemetry");
|
|
1696
|
+
this.telemetryDir = telemetryDir ?? defaultDir;
|
|
1697
|
+
this.outputDir = telemetryDir ? (0, import_node_path5.join)(telemetryDir, "..") : (0, import_node_path5.join)((0, import_node_os4.homedir)(), ".kairos");
|
|
1698
|
+
}
|
|
1699
|
+
async loadPreviousPatterns() {
|
|
1490
1700
|
try {
|
|
1491
|
-
|
|
1701
|
+
const raw = await (0, import_promises3.readFile)((0, import_node_path5.join)(this.outputDir, "patterns.json"), "utf-8");
|
|
1702
|
+
const prev = JSON.parse(raw);
|
|
1703
|
+
const version = prev.schemaVersion ?? 0;
|
|
1704
|
+
const patterns = prev.topFailureRules ?? [];
|
|
1705
|
+
if (version === PATTERN_SCHEMA_VERSION) return patterns;
|
|
1706
|
+
return this.migratePatterns(patterns, version);
|
|
1492
1707
|
} catch {
|
|
1493
1708
|
return [];
|
|
1494
1709
|
}
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1710
|
+
}
|
|
1711
|
+
migratePatterns(patterns, fromVersion) {
|
|
1712
|
+
let migrated = patterns;
|
|
1713
|
+
if (fromVersion < 1) {
|
|
1714
|
+
migrated = migrated.map((p) => ({
|
|
1715
|
+
...p,
|
|
1716
|
+
compositeScore: p.compositeScore ?? 0,
|
|
1717
|
+
scoringFactors: p.scoringFactors ?? { rawConfidence: 0, impact: 0, recency: 0, stickinessBoost: 0 },
|
|
1718
|
+
pipelineStage: p.pipelineStage ?? "node_generation"
|
|
1719
|
+
}));
|
|
1720
|
+
}
|
|
1721
|
+
if (fromVersion < 2) {
|
|
1722
|
+
migrated = migrated.map((p) => ({
|
|
1723
|
+
...p,
|
|
1724
|
+
scoringFactors: {
|
|
1725
|
+
...p.scoringFactors,
|
|
1726
|
+
stickinessBoost: p.scoringFactors.stickinessBoost ?? p.scoringFactors["validationBoost"] ?? 0
|
|
1727
|
+
}
|
|
1728
|
+
}));
|
|
1729
|
+
}
|
|
1730
|
+
return migrated;
|
|
1731
|
+
}
|
|
1732
|
+
async analyze(days = 30) {
|
|
1733
|
+
const previousPatterns = await this.loadPreviousPatterns();
|
|
1734
|
+
const events = await this.readAllEvents(days);
|
|
1735
|
+
const starts = events.filter((e) => e.eventType === "build_start");
|
|
1736
|
+
const attempts = events.filter((e) => e.eventType === "generation_attempt");
|
|
1737
|
+
const passed = attempts.filter(
|
|
1738
|
+
(a) => a.data.validationPassed === true
|
|
1739
|
+
);
|
|
1740
|
+
const failed = attempts.filter(
|
|
1741
|
+
(a) => a.data.validationPassed === false
|
|
1742
|
+
);
|
|
1743
|
+
const ruleFailures = /* @__PURE__ */ new Map();
|
|
1744
|
+
const credentialFailures = /* @__PURE__ */ new Map();
|
|
1745
|
+
for (const a of failed) {
|
|
1746
|
+
const weight = this.recencyWeight(a.fileDate);
|
|
1747
|
+
const data = a.data;
|
|
1748
|
+
for (const issue of data.issues ?? []) {
|
|
1749
|
+
const entry = ruleFailures.get(issue.rule) ?? { count: 0, sessions: /* @__PURE__ */ new Set(), recencyWeights: [], allMessages: [] };
|
|
1750
|
+
entry.count++;
|
|
1751
|
+
entry.sessions.add(a.sessionId);
|
|
1752
|
+
entry.recencyWeights.push(weight);
|
|
1753
|
+
entry.allMessages.push(issue.message);
|
|
1754
|
+
ruleFailures.set(issue.rule, entry);
|
|
1755
|
+
if (issue.rule === 17) {
|
|
1756
|
+
const credPatterns = [
|
|
1757
|
+
/credential\s+"([^"]+)"/,
|
|
1758
|
+
/credentialType[:\s]+"?([^"'\s]+)"?/,
|
|
1759
|
+
/missing\s+credential\s+(?:for\s+)?["']?([^"'\s]+)/i
|
|
1760
|
+
];
|
|
1761
|
+
let credType = "unknown";
|
|
1762
|
+
for (const re of credPatterns) {
|
|
1763
|
+
const m = issue.message.match(re);
|
|
1764
|
+
if (m?.[1]) {
|
|
1765
|
+
credType = m[1];
|
|
1766
|
+
break;
|
|
1767
|
+
}
|
|
1509
1768
|
}
|
|
1769
|
+
credentialFailures.set(credType, (credentialFailures.get(credType) ?? 0) + 1);
|
|
1510
1770
|
}
|
|
1511
|
-
} catch {
|
|
1512
1771
|
}
|
|
1513
1772
|
}
|
|
1514
|
-
|
|
1773
|
+
const failedByDate = /* @__PURE__ */ new Map();
|
|
1774
|
+
for (const a of failed) {
|
|
1775
|
+
failedByDate.set(a.fileDate, (failedByDate.get(a.fileDate) ?? 0) + 1);
|
|
1776
|
+
}
|
|
1777
|
+
const sortedFailDates = [...failedByDate.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
1778
|
+
const hasTrendData = sortedFailDates.length >= 3;
|
|
1779
|
+
let midDate = "";
|
|
1780
|
+
if (hasTrendData) {
|
|
1781
|
+
const halfTotal = failed.length / 2;
|
|
1782
|
+
let cumulative = 0;
|
|
1783
|
+
for (const [date, count] of sortedFailDates) {
|
|
1784
|
+
cumulative += count;
|
|
1785
|
+
if (cumulative >= halfTotal) {
|
|
1786
|
+
midDate = date;
|
|
1787
|
+
break;
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
const ruleTrends = /* @__PURE__ */ new Map();
|
|
1792
|
+
if (hasTrendData) {
|
|
1793
|
+
for (const a of failed) {
|
|
1794
|
+
const data = a.data;
|
|
1795
|
+
const isNewer = a.fileDate > midDate;
|
|
1796
|
+
for (const issue of data.issues ?? []) {
|
|
1797
|
+
const entry = ruleTrends.get(issue.rule) ?? { older: 0, newer: 0 };
|
|
1798
|
+
if (isNewer) entry.newer++;
|
|
1799
|
+
else entry.older++;
|
|
1800
|
+
ruleTrends.set(issue.rule, entry);
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
1805
|
+
for (const a of attempts) {
|
|
1806
|
+
const list = sessions.get(a.sessionId) ?? [];
|
|
1807
|
+
list.push(a);
|
|
1808
|
+
sessions.set(a.sessionId, list);
|
|
1809
|
+
}
|
|
1810
|
+
let firstTryPass = 0;
|
|
1811
|
+
let correctionNeeded = 0;
|
|
1812
|
+
let singleAttemptFail = 0;
|
|
1813
|
+
for (const sessionAttempts of sessions.values()) {
|
|
1814
|
+
const lastAttempt = sessionAttempts[sessionAttempts.length - 1];
|
|
1815
|
+
const lastPassed = lastAttempt.data.validationPassed === true;
|
|
1816
|
+
if (sessionAttempts.length === 1 && lastPassed) {
|
|
1817
|
+
firstTryPass++;
|
|
1818
|
+
} else if (sessionAttempts.length > 1 && lastPassed) {
|
|
1819
|
+
correctionNeeded++;
|
|
1820
|
+
} else {
|
|
1821
|
+
singleAttemptFail++;
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
const durations = attempts.map((a) => a.data.durationMs).filter((d) => typeof d === "number" && d > 0);
|
|
1825
|
+
const avgDuration = durations.length > 0 ? durations.reduce((s, d) => s + d, 0) / durations.length : 0;
|
|
1826
|
+
const totalInput = attempts.reduce((s, a) => s + (a.data.tokensInput ?? 0), 0);
|
|
1827
|
+
const totalOutput = attempts.reduce((s, a) => s + (a.data.tokensOutput ?? 0), 0);
|
|
1828
|
+
const totalSessions = Math.max(sessions.size, 1);
|
|
1829
|
+
const stickinessCount = /* @__PURE__ */ new Map();
|
|
1830
|
+
for (const sessionAttempts of sessions.values()) {
|
|
1831
|
+
if (sessionAttempts.length < 2) continue;
|
|
1832
|
+
for (let i = 0; i < sessionAttempts.length - 1; i++) {
|
|
1833
|
+
const curr = sessionAttempts[i].data;
|
|
1834
|
+
const next = sessionAttempts[i + 1].data;
|
|
1835
|
+
if (curr.validationPassed !== false || next.validationPassed !== false) continue;
|
|
1836
|
+
const currRules = new Set((curr.issues ?? []).map((iss) => iss.rule));
|
|
1837
|
+
const nextRules = new Set((next.issues ?? []).map((iss) => iss.rule));
|
|
1838
|
+
for (const rule of currRules) {
|
|
1839
|
+
if (nextRules.has(rule)) {
|
|
1840
|
+
stickinessCount.set(rule, (stickinessCount.get(rule) ?? 0) + 1);
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
const CONFIRMED_THRESHOLD = 3;
|
|
1846
|
+
const BUILDS_SINCE_LAST_FAILURE_THRESHOLD = 5;
|
|
1847
|
+
const RESOLVED_TTL_DAYS = 90;
|
|
1848
|
+
const activePatterns = [...ruleFailures.entries()].map(([rule, entry]) => {
|
|
1849
|
+
const t = ruleTrends.get(rule) ?? { older: 0, newer: 0 };
|
|
1850
|
+
const rawConfidence = Math.min(entry.sessions.size / totalSessions, 1);
|
|
1851
|
+
const state = entry.count >= CONFIRMED_THRESHOLD ? "confirmed" : "draft";
|
|
1852
|
+
const avgRecency = entry.recencyWeights.length > 0 ? entry.recencyWeights.reduce((s, w) => s + w, 0) / entry.recencyWeights.length : 1;
|
|
1853
|
+
const stickiness = stickinessCount.get(rule) ?? 0;
|
|
1854
|
+
const { compositeScore, factors } = this.computeCompositeScore(rawConfidence, entry.count, state, avgRecency, stickiness);
|
|
1855
|
+
return {
|
|
1856
|
+
rule,
|
|
1857
|
+
failureCount: entry.count,
|
|
1858
|
+
confidence: Math.round(rawConfidence * 1e3) / 1e3,
|
|
1859
|
+
compositeScore,
|
|
1860
|
+
scoringFactors: factors,
|
|
1861
|
+
state,
|
|
1862
|
+
trend: this.classifyTrend(t.older, t.newer),
|
|
1863
|
+
pipelineStage: RULE_PIPELINE_STAGES[rule] ?? "node_generation",
|
|
1864
|
+
exampleMessages: this.deduplicateMessages(entry.allMessages),
|
|
1865
|
+
mitigation: RULE_MITIGATIONS[rule] ?? null
|
|
1866
|
+
};
|
|
1867
|
+
}).sort((a, b) => b.compositeScore - a.compositeScore);
|
|
1868
|
+
const activeRules = new Set(activePatterns.map((p) => p.rule));
|
|
1869
|
+
for (const p of activePatterns) {
|
|
1870
|
+
const prev = previousPatterns.find((pp) => pp.rule === p.rule);
|
|
1871
|
+
if (prev?.state === "resolved") {
|
|
1872
|
+
p.trend = "worsening";
|
|
1873
|
+
p.regressed = true;
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
const ruleLastFailureDate = /* @__PURE__ */ new Map();
|
|
1877
|
+
for (const a of failed) {
|
|
1878
|
+
const data = a.data;
|
|
1879
|
+
for (const issue of data.issues ?? []) {
|
|
1880
|
+
const existing = ruleLastFailureDate.get(issue.rule);
|
|
1881
|
+
if (!existing || a.fileDate > existing) {
|
|
1882
|
+
ruleLastFailureDate.set(issue.rule, a.fileDate);
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
const newlyResolved = previousPatterns.filter((p) => {
|
|
1887
|
+
if (p.state !== "confirmed" || activeRules.has(p.rule)) return false;
|
|
1888
|
+
const lastFailDate = ruleLastFailureDate.get(p.rule) ?? "";
|
|
1889
|
+
const buildsSince = starts.filter((s) => s.fileDate > lastFailDate).length;
|
|
1890
|
+
return buildsSince >= BUILDS_SINCE_LAST_FAILURE_THRESHOLD;
|
|
1891
|
+
}).map((p) => ({
|
|
1892
|
+
...p,
|
|
1893
|
+
state: "resolved",
|
|
1894
|
+
trend: "improving",
|
|
1895
|
+
pipelineStage: p.pipelineStage ?? RULE_PIPELINE_STAGES[p.rule] ?? "node_generation",
|
|
1896
|
+
confidence: 0,
|
|
1897
|
+
compositeScore: 0,
|
|
1898
|
+
scoringFactors: { rawConfidence: 0, impact: 0, recency: 0, stickinessBoost: 0 },
|
|
1899
|
+
failureCount: 0,
|
|
1900
|
+
resolvedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1901
|
+
}));
|
|
1902
|
+
const ttlCutoff = /* @__PURE__ */ new Date();
|
|
1903
|
+
ttlCutoff.setDate(ttlCutoff.getDate() - RESOLVED_TTL_DAYS);
|
|
1904
|
+
const ttlCutoffStr = ttlCutoff.toISOString();
|
|
1905
|
+
const carriedResolved = previousPatterns.filter((p) => p.state === "resolved" && !activeRules.has(p.rule) && (!p.resolvedAt || p.resolvedAt >= ttlCutoffStr)).map((p) => ({ ...p }));
|
|
1906
|
+
const newlyResolvedRules = new Set(newlyResolved.map((p) => p.rule));
|
|
1907
|
+
const pendingResolution = previousPatterns.filter((p) => p.state === "confirmed" && !activeRules.has(p.rule) && !newlyResolvedRules.has(p.rule)).map((p) => ({ ...p }));
|
|
1908
|
+
const deduped = [
|
|
1909
|
+
...newlyResolved,
|
|
1910
|
+
...carriedResolved.filter((p) => !newlyResolvedRules.has(p.rule)),
|
|
1911
|
+
...pendingResolution
|
|
1912
|
+
];
|
|
1913
|
+
const patterns = [...activePatterns, ...deduped];
|
|
1914
|
+
const credTypes = [...credentialFailures.entries()].sort((a, b) => b[1] - a[1]).map(([type, count]) => ({ type, count }));
|
|
1915
|
+
const drift = this.detectDrift(patterns);
|
|
1916
|
+
const warnEffMap = /* @__PURE__ */ new Map();
|
|
1917
|
+
const buildCompletes = events.filter((e) => e.eventType === "build_complete");
|
|
1918
|
+
for (const bc of buildCompletes) {
|
|
1919
|
+
const bcData = bc.data;
|
|
1920
|
+
const warned = bcData.warnedRules ?? [];
|
|
1921
|
+
if (warned.length === 0) continue;
|
|
1922
|
+
const sessionFailedRules = /* @__PURE__ */ new Set();
|
|
1923
|
+
const sessionAttempts = sessions.get(bc.sessionId) ?? [];
|
|
1924
|
+
for (const a of sessionAttempts) {
|
|
1925
|
+
const ad = a.data;
|
|
1926
|
+
if (ad.validationPassed === false) {
|
|
1927
|
+
for (const issue of ad.issues ?? []) {
|
|
1928
|
+
sessionFailedRules.add(issue.rule);
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
for (const rule of warned) {
|
|
1933
|
+
const entry = warnEffMap.get(rule) ?? { warned: 0, passed: 0, failed: 0 };
|
|
1934
|
+
entry.warned++;
|
|
1935
|
+
if (sessionFailedRules.has(rule)) entry.failed++;
|
|
1936
|
+
else entry.passed++;
|
|
1937
|
+
warnEffMap.set(rule, entry);
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
const warningEffectiveness = [...warnEffMap.entries()].map(([rule, e]) => ({
|
|
1941
|
+
rule,
|
|
1942
|
+
timesWarned: e.warned,
|
|
1943
|
+
timesWarnedAndPassed: e.passed,
|
|
1944
|
+
timesWarnedAndFailed: e.failed,
|
|
1945
|
+
effectivenessRate: e.warned > 0 ? Math.round(e.passed / e.warned * 1e3) / 1e3 : 0
|
|
1946
|
+
})).sort((a, b) => b.timesWarned - a.timesWarned);
|
|
1947
|
+
const coOccurrenceMap = /* @__PURE__ */ new Map();
|
|
1948
|
+
for (const a of failed) {
|
|
1949
|
+
const data = a.data;
|
|
1950
|
+
const rules = [...new Set((data.issues ?? []).map((i) => i.rule))].sort((x, y) => x - y);
|
|
1951
|
+
for (let i = 0; i < rules.length; i++) {
|
|
1952
|
+
for (let j = i + 1; j < rules.length; j++) {
|
|
1953
|
+
const key = `${rules[i]},${rules[j]}`;
|
|
1954
|
+
coOccurrenceMap.set(key, (coOccurrenceMap.get(key) ?? 0) + 1);
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
const ruleCoOccurrence = [...coOccurrenceMap.entries()].filter(([, count]) => count >= 3).map(([key, count]) => {
|
|
1959
|
+
const [a, b] = key.split(",").map(Number);
|
|
1960
|
+
return { rules: [a, b], count };
|
|
1961
|
+
}).sort((a, b) => b.count - a.count);
|
|
1962
|
+
const attemptDistribution = {};
|
|
1963
|
+
for (const sessionAttempts of sessions.values()) {
|
|
1964
|
+
const depth = sessionAttempts.length;
|
|
1965
|
+
attemptDistribution[depth] = (attemptDistribution[depth] ?? 0) + 1;
|
|
1966
|
+
}
|
|
1967
|
+
return {
|
|
1968
|
+
schemaVersion: PATTERN_SCHEMA_VERSION,
|
|
1969
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1970
|
+
summary: {
|
|
1971
|
+
totalBuilds: starts.length,
|
|
1972
|
+
totalAttempts: attempts.length,
|
|
1973
|
+
firstTryPassRate: Math.round(firstTryPass / totalSessions * 1e3) / 1e3,
|
|
1974
|
+
correctionRate: Math.round(correctionNeeded / totalSessions * 1e3) / 1e3,
|
|
1975
|
+
singleAttemptFailRate: Math.round(singleAttemptFail / totalSessions * 1e3) / 1e3,
|
|
1976
|
+
avgDurationMs: Math.round(avgDuration),
|
|
1977
|
+
totalTokensInput: totalInput,
|
|
1978
|
+
totalTokensOutput: totalOutput,
|
|
1979
|
+
attemptDistribution
|
|
1980
|
+
},
|
|
1981
|
+
topFailureRules: patterns,
|
|
1982
|
+
failingCredentialTypes: credTypes,
|
|
1983
|
+
drift,
|
|
1984
|
+
warningEffectiveness,
|
|
1985
|
+
ruleCoOccurrence
|
|
1986
|
+
};
|
|
1987
|
+
}
|
|
1988
|
+
async analyzeAndSave(days = 30) {
|
|
1989
|
+
const analysis = await this.analyze(days);
|
|
1990
|
+
await (0, import_promises3.mkdir)(this.outputDir, { recursive: true });
|
|
1991
|
+
const outputPath = (0, import_node_path5.join)(this.outputDir, "patterns.json");
|
|
1992
|
+
const tmpPath = `${outputPath}.tmp`;
|
|
1993
|
+
await (0, import_promises3.writeFile)(tmpPath, JSON.stringify(analysis, null, 2), "utf-8");
|
|
1994
|
+
await (0, import_promises3.rename)(tmpPath, outputPath);
|
|
1995
|
+
const historySummary = {
|
|
1996
|
+
timestamp: analysis.generatedAt,
|
|
1997
|
+
totalBuilds: analysis.summary.totalBuilds,
|
|
1998
|
+
firstTryPassRate: analysis.summary.firstTryPassRate,
|
|
1999
|
+
correctionRate: analysis.summary.correctionRate,
|
|
2000
|
+
singleAttemptFailRate: analysis.summary.singleAttemptFailRate,
|
|
2001
|
+
activePatternCount: analysis.topFailureRules.filter((p) => p.state !== "resolved").length,
|
|
2002
|
+
topRules: analysis.topFailureRules.filter((p) => p.state !== "resolved").slice(0, 5).map((p) => ({ rule: p.rule, compositeScore: p.compositeScore, state: p.state }))
|
|
2003
|
+
};
|
|
2004
|
+
const historyPath = (0, import_node_path5.join)(this.outputDir, "pattern-history.jsonl");
|
|
2005
|
+
await (0, import_promises3.appendFile)(historyPath, JSON.stringify(historySummary) + "\n", "utf-8");
|
|
2006
|
+
return analysis;
|
|
2007
|
+
}
|
|
2008
|
+
async getHistory(limit = 20) {
|
|
2009
|
+
try {
|
|
2010
|
+
const raw = await (0, import_promises3.readFile)((0, import_node_path5.join)(this.outputDir, "pattern-history.jsonl"), "utf-8");
|
|
2011
|
+
return raw.trim().split("\n").filter(Boolean).map((l) => JSON.parse(l)).slice(-limit);
|
|
2012
|
+
} catch {
|
|
2013
|
+
return [];
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
static fromEnv() {
|
|
2017
|
+
const dir = process.env["KAIROS_TELEMETRY"];
|
|
2018
|
+
return dir && dir !== "true" && dir !== "false" ? new _PatternAnalyzer(dir) : new _PatternAnalyzer();
|
|
2019
|
+
}
|
|
2020
|
+
detectDrift(patterns) {
|
|
2021
|
+
const VALIDATOR_RULES = VALIDATOR_RULE_IDS;
|
|
2022
|
+
const validatorRuleSet = new Set(VALIDATOR_RULES);
|
|
2023
|
+
const alerts = [];
|
|
2024
|
+
for (const p of patterns) {
|
|
2025
|
+
if (p.state !== "resolved" && !validatorRuleSet.has(p.rule)) {
|
|
2026
|
+
alerts.push({
|
|
2027
|
+
type: "stale_pattern",
|
|
2028
|
+
rule: p.rule,
|
|
2029
|
+
message: `Pattern references Rule ${p.rule} which does not exist in the current validator (rules 1-23)`
|
|
2030
|
+
});
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
for (const rule of VALIDATOR_RULES) {
|
|
2034
|
+
if (!(rule in RULE_MITIGATIONS)) {
|
|
2035
|
+
alerts.push({
|
|
2036
|
+
type: "missing_mitigation",
|
|
2037
|
+
rule,
|
|
2038
|
+
message: `Rule ${rule} has no mitigation text \u2014 if it fails, the system can't advise the LLM how to fix it`
|
|
2039
|
+
});
|
|
2040
|
+
}
|
|
2041
|
+
if (!(rule in RULE_PIPELINE_STAGES)) {
|
|
2042
|
+
alerts.push({
|
|
2043
|
+
type: "missing_stage_mapping",
|
|
2044
|
+
rule,
|
|
2045
|
+
message: `Rule ${rule} has no pipeline stage mapping \u2014 failures won't be grouped correctly`
|
|
2046
|
+
});
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
const coveredRules = VALIDATOR_RULES.filter((r) => r in RULE_MITIGATIONS && r in RULE_PIPELINE_STAGES).length;
|
|
2050
|
+
return {
|
|
2051
|
+
healthy: alerts.length === 0,
|
|
2052
|
+
alerts,
|
|
2053
|
+
coveredRules,
|
|
2054
|
+
totalRules: VALIDATOR_RULES.length
|
|
2055
|
+
};
|
|
2056
|
+
}
|
|
2057
|
+
computeCompositeScore(rawConfidence, sampleSize, state, avgRecency, stickiness) {
|
|
2058
|
+
const stateWeights = { draft: 0.3, confirmed: 0.8, resolved: 0.1 };
|
|
2059
|
+
const stateWeight = stateWeights[state];
|
|
2060
|
+
const impact = (1 - Math.exp(-sampleSize / 5)) * stateWeight;
|
|
2061
|
+
const stickinessBoost = Math.min(0.15, stickiness * 0.05);
|
|
2062
|
+
const compositeScore = Math.min(Math.round(rawConfidence * impact * avgRecency * (1 + stickinessBoost) * 1e3) / 1e3, 1);
|
|
2063
|
+
return {
|
|
2064
|
+
compositeScore,
|
|
2065
|
+
factors: {
|
|
2066
|
+
rawConfidence: Math.round(rawConfidence * 1e3) / 1e3,
|
|
2067
|
+
impact: Math.round(impact * 1e3) / 1e3,
|
|
2068
|
+
recency: Math.round(avgRecency * 1e3) / 1e3,
|
|
2069
|
+
stickinessBoost: Math.round(stickinessBoost * 1e3) / 1e3
|
|
2070
|
+
}
|
|
2071
|
+
};
|
|
2072
|
+
}
|
|
2073
|
+
classifyTrend(older, newer) {
|
|
2074
|
+
const total = older + newer;
|
|
2075
|
+
if (total === 0) return "stable";
|
|
2076
|
+
if (older === 0) return "new";
|
|
2077
|
+
const newerRatio = newer / total;
|
|
2078
|
+
if (newerRatio >= 0.65) return "worsening";
|
|
2079
|
+
if (newerRatio <= 0.35) return "improving";
|
|
2080
|
+
return "stable";
|
|
2081
|
+
}
|
|
2082
|
+
deduplicateMessages(messages, maxCount = 3) {
|
|
2083
|
+
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();
|
|
2084
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2085
|
+
const unique = [];
|
|
2086
|
+
for (const msg of messages) {
|
|
2087
|
+
const key = normalize(msg);
|
|
2088
|
+
if (!seen.has(key) && unique.length < maxCount) {
|
|
2089
|
+
seen.add(key);
|
|
2090
|
+
unique.push(msg);
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
return unique;
|
|
2094
|
+
}
|
|
2095
|
+
recencyWeight(fileDate, halfLifeDays = 30) {
|
|
2096
|
+
const daysAgo = Math.max(0, (Date.now() - (/* @__PURE__ */ new Date(fileDate + "T12:00:00Z")).getTime()) / (1e3 * 60 * 60 * 24));
|
|
2097
|
+
return Math.max(0.1, Math.exp(-Math.LN2 * daysAgo / halfLifeDays));
|
|
2098
|
+
}
|
|
2099
|
+
async readAllEvents(days) {
|
|
2100
|
+
return readTelemetryEvents(this.telemetryDir, days);
|
|
1515
2101
|
}
|
|
1516
2102
|
};
|
|
1517
2103
|
|
|
@@ -1537,6 +2123,7 @@ var Kairos = class {
|
|
|
1537
2123
|
logger;
|
|
1538
2124
|
telemetry;
|
|
1539
2125
|
telemetryReader;
|
|
2126
|
+
patternAnalyzer;
|
|
1540
2127
|
model;
|
|
1541
2128
|
saveQueue = Promise.resolve(null);
|
|
1542
2129
|
constructor(options) {
|
|
@@ -1562,12 +2149,15 @@ var Kairos = class {
|
|
|
1562
2149
|
if (options.telemetry === true) {
|
|
1563
2150
|
this.telemetry = new TelemetryCollector();
|
|
1564
2151
|
this.telemetryReader = new TelemetryReader();
|
|
2152
|
+
this.patternAnalyzer = new PatternAnalyzer();
|
|
1565
2153
|
} else if (typeof options.telemetry === "string") {
|
|
1566
2154
|
this.telemetry = new TelemetryCollector(options.telemetry);
|
|
1567
2155
|
this.telemetryReader = new TelemetryReader(options.telemetry);
|
|
2156
|
+
this.patternAnalyzer = new PatternAnalyzer(options.telemetry);
|
|
1568
2157
|
} else {
|
|
1569
2158
|
this.telemetry = null;
|
|
1570
2159
|
this.telemetryReader = null;
|
|
2160
|
+
this.patternAnalyzer = null;
|
|
1571
2161
|
}
|
|
1572
2162
|
}
|
|
1573
2163
|
requireProvider() {
|
|
@@ -1605,11 +2195,45 @@ var Kairos = class {
|
|
|
1605
2195
|
this.logger.info(`Telemetry: ${highFreq.length} high-frequency failure rule(s) will be warned about`);
|
|
1606
2196
|
}
|
|
1607
2197
|
}
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
2198
|
+
let designResult;
|
|
2199
|
+
try {
|
|
2200
|
+
designResult = await this.designer.design(
|
|
2201
|
+
{ description, ...options?.name ? { name: options.name } : {} },
|
|
2202
|
+
matches,
|
|
2203
|
+
globalFailureRates
|
|
2204
|
+
);
|
|
2205
|
+
} catch (err) {
|
|
2206
|
+
if (err instanceof ValidationError && err.attemptMetadata) {
|
|
2207
|
+
for (const meta of err.attemptMetadata) {
|
|
2208
|
+
await this.telemetry?.emit("generation_attempt", {
|
|
2209
|
+
description,
|
|
2210
|
+
attempt: meta.attempt,
|
|
2211
|
+
temperature: meta.temperature,
|
|
2212
|
+
durationMs: meta.durationMs,
|
|
2213
|
+
tokensInput: meta.tokensInput,
|
|
2214
|
+
tokensOutput: meta.tokensOutput,
|
|
2215
|
+
validationPassed: meta.validationPassed,
|
|
2216
|
+
issueCount: meta.issues.length,
|
|
2217
|
+
issues: meta.issues.map((i) => ({ rule: i.rule, message: i.message, nodeId: i.nodeId ?? null, nodeType: i.nodeType ?? null }))
|
|
2218
|
+
});
|
|
2219
|
+
}
|
|
2220
|
+
await this.telemetry?.emit("build_complete", {
|
|
2221
|
+
description,
|
|
2222
|
+
success: false,
|
|
2223
|
+
totalAttempts: err.attemptMetadata.length,
|
|
2224
|
+
totalDurationMs: Date.now() - buildStart,
|
|
2225
|
+
totalTokensInput: err.attemptMetadata.reduce((s, m) => s + m.tokensInput, 0),
|
|
2226
|
+
totalTokensOutput: err.attemptMetadata.reduce((s, m) => s + m.tokensOutput, 0),
|
|
2227
|
+
workflowName: null,
|
|
2228
|
+
workflowId: null,
|
|
2229
|
+
dryRun: options?.dryRun ?? false,
|
|
2230
|
+
credentialsNeeded: 0,
|
|
2231
|
+
warnedRules: err.warnedRules ?? []
|
|
2232
|
+
});
|
|
2233
|
+
this.updatePatterns();
|
|
2234
|
+
}
|
|
2235
|
+
throw err;
|
|
2236
|
+
}
|
|
1613
2237
|
await this.emitAttemptTelemetry(description, designResult);
|
|
1614
2238
|
const workflow = options?.name ? { ...designResult.workflow, name: options.name } : designResult.workflow;
|
|
1615
2239
|
this.saveToLibrary(workflow, description, designResult, matches);
|
|
@@ -1626,8 +2250,10 @@ var Kairos = class {
|
|
|
1626
2250
|
workflowName: workflow.name,
|
|
1627
2251
|
workflowId: null,
|
|
1628
2252
|
dryRun: true,
|
|
1629
|
-
credentialsNeeded: designResult.credentialsNeeded.length
|
|
2253
|
+
credentialsNeeded: designResult.credentialsNeeded.length,
|
|
2254
|
+
warnedRules: designResult.warnedRules
|
|
1630
2255
|
});
|
|
2256
|
+
this.updatePatterns();
|
|
1631
2257
|
return {
|
|
1632
2258
|
workflowId: null,
|
|
1633
2259
|
name: workflow.name,
|
|
@@ -1656,8 +2282,10 @@ var Kairos = class {
|
|
|
1656
2282
|
workflowName: deployed.name,
|
|
1657
2283
|
workflowId: deployed.workflowId,
|
|
1658
2284
|
dryRun: false,
|
|
1659
|
-
credentialsNeeded: designResult.credentialsNeeded.length
|
|
2285
|
+
credentialsNeeded: designResult.credentialsNeeded.length,
|
|
2286
|
+
warnedRules: designResult.warnedRules
|
|
1660
2287
|
});
|
|
2288
|
+
this.updatePatterns();
|
|
1661
2289
|
return {
|
|
1662
2290
|
workflowId: deployed.workflowId,
|
|
1663
2291
|
name: deployed.name,
|
|
@@ -1680,7 +2308,41 @@ var Kairos = class {
|
|
|
1680
2308
|
await this.library.initialize();
|
|
1681
2309
|
const matches = await this.library.search(description);
|
|
1682
2310
|
const globalFailureRates = await this.telemetryReader?.getFailureRates() ?? [];
|
|
1683
|
-
|
|
2311
|
+
let designResult;
|
|
2312
|
+
try {
|
|
2313
|
+
designResult = await this.designer.design({ description }, matches, globalFailureRates);
|
|
2314
|
+
} catch (err) {
|
|
2315
|
+
if (err instanceof ValidationError && err.attemptMetadata) {
|
|
2316
|
+
for (const meta of err.attemptMetadata) {
|
|
2317
|
+
await this.telemetry?.emit("generation_attempt", {
|
|
2318
|
+
description,
|
|
2319
|
+
attempt: meta.attempt,
|
|
2320
|
+
temperature: meta.temperature,
|
|
2321
|
+
durationMs: meta.durationMs,
|
|
2322
|
+
tokensInput: meta.tokensInput,
|
|
2323
|
+
tokensOutput: meta.tokensOutput,
|
|
2324
|
+
validationPassed: meta.validationPassed,
|
|
2325
|
+
issueCount: meta.issues.length,
|
|
2326
|
+
issues: meta.issues.map((i) => ({ rule: i.rule, message: i.message, nodeId: i.nodeId ?? null, nodeType: i.nodeType ?? null }))
|
|
2327
|
+
});
|
|
2328
|
+
}
|
|
2329
|
+
await this.telemetry?.emit("build_complete", {
|
|
2330
|
+
description,
|
|
2331
|
+
success: false,
|
|
2332
|
+
totalAttempts: err.attemptMetadata.length,
|
|
2333
|
+
totalDurationMs: Date.now() - buildStart,
|
|
2334
|
+
totalTokensInput: err.attemptMetadata.reduce((s, m) => s + m.tokensInput, 0),
|
|
2335
|
+
totalTokensOutput: err.attemptMetadata.reduce((s, m) => s + m.tokensOutput, 0),
|
|
2336
|
+
workflowName: null,
|
|
2337
|
+
workflowId: null,
|
|
2338
|
+
dryRun: false,
|
|
2339
|
+
credentialsNeeded: 0,
|
|
2340
|
+
warnedRules: err.warnedRules ?? []
|
|
2341
|
+
});
|
|
2342
|
+
this.updatePatterns();
|
|
2343
|
+
}
|
|
2344
|
+
throw err;
|
|
2345
|
+
}
|
|
1684
2346
|
await this.emitAttemptTelemetry(description, designResult);
|
|
1685
2347
|
const provider = this.requireProvider();
|
|
1686
2348
|
const deployed = await provider.update(id, designResult.workflow);
|
|
@@ -1698,8 +2360,10 @@ var Kairos = class {
|
|
|
1698
2360
|
workflowName: deployed.name,
|
|
1699
2361
|
workflowId: deployed.workflowId,
|
|
1700
2362
|
dryRun: false,
|
|
1701
|
-
credentialsNeeded: designResult.credentialsNeeded.length
|
|
2363
|
+
credentialsNeeded: designResult.credentialsNeeded.length,
|
|
2364
|
+
warnedRules: designResult.warnedRules
|
|
1702
2365
|
});
|
|
2366
|
+
this.updatePatterns();
|
|
1703
2367
|
return {
|
|
1704
2368
|
workflowId: deployed.workflowId,
|
|
1705
2369
|
name: deployed.name,
|
|
@@ -1714,6 +2378,13 @@ var Kairos = class {
|
|
|
1714
2378
|
await this.saveQueue.catch(() => {
|
|
1715
2379
|
});
|
|
1716
2380
|
}
|
|
2381
|
+
updatePatterns() {
|
|
2382
|
+
if (!this.patternAnalyzer) return;
|
|
2383
|
+
this.saveQueue = this.saveQueue.then(() => this.patternAnalyzer.analyzeAndSave()).then(() => null).catch((err) => {
|
|
2384
|
+
this.logger.warn("Pattern analysis failed (non-fatal)", { err: String(err) });
|
|
2385
|
+
return null;
|
|
2386
|
+
});
|
|
2387
|
+
}
|
|
1717
2388
|
async emitAttemptTelemetry(description, designResult) {
|
|
1718
2389
|
for (const meta of designResult.attemptMetadata) {
|
|
1719
2390
|
await this.telemetry?.emit("generation_attempt", {
|
|
@@ -1725,7 +2396,7 @@ var Kairos = class {
|
|
|
1725
2396
|
tokensOutput: meta.tokensOutput,
|
|
1726
2397
|
validationPassed: meta.validationPassed,
|
|
1727
2398
|
issueCount: meta.issues.length,
|
|
1728
|
-
issues: meta.issues.map((i) => ({ rule: i.rule, message: i.message }))
|
|
2399
|
+
issues: meta.issues.map((i) => ({ rule: i.rule, message: i.message, nodeId: i.nodeId ?? null, nodeType: i.nodeType ?? null }))
|
|
1729
2400
|
});
|
|
1730
2401
|
}
|
|
1731
2402
|
}
|
|
@@ -1824,9 +2495,9 @@ var Kairos = class {
|
|
|
1824
2495
|
};
|
|
1825
2496
|
|
|
1826
2497
|
// src/library/file-library.ts
|
|
1827
|
-
var
|
|
1828
|
-
var
|
|
1829
|
-
var
|
|
2498
|
+
var import_promises4 = require("fs/promises");
|
|
2499
|
+
var import_node_path6 = require("path");
|
|
2500
|
+
var import_node_os5 = require("os");
|
|
1830
2501
|
|
|
1831
2502
|
// src/library/scorer.ts
|
|
1832
2503
|
var WEIGHTS = {
|
|
@@ -2062,7 +2733,7 @@ var FileLibrary = class {
|
|
|
2062
2733
|
initPromise = null;
|
|
2063
2734
|
writeQueue = Promise.resolve();
|
|
2064
2735
|
constructor(dir) {
|
|
2065
|
-
this.dir = dir ?? (0,
|
|
2736
|
+
this.dir = dir ?? (0, import_node_path6.join)((0, import_node_os5.homedir)(), ".kairos", "library");
|
|
2066
2737
|
}
|
|
2067
2738
|
async initialize() {
|
|
2068
2739
|
if (!this.initPromise) {
|
|
@@ -2071,10 +2742,10 @@ var FileLibrary = class {
|
|
|
2071
2742
|
return this.initPromise;
|
|
2072
2743
|
}
|
|
2073
2744
|
async doInitialize() {
|
|
2074
|
-
await (0,
|
|
2075
|
-
const indexPath = (0,
|
|
2745
|
+
await (0, import_promises4.mkdir)(this.dir, { recursive: true });
|
|
2746
|
+
const indexPath = (0, import_node_path6.join)(this.dir, "index.json");
|
|
2076
2747
|
try {
|
|
2077
|
-
const raw = await (0,
|
|
2748
|
+
const raw = await (0, import_promises4.readFile)(indexPath, "utf-8");
|
|
2078
2749
|
const parsed = JSON.parse(raw);
|
|
2079
2750
|
if (!Array.isArray(parsed)) {
|
|
2080
2751
|
this.workflows = [];
|
|
@@ -2204,10 +2875,10 @@ var FileLibrary = class {
|
|
|
2204
2875
|
}
|
|
2205
2876
|
persist() {
|
|
2206
2877
|
this.writeQueue = this.writeQueue.then(async () => {
|
|
2207
|
-
const indexPath = (0,
|
|
2878
|
+
const indexPath = (0, import_node_path6.join)(this.dir, "index.json");
|
|
2208
2879
|
const tmpPath = `${indexPath}.tmp`;
|
|
2209
|
-
await (0,
|
|
2210
|
-
await (0,
|
|
2880
|
+
await (0, import_promises4.writeFile)(tmpPath, JSON.stringify(this.workflows, null, 2), "utf-8");
|
|
2881
|
+
await (0, import_promises4.rename)(tmpPath, indexPath);
|
|
2211
2882
|
});
|
|
2212
2883
|
return this.writeQueue;
|
|
2213
2884
|
}
|
|
@@ -2395,6 +3066,7 @@ Kairos SDK \u2014 LLM-powered n8n workflow generation
|
|
|
2395
3066
|
Usage:
|
|
2396
3067
|
kairos init First-time setup wizard
|
|
2397
3068
|
kairos build <description> [options]
|
|
3069
|
+
kairos patterns [options]
|
|
2398
3070
|
kairos list
|
|
2399
3071
|
kairos get <id>
|
|
2400
3072
|
kairos activate <id>
|
|
@@ -2407,6 +3079,10 @@ Build options:
|
|
|
2407
3079
|
--name <name> Override the generated workflow name
|
|
2408
3080
|
--activate Activate the workflow after deployment
|
|
2409
3081
|
|
|
3082
|
+
Patterns options:
|
|
3083
|
+
--days <days> Analysis window (default: 30)
|
|
3084
|
+
--json Output raw JSON instead of summary
|
|
3085
|
+
|
|
2410
3086
|
Sync options:
|
|
2411
3087
|
--max <count> Maximum templates to fetch (default: 500)
|
|
2412
3088
|
|
|
@@ -2604,10 +3280,85 @@ async function handleSyncTemplates(flags) {
|
|
|
2604
3280
|
console.error(` Duplicates: ${result.skippedDuplicate} (already in library)`);
|
|
2605
3281
|
console.error(` Paid: ${result.skippedPaid} (skipped)`);
|
|
2606
3282
|
}
|
|
3283
|
+
async function handlePatterns(flags) {
|
|
3284
|
+
const days = typeof flags["days"] === "string" ? parseInt(flags["days"], 10) : 30;
|
|
3285
|
+
const analyzer = PatternAnalyzer.fromEnv();
|
|
3286
|
+
const analysis = await analyzer.analyzeAndSave(days);
|
|
3287
|
+
if (flags["json"] === true) {
|
|
3288
|
+
console.log(JSON.stringify(analysis, null, 2));
|
|
3289
|
+
return;
|
|
3290
|
+
}
|
|
3291
|
+
console.log(`
|
|
3292
|
+
Kairos Pattern Analysis (last ${days} days)`);
|
|
3293
|
+
console.log("\u2500".repeat(45));
|
|
3294
|
+
console.log(` Builds: ${analysis.summary.totalBuilds}`);
|
|
3295
|
+
console.log(` Attempts: ${analysis.summary.totalAttempts}`);
|
|
3296
|
+
console.log(` First-try pass: ${(analysis.summary.firstTryPassRate * 100).toFixed(1)}%`);
|
|
3297
|
+
console.log(` Correction rate: ${(analysis.summary.correctionRate * 100).toFixed(1)}%`);
|
|
3298
|
+
if (analysis.summary.singleAttemptFailRate !== void 0) {
|
|
3299
|
+
console.log(` Single-attempt failures: ${(analysis.summary.singleAttemptFailRate * 100).toFixed(1)}%`);
|
|
3300
|
+
}
|
|
3301
|
+
console.log(` Avg duration: ${(analysis.summary.avgDurationMs / 1e3).toFixed(1)}s`);
|
|
3302
|
+
const active = analysis.topFailureRules.filter((p) => p.state !== "resolved");
|
|
3303
|
+
const resolved = analysis.topFailureRules.filter((p) => p.state === "resolved");
|
|
3304
|
+
if (active.length > 0) {
|
|
3305
|
+
console.log(`
|
|
3306
|
+
Active Failure Patterns:`);
|
|
3307
|
+
for (const p of active) {
|
|
3308
|
+
const regressionTag = p.regressed ? "[REGRESSION] " : "";
|
|
3309
|
+
const stateTag = p.state === "confirmed" ? "[CONFIRMED]" : "[DRAFT]";
|
|
3310
|
+
const trendIcon = p.trend === "improving" ? " ^" : p.trend === "worsening" ? " v" : p.trend === "new" ? " *" : "";
|
|
3311
|
+
const stage = p.pipelineStage.replace(/_/g, " ");
|
|
3312
|
+
const scoreStr = p.compositeScore.toFixed(3);
|
|
3313
|
+
console.log(` Rule ${p.rule} ${regressionTag}${stateTag}${trendIcon} \u2014 score ${scoreStr} | ${p.failureCount} failures (${(p.confidence * 100).toFixed(1)}%) [${stage}]`);
|
|
3314
|
+
const f = p.scoringFactors;
|
|
3315
|
+
console.log(` Factors: confidence=${f.rawConfidence} \xD7 impact=${f.impact} \xD7 recency=${f.recency} + boost=${f.stickinessBoost}`);
|
|
3316
|
+
if (p.mitigation) console.log(` Fix: ${p.mitigation}`);
|
|
3317
|
+
if (p.exampleMessages.length > 0) console.log(` e.g. ${p.exampleMessages[0]}`);
|
|
3318
|
+
}
|
|
3319
|
+
} else {
|
|
3320
|
+
console.log(`
|
|
3321
|
+
No active failure patterns.`);
|
|
3322
|
+
}
|
|
3323
|
+
if (resolved.length > 0) {
|
|
3324
|
+
console.log(`
|
|
3325
|
+
Resolved Patterns:`);
|
|
3326
|
+
for (const p of resolved) {
|
|
3327
|
+
console.log(` Rule ${p.rule} \u2014 previously confirmed, 0 failures in current window`);
|
|
3328
|
+
}
|
|
3329
|
+
}
|
|
3330
|
+
if (analysis.failingCredentialTypes.length > 0) {
|
|
3331
|
+
console.log(`
|
|
3332
|
+
Failing Credential Types:`);
|
|
3333
|
+
for (const c of analysis.failingCredentialTypes) {
|
|
3334
|
+
console.log(` ${c.type}: ${c.count} failures`);
|
|
3335
|
+
}
|
|
3336
|
+
}
|
|
3337
|
+
if (analysis.warningEffectiveness && analysis.warningEffectiveness.length > 0) {
|
|
3338
|
+
console.log(`
|
|
3339
|
+
Warning Effectiveness:`);
|
|
3340
|
+
for (const w of analysis.warningEffectiveness) {
|
|
3341
|
+
console.log(` Rule ${w.rule}: warned ${w.timesWarned}x, prevented ${w.timesWarnedAndPassed}x (${Math.round(w.effectivenessRate * 100)}% effective)`);
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
const drift = analysis.drift;
|
|
3345
|
+
if (drift) {
|
|
3346
|
+
console.log(`
|
|
3347
|
+
Drift Detection: ${drift.healthy ? "HEALTHY" : "ALERTS FOUND"}`);
|
|
3348
|
+
console.log(` Coverage: ${drift.coveredRules}/${drift.totalRules} rules have mitigations + stage mappings`);
|
|
3349
|
+
if (drift.alerts.length > 0) {
|
|
3350
|
+
for (const a of drift.alerts) {
|
|
3351
|
+
console.log(` [${a.type}] Rule ${a.rule}: ${a.message}`);
|
|
3352
|
+
}
|
|
3353
|
+
}
|
|
3354
|
+
}
|
|
3355
|
+
console.log(`
|
|
3356
|
+
Patterns saved to ~/.kairos/patterns.json`);
|
|
3357
|
+
}
|
|
2607
3358
|
async function handleInit() {
|
|
2608
|
-
const { writeFile:
|
|
2609
|
-
const { join:
|
|
2610
|
-
const { homedir:
|
|
3359
|
+
const { writeFile: writeFile3, readFile: readFile2, mkdir: mkdir4 } = await import("fs/promises");
|
|
3360
|
+
const { join: join7 } = await import("path");
|
|
3361
|
+
const { homedir: homedir6 } = await import("os");
|
|
2611
3362
|
const readline = await import("readline");
|
|
2612
3363
|
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
2613
3364
|
const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
|
|
@@ -2615,10 +3366,10 @@ async function handleInit() {
|
|
|
2615
3366
|
console.error(" Kairos SDK \u2014 Setup Wizard");
|
|
2616
3367
|
console.error(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
2617
3368
|
console.error("");
|
|
2618
|
-
const envPath =
|
|
3369
|
+
const envPath = join7(process.cwd(), ".env");
|
|
2619
3370
|
let existingEnv = "";
|
|
2620
3371
|
try {
|
|
2621
|
-
existingEnv = await
|
|
3372
|
+
existingEnv = await readFile2(envPath, "utf-8");
|
|
2622
3373
|
} catch {
|
|
2623
3374
|
}
|
|
2624
3375
|
const has = (key) => existingEnv.includes(key) || !!process.env[key];
|
|
@@ -2644,7 +3395,7 @@ async function handleInit() {
|
|
|
2644
3395
|
rl.close();
|
|
2645
3396
|
if (lines.length > 0) {
|
|
2646
3397
|
const newContent = existingEnv ? existingEnv.trimEnd() + "\n" + lines.join("\n") + "\n" : lines.join("\n") + "\n";
|
|
2647
|
-
await
|
|
3398
|
+
await writeFile3(envPath, newContent, "utf-8");
|
|
2648
3399
|
console.error(`
|
|
2649
3400
|
Saved to ${envPath}`);
|
|
2650
3401
|
} else {
|
|
@@ -2679,8 +3430,8 @@ async function handleInit() {
|
|
|
2679
3430
|
});
|
|
2680
3431
|
console.error(` Synced ${result.saved} templates (${result.blocked} blocked, ${result.skippedDuplicate} duplicates)`);
|
|
2681
3432
|
}
|
|
2682
|
-
const kairosDir =
|
|
2683
|
-
await
|
|
3433
|
+
const kairosDir = join7(homedir6(), ".kairos");
|
|
3434
|
+
await mkdir4(join7(kairosDir, "telemetry"), { recursive: true });
|
|
2684
3435
|
console.error("");
|
|
2685
3436
|
console.error(" Setup complete! Try:");
|
|
2686
3437
|
console.error("");
|
|
@@ -2700,6 +3451,9 @@ async function main() {
|
|
|
2700
3451
|
case "build":
|
|
2701
3452
|
await handleBuild(positional, flags);
|
|
2702
3453
|
break;
|
|
3454
|
+
case "patterns":
|
|
3455
|
+
await handlePatterns(flags);
|
|
3456
|
+
break;
|
|
2703
3457
|
case "list":
|
|
2704
3458
|
await handleList();
|
|
2705
3459
|
break;
|