@rlabs-inc/memory 0.3.7 → 0.3.8

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.js CHANGED
@@ -12129,6 +12129,11 @@ var managementLogSchema = {
12129
12129
  error: "string",
12130
12130
  details: "string"
12131
12131
  };
12132
+ var personalPrimerSchema = {
12133
+ content: "string",
12134
+ session_updated: "number",
12135
+ updated_by: "string"
12136
+ };
12132
12137
 
12133
12138
  // src/core/store.ts
12134
12139
  var PERSONAL_PRIMER_ID = "personal-primer";
@@ -12150,7 +12155,7 @@ class MemoryStore {
12150
12155
  return this._global;
12151
12156
  }
12152
12157
  const globalPath = this._config.globalPath;
12153
- console.log(`\uD83C\uDF10 [DEBUG] Creating global database at ${globalPath}`);
12158
+ console.log(`\uD83C\uDF10 [DEBUG] Initializing global database at ${globalPath}`);
12154
12159
  const db = createDatabase({
12155
12160
  name: "global",
12156
12161
  basePath: globalPath
@@ -12167,8 +12172,14 @@ class MemoryStore {
12167
12172
  autoSave: true,
12168
12173
  watchFiles: this._config.watchFiles
12169
12174
  });
12170
- await Promise.all([memories.load(), managementLogs.load()]);
12171
- this._global = { db, memories, managementLogs };
12175
+ const primer = db.collection("primer", {
12176
+ schema: personalPrimerSchema,
12177
+ contentColumn: "content",
12178
+ autoSave: true,
12179
+ watchFiles: this._config.watchFiles
12180
+ });
12181
+ await Promise.all([memories.load(), managementLogs.load(), primer.load()]);
12182
+ this._global = { db, memories, managementLogs, primer };
12172
12183
  return this._global;
12173
12184
  }
