memorix 1.0.2 → 1.0.3
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/CHANGELOG.md +17 -0
- package/README.md +7 -6
- package/README.zh-CN.md +8 -7
- package/dist/cli/index.js +753 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +693 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3577,7 +3577,7 @@ async function promoteToMiniSkill(projectDir2, projectId, observations2, options
|
|
|
3577
3577
|
const title = generateTitle(observations2);
|
|
3578
3578
|
const instruction = options?.instruction || generateInstruction(observations2);
|
|
3579
3579
|
const trigger = options?.trigger || generateTrigger(observations2);
|
|
3580
|
-
const facts =
|
|
3580
|
+
const facts = extractFacts2(observations2);
|
|
3581
3581
|
const tags = [
|
|
3582
3582
|
...options?.tags || [],
|
|
3583
3583
|
...extractTags(observations2)
|
|
@@ -3685,7 +3685,7 @@ function generateTrigger(observations2) {
|
|
|
3685
3685
|
}
|
|
3686
3686
|
return parts.length > 0 ? parts.join("; ") : `Related to ${obs.title}`;
|
|
3687
3687
|
}
|
|
3688
|
-
function
|
|
3688
|
+
function extractFacts2(observations2) {
|
|
3689
3689
|
const facts = /* @__PURE__ */ new Set();
|
|
3690
3690
|
for (const obs of observations2) {
|
|
3691
3691
|
for (const f of obs.facts) {
|
|
@@ -5841,6 +5841,10 @@ var KnowledgeGraphManager = class {
|
|
|
5841
5841
|
);
|
|
5842
5842
|
await this.save();
|
|
5843
5843
|
}
|
|
5844
|
+
/** Get all entity names (for Formation Pipeline entity resolution) */
|
|
5845
|
+
getEntityNames() {
|
|
5846
|
+
return this.entities.map((e) => e.name);
|
|
5847
|
+
}
|
|
5844
5848
|
/** Read the entire graph */
|
|
5845
5849
|
async readGraph() {
|
|
5846
5850
|
await this.init();
|
|
@@ -8207,6 +8211,588 @@ var WorkspaceSyncEngine = class _WorkspaceSyncEngine {
|
|
|
8207
8211
|
// src/server.ts
|
|
8208
8212
|
init_provider2();
|
|
8209
8213
|
init_memory_manager();
|
|
8214
|
+
|
|
8215
|
+
// src/memory/formation/index.ts
|
|
8216
|
+
init_esm_shims();
|
|
8217
|
+
|
|
8218
|
+
// src/memory/formation/extract.ts
|
|
8219
|
+
init_esm_shims();
|
|
8220
|
+
var FACT_PATTERNS = [
|
|
8221
|
+
// Key: Value pairs (e.g., "Port: 3000", "Timeout = 60s")
|
|
8222
|
+
{
|
|
8223
|
+
pattern: /\b([A-Z][a-zA-Z_-]{2,30})\s*[:=]\s*([^\n,;]{2,60})/g,
|
|
8224
|
+
format: (m) => `${m[1]}: ${m[2].trim()}`
|
|
8225
|
+
},
|
|
8226
|
+
// Arrow notation (e.g., "MySQL → PostgreSQL", "v1.0 → v2.0")
|
|
8227
|
+
{
|
|
8228
|
+
pattern: /\b(\S{2,30})\s*[→➜\->]+\s*(\S{2,30})/g,
|
|
8229
|
+
format: (m) => `${m[1]} \u2192 ${m[2]}`
|
|
8230
|
+
},
|
|
8231
|
+
// Version numbers (e.g., "v1.2.3", "version 2.0")
|
|
8232
|
+
{
|
|
8233
|
+
pattern: /\b(?:v(?:ersion)?\s*)(\d+\.\d+(?:\.\d+)?(?:-[\w.]+)?)\b/gi,
|
|
8234
|
+
format: (m) => `Version: ${m[1]}`
|
|
8235
|
+
},
|
|
8236
|
+
// Error messages (e.g., "Error: ...", "ERR_...")
|
|
8237
|
+
{
|
|
8238
|
+
pattern: /\b(?:Error|ERR|ENOENT|ECONNREFUSED|TypeError|RangeError|SyntaxError|ReferenceError)[:\s]+([^\n]{5,80})/gi,
|
|
8239
|
+
format: (m) => `Error: ${m[1].trim()}`
|
|
8240
|
+
},
|
|
8241
|
+
// Port numbers in context
|
|
8242
|
+
{
|
|
8243
|
+
pattern: /\b(?:port|PORT)\s*[:=]?\s*(\d{2,5})\b/gi,
|
|
8244
|
+
format: (m) => `Port: ${m[1]}`
|
|
8245
|
+
},
|
|
8246
|
+
// Environment variables
|
|
8247
|
+
{
|
|
8248
|
+
pattern: /\b([A-Z][A-Z0-9_]{3,30})\s*=\s*(\S{1,60})/g,
|
|
8249
|
+
format: (m) => `${m[1]}=${m[2]}`
|
|
8250
|
+
},
|
|
8251
|
+
// npm/package versions (e.g., "react@18.2.0")
|
|
8252
|
+
{
|
|
8253
|
+
pattern: /\b([@a-z][\w./-]+)@(\d+\.\d+\.\d+(?:-[\w.]+)?)\b/g,
|
|
8254
|
+
format: (m) => `${m[1]}@${m[2]}`
|
|
8255
|
+
}
|
|
8256
|
+
];
|
|
8257
|
+
var GENERIC_TITLE_PATTERNS = [
|
|
8258
|
+
/^Updated \S+\.\w+$/i,
|
|
8259
|
+
/^Created \S+\.\w+$/i,
|
|
8260
|
+
/^Deleted \S+\.\w+$/i,
|
|
8261
|
+
/^Modified \S+\.\w+$/i,
|
|
8262
|
+
/^Changed \S+\.\w+$/i,
|
|
8263
|
+
/^Session activity/i,
|
|
8264
|
+
/^Activity \(/i,
|
|
8265
|
+
/^Used \w+$/i,
|
|
8266
|
+
/^Ran: /i
|
|
8267
|
+
];
|
|
8268
|
+
var TYPE_SIGNALS = [
|
|
8269
|
+
{
|
|
8270
|
+
type: "problem-solution",
|
|
8271
|
+
patterns: [
|
|
8272
|
+
/\b(fix|fixed|bug|error|issue|crash|broken|resolved|workaround|patch)\b/i,
|
|
8273
|
+
/\b(修复|修正|解决|报错|崩溃|异常)\b/
|
|
8274
|
+
]
|
|
8275
|
+
},
|
|
8276
|
+
{
|
|
8277
|
+
type: "gotcha",
|
|
8278
|
+
patterns: [
|
|
8279
|
+
/\b(gotcha|pitfall|trap|careful|warning|caveat|footgun|unexpected|beware)\b/i,
|
|
8280
|
+
/\b(坑|陷阱|注意|小心|踩坑)\b/
|
|
8281
|
+
]
|
|
8282
|
+
},
|
|
8283
|
+
{
|
|
8284
|
+
type: "decision",
|
|
8285
|
+
patterns: [
|
|
8286
|
+
/\b(decided|chose|chosen|selected|adopted|rejected|evaluated|compared)\b/i,
|
|
8287
|
+
/\b(决定|选择|采用|弃用|对比|评估)\b/
|
|
8288
|
+
]
|
|
8289
|
+
},
|
|
8290
|
+
{
|
|
8291
|
+
type: "what-changed",
|
|
8292
|
+
patterns: [
|
|
8293
|
+
/\b(changed|migrated|upgraded|refactored|replaced|renamed|moved|removed|added)\b/i,
|
|
8294
|
+
/\b(改|迁移|升级|重构|替换|重命名|删除|新增)\b/
|
|
8295
|
+
]
|
|
8296
|
+
},
|
|
8297
|
+
{
|
|
8298
|
+
type: "how-it-works",
|
|
8299
|
+
patterns: [
|
|
8300
|
+
/\b(works by|architecture|mechanism|pipeline|flow|under the hood|internally)\b/i,
|
|
8301
|
+
/\b(原理|机制|流程|架构|内部)\b/
|
|
8302
|
+
]
|
|
8303
|
+
},
|
|
8304
|
+
{
|
|
8305
|
+
type: "trade-off",
|
|
8306
|
+
patterns: [
|
|
8307
|
+
/\b(trade.?off|compromise|downside|cost|benefit|pro|con|versus|vs)\b/i,
|
|
8308
|
+
/\b(权衡|折中|代价|收益|优缺点)\b/
|
|
8309
|
+
]
|
|
8310
|
+
}
|
|
8311
|
+
];
|
|
8312
|
+
function extractFacts(narrative, existingFacts) {
|
|
8313
|
+
const existingLower = new Set(existingFacts.map((f) => f.toLowerCase().trim()));
|
|
8314
|
+
const extracted = [];
|
|
8315
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8316
|
+
for (const { pattern, format } of FACT_PATTERNS) {
|
|
8317
|
+
pattern.lastIndex = 0;
|
|
8318
|
+
let match;
|
|
8319
|
+
while ((match = pattern.exec(narrative)) !== null) {
|
|
8320
|
+
const fact = format(match);
|
|
8321
|
+
const normalized = fact.toLowerCase().trim();
|
|
8322
|
+
if (existingLower.has(normalized) || seen.has(normalized)) continue;
|
|
8323
|
+
if (fact.length < 5 || fact.length > 120) continue;
|
|
8324
|
+
seen.add(normalized);
|
|
8325
|
+
extracted.push(fact);
|
|
8326
|
+
}
|
|
8327
|
+
}
|
|
8328
|
+
return extracted.slice(0, 10);
|
|
8329
|
+
}
|
|
8330
|
+
function improveTitle(title, narrative) {
|
|
8331
|
+
const isGeneric = GENERIC_TITLE_PATTERNS.some((p) => p.test(title));
|
|
8332
|
+
if (!isGeneric) return { title, improved: false };
|
|
8333
|
+
const sentences = narrative.replace(/```[\s\S]*?```/g, "").split(/[.。!!?\n]/).map((s) => s.trim()).filter((s) => s.length >= 15);
|
|
8334
|
+
if (sentences.length > 0) {
|
|
8335
|
+
return { title: sentences[0].slice(0, 60), improved: true };
|
|
8336
|
+
}
|
|
8337
|
+
return { title, improved: false };
|
|
8338
|
+
}
|
|
8339
|
+
function resolveEntity(entityName, existingEntities) {
|
|
8340
|
+
if (existingEntities.length === 0) return { entityName, resolved: false };
|
|
8341
|
+
const lower = entityName.toLowerCase().replace(/[-_]/g, "");
|
|
8342
|
+
for (const existing of existingEntities) {
|
|
8343
|
+
const existingLower = existing.toLowerCase().replace(/[-_]/g, "");
|
|
8344
|
+
if (lower === existingLower) {
|
|
8345
|
+
return { entityName: existing, resolved: existing !== entityName };
|
|
8346
|
+
}
|
|
8347
|
+
if (lower.length >= 3 && existingLower.length >= 3) {
|
|
8348
|
+
if (existingLower.includes(lower) || lower.includes(existingLower)) {
|
|
8349
|
+
const canonical = existing.length >= entityName.length ? existing : entityName;
|
|
8350
|
+
return { entityName: canonical, resolved: canonical !== entityName };
|
|
8351
|
+
}
|
|
8352
|
+
}
|
|
8353
|
+
}
|
|
8354
|
+
return { entityName, resolved: false };
|
|
8355
|
+
}
|
|
8356
|
+
function verifyType(declaredType, narrative, title) {
|
|
8357
|
+
const content = `${title} ${narrative}`;
|
|
8358
|
+
const scores = [];
|
|
8359
|
+
for (const { type, patterns } of TYPE_SIGNALS) {
|
|
8360
|
+
let score = 0;
|
|
8361
|
+
for (const p of patterns) {
|
|
8362
|
+
const regex = new RegExp(p.source, p.flags.includes("g") ? p.flags : p.flags + "g");
|
|
8363
|
+
const matches = [...content.matchAll(regex)];
|
|
8364
|
+
score += matches.length;
|
|
8365
|
+
}
|
|
8366
|
+
if (score > 0) scores.push({ type, score });
|
|
8367
|
+
}
|
|
8368
|
+
if (scores.length === 0) return { type: declaredType, corrected: false };
|
|
8369
|
+
scores.sort((a, b) => b.score - a.score);
|
|
8370
|
+
const best = scores[0];
|
|
8371
|
+
if (best.type !== declaredType && best.score >= 2) {
|
|
8372
|
+
const declaredScore = scores.find((s) => s.type === declaredType)?.score ?? 0;
|
|
8373
|
+
if (declaredScore === 0) {
|
|
8374
|
+
return { type: best.type, corrected: true };
|
|
8375
|
+
}
|
|
8376
|
+
}
|
|
8377
|
+
return { type: declaredType, corrected: false };
|
|
8378
|
+
}
|
|
8379
|
+
function runExtract(input, existingEntities) {
|
|
8380
|
+
const callerFacts = input.facts ?? [];
|
|
8381
|
+
const extractedFacts = extractFacts(input.narrative, callerFacts);
|
|
8382
|
+
const allFacts = [...callerFacts, ...extractedFacts];
|
|
8383
|
+
const { title, improved: titleImproved } = improveTitle(input.title, input.narrative);
|
|
8384
|
+
const { entityName, resolved: entityResolved } = resolveEntity(
|
|
8385
|
+
input.entityName,
|
|
8386
|
+
existingEntities
|
|
8387
|
+
);
|
|
8388
|
+
const { type, corrected: typeCorrected } = verifyType(
|
|
8389
|
+
input.type,
|
|
8390
|
+
input.narrative,
|
|
8391
|
+
input.title
|
|
8392
|
+
);
|
|
8393
|
+
return {
|
|
8394
|
+
title,
|
|
8395
|
+
titleImproved,
|
|
8396
|
+
narrative: input.narrative,
|
|
8397
|
+
facts: allFacts,
|
|
8398
|
+
extractedFacts,
|
|
8399
|
+
entityName,
|
|
8400
|
+
entityResolved,
|
|
8401
|
+
type,
|
|
8402
|
+
typeCorrected
|
|
8403
|
+
};
|
|
8404
|
+
}
|
|
8405
|
+
|
|
8406
|
+
// src/memory/formation/resolve.ts
|
|
8407
|
+
init_esm_shims();
|
|
8408
|
+
var SIMILARITY_HIGH2 = 0.75;
|
|
8409
|
+
var SIMILARITY_MEDIUM2 = 0.5;
|
|
8410
|
+
var SIMILARITY_DUPLICATE = 0.9;
|
|
8411
|
+
function wordOverlap(a, b) {
|
|
8412
|
+
const wordsA = new Set(a.toLowerCase().split(/\s+/).filter((w) => w.length > 2));
|
|
8413
|
+
const wordsB = new Set(b.toLowerCase().split(/\s+/).filter((w) => w.length > 2));
|
|
8414
|
+
if (wordsA.size === 0 || wordsB.size === 0) return 0;
|
|
8415
|
+
let intersection = 0;
|
|
8416
|
+
for (const w of wordsA) {
|
|
8417
|
+
if (wordsB.has(w)) intersection++;
|
|
8418
|
+
}
|
|
8419
|
+
return intersection / Math.max(wordsA.size, wordsB.size);
|
|
8420
|
+
}
|
|
8421
|
+
function entitiesMatch(a, b) {
|
|
8422
|
+
const na = a.toLowerCase().replace(/[-_]/g, "");
|
|
8423
|
+
const nb = b.toLowerCase().replace(/[-_]/g, "");
|
|
8424
|
+
if (na === nb) return true;
|
|
8425
|
+
if (na.length >= 3 && nb.length >= 3) {
|
|
8426
|
+
if (na.includes(nb) || nb.includes(na)) return true;
|
|
8427
|
+
}
|
|
8428
|
+
return false;
|
|
8429
|
+
}
|
|
8430
|
+
function hasContradiction(oldText, newText) {
|
|
8431
|
+
const negationPatterns = [
|
|
8432
|
+
/\bnot\s+(\w+)/gi,
|
|
8433
|
+
/\bno longer\b/i,
|
|
8434
|
+
/\binstead of\b/i,
|
|
8435
|
+
/\breplaced\b.*\bwith\b/i,
|
|
8436
|
+
/\bremoved\b/i,
|
|
8437
|
+
/\bdeprecated\b/i,
|
|
8438
|
+
/\bobsolete\b/i,
|
|
8439
|
+
/不再/,
|
|
8440
|
+
/已弃用/,
|
|
8441
|
+
/替换为/,
|
|
8442
|
+
/改为/
|
|
8443
|
+
];
|
|
8444
|
+
return negationPatterns.some((p) => p.test(newText));
|
|
8445
|
+
}
|
|
8446
|
+
function mergeNarratives(oldNarrative, newNarrative) {
|
|
8447
|
+
if (newNarrative.length > oldNarrative.length * 1.5) return newNarrative;
|
|
8448
|
+
if (oldNarrative.length > newNarrative.length * 1.5) return oldNarrative;
|
|
8449
|
+
return `${newNarrative}
|
|
8450
|
+
|
|
8451
|
+
[Previous context]: ${oldNarrative}`;
|
|
8452
|
+
}
|
|
8453
|
+
function mergeFacts2(oldFacts, newFacts) {
|
|
8454
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8455
|
+
const merged = [];
|
|
8456
|
+
for (const f of newFacts) {
|
|
8457
|
+
const norm = f.toLowerCase().trim();
|
|
8458
|
+
if (!seen.has(norm) && f.trim().length > 0) {
|
|
8459
|
+
seen.add(norm);
|
|
8460
|
+
merged.push(f);
|
|
8461
|
+
}
|
|
8462
|
+
}
|
|
8463
|
+
for (const f of oldFacts) {
|
|
8464
|
+
const norm = f.toLowerCase().trim();
|
|
8465
|
+
if (!seen.has(norm) && f.trim().length > 0) {
|
|
8466
|
+
seen.add(norm);
|
|
8467
|
+
merged.push(f);
|
|
8468
|
+
}
|
|
8469
|
+
}
|
|
8470
|
+
return merged;
|
|
8471
|
+
}
|
|
8472
|
+
function scoreCandidate(extracted, candidate) {
|
|
8473
|
+
const entityMatch = entitiesMatch(extracted.entityName, candidate.entityName);
|
|
8474
|
+
const contentOverlap = wordOverlap(
|
|
8475
|
+
`${extracted.title} ${extracted.narrative}`,
|
|
8476
|
+
`${candidate.title} ${candidate.narrative}`
|
|
8477
|
+
);
|
|
8478
|
+
const score = candidate.score * 0.6 + (entityMatch ? 0.2 : 0) + contentOverlap * 0.2;
|
|
8479
|
+
const newLength = extracted.narrative.length + extracted.facts.join(" ").length;
|
|
8480
|
+
const oldLength = candidate.narrative.length + candidate.facts.length;
|
|
8481
|
+
const richer = newLength > oldLength * 1.15;
|
|
8482
|
+
const contradiction = hasContradiction(candidate.narrative, extracted.narrative);
|
|
8483
|
+
return { score, entityMatch, richer, contradiction };
|
|
8484
|
+
}
|
|
8485
|
+
async function runResolve(extracted, projectId, searchMemories, getObservation2) {
|
|
8486
|
+
const query = `${extracted.title} ${extracted.narrative.substring(0, 200)}`;
|
|
8487
|
+
let hits;
|
|
8488
|
+
try {
|
|
8489
|
+
hits = await searchMemories(query, 5, projectId);
|
|
8490
|
+
} catch {
|
|
8491
|
+
return { action: "new", reason: "Search unavailable, defaulting to new" };
|
|
8492
|
+
}
|
|
8493
|
+
if (hits.length === 0) {
|
|
8494
|
+
return { action: "new", reason: "No similar existing memories found" };
|
|
8495
|
+
}
|
|
8496
|
+
const scored = hits.map((hit) => ({
|
|
8497
|
+
hit,
|
|
8498
|
+
...scoreCandidate(extracted, hit)
|
|
8499
|
+
}));
|
|
8500
|
+
scored.sort((a, b) => b.score - a.score);
|
|
8501
|
+
const best = scored[0];
|
|
8502
|
+
if (best.hit.score >= SIMILARITY_DUPLICATE) {
|
|
8503
|
+
if (best.richer) {
|
|
8504
|
+
const existing = getObservation2(best.hit.observationId);
|
|
8505
|
+
const oldFacts = existing?.facts ?? best.hit.facts.split("\n").filter(Boolean);
|
|
8506
|
+
return {
|
|
8507
|
+
action: "evolve",
|
|
8508
|
+
targetId: best.hit.observationId,
|
|
8509
|
+
reason: `Near-duplicate of #${best.hit.observationId} but richer content (score: ${best.score.toFixed(2)})`,
|
|
8510
|
+
mergedNarrative: mergeNarratives(best.hit.narrative, extracted.narrative),
|
|
8511
|
+
mergedFacts: mergeFacts2(oldFacts, extracted.facts)
|
|
8512
|
+
};
|
|
8513
|
+
}
|
|
8514
|
+
return {
|
|
8515
|
+
action: "discard",
|
|
8516
|
+
targetId: best.hit.observationId,
|
|
8517
|
+
reason: `Duplicate of #${best.hit.observationId} (score: ${best.score.toFixed(2)})`
|
|
8518
|
+
};
|
|
8519
|
+
}
|
|
8520
|
+
if (best.score >= SIMILARITY_HIGH2) {
|
|
8521
|
+
if (best.contradiction) {
|
|
8522
|
+
const existing = getObservation2(best.hit.observationId);
|
|
8523
|
+
const oldFacts = existing?.facts ?? best.hit.facts.split("\n").filter(Boolean);
|
|
8524
|
+
return {
|
|
8525
|
+
action: "evolve",
|
|
8526
|
+
targetId: best.hit.observationId,
|
|
8527
|
+
reason: `Supersedes #${best.hit.observationId}: contradiction detected (score: ${best.score.toFixed(2)})`,
|
|
8528
|
+
mergedNarrative: extracted.narrative,
|
|
8529
|
+
mergedFacts: mergeFacts2(oldFacts, extracted.facts)
|
|
8530
|
+
};
|
|
8531
|
+
}
|
|
8532
|
+
if (best.richer) {
|
|
8533
|
+
const existing = getObservation2(best.hit.observationId);
|
|
8534
|
+
const oldFacts = existing?.facts ?? best.hit.facts.split("\n").filter(Boolean);
|
|
8535
|
+
return {
|
|
8536
|
+
action: "merge",
|
|
8537
|
+
targetId: best.hit.observationId,
|
|
8538
|
+
reason: `Merging with #${best.hit.observationId}: same topic, new content is richer (score: ${best.score.toFixed(2)})`,
|
|
8539
|
+
mergedNarrative: mergeNarratives(best.hit.narrative, extracted.narrative),
|
|
8540
|
+
mergedFacts: mergeFacts2(oldFacts, extracted.facts)
|
|
8541
|
+
};
|
|
8542
|
+
}
|
|
8543
|
+
return {
|
|
8544
|
+
action: "discard",
|
|
8545
|
+
targetId: best.hit.observationId,
|
|
8546
|
+
reason: `Already covered by #${best.hit.observationId} (score: ${best.score.toFixed(2)})`
|
|
8547
|
+
};
|
|
8548
|
+
}
|
|
8549
|
+
if (best.score >= SIMILARITY_MEDIUM2 && best.entityMatch) {
|
|
8550
|
+
const existing = getObservation2(best.hit.observationId);
|
|
8551
|
+
const oldFacts = existing?.facts ?? best.hit.facts.split("\n").filter(Boolean);
|
|
8552
|
+
const newFactCount = extracted.facts.length;
|
|
8553
|
+
const oldFactCount = oldFacts.length;
|
|
8554
|
+
if (newFactCount > oldFactCount) {
|
|
8555
|
+
return {
|
|
8556
|
+
action: "merge",
|
|
8557
|
+
targetId: best.hit.observationId,
|
|
8558
|
+
reason: `Same entity "${extracted.entityName}", new memory has more facts (${newFactCount} > ${oldFactCount})`,
|
|
8559
|
+
mergedNarrative: mergeNarratives(best.hit.narrative, extracted.narrative),
|
|
8560
|
+
mergedFacts: mergeFacts2(oldFacts, extracted.facts)
|
|
8561
|
+
};
|
|
8562
|
+
}
|
|
8563
|
+
}
|
|
8564
|
+
return { action: "new", reason: `Different from existing memories (best score: ${best.score.toFixed(2)})` };
|
|
8565
|
+
}
|
|
8566
|
+
|
|
8567
|
+
// src/memory/formation/evaluate.ts
|
|
8568
|
+
init_esm_shims();
|
|
8569
|
+
var TYPE_WEIGHTS = {
|
|
8570
|
+
"gotcha": 0.85,
|
|
8571
|
+
"decision": 0.8,
|
|
8572
|
+
"problem-solution": 0.75,
|
|
8573
|
+
"trade-off": 0.7,
|
|
8574
|
+
"why-it-exists": 0.65,
|
|
8575
|
+
"how-it-works": 0.6,
|
|
8576
|
+
"discovery": 0.55,
|
|
8577
|
+
"what-changed": 0.45,
|
|
8578
|
+
"session-request": 0.4
|
|
8579
|
+
};
|
|
8580
|
+
var SPECIFICITY_PATTERNS = [
|
|
8581
|
+
/\b\d+\.\d+\.\d+\b/,
|
|
8582
|
+
// Semantic version numbers
|
|
8583
|
+
/\b(ERR_|ENOENT|ECONNREFUSED|E[A-Z]{3,})\b/,
|
|
8584
|
+
// Error codes
|
|
8585
|
+
/\b(port|PORT)\s*[:=]?\s*\d{2,5}\b/i,
|
|
8586
|
+
// Port numbers
|
|
8587
|
+
/\bhttps?:\/\/\S+/,
|
|
8588
|
+
// URLs
|
|
8589
|
+
/`[^`]{3,60}`/,
|
|
8590
|
+
// Inline code references
|
|
8591
|
+
/\b[A-Z][A-Z0-9_]{3,}\b/,
|
|
8592
|
+
// Constants (e.g., MAX_RETRIES)
|
|
8593
|
+
/\b\d+\s*(ms|s|sec|min|MB|GB|KB)\b/i
|
|
8594
|
+
// Measurements with units
|
|
8595
|
+
];
|
|
8596
|
+
var CAUSAL_PATTERNS = [
|
|
8597
|
+
/\b(because|therefore|due to|caused by|as a result|fixed by|resolved by)\b/i,
|
|
8598
|
+
/\b(so that|in order to|leads to|results in|prevents)\b/i,
|
|
8599
|
+
/(?:因为|所以|由于|导致|造成|因此|为了|解决)/
|
|
8600
|
+
];
|
|
8601
|
+
var NOISE_PATTERNS = [
|
|
8602
|
+
/^Session activity/i,
|
|
8603
|
+
/^Updated \S+\.\w+$/i,
|
|
8604
|
+
/^Created \S+\.\w+$/i,
|
|
8605
|
+
/^Deleted \S+\.\w+$/i,
|
|
8606
|
+
/^File written successfully/i,
|
|
8607
|
+
/^Command executed/i,
|
|
8608
|
+
/^Tool: (read_file|list_dir|find_by_name)/i,
|
|
8609
|
+
/^\s*$/
|
|
8610
|
+
];
|
|
8611
|
+
var TOOL_OUTPUT_PATTERNS = [
|
|
8612
|
+
/^(file|directory|folder)\s+(created|deleted|moved|copied)/i,
|
|
8613
|
+
/^Successfully\s+(installed|updated|removed)/i,
|
|
8614
|
+
/^\d+ files? changed/i,
|
|
8615
|
+
/^npm (WARN|notice)/i,
|
|
8616
|
+
/^\s*at\s+\S+\s+\(/
|
|
8617
|
+
// Stack trace lines
|
|
8618
|
+
];
|
|
8619
|
+
function factDensity(facts, narrativeLength) {
|
|
8620
|
+
if (narrativeLength === 0) return 0;
|
|
8621
|
+
const structuredChars = facts.reduce((sum, f) => sum + f.length, 0);
|
|
8622
|
+
return Math.min(1, structuredChars / Math.max(narrativeLength, 100));
|
|
8623
|
+
}
|
|
8624
|
+
function specificityScore(content) {
|
|
8625
|
+
let count2 = 0;
|
|
8626
|
+
for (const p of SPECIFICITY_PATTERNS) {
|
|
8627
|
+
p.lastIndex = 0;
|
|
8628
|
+
if (p.test(content)) count2++;
|
|
8629
|
+
}
|
|
8630
|
+
return Math.min(1, count2 / 3);
|
|
8631
|
+
}
|
|
8632
|
+
function causalScore(content) {
|
|
8633
|
+
let count2 = 0;
|
|
8634
|
+
for (const p of CAUSAL_PATTERNS) {
|
|
8635
|
+
p.lastIndex = 0;
|
|
8636
|
+
if (p.test(content)) count2++;
|
|
8637
|
+
}
|
|
8638
|
+
return Math.min(1, count2 / 2);
|
|
8639
|
+
}
|
|
8640
|
+
function noiseScore(title, narrative) {
|
|
8641
|
+
let noisiness = 0;
|
|
8642
|
+
for (const p of NOISE_PATTERNS) {
|
|
8643
|
+
if (p.test(title)) {
|
|
8644
|
+
noisiness += 0.3;
|
|
8645
|
+
break;
|
|
8646
|
+
}
|
|
8647
|
+
}
|
|
8648
|
+
const lines = narrative.split("\n").filter((l) => l.trim().length > 0);
|
|
8649
|
+
let toolOutputLines = 0;
|
|
8650
|
+
for (const line of lines) {
|
|
8651
|
+
for (const p of TOOL_OUTPUT_PATTERNS) {
|
|
8652
|
+
if (p.test(line)) {
|
|
8653
|
+
toolOutputLines++;
|
|
8654
|
+
break;
|
|
8655
|
+
}
|
|
8656
|
+
}
|
|
8657
|
+
}
|
|
8658
|
+
if (lines.length > 0) {
|
|
8659
|
+
noisiness += toolOutputLines / lines.length * 0.5;
|
|
8660
|
+
}
|
|
8661
|
+
if (narrative.length < 50) noisiness += 0.2;
|
|
8662
|
+
return Math.min(1, noisiness);
|
|
8663
|
+
}
|
|
8664
|
+
function categorize(score) {
|
|
8665
|
+
if (score >= 0.6) return "core";
|
|
8666
|
+
if (score >= 0.35) return "contextual";
|
|
8667
|
+
return "ephemeral";
|
|
8668
|
+
}
|
|
8669
|
+
function buildReason(typeWeight, factDens, specificity, causal, noise, category) {
|
|
8670
|
+
const parts = [];
|
|
8671
|
+
if (typeWeight >= 0.7) parts.push("high-value type");
|
|
8672
|
+
else if (typeWeight <= 0.45) parts.push("low-value type");
|
|
8673
|
+
if (factDens > 0.3) parts.push("fact-dense");
|
|
8674
|
+
if (specificity > 0.3) parts.push("specific (versions/codes/paths)");
|
|
8675
|
+
if (causal > 0.3) parts.push("causal reasoning");
|
|
8676
|
+
if (noise > 0.3) parts.push("noisy content");
|
|
8677
|
+
const detail = parts.length > 0 ? parts.join(", ") : "average content";
|
|
8678
|
+
return `${category}: ${detail}`;
|
|
8679
|
+
}
|
|
8680
|
+
function runEvaluate(extracted) {
|
|
8681
|
+
const content = `${extracted.title} ${extracted.narrative} ${extracted.facts.join(" ")}`;
|
|
8682
|
+
const typeWeight = TYPE_WEIGHTS[extracted.type] ?? 0.5;
|
|
8683
|
+
const factDens = factDensity(extracted.facts, extracted.narrative.length);
|
|
8684
|
+
const specificity = specificityScore(content);
|
|
8685
|
+
const causal = causalScore(content);
|
|
8686
|
+
const noise = noiseScore(extracted.title, extracted.narrative);
|
|
8687
|
+
const rawScore = typeWeight * 0.5 + factDens * 0.12 + specificity * 0.12 + causal * 0.12 - noise * 0.14;
|
|
8688
|
+
const extractionBonus = extracted.extractedFacts.length > 0 ? 0.05 : 0;
|
|
8689
|
+
const titlePenalty = extracted.titleImproved ? -0.03 : 0;
|
|
8690
|
+
const correctionBonus = extracted.typeCorrected ? 0.03 : 0;
|
|
8691
|
+
const score = Math.max(0, Math.min(1, rawScore + extractionBonus + titlePenalty + correctionBonus));
|
|
8692
|
+
const category = categorize(score);
|
|
8693
|
+
const reason = buildReason(typeWeight, factDens, specificity, causal, noise, category);
|
|
8694
|
+
return { score, category, reason };
|
|
8695
|
+
}
|
|
8696
|
+
|
|
8697
|
+
// src/memory/formation/index.ts
|
|
8698
|
+
var metricsBuffer = [];
|
|
8699
|
+
var MAX_METRICS_BUFFER = 500;
|
|
8700
|
+
function getMetricsSummary() {
|
|
8701
|
+
const total = metricsBuffer.length;
|
|
8702
|
+
if (total === 0) {
|
|
8703
|
+
return {
|
|
8704
|
+
total: 0,
|
|
8705
|
+
avgValueScore: 0,
|
|
8706
|
+
avgExtractedFacts: 0,
|
|
8707
|
+
titleImprovedRate: 0,
|
|
8708
|
+
entityResolvedRate: 0,
|
|
8709
|
+
typeCorectedRate: 0,
|
|
8710
|
+
resolutionBreakdown: {},
|
|
8711
|
+
categoryBreakdown: {},
|
|
8712
|
+
avgDurationMs: 0
|
|
8713
|
+
};
|
|
8714
|
+
}
|
|
8715
|
+
const sum = (fn) => metricsBuffer.reduce((s, m) => s + fn(m), 0);
|
|
8716
|
+
const resolutionBreakdown = {};
|
|
8717
|
+
const categoryBreakdown = {};
|
|
8718
|
+
for (const m of metricsBuffer) {
|
|
8719
|
+
resolutionBreakdown[m.resolutionAction] = (resolutionBreakdown[m.resolutionAction] ?? 0) + 1;
|
|
8720
|
+
categoryBreakdown[m.valueCategory] = (categoryBreakdown[m.valueCategory] ?? 0) + 1;
|
|
8721
|
+
}
|
|
8722
|
+
return {
|
|
8723
|
+
total,
|
|
8724
|
+
avgValueScore: sum((m) => m.valueScore) / total,
|
|
8725
|
+
avgExtractedFacts: sum((m) => m.systemExtractedFacts) / total,
|
|
8726
|
+
titleImprovedRate: sum((m) => m.titleImproved ? 1 : 0) / total,
|
|
8727
|
+
entityResolvedRate: sum((m) => m.entityResolved ? 1 : 0) / total,
|
|
8728
|
+
typeCorectedRate: sum((m) => m.typeCorrected ? 1 : 0) / total,
|
|
8729
|
+
resolutionBreakdown,
|
|
8730
|
+
categoryBreakdown,
|
|
8731
|
+
avgDurationMs: sum((m) => m.durationMs) / total
|
|
8732
|
+
};
|
|
8733
|
+
}
|
|
8734
|
+
async function runFormation(input, config) {
|
|
8735
|
+
const startTime = Date.now();
|
|
8736
|
+
let stagesCompleted = 0;
|
|
8737
|
+
const existingEntities = config.getEntityNames();
|
|
8738
|
+
const extraction = runExtract(input, existingEntities);
|
|
8739
|
+
stagesCompleted = 1;
|
|
8740
|
+
let resolution;
|
|
8741
|
+
if (input.topicKey) {
|
|
8742
|
+
resolution = {
|
|
8743
|
+
action: "new",
|
|
8744
|
+
reason: "TopicKey upsert \u2014 bypasses resolve stage"
|
|
8745
|
+
};
|
|
8746
|
+
} else {
|
|
8747
|
+
resolution = await runResolve(
|
|
8748
|
+
extraction,
|
|
8749
|
+
input.projectId,
|
|
8750
|
+
config.searchMemories,
|
|
8751
|
+
config.getObservation
|
|
8752
|
+
);
|
|
8753
|
+
}
|
|
8754
|
+
stagesCompleted = 2;
|
|
8755
|
+
const evaluation = runEvaluate(extraction);
|
|
8756
|
+
stagesCompleted = 3;
|
|
8757
|
+
const durationMs = Date.now() - startTime;
|
|
8758
|
+
const formed = {
|
|
8759
|
+
// Final enriched data
|
|
8760
|
+
entityName: extraction.entityName,
|
|
8761
|
+
type: extraction.type,
|
|
8762
|
+
title: extraction.title,
|
|
8763
|
+
narrative: resolution.mergedNarrative ?? extraction.narrative,
|
|
8764
|
+
facts: resolution.mergedFacts ?? extraction.facts,
|
|
8765
|
+
// Stage results
|
|
8766
|
+
extraction,
|
|
8767
|
+
resolution,
|
|
8768
|
+
evaluation,
|
|
8769
|
+
// Pipeline metadata
|
|
8770
|
+
pipeline: {
|
|
8771
|
+
mode: "rules",
|
|
8772
|
+
durationMs,
|
|
8773
|
+
stagesCompleted,
|
|
8774
|
+
shadow: config.shadow
|
|
8775
|
+
}
|
|
8776
|
+
};
|
|
8777
|
+
const metrics = {
|
|
8778
|
+
systemExtractedFacts: extraction.extractedFacts.length,
|
|
8779
|
+
titleImproved: extraction.titleImproved,
|
|
8780
|
+
entityResolved: extraction.entityResolved,
|
|
8781
|
+
typeCorrected: extraction.typeCorrected,
|
|
8782
|
+
resolutionAction: resolution.action,
|
|
8783
|
+
valueScore: evaluation.score,
|
|
8784
|
+
valueCategory: evaluation.category,
|
|
8785
|
+
durationMs,
|
|
8786
|
+
mode: "rules"
|
|
8787
|
+
};
|
|
8788
|
+
if (metricsBuffer.length >= MAX_METRICS_BUFFER) {
|
|
8789
|
+
metricsBuffer.shift();
|
|
8790
|
+
}
|
|
8791
|
+
metricsBuffer.push(metrics);
|
|
8792
|
+
return formed;
|
|
8793
|
+
}
|
|
8794
|
+
|
|
8795
|
+
// src/server.ts
|
|
8210
8796
|
var lastInternalWriteMs = 0;
|
|
8211
8797
|
var markInternalWrite = () => {
|
|
8212
8798
|
lastInternalWriteMs = Date.now();
|
|
@@ -8338,7 +8924,7 @@ async function createMemorixServer(cwd, existingServer, sharedTeam) {
|
|
|
8338
8924
|
let syncAdvisory = null;
|
|
8339
8925
|
const server = existingServer ?? new McpServer({
|
|
8340
8926
|
name: "memorix",
|
|
8341
|
-
version: true ? "1.0.
|
|
8927
|
+
version: true ? "1.0.3" : "1.0.1"
|
|
8342
8928
|
});
|
|
8343
8929
|
server.registerTool(
|
|
8344
8930
|
"memorix_store",
|
|
@@ -8494,12 +9080,68 @@ Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
|
|
|
8494
9080
|
const enrichment = enrichmentParts.length > 0 ? `
|
|
8495
9081
|
Auto-enriched: ${enrichmentParts.join(", ")}` : "";
|
|
8496
9082
|
const action = upserted ? "\u{1F504} Updated" : "\u2705 Stored";
|
|
9083
|
+
let formationNote = "";
|
|
9084
|
+
try {
|
|
9085
|
+
const formationConfig = {
|
|
9086
|
+
shadow: true,
|
|
9087
|
+
useLLM: false,
|
|
9088
|
+
minValueScore: 0.3,
|
|
9089
|
+
searchMemories: async (q, limit, pid) => {
|
|
9090
|
+
const result = await compactSearch({ query: q, limit, projectId: pid, status: "active" });
|
|
9091
|
+
if (result.entries.length === 0) return [];
|
|
9092
|
+
const details = await compactDetail(result.entries.map((e) => e.id));
|
|
9093
|
+
return details.documents.map((d, i) => ({
|
|
9094
|
+
id: Number(d.id.replace("obs-", "")),
|
|
9095
|
+
observationId: d.observationId,
|
|
9096
|
+
title: d.title,
|
|
9097
|
+
narrative: d.narrative,
|
|
9098
|
+
facts: d.facts,
|
|
9099
|
+
entityName: d.entityName,
|
|
9100
|
+
type: d.type,
|
|
9101
|
+
score: result.entries[i]?.score ?? 0
|
|
9102
|
+
}));
|
|
9103
|
+
},
|
|
9104
|
+
getObservation: (id) => {
|
|
9105
|
+
const o = getObservation(id);
|
|
9106
|
+
if (!o) return null;
|
|
9107
|
+
return {
|
|
9108
|
+
id: o.id,
|
|
9109
|
+
entityName: o.entityName,
|
|
9110
|
+
type: o.type,
|
|
9111
|
+
title: o.title,
|
|
9112
|
+
narrative: o.narrative,
|
|
9113
|
+
facts: o.facts,
|
|
9114
|
+
topicKey: o.topicKey
|
|
9115
|
+
};
|
|
9116
|
+
},
|
|
9117
|
+
getEntityNames: () => graphManager.getEntityNames()
|
|
9118
|
+
};
|
|
9119
|
+
const formed = await runFormation({
|
|
9120
|
+
entityName,
|
|
9121
|
+
type,
|
|
9122
|
+
title,
|
|
9123
|
+
narrative,
|
|
9124
|
+
facts: safeFacts,
|
|
9125
|
+
projectId: project.id,
|
|
9126
|
+
source: "explicit",
|
|
9127
|
+
topicKey
|
|
9128
|
+
}, formationConfig);
|
|
9129
|
+
formationNote = `
|
|
9130
|
+
\u{1F52C} Formation[shadow]: ${formed.evaluation.category} (${formed.evaluation.score.toFixed(2)}) | ${formed.resolution.action} | ${formed.pipeline.durationMs}ms`;
|
|
9131
|
+
if (formed.extraction.extractedFacts.length > 0) {
|
|
9132
|
+
formationNote += ` | +${formed.extraction.extractedFacts.length} facts`;
|
|
9133
|
+
}
|
|
9134
|
+
if (formed.extraction.titleImproved) formationNote += " | title\u2191";
|
|
9135
|
+
if (formed.extraction.entityResolved) formationNote += ` | entity\u2192${formed.entityName}`;
|
|
9136
|
+
if (formed.extraction.typeCorrected) formationNote += ` | type\u2192${formed.type}`;
|
|
9137
|
+
} catch {
|
|
9138
|
+
}
|
|
8497
9139
|
return {
|
|
8498
9140
|
content: [
|
|
8499
9141
|
{
|
|
8500
9142
|
type: "text",
|
|
8501
9143
|
text: `${action} observation #${obs.id} "${title}" (~${obs.tokens} tokens)
|
|
8502
|
-
Entity: ${entityName} | Type: ${type} | Project: ${project.id}${obs.topicKey ? ` | Topic: ${obs.topicKey}` : ""}${compactAction}${compressionNote}${enrichment}`
|
|
9144
|
+
Entity: ${entityName} | Type: ${type} | Project: ${project.id}${obs.topicKey ? ` | Topic: ${obs.topicKey}` : ""}${compactAction}${compressionNote}${enrichment}${formationNote}`
|
|
8503
9145
|
}
|
|
8504
9146
|
]
|
|
8505
9147
|
};
|
|
@@ -8854,6 +9496,53 @@ Archived memories can be restored manually if needed.` }]
|
|
|
8854
9496
|
};
|
|
8855
9497
|
}
|
|
8856
9498
|
);
|
|
9499
|
+
server.registerTool(
|
|
9500
|
+
"memorix_formation_metrics",
|
|
9501
|
+
{
|
|
9502
|
+
title: "Formation Pipeline Metrics",
|
|
9503
|
+
description: "Show aggregated metrics from the Memory Formation Pipeline running in shadow mode. Reports value scores, resolution actions, fact extraction rates, and processing times.",
|
|
9504
|
+
inputSchema: {}
|
|
9505
|
+
},
|
|
9506
|
+
async () => {
|
|
9507
|
+
const summary = getMetricsSummary();
|
|
9508
|
+
if (summary.total === 0) {
|
|
9509
|
+
return {
|
|
9510
|
+
content: [{
|
|
9511
|
+
type: "text",
|
|
9512
|
+
text: "\u{1F4CA} Formation Pipeline: No metrics collected yet.\nStore some observations to start collecting shadow mode data."
|
|
9513
|
+
}]
|
|
9514
|
+
};
|
|
9515
|
+
}
|
|
9516
|
+
const lines = [
|
|
9517
|
+
"\u{1F4CA} **Formation Pipeline Metrics** (shadow mode)",
|
|
9518
|
+
"",
|
|
9519
|
+
`**Total observations processed:** ${summary.total}`,
|
|
9520
|
+
`**Average value score:** ${summary.avgValueScore.toFixed(3)}`,
|
|
9521
|
+
`**Average processing time:** ${summary.avgDurationMs.toFixed(1)}ms`,
|
|
9522
|
+
"",
|
|
9523
|
+
"### Quality Indicators",
|
|
9524
|
+
`- **Avg system-extracted facts:** ${summary.avgExtractedFacts.toFixed(1)} per observation`,
|
|
9525
|
+
`- **Title improved rate:** ${(summary.titleImprovedRate * 100).toFixed(1)}%`,
|
|
9526
|
+
`- **Entity resolved rate:** ${(summary.entityResolvedRate * 100).toFixed(1)}%`,
|
|
9527
|
+
`- **Type corrected rate:** ${(summary.typeCorectedRate * 100).toFixed(1)}%`,
|
|
9528
|
+
"",
|
|
9529
|
+
"### Value Categories"
|
|
9530
|
+
];
|
|
9531
|
+
for (const [cat, count2] of Object.entries(summary.categoryBreakdown)) {
|
|
9532
|
+
const pct = (count2 / summary.total * 100).toFixed(1);
|
|
9533
|
+
const icon = cat === "core" ? "\u{1F7E2}" : cat === "contextual" ? "\u{1F7E1}" : "\u{1F534}";
|
|
9534
|
+
lines.push(`- ${icon} **${cat}:** ${count2} (${pct}%)`);
|
|
9535
|
+
}
|
|
9536
|
+
lines.push("", "### Resolution Actions");
|
|
9537
|
+
for (const [action, count2] of Object.entries(summary.resolutionBreakdown)) {
|
|
9538
|
+
const pct = (count2 / summary.total * 100).toFixed(1);
|
|
9539
|
+
lines.push(`- **${action}:** ${count2} (${pct}%)`);
|
|
9540
|
+
}
|
|
9541
|
+
return {
|
|
9542
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
9543
|
+
};
|
|
9544
|
+
}
|
|
9545
|
+
);
|
|
8857
9546
|
let enableKG = false;
|
|
8858
9547
|
try {
|
|
8859
9548
|
const { homedir: homedir16 } = await import("os");
|