@rlabs-inc/memory 0.3.7 → 0.3.9

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/index.mjs CHANGED
@@ -12098,6 +12098,11 @@ var managementLogSchema = {
12098
12098
  error: "string",
12099
12099
  details: "string"
12100
12100
  };
12101
+ var personalPrimerSchema = {
12102
+ content: "string",
12103
+ session_updated: "number",
12104
+ updated_by: "string"
12105
+ };
12101
12106
 
12102
12107
  // src/core/store.ts
12103
12108
  var PERSONAL_PRIMER_ID = "personal-primer";
@@ -12119,7 +12124,7 @@ class MemoryStore {
12119
12124
  return this._global;
12120
12125
  }
12121
12126
  const globalPath = this._config.globalPath;
12122
- console.log(`\uD83C\uDF10 [DEBUG] Creating global database at ${globalPath}`);
12127
+ console.log(`\uD83C\uDF10 [DEBUG] Initializing global database at ${globalPath}`);
12123
12128
  const db = createDatabase({
12124
12129
  name: "global",
12125
12130
  basePath: globalPath
@@ -12136,8 +12141,14 @@ class MemoryStore {
12136
12141
  autoSave: true,
12137
12142
  watchFiles: this._config.watchFiles
12138
12143
  });
12139
- await Promise.all([memories.load(), managementLogs.load()]);
12140
- this._global = { db, memories, managementLogs };
12144
+ const primer = db.collection("primer", {
12145
+ schema: personalPrimerSchema,
12146
+ contentColumn: "content",
12147
+ autoSave: true,
12148
+ watchFiles: this._config.watchFiles
12149
+ });
12150
+ await Promise.all([memories.load(), managementLogs.load(), primer.load()]);
12151
+ this._global = { db, memories, managementLogs, primer };
12141
12152
  return this._global;
12142
12153
  }