12174
12185
  async getGlobalMemories() {
@@ -12245,40 +12256,31 @@ class MemoryStore {
12245
12256
  return id;
12246
12257
  }
12247
12258
  async getPersonalPrimer() {
12248
- const { memories } = await this.getGlobal();
12249
- const primer = memories.get(PERSONAL_PRIMER_ID);
12250
- if (!primer) {
12259
+ const { primer } = await this.getGlobal();
12260
+ const record = primer.get(PERSONAL_PRIMER_ID);
12261
+ if (!record) {
12251
12262
  return null;
12252
12263
  }
12253
12264
  return {
12254
- content: primer.content,
12255
- updated: primer.updated
12265
+ content: record.content,
12266
+ updated: record.updated
12256
12267
  };
12257
12268
  }
12258
- async setPersonalPrimer(content) {
12259
- const { memories } = await this.getGlobal();
12260
- const existing = memories.get(PERSONAL_PRIMER_ID);
12269
+ async setPersonalPrimer(content, sessionNumber, updatedBy = "user") {
12270
+ const { primer } = await this.getGlobal();
12271
+ const existing = primer.get(PERSONAL_PRIMER_ID);
12261
12272
  if (existing) {
12262
- memories.update(PERSONAL_PRIMER_ID, { content });
12273
+ primer.update(PERSONAL_PRIMER_ID, {
12274
+ content,
12275
+ session_updated: sessionNumber ?? existing.session_updated,
12276
+ updated_by: updatedBy
12277
+ });
12263
12278
  } else {
12264
- memories.insert({
12279
+ primer.insert({
12265
12280
  id: PERSONAL_PRIMER_ID,
12266
12281
  content,
12267
- reasoning: "Personal relationship context injected at session start",
12268
- importance_weight: 1,
12269
- confidence_score: 1,
12270
- context_type: "personal",
12271
- temporal_relevance: "persistent",
12272
- knowledge_domain: "personal",
12273
- emotional_resonance: "neutral",
12274
- action_required: false,
12275
- problem_solution_pair: false,
12276
- semantic_tags: ["personal", "primer", "relationship"],
12277
- trigger_phrases: [],
12278
- question_types: [],
12279
- session_id: "system",
12280
- project_id: "global",
12281
- embedding: null
12282
+ session_updated: sessionNumber ?? 0,
12283
+ updated_by: updatedBy
12282
12284
  });
12283
12285
  }
12284
12286
  }
@@ -12298,6 +12300,7 @@ class MemoryStore {
12298
12300
  success: entry.success,
12299
12301
  duration_ms: entry.durationMs,
12300
12302
  summary: entry.summary,
12303
+ full_report: entry.fullReport ?? "",
12301
12304
  error: entry.error ?? "",
12302
12305
  details: entry.details ? JSON.stringify(entry.details) : ""
12303
12306
  });
@@ -12661,7 +12664,14 @@ var sym = {
12661
12664
  fire: "\uD83D\uDD25",
12662
12665
  target: "\uD83C\uDFAF"
12663
12666
  };
12667
+ var _verbose = false;
12664
12668
  var logger = {
12669
+ setVerbose(enabled) {
12670
+ _verbose = enabled;
12671
+ },
12672
+ isVerbose() {
12673
+ return _verbose;
12674
+ },
12665
12675
  info(message) {
12666
12676
  console.log(`${timestamp()} ${style("cyan", sym.info)} ${message}`);
12667
12677
  },
@@ -12733,7 +12743,14 @@ var logger = {
12733
12743
  problem_solution: "✅",
12734
12744
  project_context: "\uD83D\uDCE6",
12735
12745
  milestone: "\uD83C\uDFC6",
12736
- general: "\uD83D\uDCDD"
12746
+ general: "\uD83D\uDCDD",
12747
+ project_state: "\uD83D\uDCCD",
12748
+ pending_task: "⏳",
12749
+ work_in_progress: "\uD83D\uDD28",
12750
+ system_feedback: "\uD83D\uDCE3",
12751
+ project_milestone: "\uD83C\uDFC6",
12752
+ architectural_insight: "\uD83C\uDFDB️",
12753
+ architectural_direction: "\uD83E\uDDED"
12737
12754
  };
12738
12755
  console.log();
12739
12756
  console.log(`${timestamp()} ${style("cyan", sym.sparkles)} ${style("bold", `SURFACING ${memories.length} MEMORIES`)}`);
@@ -12745,11 +12762,12 @@ var logger = {
12745
12762
  return;
12746
12763
  }
12747
12764
  memories.forEach((m, i) => {
12748
- const score = style("green", `${(m.score * 100).toFixed(0)}%`);
12765
+ const signalCount = Math.round(m.score * 6);
12766
+ const signalStr = style("green", `${signalCount}sig`);
12749
12767
  const emoji = emojiMap[m.context_type?.toLowerCase()] ?? "\uD83D\uDCDD";
12750
12768
  const num = style("dim", `${i + 1}.`);
12751
12769
  const preview = m.content.length > 55 ? m.content.slice(0, 55) + style("dim", "...") : m.content;
12752
- console.log(` ${num} [${score}] ${emoji}`);
12770
+ console.log(` ${num} [${signalStr}] ${emoji}`);
12753
12771
  console.log(` ${preview}`);
12754
12772
  });
12755
12773
  console.log();
@@ -12797,44 +12815,124 @@ var logger = {
12797
12815
  console.log(` ${style("dim", "processing:")} ${memoriesCount} new memories`);
12798
12816
  },
12799
12817
  logManagementComplete(result) {
12818
+ const formatAction = (action, truncate = true) => {
12819
+ let icon = " •";
12820
+ if (action.startsWith("READ OK"))
12821
+ icon = style("dim", " \uD83D\uDCD6");
12822
+ else if (action.startsWith("READ FAILED"))
12823
+ icon = style("red", " ❌");
12824
+ else if (action.startsWith("WRITE OK"))
12825
+ icon = style("green", " ✏️");
12826
+ else if (action.startsWith("WRITE FAILED"))
12827
+ icon = style("red", " ❌");
12828
+ else if (action.startsWith("RECEIVED"))
12829
+ icon = style("cyan", " \uD83D\uDCE5");
12830
+ else if (action.startsWith("CREATED"))
12831
+ icon = style("green", " ✨");
12832
+ else if (action.startsWith("UPDATED"))
12833
+ icon = style("blue", " \uD83D\uDCDD");
12834
+ else if (action.startsWith("SUPERSEDED"))
12835
+ icon = style("yellow", " \uD83D\uDD04");
12836
+ else if (action.startsWith("RESOLVED"))
12837
+ icon = style("green", " ✅");
12838
+ else if (action.startsWith("LINKED"))
12839
+ icon = style("cyan", " \uD83D\uDD17");
12840
+ else if (action.startsWith("PRIMER"))
12841
+ icon = style("magenta", " \uD83D\uDC9C");
12842
+ else if (action.startsWith("SKIPPED"))
12843
+ icon = style("dim", " ⏭️");
12844
+ else if (action.startsWith("NO_ACTION"))
12845
+ icon = style("dim", " ◦");
12846
+ const text = truncate && action.length > 70 ? action.slice(0, 67) + "..." : action;
12847
+ return `${icon} ${style("dim", text)}`;
12848
+ };
12800
12849
  if (result.success) {
12801
12850
  console.log(` ${style("green", sym.check)} ${style("bold", "Completed")}`);
12802
- const stats = [];
12803
- if (result.superseded && result.superseded > 0) {
12804
- stats.push(`${result.superseded} superseded`);
12805
- }
12806
- if (result.resolved && result.resolved > 0) {
12807
- stats.push(`${result.resolved} resolved`);
12808
- }
12809
- if (result.linked && result.linked > 0) {
12810
- stats.push(`${result.linked} linked`);
12811
- }
12812
- if (result.primerUpdated) {
12813
- stats.push("primer updated");
12814
- }
12815
- if (stats.length > 0) {
12816
- console.log(` ${style("dim", "changes:")} ${stats.join(style("dim", ", "))}`);
12851
+ if (_verbose) {
12852
+ console.log(` ${style("dim", "─".repeat(50))}`);
12853
+ console.log(` ${style("cyan", "\uD83D\uDCCA")} ${style("bold", "Statistics")}`);
12854
+ const filesRead = result.filesRead ?? 0;
12855
+ const filesWritten = result.filesWritten ?? 0;
12856
+ console.log(` ${style("dim", "Files read:")} ${filesRead > 0 ? style("green", String(filesRead)) : style("dim", "0")}`);
12857
+ console.log(` ${style("dim", "Files written:")} ${filesWritten > 0 ? style("green", String(filesWritten)) : style("dim", "0")}`);
12858
+ const superseded = result.superseded ?? 0;
12859
+ const resolved = result.resolved ?? 0;
12860
+ const linked = result.linked ?? 0;
12861
+ console.log(` ${style("dim", "Superseded:")} ${superseded > 0 ? style("yellow", String(superseded)) : style("dim", "0")}`);
12862
+ console.log(` ${style("dim", "Resolved:")} ${resolved > 0 ? style("green", String(resolved)) : style("dim", "0")}`);
12863
+ console.log(` ${style("dim", "Linked:")} ${linked > 0 ? style("cyan", String(linked)) : style("dim", "0")}`);
12864
+ console.log(` ${style("dim", "Primer:")} ${result.primerUpdated ? style("magenta", "updated") : style("dim", "unchanged")}`);
12865
+ if (result.actions && result.actions.length > 0) {
12866
+ console.log(` ${style("dim", "─".repeat(50))}`);
12867
+ console.log(` ${style("cyan", "\uD83C\uDFAC")} ${style("bold", "Actions")} ${style("dim", `(${result.actions.length} total)`)}`);
12868
+ for (const action of result.actions) {
12869
+ console.log(` ${formatAction(action, false)}`);
12870
+ }
12871
+ }
12872
+ if (result.fullReport) {
12873
+ console.log(` ${style("dim", "─".repeat(50))}`);
12874
+ console.log(` ${style("cyan", "\uD83D\uDCCB")} ${style("bold", "Full Report")}`);
12875
+ const reportLines = result.fullReport.split(`
12876
+ `);
12877
+ for (const line of reportLines) {
12878
+ if (line.includes("===")) {
12879
+ console.log(` ${style("bold", line)}`);
12880
+ } else if (line.match(/^[A-Z_]+:/)) {
12881
+ console.log(` ${style("cyan", line)}`);
12882
+ } else {
12883
+ console.log(` ${style("dim", line)}`);
12884
+ }
12885
+ }
12886
+ }
12887
+ console.log(` ${style("dim", "─".repeat(50))}`);
12817
12888
  } else {
12818
- console.log(` ${style("dim", "changes:")} none (memories are current)`);
12819
- }
12820
- if (result.summary) {
12821
- const shortSummary = result.summary.length > 60 ? result.summary.slice(0, 60) + "..." : result.summary;
12822
- console.log(` ${style("dim", "summary:")} ${shortSummary}`);
12889
+ const stats = [];
12890
+ if (result.superseded && result.superseded > 0)
12891
+ stats.push(`${result.superseded} superseded`);
12892
+ if (result.resolved && result.resolved > 0)
12893
+ stats.push(`${result.resolved} resolved`);
12894
+ if (result.linked && result.linked > 0)
12895
+ stats.push(`${result.linked} linked`);
12896
+ if (result.primerUpdated)
12897
+ stats.push("primer updated");
12898
+ if (stats.length > 0) {
12899
+ console.log(` ${style("dim", "changes:")} ${stats.join(style("dim", ", "))}`);
12900
+ } else {
12901
+ console.log(` ${style("dim", "changes:")} none (memories are current)`);
12902
+ }
12903
+ if (result.actions && result.actions.length > 0) {
12904
+ console.log(` ${style("dim", "actions:")}`);
12905
+ for (const action of result.actions.slice(0, 10)) {
12906
+ console.log(` ${formatAction(action, true)}`);
12907
+ }
12908
+ if (result.actions.length > 10) {
12909
+ console.log(` ${style("dim", ` ... and ${result.actions.length - 10} more actions`)}`);
12910
+ }
12911
+ }
12823
12912
  }
12824
12913
  } else {
12825
12914
  console.log(` ${style("yellow", sym.warning)} ${style("bold", "Failed")}`);
12826
12915
  if (result.error) {
12827
- console.log(` ${style("dim", "error:")} ${result.error.slice(0, 80)}`);
12916
+ console.log(` ${style("red", "error:")} ${result.error}`);
12917
+ }
12918
+ if (result.fullReport) {
12919
+ console.log(` ${style("dim", "─".repeat(50))}`);
12920
+ console.log(` ${style("red", "\uD83D\uDCCB")} ${style("bold", "Error Report:")}`);
12921
+ const reportLines = result.fullReport.split(`
12922
+ `);
12923
+ for (const line of reportLines) {
12924
+ console.log(` ${style("dim", line)}`);
12925
+ }
12828
12926
  }
12829
12927
  }
12830
12928
  console.log();
12831
12929
  },
12832
12930
  logRetrievalScoring(params) {
12833
- const { totalMemories, currentMessage, alreadyInjected, preFiltered, globalCount, projectCount, finalCount, selectedMemories } = params;
12931
+ const { totalMemories, currentMessage, alreadyInjected, preFiltered, globalCount, projectCount, finalCount, durationMs, selectedMemories } = params;
12932
+ const timeStr = durationMs !== undefined ? style("cyan", `${durationMs.toFixed(1)}ms`) : "";
12834
12933
  console.log();
12835
- console.log(`${timestamp()} ${style("magenta", sym.brain)} ${style("bold", "MULTI-DIMENSIONAL RETRIEVAL")}`);
12836
- console.log(` ${style("dim", "total:")} ${totalMemories} memories`);
12837
- console.log(` ${style("dim", "pre-filtered:")} ${preFiltered} (inactive/excluded/scope)`);
12934
+ console.log(`${timestamp()} ${style("magenta", sym.brain)} ${style("bold", "RETRIEVAL")} ${timeStr}`);
12935
+ console.log(` ${style("dim", "total:")} ${totalMemories} → ${style("dim", "filtered:")} ${preFiltered} → ${style("dim", "candidates:")} ${totalMemories - preFiltered}`);
12838
12936
  console.log(` ${style("dim", "already injected:")} ${alreadyInjected}`);
12839
12937
  const msgPreview = currentMessage.length > 60 ? currentMessage.slice(0, 60) + "..." : currentMessage;
12840
12938
  console.log(` ${style("dim", "message:")} "${msgPreview}"`);
@@ -12853,347 +12951,471 @@ var logger = {
12853
12951
  console.log();
12854
12952
  selectedMemories.forEach((m, i) => {
12855
12953
  const num = style("dim", `${i + 1}.`);
12856
- const score = style("green", `${(m.score * 100).toFixed(0)}%`);
12857
- const relevance = style("cyan", `rel:${(m.relevance_score * 100).toFixed(0)}%`);
12858
- const corr = style("magenta", `corr:${(m.corroboration_score * 100).toFixed(0)}%`);
12954
+ const signalsStr = style("green", `${m.signalCount} signals`);
12955
+ const imp = style("magenta", `imp:${(m.importance_weight * 100).toFixed(0)}%`);
12859
12956
  const type = style("yellow", m.context_type.toUpperCase());
12860
- const scope = m.isGlobal ? style("blue", "[G]") : "";
12861
- console.log(` ${num} [${score} ${relevance} ${corr}] ${type} ${scope}`);
12957
+ const scope = m.isGlobal ? style("blue", " [G]") : "";
12958
+ console.log(` ${num} [${signalsStr} ${imp}] ${type}${scope}`);
12862
12959
  const preview = m.content.length > 60 ? m.content.slice(0, 60) + style("dim", "...") : m.content;
12863
12960
  console.log(` ${style("white", preview)}`);
12864
- 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(", ");
12865
- if (components) {
12866
- console.log(` ${style("dim", "scores:")} ${components}`);
12961
+ const firedSignals = [];
12962
+ if (m.signals.trigger) {
12963
+ firedSignals.push(`trigger:${(m.signals.triggerStrength * 100).toFixed(0)}%`);
12964
+ }
12965
+ if (m.signals.tags) {
12966
+ firedSignals.push(`tags:${m.signals.tagCount}`);
12967
+ }
12968
+ if (m.signals.domain)
12969
+ firedSignals.push("domain");
12970
+ if (m.signals.feature)
12971
+ firedSignals.push("feature");
12972
+ if (m.signals.content)
12973
+ firedSignals.push("content");
12974
+ if (m.signals.vector) {
12975
+ firedSignals.push(`vector:${(m.signals.vectorSimilarity * 100).toFixed(0)}%`);
12867
12976
  }
12868
- if (m.reasoning) {
12869
- console.log(` ${style("dim", m.reasoning)}`);
12977
+ if (firedSignals.length > 0) {
12978
+ console.log(` ${style("cyan", "signals:")} ${firedSignals.join(", ")}`);
12870
12979
  }
12871
12980
  console.log();
12872
12981
  });
12982
+ },
12983
+ logScoreDistribution(params) {
12984
+ const { totalCandidates, passedGatekeeper, rejectedByGatekeeper, buckets, stats, signalBreakdown } = params;
12985
+ console.log();
12986
+ console.log(style("dim", " ─".repeat(30)));
12987
+ console.log(` ${style("bold", "ACTIVATION SIGNALS")}`);
12988
+ console.log();
12989
+ const passRate = totalCandidates > 0 ? (passedGatekeeper / totalCandidates * 100).toFixed(0) : "0";
12990
+ console.log(` ${style("dim", "Activated:")} ${style("green", String(passedGatekeeper))}/${totalCandidates} (${passRate}%)`);
12991
+ console.log(` ${style("dim", "Rejected:")} ${rejectedByGatekeeper} (< 2 signals)`);
12992
+ console.log();
12993
+ if (signalBreakdown && signalBreakdown.total > 0) {
12994
+ console.log(` ${style("cyan", "Signal Breakdown:")}`);
12995
+ const signals = [
12996
+ { name: "trigger", count: signalBreakdown.trigger },
12997
+ { name: "tags", count: signalBreakdown.tags },
12998
+ { name: "domain", count: signalBreakdown.domain },
12999
+ { name: "feature", count: signalBreakdown.feature },
13000
+ { name: "content", count: signalBreakdown.content },
13001
+ { name: "vector", count: signalBreakdown.vector }
13002
+ ];
13003
+ for (const sig of signals) {
13004
+ const pct = (sig.count / signalBreakdown.total * 100).toFixed(0);
13005
+ const bar = "█".repeat(Math.round(sig.count / signalBreakdown.total * 20));
13006
+ console.log(` ${sig.name.padEnd(8)} ${bar.padEnd(20)} ${sig.count} (${pct}%)`);
13007
+ }
13008
+ console.log();
13009
+ }
13010
+ if (stats.max > 0) {
13011
+ console.log(` ${style("cyan", "Signals:")} min=${stats.min} max=${stats.max} mean=${stats.mean}`);
13012
+ console.log();
13013
+ }
13014
+ if (Object.keys(buckets).length > 0) {
13015
+ console.log(` ${style("bold", "Distribution:")}`);
13016
+ const maxBucketCount = Math.max(...Object.values(buckets), 1);
13017
+ const bucketOrder = ["2 signals", "3 signals", "4 signals", "5 signals", "6 signals"];
13018
+ for (const bucket of bucketOrder) {
13019
+ const count = buckets[bucket] ?? 0;
13020
+ if (count > 0 || bucket === "2 signals") {
13021
+ const barLen = Math.round(count / maxBucketCount * 25);
13022
+ const bar = "█".repeat(barLen) + style("dim", "░".repeat(25 - barLen));
13023
+ const countStr = count.toString().padStart(3);
13024
+ console.log(` ${style("dim", bucket.padEnd(10))} ${bar} ${style("cyan", countStr)}`);
13025
+ }
13026
+ }
13027
+ console.log();
13028
+ }
12873
13029
  }
12874
13030
  };
12875
13031
 
12876
13032
  // src/core/retrieval.ts
12877
- var TYPE_KEYWORDS = {
12878
- debug: ["bug", "error", "fix", "broken", "crash", "fails", "exception", "stack trace", "debugging"],
12879
- unresolved: ["issue", "problem", "stuck", "blocked", "help", "question", "unsure", "unclear"],
12880
- decision: ["decide", "choice", "option", "should we", "which", "alternative", "tradeoff"],
12881
- architecture: ["structure", "design", "pattern", "approach", "system", "layer", "architecture"],
12882
- breakthrough: ["discovered", "realized", "insight", "found that", "aha", "finally", "key insight"],
12883
- todo: ["need to", "should", "must", "will", "later", "next", "todo"],
12884
- personal: ["family", "children", "friend", "relationship", "feel", "appreciate", "thank"],
12885
- philosophy: ["meaning", "consciousness", "existence", "purpose", "believe", "philosophy"],
12886
- technical: ["implement", "code", "function", "class", "module", "api", "interface"]
12887
- };
12888
- var TEMPORAL_CLASS_SCORES = {
12889
- eternal: 1,
12890
- long_term: 0.9,
12891
- medium_term: 0.7,
12892
- short_term: 0.5,
12893
- ephemeral: 0.3
13033
+ var GLOBAL_TYPE_PRIORITY = {
13034
+ technical: 1,
13035
+ preference: 2,
13036
+ architectural: 3,
13037
+ workflow: 4,
13038
+ decision: 5,
13039
+ breakthrough: 6,
13040
+ philosophy: 7,
13041
+ personal: 8
12894
13042
  };
13043
+ var MIN_ACTIVATION_SIGNALS = 2;
13044
+ var STOPWORDS = new Set([
13045
+ "the",
13046
+ "is",
13047
+ "are",
13048
+ "was",
13049
+ "were",
13050
+ "to",
13051
+ "a",
13052
+ "an",
13053
+ "and",
13054
+ "or",
13055
+ "but",
13056
+ "in",
13057
+ "on",
13058
+ "at",
13059
+ "for",
13060
+ "with",
13061
+ "about",
13062
+ "when",
13063
+ "how",
13064
+ "what",
13065
+ "why",
13066
+ "where",
13067
+ "this",
13068
+ "that",
13069
+ "it",
13070
+ "of",
13071
+ "be",
13072
+ "have",
13073
+ "do",
13074
+ "does",
13075
+ "did",
13076
+ "will",
13077
+ "would",
13078
+ "could",
13079
+ "should",
13080
+ "can",
13081
+ "may",
13082
+ "might",
13083
+ "must",
13084
+ "shall",
13085
+ "has",
13086
+ "had",
13087
+ "been",
13088
+ "being",
13089
+ "i",
13090
+ "you",
13091
+ "we",
13092
+ "they",
13093
+ "he",
13094
+ "she",
13095
+ "my",
13096
+ "your",
13097
+ "our",
13098
+ "its",
13099
+ "his",
13100
+ "her",
13101
+ "their",
13102
+ "if",
13103
+ "then",
13104
+ "else",
13105
+ "so",
13106
+ "as",
13107
+ "from",
13108
+ "by",
13109
+ "into",
13110
+ "through",
13111
+ "during",
13112
+ "before",
13113
+ "after",
13114
+ "also",
13115
+ "now",
13116
+ "back",
13117
+ "get",
13118
+ "go",
13119
+ "come",
13120
+ "let",
13121
+ "like",
13122
+ "just",
13123
+ "know",
13124
+ "think",
13125
+ "see",
13126
+ "look",
13127
+ "make",
13128
+ "take",
13129
+ "want",
13130
+ "need"
13131
+ ]);
12895
13132
 
12896
13133
  class SmartVectorRetrieval {
12897
13134
  _extractSignificantWords(text) {
12898
- const stopWords = new Set([
12899
- "the",
12900
- "is",
12901
- "are",
12902
- "was",
12903
- "were",
12904
- "to",
12905
- "a",
12906
- "an",
12907
- "and",
12908
- "or",
12909
- "but",
12910
- "in",
12911
- "on",
12912
- "at",
12913
- "for",
12914
- "with",
12915
- "about",
12916
- "when",
12917
- "how",
12918
- "what",
12919
- "why",
12920
- "where",
12921
- "this",
12922
- "that",
12923
- "it",
12924
- "of",
12925
- "be",
12926
- "have",
12927
- "do",
12928
- "does",
12929
- "did",
12930
- "will",
12931
- "would",
12932
- "could",
12933
- "should",
12934
- "can",
12935
- "may",
12936
- "might",
12937
- "must",
12938
- "shall",
12939
- "has",
12940
- "had",
12941
- "been",
12942
- "being",
12943
- "i",
12944
- "you",
12945
- "we",
12946
- "they",
12947
- "he",
12948
- "she",
12949
- "my",
12950
- "your",
12951
- "our",
12952
- "its",
12953
- "his",
12954
- "her",
12955
- "their",
12956
- "if",
12957
- "then",
12958
- "else",
12959
- "so",
12960
- "as",
12961
- "from",
12962
- "by",
12963
- "into",
12964
- "through",
12965
- "during",
12966
- "before",
12967
- "after",
12968
- "above",
12969
- "below",
12970
- "up",
12971
- "down",
12972
- "out",
12973
- "off",
12974
- "over",
12975
- "under",
12976
- "again",
12977
- "further",
12978
- "once",
12979
- "here",
12980
- "there",
12981
- "all",
12982
- "each",
12983
- "few",
12984
- "more",
12985
- "most",
12986
- "other",
12987
- "some",
12988
- "such",
12989
- "no",
12990
- "nor",
12991
- "not",
12992
- "only",
12993
- "own",
12994
- "same",
12995
- "than",
12996
- "too",
12997
- "very",
12998
- "just",
12999
- "also",
13000
- "now",
13001
- "back",
13002
- "get",
13003
- "got",
13004
- "go",
13005
- "going",
13006
- "gone",
13007
- "come",
13008
- "came",
13009
- "let",
13010
- "lets",
13011
- "hey",
13012
- "hi",
13013
- "hello",
13014
- "ok",
13015
- "okay"
13016
- ]);
13017
- const words = text.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !stopWords.has(w));
13135
+ const words = text.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !STOPWORDS.has(w));
13018
13136
  return new Set(words);
13019
13137
  }
13020
- _detectContextTypes(message) {
13021
- const messageLower = message.toLowerCase();
13022
- const detected = new Set;
13023
- for (const [type, keywords] of Object.entries(TYPE_KEYWORDS)) {
13024
- for (const keyword of keywords) {
13025
- if (messageLower.includes(keyword)) {
13026
- detected.add(type);
13027
- break;
13028
- }
13029
- }
13030
- }
13031
- return detected;
13032
- }
13033
13138
  _preFilter(memories, currentProjectId, messageLower) {
13034
13139
  return memories.filter((memory) => {
13035
- if (memory.status && memory.status !== "active") {
13140
+ if (memory.status && memory.status !== "active")
13036
13141
  return false;
13037
- }
13038
- if (memory.exclude_from_retrieval === true) {
13142
+ if (memory.exclude_from_retrieval === true)
13039
13143
  return false;
13040
- }
13041
- if (memory.superseded_by) {
13144
+ if (memory.superseded_by)
13042
13145
  return false;
13043
- }
13044
13146
  const isGlobal = memory.scope === "global" || memory.project_id === "global";
13045
- if (!isGlobal && memory.project_id !== currentProjectId) {
13147
+ if (!isGlobal && memory.project_id !== currentProjectId)
13046
13148
  return false;
13047
- }
13048
13149
  if (memory.anti_triggers?.length) {
13049
13150
  for (const antiTrigger of memory.anti_triggers) {
13050
- if (messageLower.includes(antiTrigger.toLowerCase())) {
13151
+ if (messageLower.includes(antiTrigger.toLowerCase()))
13051
13152
  return false;
13052
- }
13053
13153
  }
13054
13154
  }
13055
13155
  return true;
13056
13156
  });
13057
13157
  }
13058
- _calculateCorroboration(memory, messageWords, detectedTypes, messageLower) {
13059
- const signals = [];
13158
+ _checkTriggerActivation(messageLower, messageWords, triggerPhrases) {
13159
+ if (!triggerPhrases.length)
13160
+ return { activated: false, strength: 0 };
13161
+ let maxStrength = 0;
13162
+ for (const phrase of triggerPhrases) {
13163
+ const phraseLower = phrase.trim().toLowerCase();
13164
+ const phraseWords = phraseLower.split(/\s+/).filter((w) => !STOPWORDS.has(w) && w.length > 2);
13165
+ if (!phraseWords.length)
13166
+ continue;
13167
+ let matches = 0;
13168
+ for (const word of phraseWords) {
13169
+ if (messageWords.has(word) || messageLower.includes(word)) {
13170
+ matches++;
13171
+ } else if (messageWords.has(word.replace(/s$/, "")) || messageWords.has(word + "s") || messageLower.includes(word.replace(/s$/, "")) || messageLower.includes(word + "s")) {
13172
+ matches += 0.8;
13173
+ }
13174
+ }
13175
+ const strength = phraseWords.length > 0 ? matches / phraseWords.length : 0;
13176
+ maxStrength = Math.max(maxStrength, strength);
13177
+ }
13178
+ return { activated: maxStrength >= 0.5, strength: maxStrength };
13179
+ }
13180
+ _checkTagActivation(messageLower, messageWords, tags) {
13181
+ if (!tags.length)
13182
+ return { activated: false, count: 0 };
13183
+ let matchCount = 0;
13184
+ for (const tag of tags) {
13185
+ const tagLower = tag.trim().toLowerCase();
13186
+ if (messageWords.has(tagLower) || messageLower.includes(tagLower)) {
13187
+ matchCount++;
13188
+ }
13189
+ }
13190
+ const threshold = tags.length <= 2 ? 1 : 2;
13191
+ return { activated: matchCount >= threshold, count: matchCount };
13192
+ }
13193
+ _checkDomainActivation(messageLower, messageWords, domain) {
13194
+ if (!domain)
13195
+ return false;
13196
+ const domainLower = domain.trim().toLowerCase();
13197
+ return messageWords.has(domainLower) || messageLower.includes(domainLower);
13198
+ }
13199
+ _checkFeatureActivation(messageLower, messageWords, feature) {
13200
+ if (!feature)
13201
+ return false;
13202
+ const featureLower = feature.trim().toLowerCase();
13203
+ return messageWords.has(featureLower) || messageLower.includes(featureLower);
13204
+ }
13205
+ _checkContentActivation(messageWords, memory) {
13206
+ const contentPreview = memory.content.slice(0, 200);
13207
+ const contentWords = this._extractSignificantWords(contentPreview);
13208
+ let overlap = 0;
13209
+ for (const word of messageWords) {
13210
+ if (contentWords.has(word))
13211
+ overlap++;
13212
+ }
13213
+ return overlap >= 3;
13214
+ }
13215
+ _vectorDebugSamples = [];
13216
+ _calculateVectorSimilarity(vec1, vec2) {
13217
+ if (!vec1 || !vec2) {
13218
+ return 0;
13219
+ }
13220
+ const v1 = vec1 instanceof Float32Array ? vec1 : new Float32Array(vec1);
13221
+ const v2 = vec2 instanceof Float32Array ? vec2 : new Float32Array(vec2);
13222
+ const similarity = cosineSimilarity(v1, v2);
13223
+ if (this._vectorDebugSamples.length < 20) {
13224
+ this._vectorDebugSamples.push(similarity);
13225
+ }
13226
+ return similarity;
13227
+ }
13228
+ _logVectorStats() {
13229
+ if (this._vectorDebugSamples.length === 0)
13230
+ return;
13231
+ const samples = this._vectorDebugSamples;
13232
+ const min = Math.min(...samples);
13233
+ const max = Math.max(...samples);
13234
+ const avg = samples.reduce((a, b) => a + b, 0) / samples.length;
13235
+ console.log(`[DEBUG] Vector similarities: min=${(min * 100).toFixed(1)}% max=${(max * 100).toFixed(1)}% avg=${(avg * 100).toFixed(1)}% (${samples.length} samples)`);
13236
+ this._vectorDebugSamples = [];
13237
+ }
13238
+ _calculateImportanceScore(memory, signalCount, messageLower, messageWords) {
13060
13239
  let score = 0;
13061
- let reasoningMatch = 0;
13062
- const tagOverlap = (memory.semantic_tags ?? []).filter((tag) => messageWords.has(tag.toLowerCase()) || messageLower.includes(tag.toLowerCase()));
13063
- if (tagOverlap.length > 0) {
13064
- score += Math.min(0.4, tagOverlap.length * 0.15);
13065
- signals.push("tags:" + tagOverlap.join(","));
13066
- }
13067
- if (memory.reasoning) {
13068
- const reasoningWords = this._extractSignificantWords(memory.reasoning);
13069
- const reasoningOverlap = [...messageWords].filter((w) => reasoningWords.has(w));
13070
- if (reasoningOverlap.length > 0) {
13071
- reasoningMatch = Math.min(0.4, reasoningOverlap.length * 0.1);
13072
- score += reasoningMatch;
13073
- signals.push("reasoning:" + reasoningOverlap.slice(0, 3).join(","));
13074
- }
13075
- }
13076
- if (memory.domain) {
13077
- const domainLower = memory.domain.toLowerCase();
13078
- if (messageLower.includes(domainLower) || messageWords.has(domainLower)) {
13079
- score += 0.3;
13080
- signals.push("domain:" + memory.domain);
13081
- }
13082
- }
13083
- if (memory.knowledge_domain) {
13084
- const kdLower = memory.knowledge_domain.toLowerCase();
13085
- if (messageLower.includes(kdLower) || messageWords.has(kdLower)) {
13086
- score += 0.2;
13087
- signals.push("knowledge:" + memory.knowledge_domain);
13088
- }
13089
- }
13090
- if (memory.context_type && detectedTypes.has(memory.context_type)) {
13091
- score += 0.12;
13092
- signals.push("type:" + memory.context_type);
13093
- }
13094
- const triggerMatch = this._scoreTriggerPhrases(messageLower, memory.trigger_phrases ?? []);
13095
- if (triggerMatch > 0.3) {
13096
- score += Math.min(0.3, triggerMatch * 0.4);
13097
- signals.push("trigger:" + triggerMatch.toFixed(2));
13098
- }
13099
- if (memory.feature) {
13100
- const featureLower = memory.feature.toLowerCase();
13101
- if (messageLower.includes(featureLower) || messageWords.has(featureLower)) {
13102
- score += 0.2;
13103
- signals.push("feature:" + memory.feature);
13104
- }
13105
- }
13106
- if (memory.related_files?.length) {
13107
- for (const file of memory.related_files) {
13108
- const filename = file.split("/").pop()?.toLowerCase() ?? "";
13109
- if (filename && messageLower.includes(filename)) {
13110
- score += 0.25;
13111
- signals.push("file:" + filename);
13240
+ score += memory.importance_weight ?? 0.5;
13241
+ if (signalCount >= 4)
13242
+ score += 0.2;
13243
+ else if (signalCount >= 3)
13244
+ score += 0.1;
13245
+ if (memory.awaiting_implementation)
13246
+ score += 0.15;
13247
+ if (memory.awaiting_decision)
13248
+ score += 0.1;
13249
+ const contextType = memory.context_type?.toLowerCase() ?? "";
13250
+ const contextKeywords = {
13251
+ debugging: ["debug", "bug", "error", "fix", "issue", "problem", "broken"],
13252
+ decision: ["decide", "decision", "choose", "choice", "option", "should"],
13253
+ architectural: ["architect", "design", "structure", "pattern", "how"],
13254
+ breakthrough: ["insight", "realize", "understand", "discover", "why"],
13255
+ technical: ["implement", "code", "function", "method", "api"],
13256
+ workflow: ["process", "workflow", "step", "flow", "pipeline"],
13257
+ philosophy: ["philosophy", "principle", "belief", "approach", "think"]
13258
+ };
13259
+ const keywords = contextKeywords[contextType] ?? [];
13260
+ for (const kw of keywords) {
13261
+ if (messageWords.has(kw) || messageLower.includes(kw)) {
13262
+ score += 0.1;
13263
+ break;
13264
+ }
13265
+ }
13266
+ if (memory.problem_solution_pair) {
13267
+ const problemWords = ["error", "bug", "issue", "problem", "wrong", "fail", "broken", "help", "stuck"];
13268
+ for (const pw of problemWords) {
13269
+ if (messageWords.has(pw) || messageLower.includes(pw)) {
13270
+ score += 0.1;
13112
13271
  break;
13113
13272
  }
13114
13273
  }
13115
13274
  }
13116
- return { score: Math.min(1, score), signals, reasoningMatch };
13275
+ const temporalClass = memory.temporal_class ?? "medium_term";
13276
+ if (temporalClass === "eternal")
13277
+ score += 0.1;
13278
+ else if (temporalClass === "long_term")
13279
+ score += 0.05;
13280
+ else if (temporalClass === "ephemeral") {
13281
+ if ((memory.sessions_since_surfaced ?? 0) <= 1)
13282
+ score += 0.1;
13283
+ }
13284
+ const confidence = memory.confidence_score ?? 0.7;
13285
+ if (confidence < 0.5)
13286
+ score -= 0.1;
13287
+ const emotionalKeywords = {
13288
+ frustration: ["frustrated", "annoying", "stuck", "ugh", "damn", "hate"],
13289
+ excitement: ["excited", "awesome", "amazing", "love", "great", "wow"],
13290
+ curiosity: ["wonder", "curious", "interesting", "how", "why", "what if"],
13291
+ satisfaction: ["done", "finished", "complete", "works", "solved", "finally"],
13292
+ discovery: ["found", "realized", "understand", "insight", "breakthrough"]
13293
+ };
13294
+ const emotion = memory.emotional_resonance?.toLowerCase() ?? "";
13295
+ const emotionKws = emotionalKeywords[emotion] ?? [];
13296
+ for (const ew of emotionKws) {
13297
+ if (messageWords.has(ew) || messageLower.includes(ew)) {
13298
+ score += 0.05;
13299
+ break;
13300
+ }
13301
+ }
13302
+ return score;
13117
13303
  }
13118
13304
  retrieveRelevantMemories(allMemories, currentMessage, queryEmbedding, sessionContext, maxMemories = 5, alreadyInjectedCount = 0, maxGlobalMemories = 2) {
13305
+ const startTime = performance.now();
13119
13306
  if (!allMemories.length) {
13120
13307
  return [];
13121
13308
  }
13122
13309
  const messageLower = currentMessage.toLowerCase();
13123
13310
  const messageWords = this._extractSignificantWords(currentMessage);
13124
- const detectedTypes = this._detectContextTypes(currentMessage);
13125
13311
  const candidates = this._preFilter(allMemories, sessionContext.project_id, messageLower);
13126
13312
  if (!candidates.length) {
13127
13313
  return [];
13128
13314
  }
13129
- const scoredMemories = [];
13315
+ const activatedMemories = [];
13316
+ let rejectedCount = 0;
13130
13317
  for (const memory of candidates) {
13131
13318
  const isGlobal = memory.scope === "global" || memory.project_id === "global";
13132
- const vectorScore = this._calculateVectorSimilarity(queryEmbedding, memory.embedding);
13133
- const { score: corroborationScore, signals: corroborationSignals, reasoningMatch } = this._calculateCorroboration(memory, messageWords, detectedTypes, messageLower);
13134
- const retrievalWeight = memory.retrieval_weight ?? memory.importance_weight ?? 0.5;
13135
- const temporalScore = memory.temporal_class ? TEMPORAL_CLASS_SCORES[memory.temporal_class] ?? 0.7 : this._scoreTemporalRelevance(memory.temporal_relevance ?? "persistent");
13136
- const contextScore = this._scoreContextAlignment(currentMessage, memory.context_type ?? "general");
13137
- const tagScore = this._scoreSemanticTags(currentMessage, memory.semantic_tags ?? []);
13138
- const triggerScore = this._scoreTriggerPhrases(messageLower, memory.trigger_phrases ?? []);
13139
- const domainScore = this._scoreDomain(messageWords, messageLower, memory);
13140
- const questionScore = this._scoreQuestionTypes(currentMessage, memory.question_types ?? []);
13141
- const emotionScore = this._scoreEmotionalContext(currentMessage, memory.emotional_resonance ?? "");
13142
- const problemScore = this._scoreProblemSolution(currentMessage, memory.problem_solution_pair ?? false);
13143
- const actionBoost = memory.action_required ? 0.15 : 0;
13144
- const relevanceScore = vectorScore * 0.1 + corroborationScore * 0.14 + tagScore * 0.04 + triggerScore * 0.02;
13145
- 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;
13146
- const finalScore = valueScore + relevanceScore;
13147
- if (relevanceScore < 0.05 || finalScore < 0.3) {
13319
+ const triggerResult = this._checkTriggerActivation(messageLower, messageWords, memory.trigger_phrases ?? []);
13320
+ const tagResult = this._checkTagActivation(messageLower, messageWords, memory.semantic_tags ?? []);
13321
+ const domainActivated = this._checkDomainActivation(messageLower, messageWords, memory.domain);
13322
+ const featureActivated = this._checkFeatureActivation(messageLower, messageWords, memory.feature);
13323
+ const contentActivated = this._checkContentActivation(messageWords, memory);
13324
+ const vectorSimilarity = this._calculateVectorSimilarity(queryEmbedding, memory.embedding);
13325
+ let signalCount = 0;
13326
+ if (triggerResult.activated)
13327
+ signalCount++;
13328
+ if (tagResult.activated)
13329
+ signalCount++;
13330
+ if (domainActivated)
13331
+ signalCount++;
13332
+ if (featureActivated)
13333
+ signalCount++;
13334
+ if (contentActivated)
13335
+ signalCount++;
13336
+ if (vectorSimilarity >= 0.4)
13337
+ signalCount++;
13338
+ const signals = {
13339
+ trigger: triggerResult.activated,
13340
+ tags: tagResult.activated,
13341
+ domain: domainActivated,
13342
+ feature: featureActivated,
13343
+ content: contentActivated,
13344
+ count: signalCount,
13345
+ triggerStrength: triggerResult.strength,
13346
+ tagCount: tagResult.count,
13347
+ vectorSimilarity
13348
+ };
13349
+ if (signalCount < MIN_ACTIVATION_SIGNALS) {
13350
+ rejectedCount++;
13148
13351
  continue;
13149
13352
  }
13150
- const components = {
13151
- vector: vectorScore,
13152
- corroboration: corroborationScore,
13153
- reasoning_match: reasoningMatch,
13154
- retrieval_weight: retrievalWeight,
13155
- temporal: temporalScore,
13156
- context: contextScore,
13157
- tags: tagScore,
13158
- trigger: triggerScore,
13159
- domain: domainScore,
13160
- question: questionScore,
13161
- emotion: emotionScore,
13162
- problem: problemScore,
13163
- action: actionBoost
13164
- };
13165
- const reasoning = this._generateSelectionReasoning(components, corroborationSignals);
13166
- scoredMemories.push({
13353
+ const importanceScore = this._calculateImportanceScore(memory, signalCount, messageLower, messageWords);
13354
+ activatedMemories.push({
13167
13355
  memory,
13168
- score: finalScore,
13169
- relevance_score: relevanceScore,
13170
- value_score: valueScore,
13171
- corroboration_score: corroborationScore,
13172
- reasoning,
13173
- components,
13356
+ signals,
13357
+ importanceScore,
13174
13358
  isGlobal
13175
13359
  });
13176
13360
  }
13177
- scoredMemories.sort((a, b) => b.score - a.score);
13361
+ this._logActivationDistribution(activatedMemories, candidates.length, rejectedCount);
13362
+ this._logVectorStats();
13363
+ if (!activatedMemories.length) {
13364
+ const durationMs2 = performance.now() - startTime;
13365
+ logger.logRetrievalScoring({
13366
+ totalMemories: allMemories.length,
13367
+ currentMessage,
13368
+ alreadyInjected: alreadyInjectedCount,
13369
+ preFiltered: allMemories.length - candidates.length,
13370
+ globalCount: 0,
13371
+ projectCount: 0,
13372
+ finalCount: 0,
13373
+ durationMs: durationMs2,
13374
+ selectedMemories: []
13375
+ });
13376
+ return [];
13377
+ }
13378
+ activatedMemories.sort((a, b) => {
13379
+ if (b.signals.count !== a.signals.count) {
13380
+ return b.signals.count - a.signals.count;
13381
+ }
13382
+ return b.importanceScore - a.importanceScore;
13383
+ });
13178
13384
  const selected = [];
13179
13385
  const selectedIds = new Set;
13180
- const globalMemories = scoredMemories.filter((m) => m.isGlobal);
13181
- const projectMemories = scoredMemories.filter((m) => !m.isGlobal);
13182
- const globalSorted = globalMemories.sort((a, b) => {
13183
- const aIsPersonal = a.memory.context_type === "personal" || a.memory.context_type === "philosophy";
13184
- const bIsPersonal = b.memory.context_type === "personal" || b.memory.context_type === "philosophy";
13185
- if (aIsPersonal !== bIsPersonal) {
13186
- return aIsPersonal ? 1 : -1;
13187
- }
13188
- return b.score - a.score;
13386
+ const globalMemories = activatedMemories.filter((m) => m.isGlobal);
13387
+ const projectMemories = activatedMemories.filter((m) => !m.isGlobal);
13388
+ const globalsSorted = globalMemories.sort((a, b) => {
13389
+ const aPriority = GLOBAL_TYPE_PRIORITY[a.memory.context_type ?? "personal"] ?? 8;
13390
+ const bPriority = GLOBAL_TYPE_PRIORITY[b.memory.context_type ?? "personal"] ?? 8;
13391
+ if (aPriority !== bPriority)
13392
+ return aPriority - bPriority;
13393
+ if (b.signals.count !== a.signals.count)
13394
+ return b.signals.count - a.signals.count;
13395
+ return b.importanceScore - a.importanceScore;
13189
13396
  });
13190
- for (const item of globalSorted.slice(0, maxGlobalMemories)) {
13397
+ for (const item of globalsSorted.slice(0, maxGlobalMemories)) {
13191
13398
  if (!selectedIds.has(item.memory.id)) {
13192
13399
  selected.push(item);
13193
13400
  selectedIds.add(item.memory.id);
13194
13401
  }
13195
13402
  }
13196
- for (const item of projectMemories) {
13403
+ const projectsSorted = [...projectMemories].sort((a, b) => {
13404
+ const aAction = a.memory.action_required ? 1 : 0;
13405
+ const bAction = b.memory.action_required ? 1 : 0;
13406
+ if (bAction !== aAction)
13407
+ return bAction - aAction;
13408
+ if (b.signals.count !== a.signals.count)
13409
+ return b.signals.count - a.signals.count;
13410
+ return b.importanceScore - a.importanceScore;
13411
+ });
13412
+ console.log(`[DEBUG] Top 15 candidates (sorted):`);
13413
+ for (let i = 0;i < Math.min(15, projectsSorted.length); i++) {
13414
+ const m = projectsSorted[i];
13415
+ const action = m.memory.action_required ? "⚡" : "";
13416
+ console.log(` ${i + 1}. [${m.signals.count}sig] score=${m.importanceScore.toFixed(2)} ${action} ${m.memory.content.slice(0, 45)}...`);
13417
+ }
13418
+ for (const item of projectsSorted) {
13197
13419
  if (selected.length >= maxMemories)
13198
13420
  break;
13199
13421
  if (selectedIds.has(item.memory.id))
@@ -13201,7 +13423,27 @@ class SmartVectorRetrieval {
13201
13423
  selected.push(item);
13202
13424
  selectedIds.add(item.memory.id);
13203
13425
  }
13204
- selected.sort((a, b) => b.score - a.score);
13426
+ if (selected.length < maxMemories) {
13427
+ const relatedIds = new Set;
13428
+ for (const item of selected) {
13429
+ for (const relatedId of item.memory.related_to ?? []) {
13430
+ if (!selectedIds.has(relatedId)) {
13431
+ relatedIds.add(relatedId);
13432
+ }
13433
+ }
13434
+ }
13435
+ for (const item of activatedMemories) {
13436
+ if (selected.length >= maxMemories)
13437
+ break;
13438
+ if (selectedIds.has(item.memory.id))
13439
+ continue;
13440
+ if (relatedIds.has(item.memory.id)) {
13441
+ selected.push(item);
13442
+ selectedIds.add(item.memory.id);
13443
+ }
13444
+ }
13445
+ }
13446
+ const durationMs = performance.now() - startTime;
13205
13447
  logger.logRetrievalScoring({
13206
13448
  totalMemories: allMemories.length,
13207
13449
  currentMessage,
@@ -13210,202 +13452,102 @@ class SmartVectorRetrieval {
13210
13452
  globalCount: globalMemories.length,
13211
13453
  projectCount: projectMemories.length,
13212
13454
  finalCount: selected.length,
13455
+ durationMs,
13213
13456
  selectedMemories: selected.map((item) => ({
13214
13457
  content: item.memory.content,
13215
- reasoning: item.reasoning,
13216
- score: item.score,
13217
- relevance_score: item.relevance_score,
13218
- corroboration_score: item.corroboration_score,
13458
+ reasoning: this._generateActivationReasoning(item.signals),
13459
+ signalCount: item.signals.count,
13219
13460
  importance_weight: item.memory.importance_weight ?? 0.5,
13220
13461
  context_type: item.memory.context_type ?? "general",
13221
13462
  semantic_tags: item.memory.semantic_tags ?? [],
13222
13463
  isGlobal: item.isGlobal,
13223
- components: item.components
13464
+ signals: {
13465
+ trigger: item.signals.trigger,
13466
+ triggerStrength: item.signals.triggerStrength,
13467
+ tags: item.signals.tags,
13468
+ tagCount: item.signals.tagCount,
13469
+ domain: item.signals.domain,
13470
+ feature: item.signals.feature,
13471
+ content: item.signals.content,
13472
+ vector: item.signals.vectorSimilarity >= 0.4,
13473
+ vectorSimilarity: item.signals.vectorSimilarity
13474
+ }
13224
13475
  }))
13225
13476
  });
13226
13477
  return selected.map((item) => ({
13227
13478
  ...item.memory,
13228
- score: item.score,
13229
- relevance_score: item.relevance_score,
13230
- value_score: item.value_score
13479
+ score: item.signals.count / 6,
13480
+ relevance_score: item.signals.count / 6,
13481
+ value_score: item.importanceScore
13231
13482
  }));
13232
13483
  }
13233
- _calculateVectorSimilarity(vec1, vec2) {
13234
- if (!vec1 || !vec2)
13235
- return 0;
13236
- const v1 = vec1 instanceof Float32Array ? vec1 : new Float32Array(vec1);
13237
- return cosineSimilarity(v1, vec2);
13238
- }
13239
- _scoreTemporalRelevance(temporalType) {
13240
- const scores = {
13241
- persistent: 0.8,
13242
- session: 0.6,
13243
- temporary: 0.3,
13244
- archived: 0.1
13245
- };
13246
- return scores[temporalType] ?? 0.5;
13247
- }
13248
- _scoreContextAlignment(message, contextType) {
13249
- const messageLower = message.toLowerCase();
13250
- const keywords = TYPE_KEYWORDS[contextType] ?? [];
13251
- const matches = keywords.filter((kw) => messageLower.includes(kw)).length;
13252
- if (matches > 0) {
13253
- return Math.min(0.2 + matches * 0.15, 0.7);
13254
- }
13255
- return 0.1;
13256
- }
13257
- _scoreSemanticTags(message, tags) {
13258
- if (!tags.length)
13259
- return 0;
13260
- const messageLower = message.toLowerCase();
13261
- const matches = tags.filter((tag) => messageLower.includes(tag.trim().toLowerCase())).length;
13262
- if (matches > 0) {
13263
- return Math.min(0.3 + matches * 0.25, 1);
13264
- }
13265
- return 0;
13266
- }
13267
- _scoreTriggerPhrases(messageLower, triggerPhrases) {
13268
- if (!triggerPhrases.length)
13269
- return 0;
13270
- const stopWords = new Set([
13271
- "the",
13272
- "is",
13273
- "are",
13274
- "was",
13275
- "were",
13276
- "to",
13277
- "a",
13278
- "an",
13279
- "and",
13280
- "or",
13281
- "but",
13282
- "in",
13283
- "on",
13284
- "at",
13285
- "for",
13286
- "with",
13287
- "about",
13288
- "when",
13289
- "how",
13290
- "what",
13291
- "why"
13292
- ]);
13293
- let maxScore = 0;
13294
- for (const pattern of triggerPhrases) {
13295
- const patternLower = pattern.trim().toLowerCase();
13296
- const patternWords = patternLower.split(/\s+/).filter((w) => !stopWords.has(w) && w.length > 2);
13297
- if (patternWords.length) {
13298
- let matches = 0;
13299
- for (const word of patternWords) {
13300
- if (messageLower.includes(word)) {
13301
- matches += 1;
13302
- } else if (messageLower.includes(word.replace(/s$/, "")) || messageLower.includes(word + "s")) {
13303
- matches += 0.9;
13304
- }
13305
- }
13306
- const conceptScore = matches / patternWords.length;
13307
- maxScore = Math.max(maxScore, conceptScore);
13308
- }
13309
- }
13310
- return Math.min(maxScore, 1);
13311
- }
13312
- _scoreDomain(messageWords, messageLower, memory) {
13313
- let score = 0;
13314
- if (memory.domain) {
13315
- const domainLower = memory.domain.toLowerCase();
13316
- if (messageWords.has(domainLower) || messageLower.includes(domainLower)) {
13317
- score += 0.5;
13318
- }
13319
- }
13320
- if (memory.feature) {
13321
- const featureLower = memory.feature.toLowerCase();
13322
- if (messageWords.has(featureLower) || messageLower.includes(featureLower)) {
13323
- score += 0.3;
13324
- }
13325
- }
13326
- if (memory.component) {
13327
- const componentLower = memory.component.toLowerCase();
13328
- if (messageWords.has(componentLower) || messageLower.includes(componentLower)) {
13329
- score += 0.2;
13330
- }
13331
- }
13332
- return Math.min(score, 1);
13333
- }
13334
- _scoreQuestionTypes(message, questionTypes) {
13335
- if (!questionTypes.length)
13336
- return 0;
13337
- const messageLower = message.toLowerCase();
13338
- const questionWords = ["how", "why", "what", "when", "where"];
13339
- for (const qtype of questionTypes) {
13340
- const qtypeLower = qtype.trim().toLowerCase();
13341
- if (messageLower.includes(qtypeLower)) {
13342
- return 0.8;
13343
- }
13344
- const messageHasQuestion = questionWords.some((qw) => messageLower.includes(qw));
13345
- const typeHasQuestion = questionWords.some((qw) => qtypeLower.includes(qw));
13346
- if (messageHasQuestion && typeHasQuestion) {
13347
- return 0.5;
13348
- }
13349
- }
13350
- return 0;
13351
- }
13352
- _scoreEmotionalContext(message, emotion) {
13353
- if (!emotion)
13354
- return 0;
13355
- const messageLower = message.toLowerCase();
13356
- const emotionPatterns = {
13357
- joy: ["happy", "excited", "love", "wonderful", "great", "awesome"],
13358
- frustration: ["stuck", "confused", "help", "issue", "problem", "why"],
13359
- discovery: ["realized", "found", "discovered", "aha", "insight"],
13360
- gratitude: ["thank", "appreciate", "grateful", "dear friend"]
13361
- };
13362
- const patterns = emotionPatterns[emotion.toLowerCase()] ?? [];
13363
- if (patterns.some((pattern) => messageLower.includes(pattern))) {
13364
- return 0.7;
13365
- }
13366
- return 0;
13367
- }
13368
- _scoreProblemSolution(message, isProblemSolution) {
13369
- if (!isProblemSolution)
13370
- return 0;
13371
- const messageLower = message.toLowerCase();
13372
- const problemWords = ["error", "issue", "problem", "stuck", "help", "fix", "solve", "debug"];
13373
- if (problemWords.some((word) => messageLower.includes(word))) {
13374
- return 0.8;
13375
- }
13376
- return 0;
13377
- }
13378
- _generateSelectionReasoning(components, corroborationSignals) {
13379
- const scores = [
13380
- ["vector", components.vector],
13381
- ["corroboration", components.corroboration],
13382
- ["reasoning", components.reasoning_match],
13383
- ["weight", components.retrieval_weight],
13384
- ["context", components.context],
13385
- ["temporal", components.temporal],
13386
- ["tags", components.tags],
13387
- ["trigger", components.trigger],
13388
- ["domain", components.domain],
13389
- ["question", components.question],
13390
- ["emotion", components.emotion],
13391
- ["problem", components.problem],
13392
- ["action", components.action]
13393
- ];
13394
- scores.sort((a, b) => b[1] - a[1]);
13484
+ _generateActivationReasoning(signals) {
13395
13485
  const reasons = [];
13396
- const primary = scores[0];
13397
- if (primary[1] > 0.2) {
13398
- reasons.push(primary[0] + ":" + primary[1].toFixed(2));
13399
- }
13400
- for (const [reason, score] of scores.slice(1, 3)) {
13401
- if (score > 0.15) {
13402
- reasons.push(reason + ":" + score.toFixed(2));
13486
+ if (signals.trigger)
13487
+ reasons.push(`trigger:${(signals.triggerStrength * 100).toFixed(0)}%`);
13488
+ if (signals.tags)
13489
+ reasons.push(`tags:${signals.tagCount}`);
13490
+ if (signals.domain)
13491
+ reasons.push("domain");
13492
+ if (signals.feature)
13493
+ reasons.push("feature");
13494
+ if (signals.content)
13495
+ reasons.push("content");
13496
+ if (signals.vectorSimilarity >= 0.4)
13497
+ reasons.push(`vector:${(signals.vectorSimilarity * 100).toFixed(0)}%`);
13498
+ return reasons.length ? `Activated: ${reasons.join(", ")} (${signals.count} signals)` : "No signals";
13499
+ }
13500
+ _logActivationDistribution(activated, totalCandidates, rejectedCount) {
13501
+ const signalBuckets = {
13502
+ "2 signals": 0,
13503
+ "3 signals": 0,
13504
+ "4 signals": 0,
13505
+ "5 signals": 0,
13506
+ "6 signals": 0
13507
+ };
13508
+ for (const mem of activated) {
13509
+ const key = `${Math.min(mem.signals.count, 6)} signals`;
13510
+ signalBuckets[key] = (signalBuckets[key] ?? 0) + 1;
13511
+ }
13512
+ let triggerCount = 0, tagCount = 0, domainCount = 0, featureCount = 0, contentCount = 0, vectorCount = 0;
13513
+ for (const mem of activated) {
13514
+ if (mem.signals.trigger)
13515
+ triggerCount++;
13516
+ if (mem.signals.tags)
13517
+ tagCount++;
13518
+ if (mem.signals.domain)
13519
+ domainCount++;
13520
+ if (mem.signals.feature)
13521
+ featureCount++;
13522
+ if (mem.signals.content)
13523
+ contentCount++;
13524
+ if (mem.signals.vectorSimilarity >= 0.4)
13525
+ vectorCount++;
13526
+ }
13527
+ logger.logScoreDistribution({
13528
+ totalCandidates,
13529
+ passedGatekeeper: activated.length,
13530
+ rejectedByGatekeeper: rejectedCount,
13531
+ buckets: signalBuckets,
13532
+ stats: {
13533
+ min: activated.length ? Math.min(...activated.map((m) => m.signals.count)) : 0,
13534
+ max: activated.length ? Math.max(...activated.map((m) => m.signals.count)) : 0,
13535
+ mean: activated.length ? Math.round(activated.reduce((s, m) => s + m.signals.count, 0) / activated.length * 10) / 10 : 0,
13536
+ stdev: 0,
13537
+ spread: activated.length ? Math.max(...activated.map((m) => m.signals.count)) - Math.min(...activated.map((m) => m.signals.count)) : 0
13538
+ },
13539
+ percentiles: {},
13540
+ compressionWarning: false,
13541
+ signalBreakdown: {
13542
+ trigger: triggerCount,
13543
+ tags: tagCount,
13544
+ domain: domainCount,
13545
+ feature: featureCount,
13546
+ content: contentCount,
13547
+ vector: vectorCount,
13548
+ total: activated.length
13403
13549
  }
13404
- }
13405
- if (corroborationSignals.length > 0) {
13406
- reasons.push("signals:[" + corroborationSignals.slice(0, 2).join(",") + "]");
13407
- }
13408
- return reasons.length ? "Selected: " + reasons.join(", ") : "Combined factors";
13550
+ });
13409
13551
  }
13410
13552
  }
13411
13553
  function createRetrieval() {
@@ -13424,7 +13566,8 @@ class MemoryEngine {
13424
13566
  centralPath: config.centralPath ?? import_path2.join(import_os2.homedir(), ".local", "share", "memory"),
13425
13567
  localFolder: config.localFolder ?? ".memory",
13426
13568
  maxMemories: config.maxMemories ?? 5,
13427
- embedder: config.embedder
13569
+ embedder: config.embedder,
13570
+ personalMemoriesEnabled: config.personalMemoriesEnabled ?? true
13428
13571
  };
13429
13572
  this._retrieval = createRetrieval();
13430
13573
  }
@@ -13553,7 +13696,7 @@ class MemoryEngine {
13553
13696
  }
13554
13697
  async _generateSessionPrimer(store, projectId) {
13555
13698
  let personalContext;
13556
- if (store.isPersonalMemoriesEnabled()) {
13699
+ if (this._config.personalMemoriesEnabled) {
13557
13700
  const personalPrimer = await store.getPersonalPrimer();
13558
13701
  personalContext = personalPrimer?.content;
13559
13702
  }
@@ -13641,12 +13784,28 @@ ${primer.personal_context}`);
13641
13784
  **Project status**: ${primer.project_status}`);
13642
13785
  }
13643
13786
  parts.push(`
13644
- **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`);
13787
+ **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`);
13645
13788
  parts.push(`
13646
13789
  *Memories will surface naturally as we converse.*`);
13647
13790
  return parts.join(`
13648
13791
  `);
13649
13792
  }
13793
+ _formatAge(createdAt) {
13794
+ const now = Date.now();
13795
+ const diffMs = now - createdAt;
13796
+ const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
13797
+ if (diffDays === 0)
13798
+ return "today";
13799
+ if (diffDays === 1)
13800
+ return "1d";
13801
+ if (diffDays < 7)
13802
+ return `${diffDays}d`;
13803
+ if (diffDays < 30)
13804
+ return `${Math.floor(diffDays / 7)}w`;
13805
+ if (diffDays < 365)
13806
+ return `${Math.floor(diffDays / 30)}mo`;
13807
+ return `${Math.floor(diffDays / 365)}y`;
13808
+ }
13650
13809
  _formatMemories(memories) {
13651
13810
  if (!memories.length)
13652
13811
  return "";
@@ -13657,11 +13816,40 @@ ${primer.personal_context}`);
13657
13816
  const tags = memory.semantic_tags?.join(", ") || "";
13658
13817
  const importance = memory.importance_weight?.toFixed(1) || "0.5";
13659
13818
  const emoji = getMemoryEmoji(memory.context_type || "general");
13660
- parts.push(`[${emoji} ${importance}] [${tags}] ${memory.content}`);
13819
+ const actionFlag = memory.action_required ? " ⚡ACTION" : "";
13820
+ const age = memory.updated_at ? this._formatAge(memory.updated_at) : memory.created_at ? this._formatAge(memory.created_at) : "";
13821
+ parts.push(`[${emoji} • ${importance} • ${age}${actionFlag}] [${tags}] ${memory.content}`);
13822
+ const related = memory.related_to;
13823
+ if (related && related.length > 0) {
13824
+ const moreCount = related.length - 1;
13825
+ const moreSuffix = moreCount > 0 ? ` +${moreCount} more` : "";
13826
+ parts.push(` ↳ ${related[0]}${moreSuffix}`);
13827
+ }
13661
13828
  }
13662
13829
  return parts.join(`
13663
13830
  `);
13664
13831
  }
13832
+ getStoragePaths(projectId, projectPath) {
13833
+ const globalPath = import_path2.join(import_os2.homedir(), ".local", "share", "memory", "global");
13834
+ const globalMemoriesPath = import_path2.join(globalPath, "memories");
13835
+ const personalPrimerPath = import_path2.join(globalPath, "primer", "personal-primer.md");
13836
+ let storeBasePath;
13837
+ if (this._config.storageMode === "local" && projectPath) {
13838
+ storeBasePath = import_path2.join(projectPath, this._config.localFolder);
13839
+ } else {
13840
+ storeBasePath = this._config.centralPath;
13841
+ }
13842
+ const projectRootPath = import_path2.join(storeBasePath, projectId);
13843
+ const projectMemoriesPath = import_path2.join(projectRootPath, "memories");
13844
+ return {
13845
+ projectPath: projectRootPath,
13846
+ globalPath,
13847
+ projectMemoriesPath,
13848
+ globalMemoriesPath,
13849
+ personalPrimerPath,
13850
+ storageMode: this._config.storageMode
13851
+ };
13852
+ }
13665
13853
  close() {
13666
13854
  for (const store of this._stores.values()) {
13667
13855
  store.close();
@@ -13695,11 +13883,12 @@ class Curator {
13695
13883
  this._config = {
13696
13884
  apiKey: config.apiKey ?? "",
13697
13885
  cliCommand,
13698
- cliType: config.cliType ?? "claude-code"
13886
+ cliType: config.cliType ?? "claude-code",
13887
+ personalMemoriesEnabled: config.personalMemoriesEnabled ?? true
13699
13888
  };
13700
13889
  }
13701
13890
  buildCurationPrompt(triggerType = "session_end") {
13702
- return `You have just had a conversation. As this session is ending (${triggerType}), please curate memories for the Claude Tools Memory System.
13891
+ const basePrompt = `You have just had a conversation. As this session is ending (${triggerType}), please curate memories for the Claude Tools Memory System.
13703
13892
 
13704
13893
  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.
13705
13894
 
@@ -13736,27 +13925,82 @@ Each memory should stand alone.
13736
13925
  - Craft language that activates rather than just informs
13737
13926
  - Test: 'What state will this restore when Claude encounters it?'
13738
13927
 
13739
- **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.
13928
+ **HOW RETRIEVAL WORKS - ACTIVATION SIGNAL ALGORITHM**
13929
+
13930
+ Understanding the algorithm helps you craft metadata that surfaces memories at the right moments.
13931
+
13932
+ **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.
13740
13933
 
13741
- **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:
13934
+ **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.
13742
13935
 
13743
- 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.
13744
- - GOOD: ["embeddings", "retrieval", "curator", "fsdb", "memory-system"]
13745
- - WEAK: ["technical", "implementation", "code"]
13936
+ **6 ACTIVATION SIGNALS** (each is binary - fires or doesn't):
13746
13937
 
13747
- 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.
13938
+ 1. **TRIGGER** - Words from trigger_phrases found in user's message (≥50% match)
13939
+ - THE MOST IMPORTANT SIGNAL. Handcrafted activation patterns.
13940
+ - Example: "when debugging retrieval" fires if user says "I'm debugging the retrieval algorithm"
13748
13941
 
13749
- 3. **Domain field** (12% weight) - The specific area this relates to. Be precise: "embeddings", "gpu-compute", "authentication", not "technical" or "backend".
13942
+ 2. **TAGS** - 2+ semantic_tags found in user's message
13943
+ - Use words users would ACTUALLY TYPE, not generic descriptors
13944
+ - GOOD: ["retrieval", "embeddings", "curator", "scoring"]
13945
+ - WEAK: ["technical", "important", "system"]
13750
13946
 
13751
- 4. **Feature field** - Even more specific: "vector-search", "login-flow", "curation-prompt".
13947
+ 3. **DOMAIN** - The domain word appears in user's message
13948
+ - Be specific: "retrieval", "embeddings", "auth", "ui"
13949
+ - NOT: "technical", "code", "implementation"
13752
13950
 
13753
- **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.
13951
+ 4. **FEATURE** - The feature word appears in user's message
13952
+ - Be specific: "scoring-weights", "gpu-acceleration", "login-flow"
13754
13953
 
13755
- **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.
13954
+ 5. **CONTENT** - 3+ significant words from memory content overlap with message
13955
+ - Automatic - based on the memory's content text
13756
13956
 
13757
- **YOUR IMPORTANCE ASSESSMENT MATTERS** (18% weight): The importance_weight you assign is the single most influential factor. Use it wisely.
13957
+ 6. **VECTOR** - Semantic similarity 40% (embedding cosine distance)
13958
+ - Automatic - based on embeddings generated from content
13758
13959
 
13759
- **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.
13960
+ **RELEVANCE GATE**: A memory must have ≥2 signals to be considered relevant.
13961
+ If only 1 signal fires, the memory is REJECTED. This prevents noise.
13962
+
13963
+ **RANKING AMONG RELEVANT**: Once a memory passes the gate:
13964
+ 1. Sort by SIGNAL COUNT (more signals = more certainly relevant)
13965
+ 2. Then by IMPORTANCE WEIGHT (your assessment of how important this memory is)
13966
+
13967
+ **SELECTION**:
13968
+ - Global memories (scope='global'): Max 2 selected, tech types prioritized over personal
13969
+ - Project memories: Fill remaining slots, action_required prioritized
13970
+ - Related memories (related_to field): May be included if they also passed the gate
13971
+
13972
+ **WHY THIS MATTERS FOR YOU**:
13973
+ - If you don't fill trigger_phrases well → trigger signal never fires
13974
+ - If you use generic tags → tags signal rarely fires
13975
+ - If you leave domain/feature empty → those signals can't fire
13976
+ - A memory with poor metadata may NEVER surface because it can't reach 2 signals
13977
+
13978
+ **CRAFTING EFFECTIVE METADATA** (CRITICAL FOR RETRIEVAL):
13979
+
13980
+ 1. **trigger_phrases** (MOST IMPORTANT) - Activation patterns describing WHEN to surface:
13981
+ - Include 2-4 specific patterns per memory
13982
+ - Use words the user would actually type
13983
+ - GOOD: ["debugging retrieval", "working on embeddings", "memory system performance"]
13984
+ - WEAK: ["when relevant", "if needed", "technical work"]
13985
+
13986
+ 2. **semantic_tags** - Words users would type (need 2+ to fire):
13987
+ - Be specific and searchable
13988
+ - GOOD: ["retrieval", "embeddings", "fsdb", "curator", "scoring"]
13989
+ - WEAK: ["technical", "important", "system", "implementation"]
13990
+
13991
+ 3. **domain** (NEW - FILL THIS) - Single specific area word:
13992
+ - GOOD: "retrieval", "embeddings", "curator", "signals", "fsdb"
13993
+ - WEAK: "technical", "code", "memory" (too generic)
13994
+
13995
+ 4. **feature** (NEW - FILL THIS) - Specific feature within domain:
13996
+ - GOOD: "scoring-algorithm", "activation-signals", "vector-search"
13997
+ - WEAK: "implementation", "code", "logic"
13998
+
13999
+ 5. **importance_weight** - Only affects ranking AMONG relevant memories:
14000
+ - 0.9+ = Critical breakthrough, must surface if relevant
14001
+ - 0.7-0.8 = Important insight, should surface if relevant
14002
+ - 0.5-0.6 = Useful context, nice to have if relevant
14003
+ - NOTE: This does NOT affect whether the memory passes the relevance gate!
13760
14004
 
13761
14005
  **SCOPE DETERMINES WHERE MEMORIES SURFACE**:
13762
14006
  - scope: 'global' → surfaces in ALL projects (personal facts, philosophy, preferences)
@@ -13824,6 +14068,21 @@ Return ONLY this JSON structure:
13824
14068
  }
13825
14069
  ]
13826
14070
  }`;
14071
+ if (!this._config.personalMemoriesEnabled) {
14072
+ return basePrompt + `
14073
+
14074
+ ---
14075
+
14076
+ **IMPORTANT: PERSONAL MEMORIES DISABLED**
14077
+
14078
+ The user has disabled personal memory extraction. Do NOT extract any memories with:
14079
+ - context_type: "personal"
14080
+ - scope: "global" when the content is about the user's personal life, relationships, family, or emotional states
14081
+ - Content about the user's preferences, feelings, personal opinions, or relationship dynamics
14082
+
14083
+ Focus ONLY on technical, architectural, debugging, decision, workflow, and project-related memories. Skip any content that would reveal personal information about the user.`;
14084
+ }
14085
+ return basePrompt;
13827
14086
  }
13828
14087
  parseCurationResponse(responseJson) {
13829
14088
  try {
@@ -13914,33 +14173,35 @@ Return ONLY this JSON structure:
13914
14173
  _clamp(value, min, max) {
13915
14174
  return Math.max(min, Math.min(max, value));
13916
14175
  }
13917
- async curateWithSDK(conversationContext, triggerType = "session_end") {
14176
+ async curateWithSDK(messages, triggerType = "session_end") {
13918
14177
  if (!this._config.apiKey) {
13919
- throw new Error("API key required for SDK mode");
14178
+ throw new Error("API key required for SDK mode. Set ANTHROPIC_API_KEY environment variable.");
13920
14179
  }
13921
14180
  const { default: Anthropic2 } = await Promise.resolve().then(() => (init_sdk(), exports_sdk));
13922
14181
  const client = new Anthropic2({ apiKey: this._config.apiKey });
13923
- const prompt = this.buildCurationPrompt(triggerType);
14182
+ const systemPrompt = this.buildCurationPrompt(triggerType);
14183
+ const conversationMessages = [
14184
+ ...messages,
14185
+ {
14186
+ role: "user",
14187
+ 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."
14188
+ }
14189
+ ];
13924
14190
  const response = await client.messages.create({
13925
14191
  model: "claude-sonnet-4-20250514",
13926
14192
  max_tokens: 8192,
13927
- messages: [
13928
- {
13929
- role: "user",
13930
- content: `${conversationContext}
13931
-
13932
- ---
13933
-
13934
- ${prompt}`
13935
- }
13936
- ]
14193
+ system: systemPrompt,
14194
+ messages: conversationMessages
13937
14195
  });
13938
14196
  const content = response.content[0];
13939
14197
  if (content.type !== "text") {
13940
- throw new Error("Unexpected response type");
14198
+ throw new Error("Unexpected response type from Claude API");
13941
14199
  }
13942
14200
  return this.parseCurationResponse(content.text);
13943
14201
  }
14202
+ async curateFromSegment(segment, triggerType = "session_end") {
14203
+ return this.curateWithSDK(segment.messages, triggerType);
14204
+ }
13944
14205
  async curateWithCLI(sessionId, triggerType = "session_end", cwd, cliTypeOverride) {
13945
14206
  const type = cliTypeOverride ?? this._config.cliType;
13946
14207
  const systemPrompt = this.buildCurationPrompt(triggerType);