aether-mcp-server 2.0.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/dist/aether-memory-store.js +431 -0
- package/dist/agent.js +1046 -0
- package/dist/captcha-solver.js +303 -0
- package/dist/cdp-bridge.js +2317 -0
- package/dist/cdp-client.js +1602 -0
- package/dist/index.js +71 -0
- package/dist/locator-engine.js +326 -0
- package/dist/mcp-responses.js +20 -0
- package/dist/mcp-server.js +1236 -0
- package/dist/mcp-task-memory.js +36 -0
- package/dist/page-snapshot-cache.js +75 -0
- package/dist/policy-client.js +104 -0
- package/dist/stealth.js +137 -0
- package/dist/trace-recorder.js +46 -0
- package/dist/ws-server.js +134 -0
- package/package.json +35 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.AetherMemoryStore = void 0;
|
|
37
|
+
const fs = __importStar(require("fs/promises"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const DEFAULT_CONFIG = {
|
|
40
|
+
maxLessons: 500,
|
|
41
|
+
maxLessonBytes: 5 * 1024 * 1024,
|
|
42
|
+
minConfidenceToPromote: 0.82,
|
|
43
|
+
};
|
|
44
|
+
const RESERVED_NAMES = new Set(["con", "prn", "aux", "nul", "com1", "com2", "com3", "lpt1", "lpt2", "lpt3"]);
|
|
45
|
+
class AetherMemoryStore {
|
|
46
|
+
configuredRoot;
|
|
47
|
+
canWrite(projectRoot) {
|
|
48
|
+
return Boolean(projectRoot || this.configuredRoot || process.env.AETHER_PROJECT_ROOT);
|
|
49
|
+
}
|
|
50
|
+
async configure(projectRoot) {
|
|
51
|
+
const root = await this.resolveProjectRoot(projectRoot, true);
|
|
52
|
+
const aetherDir = this.aetherDir(root);
|
|
53
|
+
const existed = await this.exists(aetherDir);
|
|
54
|
+
await fs.mkdir(path.join(aetherDir, "memory"), { recursive: true });
|
|
55
|
+
await fs.mkdir(path.join(aetherDir, "skills"), { recursive: true });
|
|
56
|
+
await this.ensureConfig(root);
|
|
57
|
+
await this.ensureGitignore(root);
|
|
58
|
+
this.configuredRoot = root;
|
|
59
|
+
return { projectRoot: root, aetherDir, created: !existed };
|
|
60
|
+
}
|
|
61
|
+
async rememberLesson(input) {
|
|
62
|
+
const root = await this.resolveProjectRoot(input.projectRoot, true);
|
|
63
|
+
await this.configure(root);
|
|
64
|
+
const now = new Date().toISOString();
|
|
65
|
+
const lessons = await this.readLessons(root);
|
|
66
|
+
const fingerprint = this.fingerprint([
|
|
67
|
+
input.title,
|
|
68
|
+
input.trigger,
|
|
69
|
+
input.problemPattern,
|
|
70
|
+
input.failedApproach || "",
|
|
71
|
+
input.betterApproach,
|
|
72
|
+
]);
|
|
73
|
+
const existing = lessons.find((lesson) => lesson.fingerprint === fingerprint);
|
|
74
|
+
if (existing) {
|
|
75
|
+
existing.confidence = this.clampConfidence(Math.max(existing.confidence, input.confidence ?? existing.confidence) + 0.03);
|
|
76
|
+
existing.uses += 1;
|
|
77
|
+
existing.status = this.promotedStatus(existing);
|
|
78
|
+
existing.updatedAt = now;
|
|
79
|
+
existing.lastVerified = now;
|
|
80
|
+
existing.evidence = this.mergeText(existing.evidence, input.evidence);
|
|
81
|
+
existing.tags = this.unique([...(existing.tags || []), ...(input.tags || [])]);
|
|
82
|
+
await this.writeLessons(root, lessons);
|
|
83
|
+
return { projectRoot: root, lesson: existing, merged: true };
|
|
84
|
+
}
|
|
85
|
+
const lesson = {
|
|
86
|
+
id: `lesson-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
|
|
87
|
+
kind: "lesson",
|
|
88
|
+
title: input.title.trim(),
|
|
89
|
+
trigger: input.trigger.trim(),
|
|
90
|
+
problemPattern: input.problemPattern.trim(),
|
|
91
|
+
symptoms: input.symptoms || [],
|
|
92
|
+
failedApproach: input.failedApproach || "",
|
|
93
|
+
betterApproach: input.betterApproach.trim(),
|
|
94
|
+
createdBecause: input.createdBecause.trim(),
|
|
95
|
+
evidence: input.evidence || "",
|
|
96
|
+
tags: input.tags || [],
|
|
97
|
+
fingerprint,
|
|
98
|
+
confidence: this.clampConfidence(input.confidence ?? 0.65),
|
|
99
|
+
uses: 1,
|
|
100
|
+
failures: 0,
|
|
101
|
+
status: "candidate",
|
|
102
|
+
createdAt: now,
|
|
103
|
+
updatedAt: now,
|
|
104
|
+
lastVerified: now,
|
|
105
|
+
};
|
|
106
|
+
lesson.status = this.promotedStatus(lesson);
|
|
107
|
+
lessons.push(lesson);
|
|
108
|
+
await this.writeLessons(root, lessons);
|
|
109
|
+
return { projectRoot: root, lesson, merged: false };
|
|
110
|
+
}
|
|
111
|
+
async recallLessons(query) {
|
|
112
|
+
const root = await this.resolveProjectRoot(query.projectRoot, false);
|
|
113
|
+
const lessons = (await this.readLessons(root)).filter((lesson) => lesson.status !== "retired");
|
|
114
|
+
const terms = this.tokenize([query.intent, query.problem, ...(query.tags || [])].filter(Boolean).join(" "));
|
|
115
|
+
const scored = lessons
|
|
116
|
+
.map((lesson) => ({ lesson, score: this.scoreLesson(lesson, terms) }))
|
|
117
|
+
.filter((item) => terms.length === 0 || item.score > 0)
|
|
118
|
+
.sort((a, b) => b.score - a.score || b.lesson.confidence - a.lesson.confidence || b.lesson.uses - a.lesson.uses)
|
|
119
|
+
.slice(0, Math.max(1, Math.min(query.limit || 8, 25)))
|
|
120
|
+
.map((item) => item.lesson);
|
|
121
|
+
return { projectRoot: root, lessons: scored };
|
|
122
|
+
}
|
|
123
|
+
async recordLessonOutcome(params) {
|
|
124
|
+
const root = await this.resolveProjectRoot(params.projectRoot, false);
|
|
125
|
+
const lessons = await this.readLessons(root);
|
|
126
|
+
const lesson = lessons.find((item) => item.id === params.id);
|
|
127
|
+
if (!lesson)
|
|
128
|
+
throw new Error(`Unknown lesson: ${params.id}`);
|
|
129
|
+
const now = new Date().toISOString();
|
|
130
|
+
if (params.success) {
|
|
131
|
+
lesson.uses += 1;
|
|
132
|
+
lesson.confidence = this.clampConfidence(lesson.confidence + 0.05);
|
|
133
|
+
lesson.lastVerified = now;
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
lesson.failures += 1;
|
|
137
|
+
lesson.confidence = this.clampConfidence(lesson.confidence - 0.12);
|
|
138
|
+
if (lesson.confidence < 0.25)
|
|
139
|
+
lesson.status = "retired";
|
|
140
|
+
}
|
|
141
|
+
lesson.status = this.promotedStatus(lesson);
|
|
142
|
+
lesson.updatedAt = now;
|
|
143
|
+
lesson.evidence = this.mergeText(lesson.evidence, params.evidence);
|
|
144
|
+
await this.writeLessons(root, lessons);
|
|
145
|
+
return { projectRoot: root, lesson };
|
|
146
|
+
}
|
|
147
|
+
async createSkill(input) {
|
|
148
|
+
const root = await this.resolveProjectRoot(input.projectRoot, true);
|
|
149
|
+
await this.configure(root);
|
|
150
|
+
const name = this.normalizeSkillName(input.name);
|
|
151
|
+
const skillDir = path.join(this.aetherDir(root), "skills", name);
|
|
152
|
+
const skillPath = path.join(skillDir, "SKILL.md");
|
|
153
|
+
const existed = await this.exists(skillPath);
|
|
154
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
155
|
+
await fs.writeFile(skillPath, this.renderSkillMarkdown({ ...input, name }), "utf8");
|
|
156
|
+
const registry = await this.readSkillRegistry(root);
|
|
157
|
+
const now = new Date().toISOString();
|
|
158
|
+
const existing = registry[name];
|
|
159
|
+
const skill = {
|
|
160
|
+
name,
|
|
161
|
+
description: input.description.trim(),
|
|
162
|
+
status: "active",
|
|
163
|
+
uses: existing?.uses || 0,
|
|
164
|
+
failures: existing?.failures || 0,
|
|
165
|
+
createdAt: existing?.createdAt || now,
|
|
166
|
+
updatedAt: now,
|
|
167
|
+
lastVerified: existing?.lastVerified,
|
|
168
|
+
maintenance: existing?.maintenance || [],
|
|
169
|
+
};
|
|
170
|
+
registry[name] = skill;
|
|
171
|
+
await this.writeSkillRegistry(root, registry);
|
|
172
|
+
return { projectRoot: root, skillPath, skill, created: !existed };
|
|
173
|
+
}
|
|
174
|
+
async listSkills(projectRoot) {
|
|
175
|
+
const root = await this.resolveProjectRoot(projectRoot, false);
|
|
176
|
+
const registry = await this.readSkillRegistry(root);
|
|
177
|
+
return {
|
|
178
|
+
projectRoot: root,
|
|
179
|
+
skills: Object.values(registry).sort((a, b) => a.name.localeCompare(b.name)),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
async maintainSkill(params) {
|
|
183
|
+
const root = await this.resolveProjectRoot(params.projectRoot, false);
|
|
184
|
+
const name = this.normalizeSkillName(params.name);
|
|
185
|
+
const registry = await this.readSkillRegistry(root);
|
|
186
|
+
const skill = registry[name];
|
|
187
|
+
if (!skill)
|
|
188
|
+
throw new Error(`Unknown skill: ${name}`);
|
|
189
|
+
const now = new Date().toISOString();
|
|
190
|
+
skill.maintenance.push({ action: params.action, reason: params.reason, timestamp: now });
|
|
191
|
+
skill.updatedAt = now;
|
|
192
|
+
if (params.action === "keep") {
|
|
193
|
+
skill.lastVerified = now;
|
|
194
|
+
skill.uses += 1;
|
|
195
|
+
}
|
|
196
|
+
else if (params.action === "patch") {
|
|
197
|
+
if (params.patchBody) {
|
|
198
|
+
const skillPath = path.join(this.aetherDir(root), "skills", name, "SKILL.md");
|
|
199
|
+
await fs.writeFile(skillPath, params.patchBody, "utf8");
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
else if (params.action === "consolidate") {
|
|
203
|
+
const target = params.consolidateInto ? this.normalizeSkillName(params.consolidateInto) : undefined;
|
|
204
|
+
if (!target)
|
|
205
|
+
throw new Error("consolidateInto is required for consolidate");
|
|
206
|
+
skill.status = "retired";
|
|
207
|
+
skill.description = `Consolidated into ${target}. ${skill.description}`;
|
|
208
|
+
}
|
|
209
|
+
else if (params.action === "prune") {
|
|
210
|
+
skill.status = "retired";
|
|
211
|
+
const skillDir = path.join(this.aetherDir(root), "skills", name);
|
|
212
|
+
await fs.rm(skillDir, { recursive: true, force: true });
|
|
213
|
+
registry[name] = skill;
|
|
214
|
+
await this.writeSkillRegistry(root, registry);
|
|
215
|
+
return { projectRoot: root, skill, removed: skillDir };
|
|
216
|
+
}
|
|
217
|
+
registry[name] = skill;
|
|
218
|
+
await this.writeSkillRegistry(root, registry);
|
|
219
|
+
return { projectRoot: root, skill };
|
|
220
|
+
}
|
|
221
|
+
async compact(projectRoot) {
|
|
222
|
+
const root = await this.resolveProjectRoot(projectRoot, false);
|
|
223
|
+
const lessons = await this.readLessons(root);
|
|
224
|
+
await this.writeLessons(root, lessons);
|
|
225
|
+
return {
|
|
226
|
+
projectRoot: root,
|
|
227
|
+
lessonCount: lessons.filter((lesson) => lesson.status !== "retired").length,
|
|
228
|
+
learnedPath: path.join(this.aetherDir(root), "memory", "learned.json"),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
async resolveProjectRoot(projectRoot, createAllowed = false) {
|
|
232
|
+
const base = path.resolve(projectRoot || this.configuredRoot || process.env.AETHER_PROJECT_ROOT || process.cwd());
|
|
233
|
+
const gitRoot = await this.findGitRoot(base);
|
|
234
|
+
const root = gitRoot || base;
|
|
235
|
+
if (!createAllowed && !(await this.exists(this.aetherDir(root)))) {
|
|
236
|
+
throw new Error(`Aether memory is not configured for ${root}. Call configure_aether_memory with the project root first.`);
|
|
237
|
+
}
|
|
238
|
+
return root;
|
|
239
|
+
}
|
|
240
|
+
async findGitRoot(start) {
|
|
241
|
+
let current = start;
|
|
242
|
+
try {
|
|
243
|
+
const stat = await fs.stat(current);
|
|
244
|
+
if (!stat.isDirectory())
|
|
245
|
+
current = path.dirname(current);
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
current = path.dirname(current);
|
|
249
|
+
}
|
|
250
|
+
while (true) {
|
|
251
|
+
if (await this.exists(path.join(current, ".git")))
|
|
252
|
+
return current;
|
|
253
|
+
const parent = path.dirname(current);
|
|
254
|
+
if (parent === current)
|
|
255
|
+
return undefined;
|
|
256
|
+
current = parent;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
aetherDir(root) {
|
|
260
|
+
return path.join(root, ".aether");
|
|
261
|
+
}
|
|
262
|
+
async ensureConfig(root) {
|
|
263
|
+
const configPath = path.join(this.aetherDir(root), "memory-config.json");
|
|
264
|
+
if (await this.exists(configPath)) {
|
|
265
|
+
const parsed = JSON.parse(await fs.readFile(configPath, "utf8"));
|
|
266
|
+
return { ...DEFAULT_CONFIG, ...parsed };
|
|
267
|
+
}
|
|
268
|
+
await fs.writeFile(configPath, `${JSON.stringify(DEFAULT_CONFIG, null, 2)}\n`, "utf8");
|
|
269
|
+
return DEFAULT_CONFIG;
|
|
270
|
+
}
|
|
271
|
+
async ensureGitignore(root) {
|
|
272
|
+
const gitignorePath = path.join(root, ".gitignore");
|
|
273
|
+
const line = ".aether/";
|
|
274
|
+
let content = "";
|
|
275
|
+
if (await this.exists(gitignorePath)) {
|
|
276
|
+
content = await fs.readFile(gitignorePath, "utf8");
|
|
277
|
+
if (content.split(/\r?\n/).some((item) => item.trim() === line))
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const prefix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
281
|
+
await fs.writeFile(gitignorePath, `${content}${prefix}${line}\n`, "utf8");
|
|
282
|
+
}
|
|
283
|
+
async readLessons(root) {
|
|
284
|
+
const file = path.join(this.aetherDir(root), "memory", "lessons.jsonl");
|
|
285
|
+
if (!(await this.exists(file)))
|
|
286
|
+
return [];
|
|
287
|
+
const text = await fs.readFile(file, "utf8");
|
|
288
|
+
return text
|
|
289
|
+
.split(/\r?\n/)
|
|
290
|
+
.map((line) => line.trim())
|
|
291
|
+
.filter(Boolean)
|
|
292
|
+
.map((line) => JSON.parse(line));
|
|
293
|
+
}
|
|
294
|
+
async writeLessons(root, lessons) {
|
|
295
|
+
const config = await this.ensureConfig(root);
|
|
296
|
+
const active = lessons
|
|
297
|
+
.sort((a, b) => b.confidence - a.confidence || b.uses - a.uses || b.updatedAt.localeCompare(a.updatedAt))
|
|
298
|
+
.slice(0, config.maxLessons);
|
|
299
|
+
let lines = active.map((lesson) => JSON.stringify(lesson));
|
|
300
|
+
while (Buffer.byteLength(`${lines.join("\n")}\n`, "utf8") > config.maxLessonBytes && lines.length > 0) {
|
|
301
|
+
lines.pop();
|
|
302
|
+
}
|
|
303
|
+
await fs.mkdir(path.join(this.aetherDir(root), "memory"), { recursive: true });
|
|
304
|
+
await fs.writeFile(path.join(this.aetherDir(root), "memory", "lessons.jsonl"), `${lines.join("\n")}${lines.length ? "\n" : ""}`, "utf8");
|
|
305
|
+
await this.writeLearnedSummary(root, active);
|
|
306
|
+
}
|
|
307
|
+
async writeLearnedSummary(root, lessons) {
|
|
308
|
+
const promoted = lessons
|
|
309
|
+
.filter((lesson) => lesson.status === "promoted" || lesson.status === "verified")
|
|
310
|
+
.sort((a, b) => b.confidence - a.confidence || b.uses - a.uses)
|
|
311
|
+
.map((lesson) => ({
|
|
312
|
+
id: lesson.id,
|
|
313
|
+
title: lesson.title,
|
|
314
|
+
trigger: lesson.trigger,
|
|
315
|
+
problemPattern: lesson.problemPattern,
|
|
316
|
+
betterApproach: lesson.betterApproach,
|
|
317
|
+
confidence: lesson.confidence,
|
|
318
|
+
uses: lesson.uses,
|
|
319
|
+
failures: lesson.failures,
|
|
320
|
+
lastVerified: lesson.lastVerified,
|
|
321
|
+
}));
|
|
322
|
+
await fs.writeFile(path.join(this.aetherDir(root), "memory", "learned.json"), `${JSON.stringify({ updatedAt: new Date().toISOString(), lessons: promoted }, null, 2)}\n`, "utf8");
|
|
323
|
+
}
|
|
324
|
+
async readSkillRegistry(root) {
|
|
325
|
+
const file = path.join(this.aetherDir(root), "skills", "_registry.json");
|
|
326
|
+
if (!(await this.exists(file)))
|
|
327
|
+
return {};
|
|
328
|
+
return JSON.parse(await fs.readFile(file, "utf8"));
|
|
329
|
+
}
|
|
330
|
+
async writeSkillRegistry(root, registry) {
|
|
331
|
+
await fs.mkdir(path.join(this.aetherDir(root), "skills"), { recursive: true });
|
|
332
|
+
await fs.writeFile(path.join(this.aetherDir(root), "skills", "_registry.json"), `${JSON.stringify(registry, null, 2)}\n`, "utf8");
|
|
333
|
+
}
|
|
334
|
+
renderSkillMarkdown(input) {
|
|
335
|
+
const name = this.normalizeSkillName(input.name);
|
|
336
|
+
const description = input.description.trim();
|
|
337
|
+
const procedure = input.procedure.map((step, index) => `${index + 1}. ${step.trim()}`).join("\n");
|
|
338
|
+
const examples = (input.examples || []).map((item) => `- ${item.trim()}`).join("\n");
|
|
339
|
+
const edgeCases = (input.edgeCases || []).map((item) => `- ${item.trim()}`).join("\n");
|
|
340
|
+
const verification = (input.verification || []).map((item) => `- ${item.trim()}`).join("\n");
|
|
341
|
+
return [
|
|
342
|
+
"---",
|
|
343
|
+
`name: ${name}`,
|
|
344
|
+
`description: ${this.yamlString(description)}`,
|
|
345
|
+
"---",
|
|
346
|
+
"",
|
|
347
|
+
`# ${this.toTitle(name)}`,
|
|
348
|
+
"",
|
|
349
|
+
input.trigger.trim(),
|
|
350
|
+
"",
|
|
351
|
+
"## Procedure",
|
|
352
|
+
"",
|
|
353
|
+
procedure,
|
|
354
|
+
"",
|
|
355
|
+
...(verification ? ["## Verify", "", verification, ""] : []),
|
|
356
|
+
...(edgeCases ? ["## Edge Cases", "", edgeCases, ""] : []),
|
|
357
|
+
...(examples ? ["## Examples", "", examples, ""] : []),
|
|
358
|
+
].join("\n");
|
|
359
|
+
}
|
|
360
|
+
normalizeSkillName(name) {
|
|
361
|
+
const normalized = name
|
|
362
|
+
.toLowerCase()
|
|
363
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
364
|
+
.replace(/^-+|-+$/g, "")
|
|
365
|
+
.slice(0, 64)
|
|
366
|
+
.replace(/-+$/g, "");
|
|
367
|
+
if (!normalized || RESERVED_NAMES.has(normalized)) {
|
|
368
|
+
throw new Error(`Invalid skill name: ${name}`);
|
|
369
|
+
}
|
|
370
|
+
return normalized;
|
|
371
|
+
}
|
|
372
|
+
toTitle(name) {
|
|
373
|
+
return name.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
374
|
+
}
|
|
375
|
+
yamlString(value) {
|
|
376
|
+
return JSON.stringify(value);
|
|
377
|
+
}
|
|
378
|
+
promotedStatus(lesson) {
|
|
379
|
+
if (lesson.status === "retired")
|
|
380
|
+
return "retired";
|
|
381
|
+
if (lesson.confidence >= DEFAULT_CONFIG.minConfidenceToPromote && lesson.uses >= 3)
|
|
382
|
+
return "promoted";
|
|
383
|
+
if (lesson.confidence >= 0.72 || lesson.uses >= 2)
|
|
384
|
+
return "verified";
|
|
385
|
+
return "candidate";
|
|
386
|
+
}
|
|
387
|
+
scoreLesson(lesson, terms) {
|
|
388
|
+
if (terms.length === 0)
|
|
389
|
+
return lesson.confidence + lesson.uses * 0.02;
|
|
390
|
+
const haystack = this.tokenize([
|
|
391
|
+
lesson.title,
|
|
392
|
+
lesson.trigger,
|
|
393
|
+
lesson.problemPattern,
|
|
394
|
+
lesson.betterApproach,
|
|
395
|
+
lesson.failedApproach,
|
|
396
|
+
lesson.symptoms.join(" "),
|
|
397
|
+
lesson.tags.join(" "),
|
|
398
|
+
].join(" "));
|
|
399
|
+
const matched = terms.filter((term) => haystack.includes(term)).length;
|
|
400
|
+
return matched + lesson.confidence + Math.min(lesson.uses, 10) * 0.05 - lesson.failures * 0.08;
|
|
401
|
+
}
|
|
402
|
+
tokenize(text) {
|
|
403
|
+
return this.unique(text.toLowerCase().match(/[a-z0-9]+/g) || []);
|
|
404
|
+
}
|
|
405
|
+
fingerprint(parts) {
|
|
406
|
+
return this.tokenize(parts.join(" ")).join("-");
|
|
407
|
+
}
|
|
408
|
+
unique(items) {
|
|
409
|
+
return Array.from(new Set(items.filter(Boolean)));
|
|
410
|
+
}
|
|
411
|
+
clampConfidence(value) {
|
|
412
|
+
return Math.max(0, Math.min(1, Number(value.toFixed(2))));
|
|
413
|
+
}
|
|
414
|
+
mergeText(current, next) {
|
|
415
|
+
if (!next)
|
|
416
|
+
return current || "";
|
|
417
|
+
if (!current)
|
|
418
|
+
return next;
|
|
419
|
+
return current.includes(next) ? current : `${current}\n${next}`;
|
|
420
|
+
}
|
|
421
|
+
async exists(filePath) {
|
|
422
|
+
try {
|
|
423
|
+
await fs.access(filePath);
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
exports.AetherMemoryStore = AetherMemoryStore;
|