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/dist/cli/index.js CHANGED
@@ -42046,6 +42046,10 @@ var init_graph = __esm({
42046
42046
  );
42047
42047
  await this.save();
42048
42048
  }
42049
+ /** Get all entity names (for Formation Pipeline entity resolution) */
42050
+ getEntityNames() {
42051
+ return this.entities.map((e3) => e3.name);
42052
+ }
42049
42053
  /** Read the entire graph */
42050
42054
  async readGraph() {
42051
42055
  await this.init();
@@ -43813,6 +43817,624 @@ Respond in JSON only:
43813
43817
  }
43814
43818
  });
43815
43819
 
43820
+ // src/memory/formation/extract.ts
43821
+ function extractFacts(narrative, existingFacts) {
43822
+ const existingLower = new Set(existingFacts.map((f4) => f4.toLowerCase().trim()));
43823
+ const extracted = [];
43824
+ const seen = /* @__PURE__ */ new Set();
43825
+ for (const { pattern, format } of FACT_PATTERNS) {
43826
+ pattern.lastIndex = 0;
43827
+ let match;
43828
+ while ((match = pattern.exec(narrative)) !== null) {
43829
+ const fact = format(match);
43830
+ const normalized = fact.toLowerCase().trim();
43831
+ if (existingLower.has(normalized) || seen.has(normalized)) continue;
43832
+ if (fact.length < 5 || fact.length > 120) continue;
43833
+ seen.add(normalized);
43834
+ extracted.push(fact);
43835
+ }
43836
+ }
43837
+ return extracted.slice(0, 10);
43838
+ }
43839
+ function improveTitle(title, narrative) {
43840
+ const isGeneric = GENERIC_TITLE_PATTERNS.some((p2) => p2.test(title));
43841
+ if (!isGeneric) return { title, improved: false };
43842
+ const sentences = narrative.replace(/```[\s\S]*?```/g, "").split(/[.。!!?\n]/).map((s2) => s2.trim()).filter((s2) => s2.length >= 15);
43843
+ if (sentences.length > 0) {
43844
+ return { title: sentences[0].slice(0, 60), improved: true };
43845
+ }
43846
+ return { title, improved: false };
43847
+ }
43848
+ function resolveEntity(entityName, existingEntities) {
43849
+ if (existingEntities.length === 0) return { entityName, resolved: false };
43850
+ const lower = entityName.toLowerCase().replace(/[-_]/g, "");
43851
+ for (const existing of existingEntities) {
43852
+ const existingLower = existing.toLowerCase().replace(/[-_]/g, "");
43853
+ if (lower === existingLower) {
43854
+ return { entityName: existing, resolved: existing !== entityName };
43855
+ }
43856
+ if (lower.length >= 3 && existingLower.length >= 3) {
43857
+ if (existingLower.includes(lower) || lower.includes(existingLower)) {
43858
+ const canonical = existing.length >= entityName.length ? existing : entityName;
43859
+ return { entityName: canonical, resolved: canonical !== entityName };
43860
+ }
43861
+ }
43862
+ }
43863
+ return { entityName, resolved: false };
43864
+ }
43865
+ function verifyType(declaredType, narrative, title) {
43866
+ const content = `${title} ${narrative}`;
43867
+ const scores = [];
43868
+ for (const { type, patterns } of TYPE_SIGNALS) {
43869
+ let score = 0;
43870
+ for (const p2 of patterns) {
43871
+ const regex2 = new RegExp(p2.source, p2.flags.includes("g") ? p2.flags : p2.flags + "g");
43872
+ const matches = [...content.matchAll(regex2)];
43873
+ score += matches.length;
43874
+ }
43875
+ if (score > 0) scores.push({ type, score });
43876
+ }
43877
+ if (scores.length === 0) return { type: declaredType, corrected: false };
43878
+ scores.sort((a3, b3) => b3.score - a3.score);
43879
+ const best = scores[0];
43880
+ if (best.type !== declaredType && best.score >= 2) {
43881
+ const declaredScore = scores.find((s2) => s2.type === declaredType)?.score ?? 0;
43882
+ if (declaredScore === 0) {
43883
+ return { type: best.type, corrected: true };
43884
+ }
43885
+ }
43886
+ return { type: declaredType, corrected: false };
43887
+ }
43888
+ function runExtract(input, existingEntities) {
43889
+ const callerFacts = input.facts ?? [];
43890
+ const extractedFacts = extractFacts(input.narrative, callerFacts);
43891
+ const allFacts = [...callerFacts, ...extractedFacts];
43892
+ const { title, improved: titleImproved } = improveTitle(input.title, input.narrative);
43893
+ const { entityName, resolved: entityResolved } = resolveEntity(
43894
+ input.entityName,
43895
+ existingEntities
43896
+ );
43897
+ const { type, corrected: typeCorrected } = verifyType(
43898
+ input.type,
43899
+ input.narrative,
43900
+ input.title
43901
+ );
43902
+ return {
43903
+ title,
43904
+ titleImproved,
43905
+ narrative: input.narrative,
43906
+ facts: allFacts,
43907
+ extractedFacts,
43908
+ entityName,
43909
+ entityResolved,
43910
+ type,
43911
+ typeCorrected
43912
+ };
43913
+ }
43914
+ var FACT_PATTERNS, GENERIC_TITLE_PATTERNS, TYPE_SIGNALS;
43915
+ var init_extract = __esm({
43916
+ "src/memory/formation/extract.ts"() {
43917
+ "use strict";
43918
+ init_esm_shims();
43919
+ FACT_PATTERNS = [
43920
+ // Key: Value pairs (e.g., "Port: 3000", "Timeout = 60s")
43921
+ {
43922
+ pattern: /\b([A-Z][a-zA-Z_-]{2,30})\s*[:=]\s*([^\n,;]{2,60})/g,
43923
+ format: (m4) => `${m4[1]}: ${m4[2].trim()}`
43924
+ },
43925
+ // Arrow notation (e.g., "MySQL → PostgreSQL", "v1.0 → v2.0")
43926
+ {
43927
+ pattern: /\b(\S{2,30})\s*[→➜\->]+\s*(\S{2,30})/g,
43928
+ format: (m4) => `${m4[1]} \u2192 ${m4[2]}`
43929
+ },
43930
+ // Version numbers (e.g., "v1.2.3", "version 2.0")
43931
+ {
43932
+ pattern: /\b(?:v(?:ersion)?\s*)(\d+\.\d+(?:\.\d+)?(?:-[\w.]+)?)\b/gi,
43933
+ format: (m4) => `Version: ${m4[1]}`
43934
+ },
43935
+ // Error messages (e.g., "Error: ...", "ERR_...")
43936
+ {
43937
+ pattern: /\b(?:Error|ERR|ENOENT|ECONNREFUSED|TypeError|RangeError|SyntaxError|ReferenceError)[:\s]+([^\n]{5,80})/gi,
43938
+ format: (m4) => `Error: ${m4[1].trim()}`
43939
+ },
43940
+ // Port numbers in context
43941
+ {
43942
+ pattern: /\b(?:port|PORT)\s*[:=]?\s*(\d{2,5})\b/gi,
43943
+ format: (m4) => `Port: ${m4[1]}`
43944
+ },
43945
+ // Environment variables
43946
+ {
43947
+ pattern: /\b([A-Z][A-Z0-9_]{3,30})\s*=\s*(\S{1,60})/g,
43948
+ format: (m4) => `${m4[1]}=${m4[2]}`
43949
+ },
43950
+ // npm/package versions (e.g., "react@18.2.0")
43951
+ {
43952
+ pattern: /\b([@a-z][\w./-]+)@(\d+\.\d+\.\d+(?:-[\w.]+)?)\b/g,
43953
+ format: (m4) => `${m4[1]}@${m4[2]}`
43954
+ }
43955
+ ];
43956
+ GENERIC_TITLE_PATTERNS = [
43957
+ /^Updated \S+\.\w+$/i,
43958
+ /^Created \S+\.\w+$/i,
43959
+ /^Deleted \S+\.\w+$/i,
43960
+ /^Modified \S+\.\w+$/i,
43961
+ /^Changed \S+\.\w+$/i,
43962
+ /^Session activity/i,
43963
+ /^Activity \(/i,
43964
+ /^Used \w+$/i,
43965
+ /^Ran: /i
43966
+ ];
43967
+ TYPE_SIGNALS = [
43968
+ {
43969
+ type: "problem-solution",
43970
+ patterns: [
43971
+ /\b(fix|fixed|bug|error|issue|crash|broken|resolved|workaround|patch)\b/i,
43972
+ /\b(修复|修正|解决|报错|崩溃|异常)\b/
43973
+ ]
43974
+ },
43975
+ {
43976
+ type: "gotcha",
43977
+ patterns: [
43978
+ /\b(gotcha|pitfall|trap|careful|warning|caveat|footgun|unexpected|beware)\b/i,
43979
+ /\b(坑|陷阱|注意|小心|踩坑)\b/
43980
+ ]
43981
+ },
43982
+ {
43983
+ type: "decision",
43984
+ patterns: [
43985
+ /\b(decided|chose|chosen|selected|adopted|rejected|evaluated|compared)\b/i,
43986
+ /\b(决定|选择|采用|弃用|对比|评估)\b/
43987
+ ]
43988
+ },
43989
+ {
43990
+ type: "what-changed",
43991
+ patterns: [
43992
+ /\b(changed|migrated|upgraded|refactored|replaced|renamed|moved|removed|added)\b/i,
43993
+ /\b(改|迁移|升级|重构|替换|重命名|删除|新增)\b/
43994
+ ]
43995
+ },
43996
+ {
43997
+ type: "how-it-works",
43998
+ patterns: [
43999
+ /\b(works by|architecture|mechanism|pipeline|flow|under the hood|internally)\b/i,
44000
+ /\b(原理|机制|流程|架构|内部)\b/
44001
+ ]
44002
+ },
44003
+ {
44004
+ type: "trade-off",
44005
+ patterns: [
44006
+ /\b(trade.?off|compromise|downside|cost|benefit|pro|con|versus|vs)\b/i,
44007
+ /\b(权衡|折中|代价|收益|优缺点)\b/
44008
+ ]
44009
+ }
44010
+ ];
44011
+ }
44012
+ });
44013
+
44014
+ // src/memory/formation/resolve.ts
44015
+ function wordOverlap(a3, b3) {
44016
+ const wordsA = new Set(a3.toLowerCase().split(/\s+/).filter((w3) => w3.length > 2));
44017
+ const wordsB = new Set(b3.toLowerCase().split(/\s+/).filter((w3) => w3.length > 2));
44018
+ if (wordsA.size === 0 || wordsB.size === 0) return 0;
44019
+ let intersection2 = 0;
44020
+ for (const w3 of wordsA) {
44021
+ if (wordsB.has(w3)) intersection2++;
44022
+ }
44023
+ return intersection2 / Math.max(wordsA.size, wordsB.size);
44024
+ }
44025
+ function entitiesMatch(a3, b3) {
44026
+ const na = a3.toLowerCase().replace(/[-_]/g, "");
44027
+ const nb = b3.toLowerCase().replace(/[-_]/g, "");
44028
+ if (na === nb) return true;
44029
+ if (na.length >= 3 && nb.length >= 3) {
44030
+ if (na.includes(nb) || nb.includes(na)) return true;
44031
+ }
44032
+ return false;
44033
+ }
44034
+ function hasContradiction(oldText, newText) {
44035
+ const negationPatterns = [
44036
+ /\bnot\s+(\w+)/gi,
44037
+ /\bno longer\b/i,
44038
+ /\binstead of\b/i,
44039
+ /\breplaced\b.*\bwith\b/i,
44040
+ /\bremoved\b/i,
44041
+ /\bdeprecated\b/i,
44042
+ /\bobsolete\b/i,
44043
+ /不再/,
44044
+ /已弃用/,
44045
+ /替换为/,
44046
+ /改为/
44047
+ ];
44048
+ return negationPatterns.some((p2) => p2.test(newText));
44049
+ }
44050
+ function mergeNarratives(oldNarrative, newNarrative) {
44051
+ if (newNarrative.length > oldNarrative.length * 1.5) return newNarrative;
44052
+ if (oldNarrative.length > newNarrative.length * 1.5) return oldNarrative;
44053
+ return `${newNarrative}
44054
+
44055
+ [Previous context]: ${oldNarrative}`;
44056
+ }
44057
+ function mergeFacts2(oldFacts, newFacts) {
44058
+ const seen = /* @__PURE__ */ new Set();
44059
+ const merged = [];
44060
+ for (const f4 of newFacts) {
44061
+ const norm = f4.toLowerCase().trim();
44062
+ if (!seen.has(norm) && f4.trim().length > 0) {
44063
+ seen.add(norm);
44064
+ merged.push(f4);
44065
+ }
44066
+ }
44067
+ for (const f4 of oldFacts) {
44068
+ const norm = f4.toLowerCase().trim();
44069
+ if (!seen.has(norm) && f4.trim().length > 0) {
44070
+ seen.add(norm);
44071
+ merged.push(f4);
44072
+ }
44073
+ }
44074
+ return merged;
44075
+ }
44076
+ function scoreCandidate(extracted, candidate) {
44077
+ const entityMatch = entitiesMatch(extracted.entityName, candidate.entityName);
44078
+ const contentOverlap = wordOverlap(
44079
+ `${extracted.title} ${extracted.narrative}`,
44080
+ `${candidate.title} ${candidate.narrative}`
44081
+ );
44082
+ const score = candidate.score * 0.6 + (entityMatch ? 0.2 : 0) + contentOverlap * 0.2;
44083
+ const newLength = extracted.narrative.length + extracted.facts.join(" ").length;
44084
+ const oldLength = candidate.narrative.length + candidate.facts.length;
44085
+ const richer = newLength > oldLength * 1.15;
44086
+ const contradiction = hasContradiction(candidate.narrative, extracted.narrative);
44087
+ return { score, entityMatch, richer, contradiction };
44088
+ }
44089
+ async function runResolve(extracted, projectId, searchMemories, getObservation2) {
44090
+ const query = `${extracted.title} ${extracted.narrative.substring(0, 200)}`;
44091
+ let hits;
44092
+ try {
44093
+ hits = await searchMemories(query, 5, projectId);
44094
+ } catch {
44095
+ return { action: "new", reason: "Search unavailable, defaulting to new" };
44096
+ }
44097
+ if (hits.length === 0) {
44098
+ return { action: "new", reason: "No similar existing memories found" };
44099
+ }
44100
+ const scored = hits.map((hit) => ({
44101
+ hit,
44102
+ ...scoreCandidate(extracted, hit)
44103
+ }));
44104
+ scored.sort((a3, b3) => b3.score - a3.score);
44105
+ const best = scored[0];
44106
+ if (best.hit.score >= SIMILARITY_DUPLICATE) {
44107
+ if (best.richer) {
44108
+ const existing = getObservation2(best.hit.observationId);
44109
+ const oldFacts = existing?.facts ?? best.hit.facts.split("\n").filter(Boolean);
44110
+ return {
44111
+ action: "evolve",
44112
+ targetId: best.hit.observationId,
44113
+ reason: `Near-duplicate of #${best.hit.observationId} but richer content (score: ${best.score.toFixed(2)})`,
44114
+ mergedNarrative: mergeNarratives(best.hit.narrative, extracted.narrative),
44115
+ mergedFacts: mergeFacts2(oldFacts, extracted.facts)
44116
+ };
44117
+ }
44118
+ return {
44119
+ action: "discard",
44120
+ targetId: best.hit.observationId,
44121
+ reason: `Duplicate of #${best.hit.observationId} (score: ${best.score.toFixed(2)})`
44122
+ };
44123
+ }
44124
+ if (best.score >= SIMILARITY_HIGH2) {
44125
+ if (best.contradiction) {
44126
+ const existing = getObservation2(best.hit.observationId);
44127
+ const oldFacts = existing?.facts ?? best.hit.facts.split("\n").filter(Boolean);
44128
+ return {
44129
+ action: "evolve",
44130
+ targetId: best.hit.observationId,
44131
+ reason: `Supersedes #${best.hit.observationId}: contradiction detected (score: ${best.score.toFixed(2)})`,
44132
+ mergedNarrative: extracted.narrative,
44133
+ mergedFacts: mergeFacts2(oldFacts, extracted.facts)
44134
+ };
44135
+ }
44136
+ if (best.richer) {
44137
+ const existing = getObservation2(best.hit.observationId);
44138
+ const oldFacts = existing?.facts ?? best.hit.facts.split("\n").filter(Boolean);
44139
+ return {
44140
+ action: "merge",
44141
+ targetId: best.hit.observationId,
44142
+ reason: `Merging with #${best.hit.observationId}: same topic, new content is richer (score: ${best.score.toFixed(2)})`,
44143
+ mergedNarrative: mergeNarratives(best.hit.narrative, extracted.narrative),
44144
+ mergedFacts: mergeFacts2(oldFacts, extracted.facts)
44145
+ };
44146
+ }
44147
+ return {
44148
+ action: "discard",
44149
+ targetId: best.hit.observationId,
44150
+ reason: `Already covered by #${best.hit.observationId} (score: ${best.score.toFixed(2)})`
44151
+ };
44152
+ }
44153
+ if (best.score >= SIMILARITY_MEDIUM2 && best.entityMatch) {
44154
+ const existing = getObservation2(best.hit.observationId);
44155
+ const oldFacts = existing?.facts ?? best.hit.facts.split("\n").filter(Boolean);
44156
+ const newFactCount = extracted.facts.length;
44157
+ const oldFactCount = oldFacts.length;
44158
+ if (newFactCount > oldFactCount) {
44159
+ return {
44160
+ action: "merge",
44161
+ targetId: best.hit.observationId,
44162
+ reason: `Same entity "${extracted.entityName}", new memory has more facts (${newFactCount} > ${oldFactCount})`,
44163
+ mergedNarrative: mergeNarratives(best.hit.narrative, extracted.narrative),
44164
+ mergedFacts: mergeFacts2(oldFacts, extracted.facts)
44165
+ };
44166
+ }
44167
+ }
44168
+ return { action: "new", reason: `Different from existing memories (best score: ${best.score.toFixed(2)})` };
44169
+ }
44170
+ var SIMILARITY_HIGH2, SIMILARITY_MEDIUM2, SIMILARITY_DUPLICATE;
44171
+ var init_resolve = __esm({
44172
+ "src/memory/formation/resolve.ts"() {
44173
+ "use strict";
44174
+ init_esm_shims();
44175
+ SIMILARITY_HIGH2 = 0.75;
44176
+ SIMILARITY_MEDIUM2 = 0.5;
44177
+ SIMILARITY_DUPLICATE = 0.9;
44178
+ }
44179
+ });
44180
+
44181
+ // src/memory/formation/evaluate.ts
44182
+ function factDensity(facts, narrativeLength) {
44183
+ if (narrativeLength === 0) return 0;
44184
+ const structuredChars = facts.reduce((sum, f4) => sum + f4.length, 0);
44185
+ return Math.min(1, structuredChars / Math.max(narrativeLength, 100));
44186
+ }
44187
+ function specificityScore(content) {
44188
+ let count3 = 0;
44189
+ for (const p2 of SPECIFICITY_PATTERNS) {
44190
+ p2.lastIndex = 0;
44191
+ if (p2.test(content)) count3++;
44192
+ }
44193
+ return Math.min(1, count3 / 3);
44194
+ }
44195
+ function causalScore(content) {
44196
+ let count3 = 0;
44197
+ for (const p2 of CAUSAL_PATTERNS) {
44198
+ p2.lastIndex = 0;
44199
+ if (p2.test(content)) count3++;
44200
+ }
44201
+ return Math.min(1, count3 / 2);
44202
+ }
44203
+ function noiseScore(title, narrative) {
44204
+ let noisiness = 0;
44205
+ for (const p2 of NOISE_PATTERNS) {
44206
+ if (p2.test(title)) {
44207
+ noisiness += 0.3;
44208
+ break;
44209
+ }
44210
+ }
44211
+ const lines = narrative.split("\n").filter((l3) => l3.trim().length > 0);
44212
+ let toolOutputLines = 0;
44213
+ for (const line of lines) {
44214
+ for (const p2 of TOOL_OUTPUT_PATTERNS) {
44215
+ if (p2.test(line)) {
44216
+ toolOutputLines++;
44217
+ break;
44218
+ }
44219
+ }
44220
+ }
44221
+ if (lines.length > 0) {
44222
+ noisiness += toolOutputLines / lines.length * 0.5;
44223
+ }
44224
+ if (narrative.length < 50) noisiness += 0.2;
44225
+ return Math.min(1, noisiness);
44226
+ }
44227
+ function categorize(score) {
44228
+ if (score >= 0.6) return "core";
44229
+ if (score >= 0.35) return "contextual";
44230
+ return "ephemeral";
44231
+ }
44232
+ function buildReason(typeWeight, factDens, specificity, causal, noise, category) {
44233
+ const parts = [];
44234
+ if (typeWeight >= 0.7) parts.push("high-value type");
44235
+ else if (typeWeight <= 0.45) parts.push("low-value type");
44236
+ if (factDens > 0.3) parts.push("fact-dense");
44237
+ if (specificity > 0.3) parts.push("specific (versions/codes/paths)");
44238
+ if (causal > 0.3) parts.push("causal reasoning");
44239
+ if (noise > 0.3) parts.push("noisy content");
44240
+ const detail = parts.length > 0 ? parts.join(", ") : "average content";
44241
+ return `${category}: ${detail}`;
44242
+ }
44243
+ function runEvaluate(extracted) {
44244
+ const content = `${extracted.title} ${extracted.narrative} ${extracted.facts.join(" ")}`;
44245
+ const typeWeight = TYPE_WEIGHTS[extracted.type] ?? 0.5;
44246
+ const factDens = factDensity(extracted.facts, extracted.narrative.length);
44247
+ const specificity = specificityScore(content);
44248
+ const causal = causalScore(content);
44249
+ const noise = noiseScore(extracted.title, extracted.narrative);
44250
+ const rawScore = typeWeight * 0.5 + factDens * 0.12 + specificity * 0.12 + causal * 0.12 - noise * 0.14;
44251
+ const extractionBonus = extracted.extractedFacts.length > 0 ? 0.05 : 0;
44252
+ const titlePenalty = extracted.titleImproved ? -0.03 : 0;
44253
+ const correctionBonus = extracted.typeCorrected ? 0.03 : 0;
44254
+ const score = Math.max(0, Math.min(1, rawScore + extractionBonus + titlePenalty + correctionBonus));
44255
+ const category = categorize(score);
44256
+ const reason = buildReason(typeWeight, factDens, specificity, causal, noise, category);
44257
+ return { score, category, reason };
44258
+ }
44259
+ var TYPE_WEIGHTS, SPECIFICITY_PATTERNS, CAUSAL_PATTERNS, NOISE_PATTERNS, TOOL_OUTPUT_PATTERNS;
44260
+ var init_evaluate = __esm({
44261
+ "src/memory/formation/evaluate.ts"() {
44262
+ "use strict";
44263
+ init_esm_shims();
44264
+ TYPE_WEIGHTS = {
44265
+ "gotcha": 0.85,
44266
+ "decision": 0.8,
44267
+ "problem-solution": 0.75,
44268
+ "trade-off": 0.7,
44269
+ "why-it-exists": 0.65,
44270
+ "how-it-works": 0.6,
44271
+ "discovery": 0.55,
44272
+ "what-changed": 0.45,
44273
+ "session-request": 0.4
44274
+ };
44275
+ SPECIFICITY_PATTERNS = [
44276
+ /\b\d+\.\d+\.\d+\b/,
44277
+ // Semantic version numbers
44278
+ /\b(ERR_|ENOENT|ECONNREFUSED|E[A-Z]{3,})\b/,
44279
+ // Error codes
44280
+ /\b(port|PORT)\s*[:=]?\s*\d{2,5}\b/i,
44281
+ // Port numbers
44282
+ /\bhttps?:\/\/\S+/,
44283
+ // URLs
44284
+ /`[^`]{3,60}`/,
44285
+ // Inline code references
44286
+ /\b[A-Z][A-Z0-9_]{3,}\b/,
44287
+ // Constants (e.g., MAX_RETRIES)
44288
+ /\b\d+\s*(ms|s|sec|min|MB|GB|KB)\b/i
44289
+ // Measurements with units
44290
+ ];
44291
+ CAUSAL_PATTERNS = [
44292
+ /\b(because|therefore|due to|caused by|as a result|fixed by|resolved by)\b/i,
44293
+ /\b(so that|in order to|leads to|results in|prevents)\b/i,
44294
+ /(?:因为|所以|由于|导致|造成|因此|为了|解决)/
44295
+ ];
44296
+ NOISE_PATTERNS = [
44297
+ /^Session activity/i,
44298
+ /^Updated \S+\.\w+$/i,
44299
+ /^Created \S+\.\w+$/i,
44300
+ /^Deleted \S+\.\w+$/i,
44301
+ /^File written successfully/i,
44302
+ /^Command executed/i,
44303
+ /^Tool: (read_file|list_dir|find_by_name)/i,
44304
+ /^\s*$/
44305
+ ];
44306
+ TOOL_OUTPUT_PATTERNS = [
44307
+ /^(file|directory|folder)\s+(created|deleted|moved|copied)/i,
44308
+ /^Successfully\s+(installed|updated|removed)/i,
44309
+ /^\d+ files? changed/i,
44310
+ /^npm (WARN|notice)/i,
44311
+ /^\s*at\s+\S+\s+\(/
44312
+ // Stack trace lines
44313
+ ];
44314
+ }
44315
+ });
44316
+
44317
+ // src/memory/formation/index.ts
44318
+ var formation_exports = {};
44319
+ __export(formation_exports, {
44320
+ clearFormationMetrics: () => clearFormationMetrics,
44321
+ getFormationMetrics: () => getFormationMetrics,
44322
+ getMetricsSummary: () => getMetricsSummary,
44323
+ runFormation: () => runFormation
44324
+ });
44325
+ function getFormationMetrics() {
44326
+ return metricsBuffer;
44327
+ }
44328
+ function clearFormationMetrics() {
44329
+ metricsBuffer.length = 0;
44330
+ }
44331
+ function getMetricsSummary() {
44332
+ const total = metricsBuffer.length;
44333
+ if (total === 0) {
44334
+ return {
44335
+ total: 0,
44336
+ avgValueScore: 0,
44337
+ avgExtractedFacts: 0,
44338
+ titleImprovedRate: 0,
44339
+ entityResolvedRate: 0,
44340
+ typeCorectedRate: 0,
44341
+ resolutionBreakdown: {},
44342
+ categoryBreakdown: {},
44343
+ avgDurationMs: 0
44344
+ };
44345
+ }
44346
+ const sum = (fn) => metricsBuffer.reduce((s2, m4) => s2 + fn(m4), 0);
44347
+ const resolutionBreakdown = {};
44348
+ const categoryBreakdown = {};
44349
+ for (const m4 of metricsBuffer) {
44350
+ resolutionBreakdown[m4.resolutionAction] = (resolutionBreakdown[m4.resolutionAction] ?? 0) + 1;
44351
+ categoryBreakdown[m4.valueCategory] = (categoryBreakdown[m4.valueCategory] ?? 0) + 1;
44352
+ }
44353
+ return {
44354
+ total,
44355
+ avgValueScore: sum((m4) => m4.valueScore) / total,
44356
+ avgExtractedFacts: sum((m4) => m4.systemExtractedFacts) / total,
44357
+ titleImprovedRate: sum((m4) => m4.titleImproved ? 1 : 0) / total,
44358
+ entityResolvedRate: sum((m4) => m4.entityResolved ? 1 : 0) / total,
44359
+ typeCorectedRate: sum((m4) => m4.typeCorrected ? 1 : 0) / total,
44360
+ resolutionBreakdown,
44361
+ categoryBreakdown,
44362
+ avgDurationMs: sum((m4) => m4.durationMs) / total
44363
+ };
44364
+ }
44365
+ async function runFormation(input, config2) {
44366
+ const startTime = Date.now();
44367
+ let stagesCompleted = 0;
44368
+ const existingEntities = config2.getEntityNames();
44369
+ const extraction = runExtract(input, existingEntities);
44370
+ stagesCompleted = 1;
44371
+ let resolution;
44372
+ if (input.topicKey) {
44373
+ resolution = {
44374
+ action: "new",
44375
+ reason: "TopicKey upsert \u2014 bypasses resolve stage"
44376
+ };
44377
+ } else {
44378
+ resolution = await runResolve(
44379
+ extraction,
44380
+ input.projectId,
44381
+ config2.searchMemories,
44382
+ config2.getObservation
44383
+ );
44384
+ }
44385
+ stagesCompleted = 2;
44386
+ const evaluation = runEvaluate(extraction);
44387
+ stagesCompleted = 3;
44388
+ const durationMs = Date.now() - startTime;
44389
+ const formed = {
44390
+ // Final enriched data
44391
+ entityName: extraction.entityName,
44392
+ type: extraction.type,
44393
+ title: extraction.title,
44394
+ narrative: resolution.mergedNarrative ?? extraction.narrative,
44395
+ facts: resolution.mergedFacts ?? extraction.facts,
44396
+ // Stage results
44397
+ extraction,
44398
+ resolution,
44399
+ evaluation,
44400
+ // Pipeline metadata
44401
+ pipeline: {
44402
+ mode: "rules",
44403
+ durationMs,
44404
+ stagesCompleted,
44405
+ shadow: config2.shadow
44406
+ }
44407
+ };
44408
+ const metrics = {
44409
+ systemExtractedFacts: extraction.extractedFacts.length,
44410
+ titleImproved: extraction.titleImproved,
44411
+ entityResolved: extraction.entityResolved,
44412
+ typeCorrected: extraction.typeCorrected,
44413
+ resolutionAction: resolution.action,
44414
+ valueScore: evaluation.score,
44415
+ valueCategory: evaluation.category,
44416
+ durationMs,
44417
+ mode: "rules"
44418
+ };
44419
+ if (metricsBuffer.length >= MAX_METRICS_BUFFER) {
44420
+ metricsBuffer.shift();
44421
+ }
44422
+ metricsBuffer.push(metrics);
44423
+ return formed;
44424
+ }
44425
+ var metricsBuffer, MAX_METRICS_BUFFER;
44426
+ var init_formation = __esm({
44427
+ "src/memory/formation/index.ts"() {
44428
+ "use strict";
44429
+ init_esm_shims();
44430
+ init_extract();
44431
+ init_resolve();
44432
+ init_evaluate();
44433
+ metricsBuffer = [];
44434
+ MAX_METRICS_BUFFER = 500;
44435
+ }
44436
+ });
44437
+
43816
44438
  // src/memory/session.ts
43817
44439
  var session_exports = {};
43818
44440
  __export(session_exports, {
@@ -44451,7 +45073,7 @@ async function promoteToMiniSkill(projectDir2, projectId, observations2, options
44451
45073
  const title = generateTitle(observations2);
44452
45074
  const instruction = options2?.instruction || generateInstruction(observations2);
44453
45075
  const trigger = options2?.trigger || generateTrigger(observations2);
44454
- const facts = extractFacts(observations2);
45076
+ const facts = extractFacts2(observations2);
44455
45077
  const tags = [
44456
45078
  ...options2?.tags || [],
44457
45079
  ...extractTags(observations2)
@@ -44559,7 +45181,7 @@ function generateTrigger(observations2) {
44559
45181
  }
44560
45182
  return parts.length > 0 ? parts.join("; ") : `Related to ${obs.title}`;
44561
45183
  }
44562
- function extractFacts(observations2) {
45184
+ function extractFacts2(observations2) {
44563
45185
  const facts = /* @__PURE__ */ new Set();
44564
45186
  for (const obs of observations2) {
44565
45187
  for (const f4 of obs.facts) {
@@ -45662,7 +46284,7 @@ async function createMemorixServer(cwd, existingServer, sharedTeam) {
45662
46284
  let syncAdvisory = null;
45663
46285
  const server = existingServer ?? new McpServer({
45664
46286
  name: "memorix",
45665
- version: true ? "1.0.2" : "1.0.1"
46287
+ version: true ? "1.0.3" : "1.0.1"
45666
46288
  });
45667
46289
  server.registerTool(
45668
46290
  "memorix_store",
@@ -45818,12 +46440,68 @@ Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
45818
46440
  const enrichment = enrichmentParts.length > 0 ? `
45819
46441
  Auto-enriched: ${enrichmentParts.join(", ")}` : "";
45820
46442
  const action = upserted ? "\u{1F504} Updated" : "\u2705 Stored";
46443
+ let formationNote = "";
46444
+ try {
46445
+ const formationConfig = {
46446
+ shadow: true,
46447
+ useLLM: false,
46448
+ minValueScore: 0.3,
46449
+ searchMemories: async (q3, limit, pid) => {
46450
+ const result = await compactSearch({ query: q3, limit, projectId: pid, status: "active" });
46451
+ if (result.entries.length === 0) return [];
46452
+ const details = await compactDetail(result.entries.map((e3) => e3.id));
46453
+ return details.documents.map((d3, i2) => ({
46454
+ id: Number(d3.id.replace("obs-", "")),
46455
+ observationId: d3.observationId,
46456
+ title: d3.title,
46457
+ narrative: d3.narrative,
46458
+ facts: d3.facts,
46459
+ entityName: d3.entityName,
46460
+ type: d3.type,
46461
+ score: result.entries[i2]?.score ?? 0
46462
+ }));
46463
+ },
46464
+ getObservation: (id) => {
46465
+ const o3 = getObservation(id);
46466
+ if (!o3) return null;
46467
+ return {
46468
+ id: o3.id,
46469
+ entityName: o3.entityName,
46470
+ type: o3.type,
46471
+ title: o3.title,
46472
+ narrative: o3.narrative,
46473
+ facts: o3.facts,
46474
+ topicKey: o3.topicKey
46475
+ };
46476
+ },
46477
+ getEntityNames: () => graphManager.getEntityNames()
46478
+ };
46479
+ const formed = await runFormation({
46480
+ entityName,
46481
+ type,
46482
+ title,
46483
+ narrative,
46484
+ facts: safeFacts,
46485
+ projectId: project.id,
46486
+ source: "explicit",
46487
+ topicKey
46488
+ }, formationConfig);
46489
+ formationNote = `
46490
+ \u{1F52C} Formation[shadow]: ${formed.evaluation.category} (${formed.evaluation.score.toFixed(2)}) | ${formed.resolution.action} | ${formed.pipeline.durationMs}ms`;
46491
+ if (formed.extraction.extractedFacts.length > 0) {
46492
+ formationNote += ` | +${formed.extraction.extractedFacts.length} facts`;
46493
+ }
46494
+ if (formed.extraction.titleImproved) formationNote += " | title\u2191";
46495
+ if (formed.extraction.entityResolved) formationNote += ` | entity\u2192${formed.entityName}`;
46496
+ if (formed.extraction.typeCorrected) formationNote += ` | type\u2192${formed.type}`;
46497
+ } catch {
46498
+ }
45821
46499
  return {
45822
46500
  content: [
45823
46501
  {
45824
46502
  type: "text",
45825
46503
  text: `${action} observation #${obs.id} "${title}" (~${obs.tokens} tokens)
45826
- Entity: ${entityName} | Type: ${type} | Project: ${project.id}${obs.topicKey ? ` | Topic: ${obs.topicKey}` : ""}${compactAction}${compressionNote}${enrichment}`
46504
+ Entity: ${entityName} | Type: ${type} | Project: ${project.id}${obs.topicKey ? ` | Topic: ${obs.topicKey}` : ""}${compactAction}${compressionNote}${enrichment}${formationNote}`
45827
46505
  }
45828
46506
  ]
45829
46507
  };
@@ -46178,6 +46856,53 @@ Archived memories can be restored manually if needed.` }]
46178
46856
  };
46179
46857
  }
46180
46858
  );
46859
+ server.registerTool(
46860
+ "memorix_formation_metrics",
46861
+ {
46862
+ title: "Formation Pipeline Metrics",
46863
+ description: "Show aggregated metrics from the Memory Formation Pipeline running in shadow mode. Reports value scores, resolution actions, fact extraction rates, and processing times.",
46864
+ inputSchema: {}
46865
+ },
46866
+ async () => {
46867
+ const summary = getMetricsSummary();
46868
+ if (summary.total === 0) {
46869
+ return {
46870
+ content: [{
46871
+ type: "text",
46872
+ text: "\u{1F4CA} Formation Pipeline: No metrics collected yet.\nStore some observations to start collecting shadow mode data."
46873
+ }]
46874
+ };
46875
+ }
46876
+ const lines = [
46877
+ "\u{1F4CA} **Formation Pipeline Metrics** (shadow mode)",
46878
+ "",
46879
+ `**Total observations processed:** ${summary.total}`,
46880
+ `**Average value score:** ${summary.avgValueScore.toFixed(3)}`,
46881
+ `**Average processing time:** ${summary.avgDurationMs.toFixed(1)}ms`,
46882
+ "",
46883
+ "### Quality Indicators",
46884
+ `- **Avg system-extracted facts:** ${summary.avgExtractedFacts.toFixed(1)} per observation`,
46885
+ `- **Title improved rate:** ${(summary.titleImprovedRate * 100).toFixed(1)}%`,
46886
+ `- **Entity resolved rate:** ${(summary.entityResolvedRate * 100).toFixed(1)}%`,
46887
+ `- **Type corrected rate:** ${(summary.typeCorectedRate * 100).toFixed(1)}%`,
46888
+ "",
46889
+ "### Value Categories"
46890
+ ];
46891
+ for (const [cat, count3] of Object.entries(summary.categoryBreakdown)) {
46892
+ const pct = (count3 / summary.total * 100).toFixed(1);
46893
+ const icon = cat === "core" ? "\u{1F7E2}" : cat === "contextual" ? "\u{1F7E1}" : "\u{1F534}";
46894
+ lines.push(`- ${icon} **${cat}:** ${count3} (${pct}%)`);
46895
+ }
46896
+ lines.push("", "### Resolution Actions");
46897
+ for (const [action, count3] of Object.entries(summary.resolutionBreakdown)) {
46898
+ const pct = (count3 / summary.total * 100).toFixed(1);
46899
+ lines.push(`- **${action}:** ${count3} (${pct}%)`);
46900
+ }
46901
+ return {
46902
+ content: [{ type: "text", text: lines.join("\n") }]
46903
+ };
46904
+ }
46905
+ );
46181
46906
  let enableKG = false;
46182
46907
  try {
46183
46908
  const { homedir: homedir17 } = await import("os");
@@ -47491,6 +48216,7 @@ var init_server4 = __esm({
47491
48216
  init_engine2();
47492
48217
  init_provider2();
47493
48218
  init_memory_manager();
48219
+ init_formation();
47494
48220
  lastInternalWriteMs = 0;
47495
48221
  markInternalWrite = () => {
47496
48222
  lastInternalWriteMs = Date.now();
@@ -47698,7 +48424,7 @@ var init_serve = __esm({
47698
48424
  });
47699
48425
  } else {
47700
48426
  console.error(`[memorix] cwd may not be a valid project, trying MCP roots protocol...`);
47701
- const mcpServer = new McpServer2({ name: "memorix", version: true ? "1.0.2" : "1.0.1" });
48427
+ const mcpServer = new McpServer2({ name: "memorix", version: true ? "1.0.3" : "1.0.1" });
47702
48428
  mcpServer.registerTool("_memorix_loading", {
47703
48429
  description: "Memorix is initializing, detecting project root...",
47704
48430
  inputSchema: {}
@@ -50448,6 +51174,28 @@ async function runHook() {
50448
51174
  const projectId = canonicalId;
50449
51175
  await initObservations2(dataDir);
50450
51176
  await storeObservation2({ ...observation, projectId });
51177
+ try {
51178
+ const { runFormation: runFormation2 } = await Promise.resolve().then(() => (init_formation(), formation_exports));
51179
+ runFormation2({
51180
+ entityName: observation.entityName,
51181
+ type: observation.type,
51182
+ title: observation.title,
51183
+ narrative: observation.narrative,
51184
+ facts: observation.facts,
51185
+ projectId,
51186
+ source: "hook"
51187
+ }, {
51188
+ shadow: true,
51189
+ useLLM: false,
51190
+ minValueScore: 0.3,
51191
+ searchMemories: async () => [],
51192
+ // Skip search in hooks for speed
51193
+ getObservation: () => null,
51194
+ getEntityNames: () => []
51195
+ }).catch(() => {
51196
+ });
51197
+ } catch {
51198
+ }
50451
51199
  const emoji2 = TYPE_EMOJI[observation.type] ?? "\u{1F4DD}";
50452
51200
  output.systemMessage = (output.systemMessage ?? "") + `
50453
51201
  ${emoji2} Memorix saved: ${observation.title} [${observation.type}]`;