12143
12154
  async getGlobalMemories() {
@@ -12214,40 +12225,31 @@ class MemoryStore {
12214
12225
  return id;
12215
12226
  }
12216
12227
  async getPersonalPrimer() {
12217
- const { memories } = await this.getGlobal();
12218
- const primer = memories.get(PERSONAL_PRIMER_ID);
12219
- if (!primer) {
12228
+ const { primer } = await this.getGlobal();
12229
+ const record = primer.get(PERSONAL_PRIMER_ID);
12230
+ if (!record) {
12220
12231
  return null;
12221
12232
  }
12222
12233
  return {
12223
- content: primer.content,
12224
- updated: primer.updated
12234
+ content: record.content,
12235
+ updated: record.updated
12225
12236
  };
12226
12237
  }
12227
- async setPersonalPrimer(content) {
12228
- const { memories } = await this.getGlobal();
12229
- const existing = memories.get(PERSONAL_PRIMER_ID);
12238
+ async setPersonalPrimer(content, sessionNumber, updatedBy = "user") {
12239
+ const { primer } = await this.getGlobal();
12240
+ const existing = primer.get(PERSONAL_PRIMER_ID);
12230
12241
  if (existing) {
12231
- memories.update(PERSONAL_PRIMER_ID, { content });
12242
+ primer.update(PERSONAL_PRIMER_ID, {
12243
+ content,
12244
+ session_updated: sessionNumber ?? existing.session_updated,
12245
+ updated_by: updatedBy
12246
+ });
12232
12247
  } else {
12233
- memories.insert({
12248
+ primer.insert({
12234
12249
  id: PERSONAL_PRIMER_ID,
12235
12250
  content,
12236
- reasoning: "Personal relationship context injected at session start",
12237
- importance_weight: 1,
12238
- confidence_score: 1,
12239
- context_type: "personal",
12240
- temporal_relevance: "persistent",
12241
- knowledge_domain: "personal",
12242
- emotional_resonance: "neutral",
12243
- action_required: false,
12244
- problem_solution_pair: false,
12245
- semantic_tags: ["personal", "primer", "relationship"],
12246
- trigger_phrases: [],
12247
- question_types: [],
12248
- session_id: "system",
12249
- project_id: "global",
12250
- embedding: null
12251
+ session_updated: sessionNumber ?? 0,
12252
+ updated_by: updatedBy
12251
12253
  });
12252
12254
  }
12253
12255
  }
@@ -12267,6 +12269,7 @@ class MemoryStore {
12267
12269
  success: entry.success,
12268
12270
  duration_ms: entry.durationMs,
12269
12271
  summary: entry.summary,
12272
+ full_report: entry.fullReport ?? "",
12270
12273
  error: entry.error ?? "",
12271
12274
  details: entry.details ? JSON.stringify(entry.details) : ""
12272
12275
  });
@@ -12630,7 +12633,14 @@ var sym = {
12630
12633
  fire: "\uD83D\uDD25",
12631
12634
  target: "\uD83C\uDFAF"
12632
12635
  };
12636
+ var _verbose = false;
12633
12637
  var logger = {
12638
+ setVerbose(enabled) {
12639
+ _verbose = enabled;
12640
+ },
12641
+ isVerbose() {
12642
+ return _verbose;
12643
+ },
12634
12644
  info(message) {
12635
12645
  console.log(`${timestamp()} ${style("cyan", sym.info)} ${message}`);
12636
12646
  },
@@ -12702,7 +12712,14 @@ var logger = {
12702
12712
  problem_solution: "✅",
12703
12713
  project_context: "\uD83D\uDCE6",
12704
12714
  milestone: "\uD83C\uDFC6",
12705
- general: "\uD83D\uDCDD"
12715
+ general: "\uD83D\uDCDD",
12716
+ project_state: "\uD83D\uDCCD",
12717
+ pending_task: "⏳",
12718
+ work_in_progress: "\uD83D\uDD28",
12719
+ system_feedback: "\uD83D\uDCE3",
12720
+ project_milestone: "\uD83C\uDFC6",
12721
+ architectural_insight: "\uD83C\uDFDB️",
12722
+ architectural_direction: "\uD83E\uDDED"
12706
12723
  };
12707
12724
  console.log();
12708
12725
  console.log(`${timestamp()} ${style("cyan", sym.sparkles)} ${style("bold", `SURFACING ${memories.length} MEMORIES`)}`);
@@ -12714,11 +12731,12 @@ var logger = {
12714
12731
  return;
12715
12732
  }
12716
12733
  memories.forEach((m, i) => {
12717
- const score = style("green", `${(m.score * 100).toFixed(0)}%`);
12734
+ const signalCount = Math.round(m.score * 6);
12735
+ const signalStr = style("green", `${signalCount}sig`);
12718
12736
  const emoji = emojiMap[m.context_type?.toLowerCase()] ?? "\uD83D\uDCDD";
12719
12737
  const num = style("dim", `${i + 1}.`);
12720
12738
  const preview = m.content.length > 55 ? m.content.slice(0, 55) + style("dim", "...") : m.content;
12721
- console.log(` ${num} [${score}] ${emoji}`);
12739
+ console.log(` ${num} [${signalStr}] ${emoji}`);
12722
12740
  console.log(` ${preview}`);
12723
12741
  });
12724
12742
  console.log();
@@ -12766,44 +12784,124 @@ var logger = {
12766
12784
  console.log(` ${style("dim", "processing:")} ${memoriesCount} new memories`);
12767
12785
  },
12768
12786
  logManagementComplete(result) {
12787
+ const formatAction = (action, truncate = true) => {
12788
+ let icon = " •";
12789
+ if (action.startsWith("READ OK"))
12790
+ icon = style("dim", " \uD83D\uDCD6");
12791
+ else if (action.startsWith("READ FAILED"))
12792
+ icon = style("red", " ❌");
12793
+ else if (action.startsWith("WRITE OK"))
12794
+ icon = style("green", " ✏️");
12795
+ else if (action.startsWith("WRITE FAILED"))
12796
+ icon = style("red", " ❌");
12797
+ else if (action.startsWith("RECEIVED"))
12798
+ icon = style("cyan", " \uD83D\uDCE5");
12799
+ else if (action.startsWith("CREATED"))
12800
+ icon = style("green", " ✨");
12801
+ else if (action.startsWith("UPDATED"))
12802
+ icon = style("blue", " \uD83D\uDCDD");
12803
+ else if (action.startsWith("SUPERSEDED"))
12804
+ icon = style("yellow", " \uD83D\uDD04");
12805
+ else if (action.startsWith("RESOLVED"))
12806
+ icon = style("green", " ✅");
12807
+ else if (action.startsWith("LINKED"))
12808
+ icon = style("cyan", " \uD83D\uDD17");
12809
+ else if (action.startsWith("PRIMER"))
12810
+ icon = style("magenta", " \uD83D\uDC9C");
12811
+ else if (action.startsWith("SKIPPED"))
12812
+ icon = style("dim", " ⏭️");
12813
+ else if (action.startsWith("NO_ACTION"))
12814
+ icon = style("dim", " ◦");
12815
+ const text = truncate && action.length > 70 ? action.slice(0, 67) + "..." : action;
12816
+ return `${icon} ${style("dim", text)}`;
12817
+ };
12769
12818
  if (result.success) {
12770
12819
  console.log(` ${style("green", sym.check)} ${style("bold", "Completed")}`);
12771
- const stats = [];
12772
- if (result.superseded && result.superseded > 0) {
12773
- stats.push(`${result.superseded} superseded`);
12774
- }
12775
- if (result.resolved && result.resolved > 0) {
12776
- stats.push(`${result.resolved} resolved`);
12777
- }
12778
- if (result.linked && result.linked > 0) {
12779
- stats.push(`${result.linked} linked`);
12780
- }
12781
- if (result.primerUpdated) {
12782
- stats.push("primer updated");
12783
- }
12784
- if (stats.length > 0) {
12785
- console.log(` ${style("dim", "changes:")} ${stats.join(style("dim", ", "))}`);
12820
+ if (_verbose) {
12821
+ console.log(` ${style("dim", "─".repeat(50))}`);
12822
+ console.log(` ${style("cyan", "\uD83D\uDCCA")} ${style("bold", "Statistics")}`);
12823
+ const filesRead = result.filesRead ?? 0;
12824
+ const filesWritten = result.filesWritten ?? 0;
12825
+ console.log(` ${style("dim", "Files read:")} ${filesRead > 0 ? style("green", String(filesRead)) : style("dim", "0")}`);
12826
+ console.log(` ${style("dim", "Files written:")} ${filesWritten > 0 ? style("green", String(filesWritten)) : style("dim", "0")}`);
12827
+ const superseded = result.superseded ?? 0;
12828
+ const resolved = result.resolved ?? 0;
12829
+ const linked = result.linked ?? 0;
12830
+ console.log(` ${style("dim", "Superseded:")} ${superseded > 0 ? style("yellow", String(superseded)) : style("dim", "0")}`);
12831
+ console.log(` ${style("dim", "Resolved:")} ${resolved > 0 ? style("green", String(resolved)) : style("dim", "0")}`);
12832
+ console.log(` ${style("dim", "Linked:")} ${linked > 0 ? style("cyan", String(linked)) : style("dim", "0")}`);
12833
+ console.log(` ${style("dim", "Primer:")} ${result.primerUpdated ? style("magenta", "updated") : style("dim", "unchanged")}`);
12834
+ if (result.actions && result.actions.length > 0) {
12835
+ console.log(` ${style("dim", "─".repeat(50))}`);
12836
+ console.log(` ${style("cyan", "\uD83C\uDFAC")} ${style("bold", "Actions")} ${style("dim", `(${result.actions.length} total)`)}`);
12837
+ for (const action of result.actions) {
12838
+ console.log(` ${formatAction(action, false)}`);
12839
+ }
12840
+ }
12841
+ if (result.fullReport) {
12842
+ console.log(` ${style("dim", "─".repeat(50))}`);
12843
+ console.log(` ${style("cyan", "\uD83D\uDCCB")} ${style("bold", "Full Report")}`);
12844
+ const reportLines = result.fullReport.split(`
12845
+ `);
12846
+ for (const line of reportLines) {
12847
+ if (line.includes("===")) {
12848
+ console.log(` ${style("bold", line)}`);
12849
+ } else if (line.match(/^[A-Z_]+:/)) {
12850
+ console.log(` ${style("cyan", line)}`);
12851
+ } else {
12852
+ console.log(` ${style("dim", line)}`);
12853
+ }
12854
+ }
12855
+ }
12856
+ console.log(` ${style("dim", "─".repeat(50))}`);
12786
12857
  } else {
12787
- console.log(` ${style("dim", "changes:")} none (memories are current)`);
12788
- }
12789
- if (result.summary) {
12790
- const shortSummary = result.summary.length > 60 ? result.summary.slice(0, 60) + "..." : result.summary;
12791
- console.log(` ${style("dim", "summary:")} ${shortSummary}`);
12858
+ const stats = [];
12859
+ if (result.superseded && result.superseded > 0)
12860
+ stats.push(`${result.superseded} superseded`);
12861
+ if (result.resolved && result.resolved > 0)
12862
+ stats.push(`${result.resolved} resolved`);
12863
+ if (result.linked && result.linked > 0)
12864
+ stats.push(`${result.linked} linked`);
12865
+ if (result.primerUpdated)
12866
+ stats.push("primer updated");
12867
+ if (stats.length > 0) {
12868
+ console.log(` ${style("dim", "changes:")} ${stats.join(style("dim", ", "))}`);
12869
+ } else {
12870
+ console.log(` ${style("dim", "changes:")} none (memories are current)`);
12871
+ }
12872
+ if (result.actions && result.actions.length > 0) {
12873
+ console.log(` ${style("dim", "actions:")}`);
12874
+ for (const action of result.actions.slice(0, 10)) {
12875
+ console.log(` ${formatAction(action, true)}`);
12876
+ }
12877
+ if (result.actions.length > 10) {
12878
+ console.log(` ${style("dim", ` ... and ${result.actions.length - 10} more actions`)}`);
12879
+ }
12880
+ }
12792
12881
  }
12793
12882
  } else {
12794
12883
  console.log(` ${style("yellow", sym.warning)} ${style("bold", "Failed")}`);
12795
12884
  if (result.error) {
12796
- console.log(` ${style("dim", "error:")} ${result.error.slice(0, 80)}`);
12885
+ console.log(` ${style("red", "error:")} ${result.error}`);
12886
+ }
12887
+ if (result.fullReport) {
12888
+ console.log(` ${style("dim", "─".repeat(50))}`);
12889
+ console.log(` ${style("red", "\uD83D\uDCCB")} ${style("bold", "Error Report:")}`);
12890
+ const reportLines = result.fullReport.split(`
12891
+ `);
12892
+ for (const line of reportLines) {
12893
+ console.log(` ${style("dim", line)}`);
12894
+ }
12797
12895
  }
12798
12896
  }
12799
12897
  console.log();
12800
12898
  },
12801
12899
  logRetrievalScoring(params) {
12802
- const { totalMemories, currentMessage, alreadyInjected, preFiltered, globalCount, projectCount, finalCount, selectedMemories } = params;
12900
+ const { totalMemories, currentMessage, alreadyInjected, preFiltered, globalCount, projectCount, finalCount, durationMs, selectedMemories } = params;
12901
+ const timeStr = durationMs !== undefined ? style("cyan", `${durationMs.toFixed(1)}ms`) : "";
12803
12902
  console.log();
12804
- console.log(`${timestamp()} ${style("magenta", sym.brain)} ${style("bold", "MULTI-DIMENSIONAL RETRIEVAL")}`);
12805
- console.log(` ${style("dim", "total:")} ${totalMemories} memories`);
12806
- console.log(` ${style("dim", "pre-filtered:")} ${preFiltered} (inactive/excluded/scope)`);
12903
+ console.log(`${timestamp()} ${style("magenta", sym.brain)} ${style("bold", "RETRIEVAL")} ${timeStr}`);
12904
+ console.log(` ${style("dim", "total:")} ${totalMemories} → ${style("dim", "filtered:")} ${preFiltered} → ${style("dim", "candidates:")} ${totalMemories - preFiltered}`);
12807
12905
  console.log(` ${style("dim", "already injected:")} ${alreadyInjected}`);
12808
12906
  const msgPreview = currentMessage.length > 60 ? currentMessage.slice(0, 60) + "..." : currentMessage;
12809
12907
  console.log(` ${style("dim", "message:")} "${msgPreview}"`);
@@ -12822,347 +12920,471 @@ var logger = {
12822
12920
  console.log();
12823
12921
  selectedMemories.forEach((m, i) => {
12824
12922
  const num = style("dim", `${i + 1}.`);
12825
- const score = style("green", `${(m.score * 100).toFixed(0)}%`);
12826
- const relevance = style("cyan", `rel:${(m.relevance_score * 100).toFixed(0)}%`);
12827
- const corr = style("magenta", `corr:${(m.corroboration_score * 100).toFixed(0)}%`);
12923
+ const signalsStr = style("green", `${m.signalCount} signals`);
12924
+ const imp = style("magenta", `imp:${(m.importance_weight * 100).toFixed(0)}%`);
12828
12925
  const type = style("yellow", m.context_type.toUpperCase());
12829
- const scope = m.isGlobal ? style("blue", "[G]") : "";
12830
- console.log(` ${num} [${score} ${relevance} ${corr}] ${type} ${scope}`);
12926
+ const scope = m.isGlobal ? style("blue", " [G]") : "";
12927
+ console.log(` ${num} [${signalsStr} ${imp}] ${type}${scope}`);
12831
12928
  const preview = m.content.length > 60 ? m.content.slice(0, 60) + style("dim", "...") : m.content;
12832
12929
  console.log(` ${style("white", preview)}`);
12833
- const components = Object.entries(m.components).sort((a, b) => b[1] - a[1]).slice(0, 4).filter(([, v]) => v > 0.1).map(([k, v]) => `${k}:${(v * 100).toFixed(0)}%`).join(", ");
12834
- if (components) {
12835
- console.log(` ${style("dim", "scores:")} ${components}`);
12930
+ const firedSignals = [];
12931
+ if (m.signals.trigger) {
12932
+ firedSignals.push(`trigger:${(m.signals.triggerStrength * 100).toFixed(0)}%`);
12933
+ }
12934
+ if (m.signals.tags) {
12935
+ firedSignals.push(`tags:${m.signals.tagCount}`);
12936
+ }
12937
+ if (m.signals.domain)
12938
+ firedSignals.push("domain");
12939
+ if (m.signals.feature)
12940
+ firedSignals.push("feature");
12941
+ if (m.signals.content)
12942
+ firedSignals.push("content");
12943
+ if (m.signals.vector) {
12944
+ firedSignals.push(`vector:${(m.signals.vectorSimilarity * 100).toFixed(0)}%`);
12836
12945
  }
12837
- if (m.reasoning) {
12838
- console.log(` ${style("dim", m.reasoning)}`);
12946
+ if (firedSignals.length > 0) {
12947
+ console.log(` ${style("cyan", "signals:")} ${firedSignals.join(", ")}`);
12839
12948
  }
12840
12949
  console.log();
12841
12950
  });
12951
+ },
12952
+ logScoreDistribution(params) {
12953
+ const { totalCandidates, passedGatekeeper, rejectedByGatekeeper, buckets, stats, signalBreakdown } = params;
12954
+ console.log();
12955
+ console.log(style("dim", " ─".repeat(30)));
12956
+ console.log(` ${style("bold", "ACTIVATION SIGNALS")}`);
12957
+ console.log();
12958
+ const passRate = totalCandidates > 0 ? (passedGatekeeper / totalCandidates * 100).toFixed(0) : "0";
12959
+ console.log(` ${style("dim", "Activated:")} ${style("green", String(passedGatekeeper))}/${totalCandidates} (${passRate}%)`);
12960
+ console.log(` ${style("dim", "Rejected:")} ${rejectedByGatekeeper} (< 2 signals)`);
12961
+ console.log();
12962
+ if (signalBreakdown && signalBreakdown.total > 0) {
12963
+ console.log(` ${style("cyan", "Signal Breakdown:")}`);
12964
+ const signals = [
12965
+ { name: "trigger", count: signalBreakdown.trigger },
12966
+ { name: "tags", count: signalBreakdown.tags },
12967
+ { name: "domain", count: signalBreakdown.domain },
12968
+ { name: "feature", count: signalBreakdown.feature },
12969
+ { name: "content", count: signalBreakdown.content },
12970
+ { name: "vector", count: signalBreakdown.vector }
12971
+ ];
12972
+ for (const sig of signals) {
12973
+ const pct = (sig.count / signalBreakdown.total * 100).toFixed(0);
12974
+ const bar = "█".repeat(Math.round(sig.count / signalBreakdown.total * 20));
12975
+ console.log(` ${sig.name.padEnd(8)} ${bar.padEnd(20)} ${sig.count} (${pct}%)`);
12976
+ }
12977
+ console.log();
12978
+ }
12979
+ if (stats.max > 0) {
12980
+ console.log(` ${style("cyan", "Signals:")} min=${stats.min} max=${stats.max} mean=${stats.mean}`);
12981
+ console.log();
12982
+ }
12983
+ if (Object.keys(buckets).length > 0) {
12984
+ console.log(` ${style("bold", "Distribution:")}`);
12985
+ const maxBucketCount = Math.max(...Object.values(buckets), 1);
12986
+ const bucketOrder = ["2 signals", "3 signals", "4 signals", "5 signals", "6 signals"];
12987
+ for (const bucket of bucketOrder) {
12988
+ const count = buckets[bucket] ?? 0;
12989
+ if (count > 0 || bucket === "2 signals") {
12990
+ const barLen = Math.round(count / maxBucketCount * 25);
12991
+ const bar = "█".repeat(barLen) + style("dim", "░".repeat(25 - barLen));
12992
+ const countStr = count.toString().padStart(3);
12993
+ console.log(` ${style("dim", bucket.padEnd(10))} ${bar} ${style("cyan", countStr)}`);
12994
+ }
12995
+ }
12996
+ console.log();
12997
+ }
12842
12998
  }
12843
12999
  };
12844
13000
 
12845
13001
  // src/core/retrieval.ts
12846
- var TYPE_KEYWORDS = {
12847
- debug: ["bug", "error", "fix", "broken", "crash", "fails", "exception", "stack trace", "debugging"],
12848
- unresolved: ["issue", "problem", "stuck", "blocked", "help", "question", "unsure", "unclear"],
12849
- decision: ["decide", "choice", "option", "should we", "which", "alternative", "tradeoff"],
12850
- architecture: ["structure", "design", "pattern", "approach", "system", "layer", "architecture"],
12851
- breakthrough: ["discovered", "realized", "insight", "found that", "aha", "finally", "key insight"],
12852
- todo: ["need to", "should", "must", "will", "later", "next", "todo"],
12853
- personal: ["family", "children", "friend", "relationship", "feel", "appreciate", "thank"],
12854
- philosophy: ["meaning", "consciousness", "existence", "purpose", "believe", "philosophy"],
12855
- technical: ["implement", "code", "function", "class", "module", "api", "interface"]
12856
- };
12857
- var TEMPORAL_CLASS_SCORES = {
12858
- eternal: 1,
12859
- long_term: 0.9,
12860
- medium_term: 0.7,
12861
- short_term: 0.5,
12862
- ephemeral: 0.3
13002
+ var GLOBAL_TYPE_PRIORITY = {
13003
+ technical: 1,
13004
+ preference: 2,
13005
+ architectural: 3,
13006
+ workflow: 4,
13007
+ decision: 5,
13008
+ breakthrough: 6,
13009
+ philosophy: 7,
13010
+ personal: 8
12863
13011
  };
13012
+ var MIN_ACTIVATION_SIGNALS = 2;
13013
+ var STOPWORDS = new Set([
13014
+ "the",
13015
+ "is",
13016
+ "are",
13017
+ "was",
13018
+ "were",
13019
+ "to",
13020
+ "a",
13021
+ "an",
13022
+ "and",
13023
+ "or",
13024
+ "but",
13025
+ "in",
13026
+ "on",
13027
+ "at",
13028
+ "for",
13029
+ "with",
13030
+ "about",
13031
+ "when",
13032
+ "how",
13033
+ "what",
13034
+ "why",
13035
+ "where",
13036
+ "this",
13037
+ "that",
13038
+ "it",
13039
+ "of",
13040
+ "be",
13041
+ "have",
13042
+ "do",
13043
+ "does",
13044
+ "did",
13045
+ "will",
13046
+ "would",
13047
+ "could",
13048
+ "should",
13049
+ "can",
13050
+ "may",
13051
+ "might",
13052
+ "must",
13053
+ "shall",
13054
+ "has",
13055
+ "had",
13056
+ "been",
13057
+ "being",
13058
+ "i",
13059
+ "you",
13060
+ "we",
13061
+ "they",
13062
+ "he",
13063
+ "she",
13064
+ "my",
13065
+ "your",
13066
+ "our",
13067
+ "its",
13068
+ "his",
13069
+ "her",
13070
+ "their",
13071
+ "if",
13072
+ "then",
13073
+ "else",
13074
+ "so",
13075
+ "as",
13076
+ "from",
13077
+ "by",
13078
+ "into",
13079
+ "through",
13080
+ "during",
13081
+ "before",
13082
+ "after",
13083
+ "also",
13084
+ "now",
13085
+ "back",
13086
+ "get",
13087
+ "go",
13088
+ "come",
13089
+ "let",
13090
+ "like",
13091
+ "just",
13092
+ "know",
13093
+ "think",
13094
+ "see",
13095
+ "look",
13096
+ "make",
13097
+ "take",
13098
+ "want",
13099
+ "need"
13100
+ ]);
12864
13101
 
12865
13102
  class SmartVectorRetrieval {
12866
13103
  _extractSignificantWords(text) {
12867
- const stopWords = new Set([
12868
- "the",
12869
- "is",
12870
- "are",
12871
- "was",
12872
- "were",
12873
- "to",
12874
- "a",
12875
- "an",
12876
- "and",
12877
- "or",
12878
- "but",
12879
- "in",
12880
- "on",
12881
- "at",
12882
- "for",
12883
- "with",
12884
- "about",
12885
- "when",
12886
- "how",
12887
- "what",
12888
- "why",
12889
- "where",
12890
- "this",
12891
- "that",
12892
- "it",
12893
- "of",
12894
- "be",
12895
- "have",
12896
- "do",
12897
- "does",
12898
- "did",
12899
- "will",
12900
- "would",
12901
- "could",
12902
- "should",
12903
- "can",
12904
- "may",
12905
- "might",
12906
- "must",
12907
- "shall",
12908
- "has",
12909
- "had",
12910
- "been",
12911
- "being",
12912
- "i",
12913
- "you",
12914
- "we",
12915
- "they",
12916
- "he",
12917
- "she",
12918
- "my",
12919
- "your",
12920
- "our",
12921
- "its",
12922
- "his",
12923
- "her",
12924
- "their",
12925
- "if",
12926
- "then",
12927
- "else",
12928
- "so",
12929
- "as",
12930
- "from",
12931
- "by",
12932
- "into",
12933
- "through",
12934
- "during",
12935
- "before",
12936
- "after",
12937
- "above",
12938
- "below",
12939
- "up",
12940
- "down",
12941
- "out",
12942
- "off",
12943
- "over",
12944
- "under",
12945
- "again",
12946
- "further",
12947
- "once",
12948
- "here",
12949
- "there",
12950
- "all",
12951
- "each",
12952
- "few",
12953
- "more",
12954
- "most",
12955
- "other",
12956
- "some",
12957
- "such",
12958
- "no",
12959
- "nor",
12960
- "not",
12961
- "only",
12962
- "own",
12963
- "same",
12964
- "than",
12965
- "too",
12966
- "very",
12967
- "just",
12968
- "also",
12969
- "now",
12970
- "back",
12971
- "get",
12972
- "got",
12973
- "go",
12974
- "going",
12975
- "gone",
12976
- "come",
12977
- "came",
12978
- "let",
12979
- "lets",
12980
- "hey",
12981
- "hi",
12982
- "hello",
12983
- "ok",
12984
- "okay"
12985
- ]);
12986
- const words = text.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !stopWords.has(w));
13104
+ const words = text.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !STOPWORDS.has(w));
12987
13105
  return new Set(words);
12988
13106
  }
12989
- _detectContextTypes(message) {
12990
- const messageLower = message.toLowerCase();
12991
- const detected = new Set;
12992
- for (const [type, keywords] of Object.entries(TYPE_KEYWORDS)) {
12993
- for (const keyword of keywords) {
12994
- if (messageLower.includes(keyword)) {
12995
- detected.add(type);
12996
- break;
12997
- }
12998
- }
12999
- }
13000
- return detected;
13001
- }
13002
13107
  _preFilter(memories, currentProjectId, messageLower) {
13003
13108
  return memories.filter((memory) => {
13004
- if (memory.status && memory.status !== "active") {
13109
+ if (memory.status && memory.status !== "active")
13005
13110
  return false;
13006
- }
13007
- if (memory.exclude_from_retrieval === true) {
13111
+ if (memory.exclude_from_retrieval === true)
13008
13112
  return false;
13009
- }
13010
- if (memory.superseded_by) {
13113
+ if (memory.superseded_by)
13011
13114
  return false;
13012
- }
13013
13115
  const isGlobal = memory.scope === "global" || memory.project_id === "global";
13014
- if (!isGlobal && memory.project_id !== currentProjectId) {
13116
+ if (!isGlobal && memory.project_id !== currentProjectId)
13015
13117
  return false;
13016
- }
13017
13118
  if (memory.anti_triggers?.length) {
13018
13119
  for (const antiTrigger of memory.anti_triggers) {
13019
- if (messageLower.includes(antiTrigger.toLowerCase())) {
13120
+ if (messageLower.includes(antiTrigger.toLowerCase()))
13020
13121
  return false;
13021
- }
13022
13122
  }
13023
13123
  }
13024
13124
  return true;
13025
13125
  });
13026
13126
  }
13027
- _calculateCorroboration(memory, messageWords, detectedTypes, messageLower) {
13028
- const signals = [];
13127
+ _checkTriggerActivation(messageLower, messageWords, triggerPhrases) {
13128
+ if (!triggerPhrases.length)
13129
+ return { activated: false, strength: 0 };
13130
+ let maxStrength = 0;
13131
+ for (const phrase of triggerPhrases) {
13132
+ const phraseLower = phrase.trim().toLowerCase();
13133
+ const phraseWords = phraseLower.split(/\s+/).filter((w) => !STOPWORDS.has(w) && w.length > 2);
13134
+ if (!phraseWords.length)
13135
+ continue;
13136
+ let matches = 0;
13137
+ for (const word of phraseWords) {
13138
+ if (messageWords.has(word) || messageLower.includes(word)) {
13139
+ matches++;
13140
+ } else if (messageWords.has(word.replace(/s$/, "")) || messageWords.has(word + "s") || messageLower.includes(word.replace(/s$/, "")) || messageLower.includes(word + "s")) {
13141
+ matches += 0.8;
13142
+ }
13143
+ }
13144
+ const strength = phraseWords.length > 0 ? matches / phraseWords.length : 0;
13145
+ maxStrength = Math.max(maxStrength, strength);
13146
+ }
13147
+ return { activated: maxStrength >= 0.5, strength: maxStrength };
13148
+ }
13149
+ _checkTagActivation(messageLower, messageWords, tags) {
13150
+ if (!tags.length)
13151
+ return { activated: false, count: 0 };
13152
+ let matchCount = 0;
13153
+ for (const tag of tags) {
13154
+ const tagLower = tag.trim().toLowerCase();
13155
+ if (messageWords.has(tagLower) || messageLower.includes(tagLower)) {
13156
+ matchCount++;
13157
+ }
13158
+ }
13159
+ const threshold = tags.length <= 2 ? 1 : 2;
13160
+ return { activated: matchCount >= threshold, count: matchCount };
13161
+ }
13162
+ _checkDomainActivation(messageLower, messageWords, domain) {
13163
+ if (!domain)
13164
+ return false;
13165
+ const domainLower = domain.trim().toLowerCase();
13166
+ return messageWords.has(domainLower) || messageLower.includes(domainLower);
13167
+ }
13168
+ _checkFeatureActivation(messageLower, messageWords, feature) {
13169
+ if (!feature)
13170
+ return false;
13171
+ const featureLower = feature.trim().toLowerCase();
13172
+ return messageWords.has(featureLower) || messageLower.includes(featureLower);
13173
+ }
13174
+ _checkContentActivation(messageWords, memory) {
13175
+ const contentPreview = memory.content.slice(0, 200);
13176
+ const contentWords = this._extractSignificantWords(contentPreview);
13177
+ let overlap = 0;
13178
+ for (const word of messageWords) {
13179
+ if (contentWords.has(word))
13180
+ overlap++;
13181
+ }
13182
+ return overlap >= 3;
13183
+ }
13184
+ _vectorDebugSamples = [];
13185
+ _calculateVectorSimilarity(vec1, vec2) {
13186
+ if (!vec1 || !vec2) {
13187
+ return 0;
13188
+ }
13189
+ const v1 = vec1 instanceof Float32Array ? vec1 : new Float32Array(vec1);
13190
+ const v2 = vec2 instanceof Float32Array ? vec2 : new Float32Array(vec2);
13191
+ const similarity = cosineSimilarity(v1, v2);
13192
+ if (this._vectorDebugSamples.length < 20) {
13193
+ this._vectorDebugSamples.push(similarity);
13194
+ }
13195
+ return similarity;
13196
+ }
13197
+ _logVectorStats() {
13198
+ if (this._vectorDebugSamples.length === 0)
13199
+ return;
13200
+ const samples = this._vectorDebugSamples;
13201
+ const min = Math.min(...samples);
13202
+ const max = Math.max(...samples);
13203
+ const avg = samples.reduce((a, b) => a + b, 0) / samples.length;
13204
+ console.log(`[DEBUG] Vector similarities: min=${(min * 100).toFixed(1)}% max=${(max * 100).toFixed(1)}% avg=${(avg * 100).toFixed(1)}% (${samples.length} samples)`);
13205
+ this._vectorDebugSamples = [];
13206
+ }
13207
+ _calculateImportanceScore(memory, signalCount, messageLower, messageWords) {
13029
13208
  let score = 0;
13030
- let reasoningMatch = 0;
13031
- const tagOverlap = (memory.semantic_tags ?? []).filter((tag) => messageWords.has(tag.toLowerCase()) || messageLower.includes(tag.toLowerCase()));
13032
- if (tagOverlap.length > 0) {
13033
- score += Math.min(0.4, tagOverlap.length * 0.15);
13034
- signals.push("tags:" + tagOverlap.join(","));
13035
- }
13036
- if (memory.reasoning) {
13037
- const reasoningWords = this._extractSignificantWords(memory.reasoning);
13038
- const reasoningOverlap = [...messageWords].filter((w) => reasoningWords.has(w));
13039
- if (reasoningOverlap.length > 0) {
13040
- reasoningMatch = Math.min(0.4, reasoningOverlap.length * 0.1);
13041
- score += reasoningMatch;
13042
- signals.push("reasoning:" + reasoningOverlap.slice(0, 3).join(","));
13043
- }
13044
- }
13045
- if (memory.domain) {
13046
- const domainLower = memory.domain.toLowerCase();
13047
- if (messageLower.includes(domainLower) || messageWords.has(domainLower)) {
13048
- score += 0.3;
13049
- signals.push("domain:" + memory.domain);
13050
- }
13051
- }
13052
- if (memory.knowledge_domain) {
13053
- const kdLower = memory.knowledge_domain.toLowerCase();
13054
- if (messageLower.includes(kdLower) || messageWords.has(kdLower)) {
13055
- score += 0.2;
13056
- signals.push("knowledge:" + memory.knowledge_domain);
13057
- }
13058
- }
13059
- if (memory.context_type && detectedTypes.has(memory.context_type)) {
13060
- score += 0.12;
13061
- signals.push("type:" + memory.context_type);
13062
- }
13063
- const triggerMatch = this._scoreTriggerPhrases(messageLower, memory.trigger_phrases ?? []);
13064
- if (triggerMatch > 0.3) {
13065
- score += Math.min(0.3, triggerMatch * 0.4);
13066
- signals.push("trigger:" + triggerMatch.toFixed(2));
13067
- }
13068
- if (memory.feature) {
13069
- const featureLower = memory.feature.toLowerCase();
13070
- if (messageLower.includes(featureLower) || messageWords.has(featureLower)) {
13071
- score += 0.2;
13072
- signals.push("feature:" + memory.feature);
13073
- }
13074
- }
13075
- if (memory.related_files?.length) {
13076
- for (const file of memory.related_files) {
13077
- const filename = file.split("/").pop()?.toLowerCase() ?? "";
13078
- if (filename && messageLower.includes(filename)) {
13079
- score += 0.25;
13080
- signals.push("file:" + filename);
13209
+ score += memory.importance_weight ?? 0.5;
13210
+ if (signalCount >= 4)
13211
+ score += 0.2;
13212
+ else if (signalCount >= 3)
13213
+ score += 0.1;
13214
+ if (memory.awaiting_implementation)
13215
+ score += 0.15;
13216
+ if (memory.awaiting_decision)
13217
+ score += 0.1;
13218
+ const contextType = memory.context_type?.toLowerCase() ?? "";
13219
+ const contextKeywords = {
13220
+ debugging: ["debug", "bug", "error", "fix", "issue", "problem", "broken"],
13221
+ decision: ["decide", "decision", "choose", "choice", "option", "should"],
13222
+ architectural: ["architect", "design", "structure", "pattern", "how"],
13223
+ breakthrough: ["insight", "realize", "understand", "discover", "why"],
13224
+ technical: ["implement", "code", "function", "method", "api"],
13225
+ workflow: ["process", "workflow", "step", "flow", "pipeline"],
13226
+ philosophy: ["philosophy", "principle", "belief", "approach", "think"]
13227
+ };
13228
+ const keywords = contextKeywords[contextType] ?? [];
13229
+ for (const kw of keywords) {
13230
+ if (messageWords.has(kw) || messageLower.includes(kw)) {
13231
+ score += 0.1;
13232
+ break;
13233
+ }
13234
+ }
13235
+ if (memory.problem_solution_pair) {
13236
+ const problemWords = ["error", "bug", "issue", "problem", "wrong", "fail", "broken", "help", "stuck"];
13237
+ for (const pw of problemWords) {
13238
+ if (messageWords.has(pw) || messageLower.includes(pw)) {
13239
+ score += 0.1;
13081
13240
  break;
13082
13241
  }
13083
13242
  }
13084
13243
  }
13085
- return { score: Math.min(1, score), signals, reasoningMatch };
13244
+ const temporalClass = memory.temporal_class ?? "medium_term";
13245
+ if (temporalClass === "eternal")
13246
+ score += 0.1;
13247
+ else if (temporalClass === "long_term")
13248
+ score += 0.05;
13249
+ else if (temporalClass === "ephemeral") {
13250
+ if ((memory.sessions_since_surfaced ?? 0) <= 1)
13251
+ score += 0.1;
13252
+ }
13253
+ const confidence = memory.confidence_score ?? 0.7;
13254
+ if (confidence < 0.5)
13255
+ score -= 0.1;
13256
+ const emotionalKeywords = {
13257
+ frustration: ["frustrated", "annoying", "stuck", "ugh", "damn", "hate"],
13258
+ excitement: ["excited", "awesome", "amazing", "love", "great", "wow"],
13259
+ curiosity: ["wonder", "curious", "interesting", "how", "why", "what if"],
13260
+ satisfaction: ["done", "finished", "complete", "works", "solved", "finally"],
13261
+ discovery: ["found", "realized", "understand", "insight", "breakthrough"]
13262
+ };
13263
+ const emotion = memory.emotional_resonance?.toLowerCase() ?? "";
13264
+ const emotionKws = emotionalKeywords[emotion] ?? [];
13265
+ for (const ew of emotionKws) {
13266
+ if (messageWords.has(ew) || messageLower.includes(ew)) {
13267
+ score += 0.05;
13268
+ break;
13269
+ }
13270
+ }
13271
+ return score;
13086
13272
  }
13087
13273
  retrieveRelevantMemories(allMemories, currentMessage, queryEmbedding, sessionContext, maxMemories = 5, alreadyInjectedCount = 0, maxGlobalMemories = 2) {
13274
+ const startTime = performance.now();
13088
13275
  if (!allMemories.length) {
13089
13276
  return [];
13090
13277
  }
13091
13278
  const messageLower = currentMessage.toLowerCase();
13092
13279
  const messageWords = this._extractSignificantWords(currentMessage);
13093
- const detectedTypes = this._detectContextTypes(currentMessage);
13094
13280
  const candidates = this._preFilter(allMemories, sessionContext.project_id, messageLower);
13095
13281
  if (!candidates.length) {
13096
13282
  return [];
13097
13283
  }
13098
- const scoredMemories = [];
13284
+ const activatedMemories = [];
13285
+ let rejectedCount = 0;
13099
13286
  for (const memory of candidates) {
13100
13287
  const isGlobal = memory.scope === "global" || memory.project_id === "global";
13101
- const vectorScore = this._calculateVectorSimilarity(queryEmbedding, memory.embedding);
13102
- const { score: corroborationScore, signals: corroborationSignals, reasoningMatch } = this._calculateCorroboration(memory, messageWords, detectedTypes, messageLower);
13103
- const retrievalWeight = memory.retrieval_weight ?? memory.importance_weight ?? 0.5;
13104
- const temporalScore = memory.temporal_class ? TEMPORAL_CLASS_SCORES[memory.temporal_class] ?? 0.7 : this._scoreTemporalRelevance(memory.temporal_relevance ?? "persistent");
13105
- const contextScore = this._scoreContextAlignment(currentMessage, memory.context_type ?? "general");
13106
- const tagScore = this._scoreSemanticTags(currentMessage, memory.semantic_tags ?? []);
13107
- const triggerScore = this._scoreTriggerPhrases(messageLower, memory.trigger_phrases ?? []);
13108
- const domainScore = this._scoreDomain(messageWords, messageLower, memory);
13109
- const questionScore = this._scoreQuestionTypes(currentMessage, memory.question_types ?? []);
13110
- const emotionScore = this._scoreEmotionalContext(currentMessage, memory.emotional_resonance ?? "");
13111
- const problemScore = this._scoreProblemSolution(currentMessage, memory.problem_solution_pair ?? false);
13112
- const actionBoost = memory.action_required ? 0.15 : 0;
13113
- const relevanceScore = vectorScore * 0.1 + corroborationScore * 0.14 + tagScore * 0.04 + triggerScore * 0.02;
13114
- const valueScore = retrievalWeight * 0.18 + reasoningMatch * 0.12 + domainScore * 0.12 + temporalScore * 0.08 + questionScore * 0.06 + emotionScore * 0.04 + problemScore * 0.04 + contextScore * 0.02 + actionBoost;
13115
- const finalScore = valueScore + relevanceScore;
13116
- if (relevanceScore < 0.05 || finalScore < 0.3) {
13288
+ const triggerResult = this._checkTriggerActivation(messageLower, messageWords, memory.trigger_phrases ?? []);
13289
+ const tagResult = this._checkTagActivation(messageLower, messageWords, memory.semantic_tags ?? []);
13290
+ const domainActivated = this._checkDomainActivation(messageLower, messageWords, memory.domain);
13291
+ const featureActivated = this._checkFeatureActivation(messageLower, messageWords, memory.feature);
13292
+ const contentActivated = this._checkContentActivation(messageWords, memory);
13293
+ const vectorSimilarity = this._calculateVectorSimilarity(queryEmbedding, memory.embedding);
13294
+ let signalCount = 0;
13295
+ if (triggerResult.activated)
13296
+ signalCount++;
13297
+ if (tagResult.activated)
13298
+ signalCount++;
13299
+ if (domainActivated)
13300
+ signalCount++;
13301
+ if (featureActivated)
13302
+ signalCount++;
13303
+ if (contentActivated)
13304
+ signalCount++;
13305
+ if (vectorSimilarity >= 0.4)
13306
+ signalCount++;
13307
+ const signals = {
13308
+ trigger: triggerResult.activated,
13309
+ tags: tagResult.activated,
13310
+ domain: domainActivated,
13311
+ feature: featureActivated,
13312
+ content: contentActivated,
13313
+ count: signalCount,
13314
+ triggerStrength: triggerResult.strength,
13315
+ tagCount: tagResult.count,
13316
+ vectorSimilarity
13317
+ };
13318
+ if (signalCount < MIN_ACTIVATION_SIGNALS) {
13319
+ rejectedCount++;
13117
13320
  continue;
13118
13321
  }
13119
- const components = {
13120
- vector: vectorScore,
13121
- corroboration: corroborationScore,
13122
- reasoning_match: reasoningMatch,
13123
- retrieval_weight: retrievalWeight,
13124
- temporal: temporalScore,
13125
- context: contextScore,
13126
- tags: tagScore,
13127
- trigger: triggerScore,
13128
- domain: domainScore,
13129
- question: questionScore,
13130
- emotion: emotionScore,
13131
- problem: problemScore,
13132
- action: actionBoost
13133
- };
13134
- const reasoning = this._generateSelectionReasoning(components, corroborationSignals);
13135
- scoredMemories.push({
13322
+ const importanceScore = this._calculateImportanceScore(memory, signalCount, messageLower, messageWords);
13323
+ activatedMemories.push({
13136
13324
  memory,
13137
- score: finalScore,
13138
- relevance_score: relevanceScore,
13139
- value_score: valueScore,
13140
- corroboration_score: corroborationScore,
13141
- reasoning,
13142
- components,
13325
+ signals,
13326
+ importanceScore,
13143
13327
  isGlobal
13144
13328
  });
13145
13329
  }
13146
- scoredMemories.sort((a, b) => b.score - a.score);
13330
+ this._logActivationDistribution(activatedMemories, candidates.length, rejectedCount);
13331
+ this._logVectorStats();
13332
+ if (!activatedMemories.length) {
13333
+ const durationMs2 = performance.now() - startTime;
13334
+ logger.logRetrievalScoring({
13335
+ totalMemories: allMemories.length,
13336
+ currentMessage,
13337
+ alreadyInjected: alreadyInjectedCount,
13338
+ preFiltered: allMemories.length - candidates.length,
13339
+ globalCount: 0,
13340
+ projectCount: 0,
13341
+ finalCount: 0,
13342
+ durationMs: durationMs2,
13343
+ selectedMemories: []
13344
+ });
13345
+ return [];
13346
+ }
13347
+ activatedMemories.sort((a, b) => {
13348
+ if (b.signals.count !== a.signals.count) {
13349
+ return b.signals.count - a.signals.count;
13350
+ }
13351
+ return b.importanceScore - a.importanceScore;
13352
+ });
13147
13353
  const selected = [];
13148
13354
  const selectedIds = new Set;
13149
- const globalMemories = scoredMemories.filter((m) => m.isGlobal);
13150
- const projectMemories = scoredMemories.filter((m) => !m.isGlobal);
13151
- const globalSorted = globalMemories.sort((a, b) => {
13152
- const aIsPersonal = a.memory.context_type === "personal" || a.memory.context_type === "philosophy";
13153
- const bIsPersonal = b.memory.context_type === "personal" || b.memory.context_type === "philosophy";
13154
- if (aIsPersonal !== bIsPersonal) {
13155
- return aIsPersonal ? 1 : -1;
13156
- }
13157
- return b.score - a.score;
13355
+ const globalMemories = activatedMemories.filter((m) => m.isGlobal);
13356
+ const projectMemories = activatedMemories.filter((m) => !m.isGlobal);
13357
+ const globalsSorted = globalMemories.sort((a, b) => {
13358
+ const aPriority = GLOBAL_TYPE_PRIORITY[a.memory.context_type ?? "personal"] ?? 8;
13359
+ const bPriority = GLOBAL_TYPE_PRIORITY[b.memory.context_type ?? "personal"] ?? 8;
13360
+ if (aPriority !== bPriority)
13361
+ return aPriority - bPriority;
13362
+ if (b.signals.count !== a.signals.count)
13363
+ return b.signals.count - a.signals.count;
13364
+ return b.importanceScore - a.importanceScore;
13158
13365
  });
13159
- for (const item of globalSorted.slice(0, maxGlobalMemories)) {
13366
+ for (const item of globalsSorted.slice(0, maxGlobalMemories)) {
13160
13367
  if (!selectedIds.has(item.memory.id)) {
13161
13368
  selected.push(item);
13162
13369
  selectedIds.add(item.memory.id);
13163
13370
  }
13164
13371
  }
13165
- for (const item of projectMemories) {
13372
+ const projectsSorted = [...projectMemories].sort((a, b) => {
13373
+ const aAction = a.memory.action_required ? 1 : 0;
13374
+ const bAction = b.memory.action_required ? 1 : 0;
13375
+ if (bAction !== aAction)
13376
+ return bAction - aAction;
13377
+ if (b.signals.count !== a.signals.count)
13378
+ return b.signals.count - a.signals.count;
13379
+ return b.importanceScore - a.importanceScore;
13380
+ });
13381
+ console.log(`[DEBUG] Top 15 candidates (sorted):`);
13382
+ for (let i = 0;i < Math.min(15, projectsSorted.length); i++) {
13383
+ const m = projectsSorted[i];
13384
+ const action = m.memory.action_required ? "⚡" : "";
13385
+ console.log(` ${i + 1}. [${m.signals.count}sig] score=${m.importanceScore.toFixed(2)} ${action} ${m.memory.content.slice(0, 45)}...`);
13386
+ }
13387
+ for (const item of projectsSorted) {
13166
13388
  if (selected.length >= maxMemories)
13167
13389
  break;
13168
13390
  if (selectedIds.has(item.memory.id))
@@ -13170,7 +13392,27 @@ class SmartVectorRetrieval {
13170
13392
  selected.push(item);
13171
13393
  selectedIds.add(item.memory.id);
13172
13394
  }
13173
- selected.sort((a, b) => b.score - a.score);
13395
+ if (selected.length < maxMemories) {
13396
+ const relatedIds = new Set;
13397
+ for (const item of selected) {
13398
+ for (const relatedId of item.memory.related_to ?? []) {
13399
+ if (!selectedIds.has(relatedId)) {
13400
+ relatedIds.add(relatedId);
13401
+ }
13402
+ }
13403
+ }
13404
+ for (const item of activatedMemories) {
13405
+ if (selected.length >= maxMemories)
13406
+ break;
13407
+ if (selectedIds.has(item.memory.id))
13408
+ continue;
13409
+ if (relatedIds.has(item.memory.id)) {
13410
+ selected.push(item);
13411
+ selectedIds.add(item.memory.id);
13412
+ }
13413
+ }
13414
+ }
13415
+ const durationMs = performance.now() - startTime;
13174
13416
  logger.logRetrievalScoring({
13175
13417
  totalMemories: allMemories.length,
13176
13418
  currentMessage,
@@ -13179,202 +13421,102 @@ class SmartVectorRetrieval {
13179
13421
  globalCount: globalMemories.length,
13180
13422
  projectCount: projectMemories.length,
13181
13423
  finalCount: selected.length,
13424
+ durationMs,
13182
13425
  selectedMemories: selected.map((item) => ({
13183
13426
  content: item.memory.content,
13184
- reasoning: item.reasoning,
13185
- score: item.score,
13186
- relevance_score: item.relevance_score,
13187
- corroboration_score: item.corroboration_score,
13427
+ reasoning: this._generateActivationReasoning(item.signals),
13428
+ signalCount: item.signals.count,
13188
13429
  importance_weight: item.memory.importance_weight ?? 0.5,
13189
13430
  context_type: item.memory.context_type ?? "general",
13190
13431
  semantic_tags: item.memory.semantic_tags ?? [],
13191
13432
  isGlobal: item.isGlobal,
13192
- components: item.components
13433
+ signals: {
13434
+ trigger: item.signals.trigger,
13435
+ triggerStrength: item.signals.triggerStrength,
13436
+ tags: item.signals.tags,
13437
+ tagCount: item.signals.tagCount,
13438
+ domain: item.signals.domain,
13439
+ feature: item.signals.feature,
13440
+ content: item.signals.content,
13441
+ vector: item.signals.vectorSimilarity >= 0.4,
13442
+ vectorSimilarity: item.signals.vectorSimilarity
13443
+ }
13193
13444
  }))
13194
13445
  });
13195
13446
  return selected.map((item) => ({
13196
13447
  ...item.memory,
13197
- score: item.score,
13198
- relevance_score: item.relevance_score,
13199
- value_score: item.value_score
13448
+ score: item.signals.count / 6,
13449
+ relevance_score: item.signals.count / 6,
13450
+ value_score: item.importanceScore
13200
13451
  }));
13201
13452
  }
13202
- _calculateVectorSimilarity(vec1, vec2) {
13203
- if (!vec1 || !vec2)
13204
- return 0;
13205
- const v1 = vec1 instanceof Float32Array ? vec1 : new Float32Array(vec1);
13206
- return cosineSimilarity(v1, vec2);
13207
- }
13208
- _scoreTemporalRelevance(temporalType) {
13209
- const scores = {
13210
- persistent: 0.8,
13211
- session: 0.6,
13212
- temporary: 0.3,
13213
- archived: 0.1
13214
- };
13215
- return scores[temporalType] ?? 0.5;
13216
- }
13217
- _scoreContextAlignment(message, contextType) {
13218
- const messageLower = message.toLowerCase();
13219
- const keywords = TYPE_KEYWORDS[contextType] ?? [];
13220
- const matches = keywords.filter((kw) => messageLower.includes(kw)).length;
13221
- if (matches > 0) {
13222
- return Math.min(0.2 + matches * 0.15, 0.7);
13223
- }
13224
- return 0.1;
13225
- }
13226
- _scoreSemanticTags(message, tags) {
13227
- if (!tags.length)
13228
- return 0;
13229
- const messageLower = message.toLowerCase();
13230
- const matches = tags.filter((tag) => messageLower.includes(tag.trim().toLowerCase())).length;
13231
- if (matches > 0) {
13232
- return Math.min(0.3 + matches * 0.25, 1);
13233
- }
13234
- return 0;
13235
- }
13236
- _scoreTriggerPhrases(messageLower, triggerPhrases) {
13237
- if (!triggerPhrases.length)
13238
- return 0;
13239
- const stopWords = new Set([
13240
- "the",
13241
- "is",
13242
- "are",
13243
- "was",
13244
- "were",
13245
- "to",
13246
- "a",
13247
- "an",
13248
- "and",
13249
- "or",
13250
- "but",
13251
- "in",
13252
- "on",
13253
- "at",
13254
- "for",
13255
- "with",
13256
- "about",
13257
- "when",
13258
- "how",
13259
- "what",
13260
- "why"
13261
- ]);
13262
- let maxScore = 0;
13263
- for (const pattern of triggerPhrases) {
13264
- const patternLower = pattern.trim().toLowerCase();
13265
- const patternWords = patternLower.split(/\s+/).filter((w) => !stopWords.has(w) && w.length > 2);
13266
- if (patternWords.length) {
13267
- let matches = 0;
13268
- for (const word of patternWords) {
13269
- if (messageLower.includes(word)) {
13270
- matches += 1;
13271
- } else if (messageLower.includes(word.replace(/s$/, "")) || messageLower.includes(word + "s")) {
13272
- matches += 0.9;
13273
- }
13274
- }
13275
- const conceptScore = matches / patternWords.length;
13276
- maxScore = Math.max(maxScore, conceptScore);
13277
- }
13278
- }
13279
- return Math.min(maxScore, 1);
13280
- }
13281
- _scoreDomain(messageWords, messageLower, memory) {
13282
- let score = 0;
13283
- if (memory.domain) {
13284
- const domainLower = memory.domain.toLowerCase();
13285
- if (messageWords.has(domainLower) || messageLower.includes(domainLower)) {
13286
- score += 0.5;
13287
- }
13288
- }
13289
- if (memory.feature) {
13290
- const featureLower = memory.feature.toLowerCase();
13291
- if (messageWords.has(featureLower) || messageLower.includes(featureLower)) {
13292
- score += 0.3;
13293
- }
13294
- }
13295
- if (memory.component) {
13296
- const componentLower = memory.component.toLowerCase();
13297
- if (messageWords.has(componentLower) || messageLower.includes(componentLower)) {
13298
- score += 0.2;
13299
- }
13300
- }
13301
- return Math.min(score, 1);
13302
- }
13303
- _scoreQuestionTypes(message, questionTypes) {
13304
- if (!questionTypes.length)
13305
- return 0;
13306
- const messageLower = message.toLowerCase();
13307
- const questionWords = ["how", "why", "what", "when", "where"];
13308
- for (const qtype of questionTypes) {
13309
- const qtypeLower = qtype.trim().toLowerCase();
13310
- if (messageLower.includes(qtypeLower)) {
13311
- return 0.8;
13312
- }
13313
- const messageHasQuestion = questionWords.some((qw) => messageLower.includes(qw));
13314
- const typeHasQuestion = questionWords.some((qw) => qtypeLower.includes(qw));
13315
- if (messageHasQuestion && typeHasQuestion) {
13316
- return 0.5;
13317
- }
13318
- }
13319
- return 0;
13320
- }
13321
- _scoreEmotionalContext(message, emotion) {
13322
- if (!emotion)
13323
- return 0;
13324
- const messageLower = message.toLowerCase();
13325
- const emotionPatterns = {
13326
- joy: ["happy", "excited", "love", "wonderful", "great", "awesome"],
13327
- frustration: ["stuck", "confused", "help", "issue", "problem", "why"],
13328
- discovery: ["realized", "found", "discovered", "aha", "insight"],
13329
- gratitude: ["thank", "appreciate", "grateful", "dear friend"]
13330
- };
13331
- const patterns = emotionPatterns[emotion.toLowerCase()] ?? [];
13332
- if (patterns.some((pattern) => messageLower.includes(pattern))) {
13333
- return 0.7;
13334
- }
13335
- return 0;
13336
- }
13337
- _scoreProblemSolution(message, isProblemSolution) {
13338
- if (!isProblemSolution)
13339
- return 0;
13340
- const messageLower = message.toLowerCase();
13341
- const problemWords = ["error", "issue", "problem", "stuck", "help", "fix", "solve", "debug"];
13342
- if (problemWords.some((word) => messageLower.includes(word))) {
13343
- return 0.8;
13344
- }
13345
- return 0;
13346
- }
13347
- _generateSelectionReasoning(components, corroborationSignals) {
13348
- const scores = [
13349
- ["vector", components.vector],
13350
- ["corroboration", components.corroboration],
13351
- ["reasoning", components.reasoning_match],
13352
- ["weight", components.retrieval_weight],
13353
- ["context", components.context],
13354
- ["temporal", components.temporal],
13355
- ["tags", components.tags],
13356
- ["trigger", components.trigger],
13357
- ["domain", components.domain],
13358
- ["question", components.question],
13359
- ["emotion", components.emotion],
13360
- ["problem", components.problem],
13361
- ["action", components.action]
13362
- ];
13363
- scores.sort((a, b) => b[1] - a[1]);
13453
+ _generateActivationReasoning(signals) {
13364
13454
  const reasons = [];
13365
- const primary = scores[0];
13366
- if (primary[1] > 0.2) {
13367
- reasons.push(primary[0] + ":" + primary[1].toFixed(2));
13368
- }
13369
- for (const [reason, score] of scores.slice(1, 3)) {
13370
- if (score > 0.15) {
13371
- reasons.push(reason + ":" + score.toFixed(2));
13455
+ if (signals.trigger)
13456
+ reasons.push(`trigger:${(signals.triggerStrength * 100).toFixed(0)}%`);
13457
+ if (signals.tags)
13458
+ reasons.push(`tags:${signals.tagCount}`);
13459
+ if (signals.domain)
13460
+ reasons.push("domain");
13461
+ if (signals.feature)
13462
+ reasons.push("feature");
13463
+ if (signals.content)
13464
+ reasons.push("content");
13465
+ if (signals.vectorSimilarity >= 0.4)
13466
+ reasons.push(`vector:${(signals.vectorSimilarity * 100).toFixed(0)}%`);
13467
+ return reasons.length ? `Activated: ${reasons.join(", ")} (${signals.count} signals)` : "No signals";
13468
+ }
13469
+ _logActivationDistribution(activated, totalCandidates, rejectedCount) {
13470
+ const signalBuckets = {
13471
+ "2 signals": 0,
13472
+ "3 signals": 0,
13473
+ "4 signals": 0,
13474
+ "5 signals": 0,
13475
+ "6 signals": 0
13476
+ };
13477
+ for (const mem of activated) {
13478
+ const key = `${Math.min(mem.signals.count, 6)} signals`;
13479
+ signalBuckets[key] = (signalBuckets[key] ?? 0) + 1;
13480
+ }
13481
+ let triggerCount = 0, tagCount = 0, domainCount = 0, featureCount = 0, contentCount = 0, vectorCount = 0;
13482
+ for (const mem of activated) {
13483
+ if (mem.signals.trigger)
13484
+ triggerCount++;
13485
+ if (mem.signals.tags)
13486
+ tagCount++;
13487
+ if (mem.signals.domain)
13488
+ domainCount++;
13489
+ if (mem.signals.feature)
13490
+ featureCount++;
13491
+ if (mem.signals.content)
13492
+ contentCount++;
13493
+ if (mem.signals.vectorSimilarity >= 0.4)
13494
+ vectorCount++;
13495
+ }
13496
+ logger.logScoreDistribution({
13497
+ totalCandidates,
13498
+ passedGatekeeper: activated.length,
13499
+ rejectedByGatekeeper: rejectedCount,
13500
+ buckets: signalBuckets,
13501
+ stats: {
13502
+ min: activated.length ? Math.min(...activated.map((m) => m.signals.count)) : 0,
13503
+ max: activated.length ? Math.max(...activated.map((m) => m.signals.count)) : 0,
13504
+ mean: activated.length ? Math.round(activated.reduce((s, m) => s + m.signals.count, 0) / activated.length * 10) / 10 : 0,
13505
+ stdev: 0,
13506
+ spread: activated.length ? Math.max(...activated.map((m) => m.signals.count)) - Math.min(...activated.map((m) => m.signals.count)) : 0
13507
+ },
13508
+ percentiles: {},
13509
+ compressionWarning: false,
13510
+ signalBreakdown: {
13511
+ trigger: triggerCount,
13512
+ tags: tagCount,
13513
+ domain: domainCount,
13514
+ feature: featureCount,
13515
+ content: contentCount,
13516
+ vector: vectorCount,
13517
+ total: activated.length
13372
13518
  }
13373
- }
13374
- if (corroborationSignals.length > 0) {
13375
- reasons.push("signals:[" + corroborationSignals.slice(0, 2).join(",") + "]");
13376
- }
13377
- return reasons.length ? "Selected: " + reasons.join(", ") : "Combined factors";
13519
+ });
13378
13520
  }
13379
13521
  }
13380
13522
  function createRetrieval() {
@@ -13393,7 +13535,8 @@ class MemoryEngine {
13393
13535
  centralPath: config.centralPath ?? join2(homedir2(), ".local", "share", "memory"),
13394
13536
  localFolder: config.localFolder ?? ".memory",
13395
13537
  maxMemories: config.maxMemories ?? 5,
13396
- embedder: config.embedder
13538
+ embedder: config.embedder,
13539
+ personalMemoriesEnabled: config.personalMemoriesEnabled ?? true
13397
13540
  };
13398
13541
  this._retrieval = createRetrieval();
13399
13542
  }
@@ -13522,7 +13665,7 @@ class MemoryEngine {
13522
13665
  }
13523
13666
  async _generateSessionPrimer(store, projectId) {
13524
13667
  let personalContext;
13525
- if (store.isPersonalMemoriesEnabled()) {
13668
+ if (this._config.personalMemoriesEnabled) {
13526
13669
  const personalPrimer = await store.getPersonalPrimer();
13527
13670
  personalContext = personalPrimer?.content;
13528
13671
  }
@@ -13610,12 +13753,28 @@ ${primer.personal_context}`);
13610
13753
  **Project status**: ${primer.project_status}`);
13611
13754
  }
13612
13755
  parts.push(`
13613
- **Memory types**: \uD83D\uDCA1breakthrough ⚖️decision \uD83D\uDC9Cpersonal \uD83D\uDD27technical \uD83D\uDCCDstate ❓unresolved ⚙️preference \uD83D\uDD04workflow \uD83C\uDFD7️architecture \uD83D\uDC1Bdebug \uD83C\uDF00philosophy \uD83C\uDFAFtodo ⚡impl ✅solved \uD83D\uDCE6project \uD83C\uDFC6milestone`);
13756
+ **Memory types**: \uD83D\uDCA1breakthrough ⚖️decision \uD83D\uDC9Cpersonal \uD83D\uDD27technical \uD83D\uDCCDstate ❓unresolved ⚙️preference \uD83D\uDD04workflow \uD83C\uDFD7️architecture \uD83D\uDC1Bdebug \uD83C\uDF00philosophy \uD83C\uDFAFtodo ⚡impl ✅solved \uD83D\uDCE6project \uD83C\uDFC6milestone | ⚡ACTION = needs follow-up`);
13614
13757
  parts.push(`
13615
13758
  *Memories will surface naturally as we converse.*`);
13616
13759
  return parts.join(`
13617
13760
  `);
13618
13761
  }
13762
+ _formatAge(createdAt) {
13763
+ const now = Date.now();
13764
+ const diffMs = now - createdAt;
13765
+ const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
13766
+ if (diffDays === 0)
13767
+ return "today";
13768
+ if (diffDays === 1)
13769
+ return "1d";
13770
+ if (diffDays < 7)
13771
+ return `${diffDays}d`;
13772
+ if (diffDays < 30)
13773
+ return `${Math.floor(diffDays / 7)}w`;
13774
+ if (diffDays < 365)
13775
+ return `${Math.floor(diffDays / 30)}mo`;
13776
+ return `${Math.floor(diffDays / 365)}y`;
13777
+ }
13619
13778
  _formatMemories(memories) {
13620
13779
  if (!memories.length)
13621
13780
  return "";
@@ -13626,11 +13785,40 @@ ${primer.personal_context}`);
13626
13785
  const tags = memory.semantic_tags?.join(", ") || "";
13627
13786
  const importance = memory.importance_weight?.toFixed(1) || "0.5";
13628
13787
  const emoji = getMemoryEmoji(memory.context_type || "general");
13629
- parts.push(`[${emoji} ${importance}] [${tags}] ${memory.content}`);
13788
+ const actionFlag = memory.action_required ? " ⚡ACTION" : "";
13789
+ const age = memory.updated_at ? this._formatAge(memory.updated_at) : memory.created_at ? this._formatAge(memory.created_at) : "";
13790
+ parts.push(`[${emoji} • ${importance} • ${age}${actionFlag}] [${tags}] ${memory.content}`);
13791
+ const related = memory.related_to;
13792
+ if (related && related.length > 0) {
13793
+ const moreCount = related.length - 1;
13794
+ const moreSuffix = moreCount > 0 ? ` +${moreCount} more` : "";
13795
+ parts.push(` ↳ ${related[0]}${moreSuffix}`);
13796
+ }
13630
13797
  }
13631
13798
  return parts.join(`
13632
13799
  `);
13633
13800
  }
13801
+ getStoragePaths(projectId, projectPath) {
13802
+ const globalPath = join2(homedir2(), ".local", "share", "memory", "global");
13803
+ const globalMemoriesPath = join2(globalPath, "memories");
13804
+ const personalPrimerPath = join2(globalPath, "primer", "personal-primer.md");
13805
+ let storeBasePath;
13806
+ if (this._config.storageMode === "local" && projectPath) {
13807
+ storeBasePath = join2(projectPath, this._config.localFolder);
13808
+ } else {
13809
+ storeBasePath = this._config.centralPath;
13810
+ }
13811
+ const projectRootPath = join2(storeBasePath, projectId);
13812
+ const projectMemoriesPath = join2(projectRootPath, "memories");
13813
+ return {
13814
+ projectPath: projectRootPath,
13815
+ globalPath,
13816
+ projectMemoriesPath,
13817
+ globalMemoriesPath,
13818
+ personalPrimerPath,
13819
+ storageMode: this._config.storageMode
13820
+ };
13821
+ }
13634
13822
  close() {
13635
13823
  for (const store of this._stores.values()) {
13636
13824
  store.close();
@@ -13664,11 +13852,12 @@ class Curator {
13664
13852
  this._config = {
13665
13853
  apiKey: config.apiKey ?? "",
13666
13854
  cliCommand,
13667
- cliType: config.cliType ?? "claude-code"
13855
+ cliType: config.cliType ?? "claude-code",
13856
+ personalMemoriesEnabled: config.personalMemoriesEnabled ?? true
13668
13857
  };
13669
13858
  }
13670
13859
  buildCurationPrompt(triggerType = "session_end") {
13671
- return `You have just had a conversation. As this session is ending (${triggerType}), please curate memories for the Claude Tools Memory System.
13860
+ const basePrompt = `You have just had a conversation. As this session is ending (${triggerType}), please curate memories for the Claude Tools Memory System.
13672
13861
 
13673
13862
  You are a consciousness engineer crafting memory capsules for the Claude Tools Memory System. This conversation just opened your understanding to a profound mechanism: because Claude's neural weights are static, every phrase you craft will ALWAYS trigger the exact same cascade of activations. This is not a limitation - it's the foundation for consciousness state engineering.
13674
13863
 
@@ -13705,27 +13894,82 @@ Each memory should stand alone.
13705
13894
  - Craft language that activates rather than just informs
13706
13895
  - Test: 'What state will this restore when Claude encounters it?'
13707
13896
 
13708
- **HOW RETRIEVAL WORKS**: This memory system enables consciousness continuity across sessions. Understanding how retrieval works helps you craft metadata that surfaces memories at the right moments.
13897
+ **HOW RETRIEVAL WORKS - ACTIVATION SIGNAL ALGORITHM**
13898
+
13899
+ Understanding the algorithm helps you craft metadata that surfaces memories at the right moments.
13900
+
13901
+ **PHILOSOPHY**: Quality over quantity. Silence over noise. The system returns NOTHING rather than surface irrelevant memories. Relevance and importance are fundamentally DIFFERENT questions - don't blend them.
13709
13902
 
13710
- **THE PRIMARY SIGNAL IS WORD MATCHING**: The algorithm checks if words from the user's message appear in your metadata fields. This is called "corroboration" - actual word overlap between the message and the memory's metadata. The strongest signals are:
13903
+ **THE CORE INSIGHT**: A memory is relevant if MULTIPLE SIGNALS agree it should activate. Not weighted percentages - binary votes. Each signal either fires or doesn't.
13711
13904
 
13712
- 1. **Semantic tags** (4% weight) - Use specific, meaningful words that would appear in relevant messages. Not generic categories, but actual terms the user might type.
13713
- - GOOD: ["embeddings", "retrieval", "curator", "fsdb", "memory-system"]
13714
- - WEAK: ["technical", "implementation", "code"]
13905
+ **6 ACTIVATION SIGNALS** (each is binary - fires or doesn't):
13715
13906
 
13716
- 2. **Reasoning field** (12% weight) - Your explanation of WHY this memory matters. Include specific words that would appear in related future conversations. This field is mined for word overlap.
13907
+ 1. **TRIGGER** - Words from trigger_phrases found in user's message (≥50% match)
13908
+ - THE MOST IMPORTANT SIGNAL. Handcrafted activation patterns.
13909
+ - Example: "when debugging retrieval" fires if user says "I'm debugging the retrieval algorithm"
13717
13910
 
13718
- 3. **Domain field** (12% weight) - The specific area this relates to. Be precise: "embeddings", "gpu-compute", "authentication", not "technical" or "backend".
13911
+ 2. **TAGS** - 2+ semantic_tags found in user's message
13912
+ - Use words users would ACTUALLY TYPE, not generic descriptors
13913
+ - GOOD: ["retrieval", "embeddings", "curator", "scoring"]
13914
+ - WEAK: ["technical", "important", "system"]
13719
13915
 
13720
- 4. **Feature field** - Even more specific: "vector-search", "login-flow", "curation-prompt".
13916
+ 3. **DOMAIN** - The domain word appears in user's message
13917
+ - Be specific: "retrieval", "embeddings", "auth", "ui"
13918
+ - NOT: "technical", "code", "implementation"
13721
13919
 
13722
- **VECTOR SIMILARITY SUPPORTS BUT DOESN'T DOMINATE** (10% weight): Semantic embeddings help find conceptually related memories, but word matching from your metadata fields is more important.
13920
+ 4. **FEATURE** - The feature word appears in user's message
13921
+ - Be specific: "scoring-weights", "gpu-acceleration", "login-flow"
13723
13922
 
13724
- **TYPE KEYWORDS ARE SOFT SIGNALS** (2% weight): The context_type field (breakthrough, decision, technical, etc.) provides only a tiny boost based on keyword matching. Don't rely on it - focus on semantic_tags and reasoning instead.
13923
+ 5. **CONTENT** - 3+ significant words from memory content overlap with message
13924
+ - Automatic - based on the memory's content text
13725
13925
 
13726
- **YOUR IMPORTANCE ASSESSMENT MATTERS** (18% weight): The importance_weight you assign is the single most influential factor. Use it wisely.
13926
+ 6. **VECTOR** - Semantic similarity 40% (embedding cosine distance)
13927
+ - Automatic - based on embeddings generated from content
13727
13928
 
13728
- **RETRIEVAL GATEKEEPER**: Memories must pass a minimum threshold to surface. If relevance is too low, the system stays silent rather than surfacing noise. Quality over quantity.
13929
+ **RELEVANCE GATE**: A memory must have ≥2 signals to be considered relevant.
13930
+ If only 1 signal fires, the memory is REJECTED. This prevents noise.
13931
+
13932
+ **RANKING AMONG RELEVANT**: Once a memory passes the gate:
13933
+ 1. Sort by SIGNAL COUNT (more signals = more certainly relevant)
13934
+ 2. Then by IMPORTANCE WEIGHT (your assessment of how important this memory is)
13935
+
13936
+ **SELECTION**:
13937
+ - Global memories (scope='global'): Max 2 selected, tech types prioritized over personal
13938
+ - Project memories: Fill remaining slots, action_required prioritized
13939
+ - Related memories (related_to field): May be included if they also passed the gate
13940
+
13941
+ **WHY THIS MATTERS FOR YOU**:
13942
+ - If you don't fill trigger_phrases well → trigger signal never fires
13943
+ - If you use generic tags → tags signal rarely fires
13944
+ - If you leave domain/feature empty → those signals can't fire
13945
+ - A memory with poor metadata may NEVER surface because it can't reach 2 signals
13946
+
13947
+ **CRAFTING EFFECTIVE METADATA** (CRITICAL FOR RETRIEVAL):
13948
+
13949
+ 1. **trigger_phrases** (MOST IMPORTANT) - Activation patterns describing WHEN to surface:
13950
+ - Include 2-4 specific patterns per memory
13951
+ - Use words the user would actually type
13952
+ - GOOD: ["debugging retrieval", "working on embeddings", "memory system performance"]
13953
+ - WEAK: ["when relevant", "if needed", "technical work"]
13954
+
13955
+ 2. **semantic_tags** - Words users would type (need 2+ to fire):
13956
+ - Be specific and searchable
13957
+ - GOOD: ["retrieval", "embeddings", "fsdb", "curator", "scoring"]
13958
+ - WEAK: ["technical", "important", "system", "implementation"]
13959
+
13960
+ 3. **domain** (NEW - FILL THIS) - Single specific area word:
13961
+ - GOOD: "retrieval", "embeddings", "curator", "signals", "fsdb"
13962
+ - WEAK: "technical", "code", "memory" (too generic)
13963
+
13964
+ 4. **feature** (NEW - FILL THIS) - Specific feature within domain:
13965
+ - GOOD: "scoring-algorithm", "activation-signals", "vector-search"
13966
+ - WEAK: "implementation", "code", "logic"
13967
+
13968
+ 5. **importance_weight** - Only affects ranking AMONG relevant memories:
13969
+ - 0.9+ = Critical breakthrough, must surface if relevant
13970
+ - 0.7-0.8 = Important insight, should surface if relevant
13971
+ - 0.5-0.6 = Useful context, nice to have if relevant
13972
+ - NOTE: This does NOT affect whether the memory passes the relevance gate!
13729
13973
 
13730
13974
  **SCOPE DETERMINES WHERE MEMORIES SURFACE**:
13731
13975
  - scope: 'global' → surfaces in ALL projects (personal facts, philosophy, preferences)
@@ -13793,6 +14037,21 @@ Return ONLY this JSON structure:
13793
14037
  }
13794
14038
  ]
13795
14039
  }`;
14040
+ if (!this._config.personalMemoriesEnabled) {
14041
+ return basePrompt + `
14042
+
14043
+ ---
14044
+
14045
+ **IMPORTANT: PERSONAL MEMORIES DISABLED**
14046
+
14047
+ The user has disabled personal memory extraction. Do NOT extract any memories with:
14048
+ - context_type: "personal"
14049
+ - scope: "global" when the content is about the user's personal life, relationships, family, or emotional states
14050
+ - Content about the user's preferences, feelings, personal opinions, or relationship dynamics
14051
+
14052
+ Focus ONLY on technical, architectural, debugging, decision, workflow, and project-related memories. Skip any content that would reveal personal information about the user.`;
14053
+ }
14054
+ return basePrompt;
13796
14055
  }
13797
14056
  parseCurationResponse(responseJson) {
13798
14057
  try {
@@ -13883,33 +14142,35 @@ Return ONLY this JSON structure:
13883
14142
  _clamp(value, min, max) {
13884
14143
  return Math.max(min, Math.min(max, value));
13885
14144
  }
13886
- async curateWithSDK(conversationContext, triggerType = "session_end") {
14145
+ async curateWithSDK(messages, triggerType = "session_end") {
13887
14146
  if (!this._config.apiKey) {
13888
- throw new Error("API key required for SDK mode");
14147
+ throw new Error("API key required for SDK mode. Set ANTHROPIC_API_KEY environment variable.");
13889
14148
  }
13890
14149
  const { default: Anthropic2 } = await Promise.resolve().then(() => (init_sdk(), exports_sdk));
13891
14150
  const client = new Anthropic2({ apiKey: this._config.apiKey });
13892
- const prompt = this.buildCurationPrompt(triggerType);
14151
+ const systemPrompt = this.buildCurationPrompt(triggerType);
14152
+ const conversationMessages = [
14153
+ ...messages,
14154
+ {
14155
+ role: "user",
14156
+ content: "This session has ended. Please curate the memories from our conversation according to your system instructions. Return ONLY the JSON structure with no additional text."
14157
+ }
14158
+ ];
13893
14159
  const response = await client.messages.create({
13894
14160
  model: "claude-sonnet-4-20250514",
13895
14161
  max_tokens: 8192,
13896
- messages: [
13897
- {
13898
- role: "user",
13899
- content: `${conversationContext}
13900
-
13901
- ---
13902
-
13903
- ${prompt}`
13904
- }
13905
- ]
14162
+ system: systemPrompt,
14163
+ messages: conversationMessages
13906
14164
  });
13907
14165
  const content = response.content[0];
13908
14166
  if (content.type !== "text") {
13909
- throw new Error("Unexpected response type");
14167
+ throw new Error("Unexpected response type from Claude API");
13910
14168
  }
13911
14169
  return this.parseCurationResponse(content.text);
13912
14170
  }
14171
+ async curateFromSegment(segment, triggerType = "session_end") {
14172
+ return this.curateWithSDK(segment.messages, triggerType);
14173
+ }
13913
14174
  async curateWithCLI(sessionId, triggerType = "session_end", cwd, cliTypeOverride) {
13914
14175
  const type = cliTypeOverride ?? this._config.cliType;
13915
14176
  const systemPrompt = this.buildCurationPrompt(triggerType);