@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
|
@@ -397,17 +397,28 @@ var N8nValidator = class {
|
|
|
397
397
|
this.checkRule21(workflow, issues);
|
|
398
398
|
this.checkRule22(workflow, issues);
|
|
399
399
|
this.checkRule23(workflow, issues);
|
|
400
|
+
if (Array.isArray(workflow.nodes)) {
|
|
401
|
+
const nodeById = new Map(workflow.nodes.map((n) => [n.id, n.type]));
|
|
402
|
+
for (const issue of issues) {
|
|
403
|
+
if (issue.nodeId && !issue.nodeType) {
|
|
404
|
+
const nt = nodeById.get(issue.nodeId);
|
|
405
|
+
if (nt) issue.nodeType = nt;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
400
409
|
const errors = issues.filter((i) => i.severity === "error");
|
|
401
410
|
return { valid: errors.length === 0, issues };
|
|
402
411
|
}
|
|
403
|
-
err(issues, rule, message, nodeId) {
|
|
412
|
+
err(issues, rule, message, nodeId, nodeType) {
|
|
404
413
|
const issue = { rule, severity: "error", message };
|
|
405
414
|
if (nodeId !== void 0) issue.nodeId = nodeId;
|
|
415
|
+
if (nodeType !== void 0) issue.nodeType = nodeType;
|
|
406
416
|
issues.push(issue);
|
|
407
417
|
}
|
|
408
|
-
warn(issues, rule, message, nodeId) {
|
|
418
|
+
warn(issues, rule, message, nodeId, nodeType) {
|
|
409
419
|
const issue = { rule, severity: "warn", message };
|
|
410
420
|
if (nodeId !== void 0) issue.nodeId = nodeId;
|
|
421
|
+
if (nodeType !== void 0) issue.nodeType = nodeType;
|
|
411
422
|
issues.push(issue);
|
|
412
423
|
}
|
|
413
424
|
isTriggerNode(node) {
|
|
@@ -752,6 +763,11 @@ var N8nValidator = class {
|
|
|
752
763
|
}
|
|
753
764
|
};
|
|
754
765
|
|
|
766
|
+
// src/generation/prompt-builder.ts
|
|
767
|
+
import { readFileSync } from "fs";
|
|
768
|
+
import { join } from "path";
|
|
769
|
+
import { homedir } from "os";
|
|
770
|
+
|
|
755
771
|
// src/generation/prompts/v1.ts
|
|
756
772
|
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.
|
|
757
773
|
|
|
@@ -944,8 +960,34 @@ function scoreToMode(score) {
|
|
|
944
960
|
return "scratch";
|
|
945
961
|
}
|
|
946
962
|
|
|
947
|
-
// src/
|
|
948
|
-
var
|
|
963
|
+
// src/validation/rule-metadata.ts
|
|
964
|
+
var VALIDATOR_RULE_IDS = Array.from({ length: 23 }, (_, i) => i + 1);
|
|
965
|
+
var RULE_PIPELINE_STAGES = {
|
|
966
|
+
1: "node_generation",
|
|
967
|
+
2: "node_generation",
|
|
968
|
+
3: "node_generation",
|
|
969
|
+
4: "node_generation",
|
|
970
|
+
5: "node_generation",
|
|
971
|
+
6: "node_generation",
|
|
972
|
+
7: "node_generation",
|
|
973
|
+
8: "node_generation",
|
|
974
|
+
9: "connection_wiring",
|
|
975
|
+
10: "connection_wiring",
|
|
976
|
+
11: "connection_wiring",
|
|
977
|
+
12: "workflow_structure",
|
|
978
|
+
13: "node_generation",
|
|
979
|
+
14: "workflow_structure",
|
|
980
|
+
15: "node_generation",
|
|
981
|
+
16: "node_generation",
|
|
982
|
+
17: "credential_injection",
|
|
983
|
+
18: "connection_wiring",
|
|
984
|
+
19: "node_generation",
|
|
985
|
+
20: "connection_wiring",
|
|
986
|
+
21: "workflow_structure",
|
|
987
|
+
22: "workflow_structure",
|
|
988
|
+
23: "node_generation"
|
|
989
|
+
};
|
|
990
|
+
var RULE_MITIGATIONS = {
|
|
949
991
|
1: "Provide a non-empty workflow name string",
|
|
950
992
|
2: "Include at least one node in the nodes array",
|
|
951
993
|
3: "Every node must have a unique UUID v4 string as its id field",
|
|
@@ -956,8 +998,10 @@ var RULE_REMEDIES = {
|
|
|
956
998
|
8: "Every node must have a non-empty name string",
|
|
957
999
|
9: "connections must be a plain object (use {} if no connections)",
|
|
958
1000
|
10: "Every node name in connections (source and target) must exactly match a name in the nodes array",
|
|
1001
|
+
11: "Every non-trigger node should have at least one incoming connection",
|
|
959
1002
|
12: "Remove forbidden fields: id, active, createdAt, updatedAt, versionId, meta, tags \u2014 these are server-assigned",
|
|
960
|
-
|
|
1003
|
+
13: "workflow.settings must be a plain object if present",
|
|
1004
|
+
14: "Include at least one trigger node (e.g. scheduleTrigger, webhookTrigger, manualTrigger, or service-specific)",
|
|
961
1005
|
15: 'Node type strings must be fully qualified: "n8n-nodes-base.httpRequest" not just "httpRequest"',
|
|
962
1006
|
16: "All node names must be unique within the workflow",
|
|
963
1007
|
17: 'Credentials must be an object with non-empty string id and name fields: { id: "placeholder-id", name: "My Credential" }',
|
|
@@ -965,9 +1009,17 @@ var RULE_REMEDIES = {
|
|
|
965
1009
|
19: "Use known safe typeVersion values for each node type",
|
|
966
1010
|
20: "Remove connection cycles \u2014 ensure no node can reach itself through the connection graph",
|
|
967
1011
|
21: 'When using webhook with responseMode "responseNode", include a respondToWebhook node in the flow',
|
|
968
|
-
22: "Ensure all required parameters are set for each node type (e.g. webhook needs httpMethod and path)"
|
|
1012
|
+
22: "Ensure all required parameters are set for each node type (e.g. webhook needs httpMethod and path)",
|
|
1013
|
+
23: "Use node types that exist in the n8n registry \u2014 check with kairos_sync"
|
|
969
1014
|
};
|
|
1015
|
+
|
|
1016
|
+
// src/generation/prompt-builder.ts
|
|
1017
|
+
var CRITICAL_SCORE_THRESHOLD = 0.15;
|
|
970
1018
|
var PromptBuilder = class {
|
|
1019
|
+
patternsPath;
|
|
1020
|
+
constructor(patternsPath) {
|
|
1021
|
+
this.patternsPath = patternsPath ?? join(homedir(), ".kairos", "patterns.json");
|
|
1022
|
+
}
|
|
971
1023
|
build(request, matches, globalFailureRates = [], dynamicCatalog) {
|
|
972
1024
|
const mode = this.resolveMode(matches);
|
|
973
1025
|
const system = this.buildSystem(matches, mode, globalFailureRates, dynamicCatalog);
|
|
@@ -1053,20 +1105,114 @@ A loosely similar workflow (score: ${hint.score.toFixed(2)}) used these node typ
|
|
|
1053
1105
|
}
|
|
1054
1106
|
return blocks;
|
|
1055
1107
|
}
|
|
1108
|
+
loadPatterns() {
|
|
1109
|
+
try {
|
|
1110
|
+
const raw = readFileSync(this.patternsPath, "utf-8");
|
|
1111
|
+
const analysis = JSON.parse(raw);
|
|
1112
|
+
const patterns = analysis.topFailureRules ?? [];
|
|
1113
|
+
return patterns.filter((p) => typeof p.pipelineStage === "string" && typeof p.state === "string");
|
|
1114
|
+
} catch {
|
|
1115
|
+
return [];
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
getWarnedRules() {
|
|
1119
|
+
return this.getActivePatterns().map((p) => p.rule);
|
|
1120
|
+
}
|
|
1121
|
+
getActivePatterns() {
|
|
1122
|
+
const MAX_WARNED = 10;
|
|
1123
|
+
const all = this.loadPatterns().filter((p) => p.state !== "resolved" && p.confidence > 0);
|
|
1124
|
+
const regressed = all.filter((p) => p.regressed).sort((a, b) => b.compositeScore - a.compositeScore);
|
|
1125
|
+
const confirmed = all.filter((p) => !p.regressed && p.state === "confirmed").sort((a, b) => b.compositeScore - a.compositeScore);
|
|
1126
|
+
const drafts = all.filter((p) => !p.regressed && p.state !== "confirmed").sort((a, b) => b.compositeScore - a.compositeScore);
|
|
1127
|
+
return [...regressed, ...confirmed, ...drafts].slice(0, MAX_WARNED);
|
|
1128
|
+
}
|
|
1056
1129
|
buildFailureWarnings(matches, globalFailureRates) {
|
|
1130
|
+
const richPatterns = this.getActivePatterns();
|
|
1131
|
+
if (richPatterns.length > 0) {
|
|
1132
|
+
return this.buildStageGroupedWarnings(richPatterns, matches);
|
|
1133
|
+
}
|
|
1134
|
+
return this.buildLegacyWarnings(matches, globalFailureRates);
|
|
1135
|
+
}
|
|
1136
|
+
buildStageGroupedWarnings(patterns, matches) {
|
|
1137
|
+
const stageLabels = {
|
|
1138
|
+
credential_injection: "CREDENTIAL FORMATTING",
|
|
1139
|
+
connection_wiring: "CONNECTION WIRING",
|
|
1140
|
+
node_generation: "NODE GENERATION",
|
|
1141
|
+
workflow_structure: "WORKFLOW STRUCTURE"
|
|
1142
|
+
};
|
|
1143
|
+
const byStage = /* @__PURE__ */ new Map();
|
|
1144
|
+
for (const p of patterns) {
|
|
1145
|
+
const list = byStage.get(p.pipelineStage) ?? [];
|
|
1146
|
+
list.push(p);
|
|
1147
|
+
byStage.set(p.pipelineStage, list);
|
|
1148
|
+
}
|
|
1149
|
+
const sections = [];
|
|
1150
|
+
for (const [stage, stagePatterns] of byStage) {
|
|
1151
|
+
const label = stageLabels[stage] ?? stage;
|
|
1152
|
+
const byMitigation = /* @__PURE__ */ new Map();
|
|
1153
|
+
for (const p of stagePatterns) {
|
|
1154
|
+
const key = p.mitigation ?? `rule_${p.rule}`;
|
|
1155
|
+
const list = byMitigation.get(key) ?? [];
|
|
1156
|
+
list.push(p);
|
|
1157
|
+
byMitigation.set(key, list);
|
|
1158
|
+
}
|
|
1159
|
+
const lines = [];
|
|
1160
|
+
for (const group of byMitigation.values()) {
|
|
1161
|
+
if (group.length === 1) {
|
|
1162
|
+
const p = group[0];
|
|
1163
|
+
const urgency = p.regressed ? "CRITICAL REGRESSION: " : (p.compositeScore ?? 0) >= CRITICAL_SCORE_THRESHOLD ? "CRITICAL: " : "";
|
|
1164
|
+
const statePrefix = p.state === "confirmed" ? "[CONFIRMED] " : "";
|
|
1165
|
+
const trendSuffix = p.trend === "worsening" ? " (GETTING WORSE)" : p.trend === "improving" ? " (improving)" : "";
|
|
1166
|
+
const remedy = p.mitigation ?? RULE_MITIGATIONS[p.rule];
|
|
1167
|
+
const remedyStr = remedy ? `
|
|
1168
|
+
Fix: ${remedy}` : "";
|
|
1169
|
+
lines.push(`- ${urgency}${statePrefix}Rule ${p.rule}${trendSuffix}: ${p.exampleMessages[0] ?? "No example"}${remedyStr}`);
|
|
1170
|
+
} else {
|
|
1171
|
+
const ruleNums = group.map((p) => p.rule).join(", ");
|
|
1172
|
+
const totalFailures = group.reduce((s, p) => s + p.failureCount, 0);
|
|
1173
|
+
const hasConfirmed = group.some((p) => p.state === "confirmed");
|
|
1174
|
+
const statePrefix = hasConfirmed ? "[CONFIRMED] " : "";
|
|
1175
|
+
const remedy = group[0].mitigation;
|
|
1176
|
+
const remedyStr = remedy ? `
|
|
1177
|
+
Fix: ${remedy}` : "";
|
|
1178
|
+
lines.push(`- ${statePrefix}Rules ${ruleNums} (${totalFailures} failures combined): same root cause${remedyStr}`);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
sections.push(`### ${label}
|
|
1182
|
+
${lines.join("\n")}`);
|
|
1183
|
+
}
|
|
1184
|
+
for (const match of matches) {
|
|
1185
|
+
const fps = match.workflow.failurePatterns;
|
|
1186
|
+
if (!fps?.length) continue;
|
|
1187
|
+
const coveredRules = new Set(patterns.map((p) => p.rule));
|
|
1188
|
+
const extra = fps.filter((fp) => !coveredRules.has(fp.rule));
|
|
1189
|
+
for (const fp of extra) {
|
|
1190
|
+
const remedy = RULE_MITIGATIONS[fp.rule];
|
|
1191
|
+
const remedyStr = remedy ? ` \u2014 Fix: ${remedy}` : "";
|
|
1192
|
+
sections.push(`- Rule ${fp.rule}: "${fp.message}"${remedyStr} (seen in similar workflows)`);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
if (sections.length === 0) return null;
|
|
1196
|
+
return `## Known Failure Patterns \u2014 AVOID THESE
|
|
1197
|
+
|
|
1198
|
+
Grouped by generation stage. Fix these BEFORE outputting your response:
|
|
1199
|
+
|
|
1200
|
+
${sections.join("\n\n")}`;
|
|
1201
|
+
}
|
|
1202
|
+
buildLegacyWarnings(matches, globalFailureRates) {
|
|
1057
1203
|
const lines = [];
|
|
1058
1204
|
for (const match of matches) {
|
|
1059
1205
|
const patterns = match.workflow.failurePatterns;
|
|
1060
1206
|
if (!patterns?.length) continue;
|
|
1061
1207
|
for (const fp of patterns) {
|
|
1062
|
-
const remedy =
|
|
1208
|
+
const remedy = RULE_MITIGATIONS[fp.rule];
|
|
1063
1209
|
const remedyStr = remedy ? ` \u2014 Fix: ${remedy}` : "";
|
|
1064
1210
|
lines.push(`- Rule ${fp.rule}: "${fp.message}"${remedyStr} (seen ${fp.occurrences}x in similar workflows)`);
|
|
1065
1211
|
}
|
|
1066
1212
|
}
|
|
1067
1213
|
const highFreqRules = globalFailureRates.filter((r) => r.rate >= 0.15);
|
|
1068
1214
|
for (const rule of highFreqRules) {
|
|
1069
|
-
const remedy =
|
|
1215
|
+
const remedy = RULE_MITIGATIONS[rule.rule];
|
|
1070
1216
|
const remedyStr = remedy ? ` \u2014 Fix: ${remedy}` : "";
|
|
1071
1217
|
lines.push(`- Rule ${rule.rule}: "${rule.commonMessage}"${remedyStr} (fails in ${Math.round(rule.rate * 100)}% of all builds)`);
|
|
1072
1218
|
}
|
|
@@ -1085,15 +1231,55 @@ Workflow name: "${request.name}"` : "";
|
|
|
1085
1231
|
};
|
|
1086
1232
|
|
|
1087
1233
|
// src/telemetry/reader.ts
|
|
1088
|
-
import {
|
|
1089
|
-
import { join } from "path";
|
|
1090
|
-
|
|
1234
|
+
import { homedir as homedir2 } from "os";
|
|
1235
|
+
import { join as join3 } from "path";
|
|
1236
|
+
|
|
1237
|
+
// src/telemetry/event-reader.ts
|
|
1238
|
+
import { readdir } from "fs/promises";
|
|
1239
|
+
import { createReadStream } from "fs";
|
|
1240
|
+
import { join as join2 } from "path";
|
|
1241
|
+
import { createInterface } from "readline";
|
|
1242
|
+
async function readTelemetryEvents(dir, days) {
|
|
1243
|
+
let files;
|
|
1244
|
+
try {
|
|
1245
|
+
files = await readdir(dir);
|
|
1246
|
+
} catch {
|
|
1247
|
+
return [];
|
|
1248
|
+
}
|
|
1249
|
+
const cutoff = /* @__PURE__ */ new Date();
|
|
1250
|
+
cutoff.setDate(cutoff.getDate() - days);
|
|
1251
|
+
const cutoffStr = cutoff.toISOString().slice(0, 10);
|
|
1252
|
+
const todayStr = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1253
|
+
const datePattern = /^\d{4}-\d{2}-\d{2}\.jsonl$/;
|
|
1254
|
+
const recentFiles = files.filter((f) => datePattern.test(f) && f >= cutoffStr && f <= `${todayStr}.jsonl`).sort();
|
|
1255
|
+
const events = [];
|
|
1256
|
+
for (const file of recentFiles) {
|
|
1257
|
+
const fileDate = file.replace(".jsonl", "");
|
|
1258
|
+
try {
|
|
1259
|
+
const rl = createInterface({
|
|
1260
|
+
input: createReadStream(join2(dir, file), "utf-8"),
|
|
1261
|
+
crlfDelay: Infinity
|
|
1262
|
+
});
|
|
1263
|
+
for await (const line of rl) {
|
|
1264
|
+
if (!line.trim()) continue;
|
|
1265
|
+
try {
|
|
1266
|
+
events.push({ ...JSON.parse(line), fileDate });
|
|
1267
|
+
} catch {
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
} catch {
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
return events;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
// src/telemetry/reader.ts
|
|
1091
1277
|
var TelemetryReader = class {
|
|
1092
1278
|
dir;
|
|
1093
1279
|
cache = null;
|
|
1094
1280
|
cacheTime = 0;
|
|
1095
1281
|
constructor(dir) {
|
|
1096
|
-
this.dir = dir ??
|
|
1282
|
+
this.dir = dir ?? join3(homedir2(), ".kairos", "telemetry");
|
|
1097
1283
|
}
|
|
1098
1284
|
async getFailureRates(days = 30) {
|
|
1099
1285
|
const now = Date.now();
|
|
@@ -1102,9 +1288,10 @@ var TelemetryReader = class {
|
|
|
1102
1288
|
}
|
|
1103
1289
|
const events = await this.readRecentEvents(days);
|
|
1104
1290
|
const buildSessions = new Set(
|
|
1105
|
-
events.filter((e) => e.eventType === "build_complete"
|
|
1291
|
+
events.filter((e) => e.eventType === "build_complete").map((e) => e.sessionId)
|
|
1106
1292
|
);
|
|
1107
|
-
|
|
1293
|
+
const MIN_BUILDS_FOR_RATES = 3;
|
|
1294
|
+
if (buildSessions.size < MIN_BUILDS_FOR_RATES) return [];
|
|
1108
1295
|
const ruleSessions = /* @__PURE__ */ new Map();
|
|
1109
1296
|
for (const event of events) {
|
|
1110
1297
|
if (event.eventType !== "generation_attempt") continue;
|
|
@@ -1142,32 +1329,425 @@ var TelemetryReader = class {
|
|
|
1142
1329
|
return rates;
|
|
1143
1330
|
}
|
|
1144
1331
|
async readRecentEvents(days) {
|
|
1145
|
-
|
|
1332
|
+
return readTelemetryEvents(this.dir, days);
|
|
1333
|
+
}
|
|
1334
|
+
};
|
|
1335
|
+
|
|
1336
|
+
// src/telemetry/pattern-analyzer.ts
|
|
1337
|
+
import { writeFile, readFile as fsReadFile, appendFile, mkdir, rename } from "fs/promises";
|
|
1338
|
+
import { join as join4 } from "path";
|
|
1339
|
+
import { homedir as homedir3 } from "os";
|
|
1340
|
+
var PATTERN_SCHEMA_VERSION = 2;
|
|
1341
|
+
var PatternAnalyzer = class _PatternAnalyzer {
|
|
1342
|
+
telemetryDir;
|
|
1343
|
+
outputDir;
|
|
1344
|
+
constructor(telemetryDir) {
|
|
1345
|
+
const defaultDir = join4(homedir3(), ".kairos", "telemetry");
|
|
1346
|
+
this.telemetryDir = telemetryDir ?? defaultDir;
|
|
1347
|
+
this.outputDir = telemetryDir ? join4(telemetryDir, "..") : join4(homedir3(), ".kairos");
|
|
1348
|
+
}
|
|
1349
|
+
async loadPreviousPatterns() {
|
|
1146
1350
|
try {
|
|
1147
|
-
|
|
1351
|
+
const raw = await fsReadFile(join4(this.outputDir, "patterns.json"), "utf-8");
|
|
1352
|
+
const prev = JSON.parse(raw);
|
|
1353
|
+
const version = prev.schemaVersion ?? 0;
|
|
1354
|
+
const patterns = prev.topFailureRules ?? [];
|
|
1355
|
+
if (version === PATTERN_SCHEMA_VERSION) return patterns;
|
|
1356
|
+
return this.migratePatterns(patterns, version);
|
|
1148
1357
|
} catch {
|
|
1149
1358
|
return [];
|
|
1150
1359
|
}
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1360
|
+
}
|
|
1361
|
+
migratePatterns(patterns, fromVersion) {
|
|
1362
|
+
let migrated = patterns;
|
|
1363
|
+
if (fromVersion < 1) {
|
|
1364
|
+
migrated = migrated.map((p) => ({
|
|
1365
|
+
...p,
|
|
1366
|
+
compositeScore: p.compositeScore ?? 0,
|
|
1367
|
+
scoringFactors: p.scoringFactors ?? { rawConfidence: 0, impact: 0, recency: 0, stickinessBoost: 0 },
|
|
1368
|
+
pipelineStage: p.pipelineStage ?? "node_generation"
|
|
1369
|
+
}));
|
|
1370
|
+
}
|
|
1371
|
+
if (fromVersion < 2) {
|
|
1372
|
+
migrated = migrated.map((p) => ({
|
|
1373
|
+
...p,
|
|
1374
|
+
scoringFactors: {
|
|
1375
|
+
...p.scoringFactors,
|
|
1376
|
+
stickinessBoost: p.scoringFactors.stickinessBoost ?? p.scoringFactors["validationBoost"] ?? 0
|
|
1377
|
+
}
|
|
1378
|
+
}));
|
|
1379
|
+
}
|
|
1380
|
+
return migrated;
|
|
1381
|
+
}
|
|
1382
|
+
async analyze(days = 30) {
|
|
1383
|
+
const previousPatterns = await this.loadPreviousPatterns();
|
|
1384
|
+
const events = await this.readAllEvents(days);
|
|
1385
|
+
const starts = events.filter((e) => e.eventType === "build_start");
|
|
1386
|
+
const attempts = events.filter((e) => e.eventType === "generation_attempt");
|
|
1387
|
+
const passed = attempts.filter(
|
|
1388
|
+
(a) => a.data.validationPassed === true
|
|
1389
|
+
);
|
|
1390
|
+
const failed = attempts.filter(
|
|
1391
|
+
(a) => a.data.validationPassed === false
|
|
1392
|
+
);
|
|
1393
|
+
const ruleFailures = /* @__PURE__ */ new Map();
|
|
1394
|
+
const credentialFailures = /* @__PURE__ */ new Map();
|
|
1395
|
+
for (const a of failed) {
|
|
1396
|
+
const weight = this.recencyWeight(a.fileDate);
|
|
1397
|
+
const data = a.data;
|
|
1398
|
+
for (const issue of data.issues ?? []) {
|
|
1399
|
+
const entry = ruleFailures.get(issue.rule) ?? { count: 0, sessions: /* @__PURE__ */ new Set(), recencyWeights: [], allMessages: [] };
|
|
1400
|
+
entry.count++;
|
|
1401
|
+
entry.sessions.add(a.sessionId);
|
|
1402
|
+
entry.recencyWeights.push(weight);
|
|
1403
|
+
entry.allMessages.push(issue.message);
|
|
1404
|
+
ruleFailures.set(issue.rule, entry);
|
|
1405
|
+
if (issue.rule === 17) {
|
|
1406
|
+
const credPatterns = [
|
|
1407
|
+
/credential\s+"([^"]+)"/,
|
|
1408
|
+
/credentialType[:\s]+"?([^"'\s]+)"?/,
|
|
1409
|
+
/missing\s+credential\s+(?:for\s+)?["']?([^"'\s]+)/i
|
|
1410
|
+
];
|
|
1411
|
+
let credType = "unknown";
|
|
1412
|
+
for (const re of credPatterns) {
|
|
1413
|
+
const m = issue.message.match(re);
|
|
1414
|
+
if (m?.[1]) {
|
|
1415
|
+
credType = m[1];
|
|
1416
|
+
break;
|
|
1417
|
+
}
|
|
1165
1418
|
}
|
|
1419
|
+
credentialFailures.set(credType, (credentialFailures.get(credType) ?? 0) + 1);
|
|
1166
1420
|
}
|
|
1167
|
-
} catch {
|
|
1168
1421
|
}
|
|
1169
1422
|
}
|
|
1170
|
-
|
|
1423
|
+
const failedByDate = /* @__PURE__ */ new Map();
|
|
1424
|
+
for (const a of failed) {
|
|
1425
|
+
failedByDate.set(a.fileDate, (failedByDate.get(a.fileDate) ?? 0) + 1);
|
|
1426
|
+
}
|
|
1427
|
+
const sortedFailDates = [...failedByDate.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
1428
|
+
const hasTrendData = sortedFailDates.length >= 3;
|
|
1429
|
+
let midDate = "";
|
|
1430
|
+
if (hasTrendData) {
|
|
1431
|
+
const halfTotal = failed.length / 2;
|
|
1432
|
+
let cumulative = 0;
|
|
1433
|
+
for (const [date, count] of sortedFailDates) {
|
|
1434
|
+
cumulative += count;
|
|
1435
|
+
if (cumulative >= halfTotal) {
|
|
1436
|
+
midDate = date;
|
|
1437
|
+
break;
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
const ruleTrends = /* @__PURE__ */ new Map();
|
|
1442
|
+
if (hasTrendData) {
|
|
1443
|
+
for (const a of failed) {
|
|
1444
|
+
const data = a.data;
|
|
1445
|
+
const isNewer = a.fileDate > midDate;
|
|
1446
|
+
for (const issue of data.issues ?? []) {
|
|
1447
|
+
const entry = ruleTrends.get(issue.rule) ?? { older: 0, newer: 0 };
|
|
1448
|
+
if (isNewer) entry.newer++;
|
|
1449
|
+
else entry.older++;
|
|
1450
|
+
ruleTrends.set(issue.rule, entry);
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
1455
|
+
for (const a of attempts) {
|
|
1456
|
+
const list = sessions.get(a.sessionId) ?? [];
|
|
1457
|
+
list.push(a);
|
|
1458
|
+
sessions.set(a.sessionId, list);
|
|
1459
|
+
}
|
|
1460
|
+
let firstTryPass = 0;
|
|
1461
|
+
let correctionNeeded = 0;
|
|
1462
|
+
let singleAttemptFail = 0;
|
|
1463
|
+
for (const sessionAttempts of sessions.values()) {
|
|
1464
|
+
const lastAttempt = sessionAttempts[sessionAttempts.length - 1];
|
|
1465
|
+
const lastPassed = lastAttempt.data.validationPassed === true;
|
|
1466
|
+
if (sessionAttempts.length === 1 && lastPassed) {
|
|
1467
|
+
firstTryPass++;
|
|
1468
|
+
} else if (sessionAttempts.length > 1 && lastPassed) {
|
|
1469
|
+
correctionNeeded++;
|
|
1470
|
+
} else {
|
|
1471
|
+
singleAttemptFail++;
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
const durations = attempts.map((a) => a.data.durationMs).filter((d) => typeof d === "number" && d > 0);
|
|
1475
|
+
const avgDuration = durations.length > 0 ? durations.reduce((s, d) => s + d, 0) / durations.length : 0;
|
|
1476
|
+
const totalInput = attempts.reduce((s, a) => s + (a.data.tokensInput ?? 0), 0);
|
|
1477
|
+
const totalOutput = attempts.reduce((s, a) => s + (a.data.tokensOutput ?? 0), 0);
|
|
1478
|
+
const totalSessions = Math.max(sessions.size, 1);
|
|
1479
|
+
const stickinessCount = /* @__PURE__ */ new Map();
|
|
1480
|
+
for (const sessionAttempts of sessions.values()) {
|
|
1481
|
+
if (sessionAttempts.length < 2) continue;
|
|
1482
|
+
for (let i = 0; i < sessionAttempts.length - 1; i++) {
|
|
1483
|
+
const curr = sessionAttempts[i].data;
|
|
1484
|
+
const next = sessionAttempts[i + 1].data;
|
|
1485
|
+
if (curr.validationPassed !== false || next.validationPassed !== false) continue;
|
|
1486
|
+
const currRules = new Set((curr.issues ?? []).map((iss) => iss.rule));
|
|
1487
|
+
const nextRules = new Set((next.issues ?? []).map((iss) => iss.rule));
|
|
1488
|
+
for (const rule of currRules) {
|
|
1489
|
+
if (nextRules.has(rule)) {
|
|
1490
|
+
stickinessCount.set(rule, (stickinessCount.get(rule) ?? 0) + 1);
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
const CONFIRMED_THRESHOLD = 3;
|
|
1496
|
+
const BUILDS_SINCE_LAST_FAILURE_THRESHOLD = 5;
|
|
1497
|
+
const RESOLVED_TTL_DAYS = 90;
|
|
1498
|
+
const activePatterns = [...ruleFailures.entries()].map(([rule, entry]) => {
|
|
1499
|
+
const t = ruleTrends.get(rule) ?? { older: 0, newer: 0 };
|
|
1500
|
+
const rawConfidence = Math.min(entry.sessions.size / totalSessions, 1);
|
|
1501
|
+
const state = entry.count >= CONFIRMED_THRESHOLD ? "confirmed" : "draft";
|
|
1502
|
+
const avgRecency = entry.recencyWeights.length > 0 ? entry.recencyWeights.reduce((s, w) => s + w, 0) / entry.recencyWeights.length : 1;
|
|
1503
|
+
const stickiness = stickinessCount.get(rule) ?? 0;
|
|
1504
|
+
const { compositeScore, factors } = this.computeCompositeScore(rawConfidence, entry.count, state, avgRecency, stickiness);
|
|
1505
|
+
return {
|
|
1506
|
+
rule,
|
|
1507
|
+
failureCount: entry.count,
|
|
1508
|
+
confidence: Math.round(rawConfidence * 1e3) / 1e3,
|
|
1509
|
+
compositeScore,
|
|
1510
|
+
scoringFactors: factors,
|
|
1511
|
+
state,
|
|
1512
|
+
trend: this.classifyTrend(t.older, t.newer),
|
|
1513
|
+
pipelineStage: RULE_PIPELINE_STAGES[rule] ?? "node_generation",
|
|
1514
|
+
exampleMessages: this.deduplicateMessages(entry.allMessages),
|
|
1515
|
+
mitigation: RULE_MITIGATIONS[rule] ?? null
|
|
1516
|
+
};
|
|
1517
|
+
}).sort((a, b) => b.compositeScore - a.compositeScore);
|
|
1518
|
+
const activeRules = new Set(activePatterns.map((p) => p.rule));
|
|
1519
|
+
for (const p of activePatterns) {
|
|
1520
|
+
const prev = previousPatterns.find((pp) => pp.rule === p.rule);
|
|
1521
|
+
if (prev?.state === "resolved") {
|
|
1522
|
+
p.trend = "worsening";
|
|
1523
|
+
p.regressed = true;
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
const ruleLastFailureDate = /* @__PURE__ */ new Map();
|
|
1527
|
+
for (const a of failed) {
|
|
1528
|
+
const data = a.data;
|
|
1529
|
+
for (const issue of data.issues ?? []) {
|
|
1530
|
+
const existing = ruleLastFailureDate.get(issue.rule);
|
|
1531
|
+
if (!existing || a.fileDate > existing) {
|
|
1532
|
+
ruleLastFailureDate.set(issue.rule, a.fileDate);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
const newlyResolved = previousPatterns.filter((p) => {
|
|
1537
|
+
if (p.state !== "confirmed" || activeRules.has(p.rule)) return false;
|
|
1538
|
+
const lastFailDate = ruleLastFailureDate.get(p.rule) ?? "";
|
|
1539
|
+
const buildsSince = starts.filter((s) => s.fileDate > lastFailDate).length;
|
|
1540
|
+
return buildsSince >= BUILDS_SINCE_LAST_FAILURE_THRESHOLD;
|
|
1541
|
+
}).map((p) => ({
|
|
1542
|
+
...p,
|
|
1543
|
+
state: "resolved",
|
|
1544
|
+
trend: "improving",
|
|
1545
|
+
pipelineStage: p.pipelineStage ?? RULE_PIPELINE_STAGES[p.rule] ?? "node_generation",
|
|
1546
|
+
confidence: 0,
|
|
1547
|
+
compositeScore: 0,
|
|
1548
|
+
scoringFactors: { rawConfidence: 0, impact: 0, recency: 0, stickinessBoost: 0 },
|
|
1549
|
+
failureCount: 0,
|
|
1550
|
+
resolvedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1551
|
+
}));
|
|
1552
|
+
const ttlCutoff = /* @__PURE__ */ new Date();
|
|
1553
|
+
ttlCutoff.setDate(ttlCutoff.getDate() - RESOLVED_TTL_DAYS);
|
|
1554
|
+
const ttlCutoffStr = ttlCutoff.toISOString();
|
|
1555
|
+
const carriedResolved = previousPatterns.filter((p) => p.state === "resolved" && !activeRules.has(p.rule) && (!p.resolvedAt || p.resolvedAt >= ttlCutoffStr)).map((p) => ({ ...p }));
|
|
1556
|
+
const newlyResolvedRules = new Set(newlyResolved.map((p) => p.rule));
|
|
1557
|
+
const pendingResolution = previousPatterns.filter((p) => p.state === "confirmed" && !activeRules.has(p.rule) && !newlyResolvedRules.has(p.rule)).map((p) => ({ ...p }));
|
|
1558
|
+
const deduped = [
|
|
1559
|
+
...newlyResolved,
|
|
1560
|
+
...carriedResolved.filter((p) => !newlyResolvedRules.has(p.rule)),
|
|
1561
|
+
...pendingResolution
|
|
1562
|
+
];
|
|
1563
|
+
const patterns = [...activePatterns, ...deduped];
|
|
1564
|
+
const credTypes = [...credentialFailures.entries()].sort((a, b) => b[1] - a[1]).map(([type, count]) => ({ type, count }));
|
|
1565
|
+
const drift = this.detectDrift(patterns);
|
|
1566
|
+
const warnEffMap = /* @__PURE__ */ new Map();
|
|
1567
|
+
const buildCompletes = events.filter((e) => e.eventType === "build_complete");
|
|
1568
|
+
for (const bc of buildCompletes) {
|
|
1569
|
+
const bcData = bc.data;
|
|
1570
|
+
const warned = bcData.warnedRules ?? [];
|
|
1571
|
+
if (warned.length === 0) continue;
|
|
1572
|
+
const sessionFailedRules = /* @__PURE__ */ new Set();
|
|
1573
|
+
const sessionAttempts = sessions.get(bc.sessionId) ?? [];
|
|
1574
|
+
for (const a of sessionAttempts) {
|
|
1575
|
+
const ad = a.data;
|
|
1576
|
+
if (ad.validationPassed === false) {
|
|
1577
|
+
for (const issue of ad.issues ?? []) {
|
|
1578
|
+
sessionFailedRules.add(issue.rule);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
for (const rule of warned) {
|
|
1583
|
+
const entry = warnEffMap.get(rule) ?? { warned: 0, passed: 0, failed: 0 };
|
|
1584
|
+
entry.warned++;
|
|
1585
|
+
if (sessionFailedRules.has(rule)) entry.failed++;
|
|
1586
|
+
else entry.passed++;
|
|
1587
|
+
warnEffMap.set(rule, entry);
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
const warningEffectiveness = [...warnEffMap.entries()].map(([rule, e]) => ({
|
|
1591
|
+
rule,
|
|
1592
|
+
timesWarned: e.warned,
|
|
1593
|
+
timesWarnedAndPassed: e.passed,
|
|
1594
|
+
timesWarnedAndFailed: e.failed,
|
|
1595
|
+
effectivenessRate: e.warned > 0 ? Math.round(e.passed / e.warned * 1e3) / 1e3 : 0
|
|
1596
|
+
})).sort((a, b) => b.timesWarned - a.timesWarned);
|
|
1597
|
+
const coOccurrenceMap = /* @__PURE__ */ new Map();
|
|
1598
|
+
for (const a of failed) {
|
|
1599
|
+
const data = a.data;
|
|
1600
|
+
const rules = [...new Set((data.issues ?? []).map((i) => i.rule))].sort((x, y) => x - y);
|
|
1601
|
+
for (let i = 0; i < rules.length; i++) {
|
|
1602
|
+
for (let j = i + 1; j < rules.length; j++) {
|
|
1603
|
+
const key = `${rules[i]},${rules[j]}`;
|
|
1604
|
+
coOccurrenceMap.set(key, (coOccurrenceMap.get(key) ?? 0) + 1);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
const ruleCoOccurrence = [...coOccurrenceMap.entries()].filter(([, count]) => count >= 3).map(([key, count]) => {
|
|
1609
|
+
const [a, b] = key.split(",").map(Number);
|
|
1610
|
+
return { rules: [a, b], count };
|
|
1611
|
+
}).sort((a, b) => b.count - a.count);
|
|
1612
|
+
const attemptDistribution = {};
|
|
1613
|
+
for (const sessionAttempts of sessions.values()) {
|
|
1614
|
+
const depth = sessionAttempts.length;
|
|
1615
|
+
attemptDistribution[depth] = (attemptDistribution[depth] ?? 0) + 1;
|
|
1616
|
+
}
|
|
1617
|
+
return {
|
|
1618
|
+
schemaVersion: PATTERN_SCHEMA_VERSION,
|
|
1619
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1620
|
+
summary: {
|
|
1621
|
+
totalBuilds: starts.length,
|
|
1622
|
+
totalAttempts: attempts.length,
|
|
1623
|
+
firstTryPassRate: Math.round(firstTryPass / totalSessions * 1e3) / 1e3,
|
|
1624
|
+
correctionRate: Math.round(correctionNeeded / totalSessions * 1e3) / 1e3,
|
|
1625
|
+
singleAttemptFailRate: Math.round(singleAttemptFail / totalSessions * 1e3) / 1e3,
|
|
1626
|
+
avgDurationMs: Math.round(avgDuration),
|
|
1627
|
+
totalTokensInput: totalInput,
|
|
1628
|
+
totalTokensOutput: totalOutput,
|
|
1629
|
+
attemptDistribution
|
|
1630
|
+
},
|
|
1631
|
+
topFailureRules: patterns,
|
|
1632
|
+
failingCredentialTypes: credTypes,
|
|
1633
|
+
drift,
|
|
1634
|
+
warningEffectiveness,
|
|
1635
|
+
ruleCoOccurrence
|
|
1636
|
+
};
|
|
1637
|
+
}
|
|
1638
|
+
async analyzeAndSave(days = 30) {
|
|
1639
|
+
const analysis = await this.analyze(days);
|
|
1640
|
+
await mkdir(this.outputDir, { recursive: true });
|
|
1641
|
+
const outputPath = join4(this.outputDir, "patterns.json");
|
|
1642
|
+
const tmpPath = `${outputPath}.tmp`;
|
|
1643
|
+
await writeFile(tmpPath, JSON.stringify(analysis, null, 2), "utf-8");
|
|
1644
|
+
await rename(tmpPath, outputPath);
|
|
1645
|
+
const historySummary = {
|
|
1646
|
+
timestamp: analysis.generatedAt,
|
|
1647
|
+
totalBuilds: analysis.summary.totalBuilds,
|
|
1648
|
+
firstTryPassRate: analysis.summary.firstTryPassRate,
|
|
1649
|
+
correctionRate: analysis.summary.correctionRate,
|
|
1650
|
+
singleAttemptFailRate: analysis.summary.singleAttemptFailRate,
|
|
1651
|
+
activePatternCount: analysis.topFailureRules.filter((p) => p.state !== "resolved").length,
|
|
1652
|
+
topRules: analysis.topFailureRules.filter((p) => p.state !== "resolved").slice(0, 5).map((p) => ({ rule: p.rule, compositeScore: p.compositeScore, state: p.state }))
|
|
1653
|
+
};
|
|
1654
|
+
const historyPath = join4(this.outputDir, "pattern-history.jsonl");
|
|
1655
|
+
await appendFile(historyPath, JSON.stringify(historySummary) + "\n", "utf-8");
|
|
1656
|
+
return analysis;
|
|
1657
|
+
}
|
|
1658
|
+
async getHistory(limit = 20) {
|
|
1659
|
+
try {
|
|
1660
|
+
const raw = await fsReadFile(join4(this.outputDir, "pattern-history.jsonl"), "utf-8");
|
|
1661
|
+
return raw.trim().split("\n").filter(Boolean).map((l) => JSON.parse(l)).slice(-limit);
|
|
1662
|
+
} catch {
|
|
1663
|
+
return [];
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
static fromEnv() {
|
|
1667
|
+
const dir = process.env["KAIROS_TELEMETRY"];
|
|
1668
|
+
return dir && dir !== "true" && dir !== "false" ? new _PatternAnalyzer(dir) : new _PatternAnalyzer();
|
|
1669
|
+
}
|
|
1670
|
+
detectDrift(patterns) {
|
|
1671
|
+
const VALIDATOR_RULES = VALIDATOR_RULE_IDS;
|
|
1672
|
+
const validatorRuleSet = new Set(VALIDATOR_RULES);
|
|
1673
|
+
const alerts = [];
|
|
1674
|
+
for (const p of patterns) {
|
|
1675
|
+
if (p.state !== "resolved" && !validatorRuleSet.has(p.rule)) {
|
|
1676
|
+
alerts.push({
|
|
1677
|
+
type: "stale_pattern",
|
|
1678
|
+
rule: p.rule,
|
|
1679
|
+
message: `Pattern references Rule ${p.rule} which does not exist in the current validator (rules 1-23)`
|
|
1680
|
+
});
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
for (const rule of VALIDATOR_RULES) {
|
|
1684
|
+
if (!(rule in RULE_MITIGATIONS)) {
|
|
1685
|
+
alerts.push({
|
|
1686
|
+
type: "missing_mitigation",
|
|
1687
|
+
rule,
|
|
1688
|
+
message: `Rule ${rule} has no mitigation text \u2014 if it fails, the system can't advise the LLM how to fix it`
|
|
1689
|
+
});
|
|
1690
|
+
}
|
|
1691
|
+
if (!(rule in RULE_PIPELINE_STAGES)) {
|
|
1692
|
+
alerts.push({
|
|
1693
|
+
type: "missing_stage_mapping",
|
|
1694
|
+
rule,
|
|
1695
|
+
message: `Rule ${rule} has no pipeline stage mapping \u2014 failures won't be grouped correctly`
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
const coveredRules = VALIDATOR_RULES.filter((r) => r in RULE_MITIGATIONS && r in RULE_PIPELINE_STAGES).length;
|
|
1700
|
+
return {
|
|
1701
|
+
healthy: alerts.length === 0,
|
|
1702
|
+
alerts,
|
|
1703
|
+
coveredRules,
|
|
1704
|
+
totalRules: VALIDATOR_RULES.length
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1707
|
+
computeCompositeScore(rawConfidence, sampleSize, state, avgRecency, stickiness) {
|
|
1708
|
+
const stateWeights = { draft: 0.3, confirmed: 0.8, resolved: 0.1 };
|
|
1709
|
+
const stateWeight = stateWeights[state];
|
|
1710
|
+
const impact = (1 - Math.exp(-sampleSize / 5)) * stateWeight;
|
|
1711
|
+
const stickinessBoost = Math.min(0.15, stickiness * 0.05);
|
|
1712
|
+
const compositeScore = Math.min(Math.round(rawConfidence * impact * avgRecency * (1 + stickinessBoost) * 1e3) / 1e3, 1);
|
|
1713
|
+
return {
|
|
1714
|
+
compositeScore,
|
|
1715
|
+
factors: {
|
|
1716
|
+
rawConfidence: Math.round(rawConfidence * 1e3) / 1e3,
|
|
1717
|
+
impact: Math.round(impact * 1e3) / 1e3,
|
|
1718
|
+
recency: Math.round(avgRecency * 1e3) / 1e3,
|
|
1719
|
+
stickinessBoost: Math.round(stickinessBoost * 1e3) / 1e3
|
|
1720
|
+
}
|
|
1721
|
+
};
|
|
1722
|
+
}
|
|
1723
|
+
classifyTrend(older, newer) {
|
|
1724
|
+
const total = older + newer;
|
|
1725
|
+
if (total === 0) return "stable";
|
|
1726
|
+
if (older === 0) return "new";
|
|
1727
|
+
const newerRatio = newer / total;
|
|
1728
|
+
if (newerRatio >= 0.65) return "worsening";
|
|
1729
|
+
if (newerRatio <= 0.35) return "improving";
|
|
1730
|
+
return "stable";
|
|
1731
|
+
}
|
|
1732
|
+
deduplicateMessages(messages, maxCount = 3) {
|
|
1733
|
+
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();
|
|
1734
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1735
|
+
const unique = [];
|
|
1736
|
+
for (const msg of messages) {
|
|
1737
|
+
const key = normalize(msg);
|
|
1738
|
+
if (!seen.has(key) && unique.length < maxCount) {
|
|
1739
|
+
seen.add(key);
|
|
1740
|
+
unique.push(msg);
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
return unique;
|
|
1744
|
+
}
|
|
1745
|
+
recencyWeight(fileDate, halfLifeDays = 30) {
|
|
1746
|
+
const daysAgo = Math.max(0, (Date.now() - (/* @__PURE__ */ new Date(fileDate + "T12:00:00Z")).getTime()) / (1e3 * 60 * 60 * 24));
|
|
1747
|
+
return Math.max(0.1, Math.exp(-Math.LN2 * daysAgo / halfLifeDays));
|
|
1748
|
+
}
|
|
1749
|
+
async readAllEvents(days) {
|
|
1750
|
+
return readTelemetryEvents(this.telemetryDir, days);
|
|
1171
1751
|
}
|
|
1172
1752
|
};
|
|
1173
1753
|
|
|
@@ -1399,9 +1979,9 @@ function rerank(candidates, clusters) {
|
|
|
1399
1979
|
}
|
|
1400
1980
|
|
|
1401
1981
|
// src/library/file-library.ts
|
|
1402
|
-
import { readFile as
|
|
1403
|
-
import { join as
|
|
1404
|
-
import { homedir as
|
|
1982
|
+
import { readFile, writeFile as writeFile2, rename as rename2, mkdir as mkdir2 } from "fs/promises";
|
|
1983
|
+
import { join as join5 } from "path";
|
|
1984
|
+
import { homedir as homedir4 } from "os";
|
|
1405
1985
|
|
|
1406
1986
|
// src/utils/uuid.ts
|
|
1407
1987
|
function generateUUID() {
|
|
@@ -1427,7 +2007,7 @@ var FileLibrary = class {
|
|
|
1427
2007
|
initPromise = null;
|
|
1428
2008
|
writeQueue = Promise.resolve();
|
|
1429
2009
|
constructor(dir) {
|
|
1430
|
-
this.dir = dir ??
|
|
2010
|
+
this.dir = dir ?? join5(homedir4(), ".kairos", "library");
|
|
1431
2011
|
}
|
|
1432
2012
|
async initialize() {
|
|
1433
2013
|
if (!this.initPromise) {
|
|
@@ -1436,10 +2016,10 @@ var FileLibrary = class {
|
|
|
1436
2016
|
return this.initPromise;
|
|
1437
2017
|
}
|
|
1438
2018
|
async doInitialize() {
|
|
1439
|
-
await
|
|
1440
|
-
const indexPath =
|
|
2019
|
+
await mkdir2(this.dir, { recursive: true });
|
|
2020
|
+
const indexPath = join5(this.dir, "index.json");
|
|
1441
2021
|
try {
|
|
1442
|
-
const raw = await
|
|
2022
|
+
const raw = await readFile(indexPath, "utf-8");
|
|
1443
2023
|
const parsed = JSON.parse(raw);
|
|
1444
2024
|
if (!Array.isArray(parsed)) {
|
|
1445
2025
|
this.workflows = [];
|
|
@@ -1569,10 +2149,10 @@ var FileLibrary = class {
|
|
|
1569
2149
|
}
|
|
1570
2150
|
persist() {
|
|
1571
2151
|
this.writeQueue = this.writeQueue.then(async () => {
|
|
1572
|
-
const indexPath =
|
|
2152
|
+
const indexPath = join5(this.dir, "index.json");
|
|
1573
2153
|
const tmpPath = `${indexPath}.tmp`;
|
|
1574
|
-
await
|
|
1575
|
-
await
|
|
2154
|
+
await writeFile2(tmpPath, JSON.stringify(this.workflows, null, 2), "utf-8");
|
|
2155
|
+
await rename2(tmpPath, indexPath);
|
|
1576
2156
|
});
|
|
1577
2157
|
return this.writeQueue;
|
|
1578
2158
|
}
|
|
@@ -1591,6 +2171,7 @@ export {
|
|
|
1591
2171
|
scoreToMode,
|
|
1592
2172
|
PromptBuilder,
|
|
1593
2173
|
TelemetryReader,
|
|
2174
|
+
PatternAnalyzer,
|
|
1594
2175
|
nullLogger,
|
|
1595
2176
|
hybridScore,
|
|
1596
2177
|
clusterWorkflows,
|
|
@@ -1599,4 +2180,4 @@ export {
|
|
|
1599
2180
|
buildSearchCorpus,
|
|
1600
2181
|
FileLibrary
|
|
1601
2182
|
};
|
|
1602
|
-
//# sourceMappingURL=chunk-
|
|
2183
|
+
//# sourceMappingURL=chunk-NJ6QZBIC.js.map
|