@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.
@@ -19865,6 +19865,11 @@ var managementLogSchema = {
19865
19865
  error: "string",
19866
19866
  details: "string"
19867
19867
  };
19868
+ var personalPrimerSchema = {
19869
+ content: "string",
19870
+ session_updated: "number",
19871
+ updated_by: "string"
19872
+ };
19868
19873
 
19869
19874
  // src/core/store.ts
19870
19875
  var PERSONAL_PRIMER_ID = "personal-primer";
@@ -19886,7 +19891,7 @@ class MemoryStore {
19886
19891
  return this._global;
19887
19892
  }
19888
19893
  const globalPath = this._config.globalPath;
19889
- console.log(`\uD83C\uDF10 [DEBUG] Creating global database at ${globalPath}`);
19894
+ console.log(`\uD83C\uDF10 [DEBUG] Initializing global database at ${globalPath}`);
19890
19895
  const db = createDatabase({
19891
19896
  name: "global",
19892
19897
  basePath: globalPath
@@ -19903,8 +19908,14 @@ class MemoryStore {
19903
19908
  autoSave: true,
19904
19909
  watchFiles: this._config.watchFiles
19905
19910
  });
19906
- await Promise.all([memories.load(), managementLogs.load()]);
19907
- this._global = { db, memories, managementLogs };
19911
+ const primer = db.collection("primer", {
19912
+ schema: personalPrimerSchema,
19913
+ contentColumn: "content",
19914
+ autoSave: true,
19915
+ watchFiles: this._config.watchFiles
19916
+ });
19917
+ await Promise.all([memories.load(), managementLogs.load(), primer.load()]);
19918
+ this._global = { db, memories, managementLogs, primer };
19908
19919
  return this._global;
19909
19920
  }
19910
19921
  async getGlobalMemories() {
@@ -19981,40 +19992,31 @@ class MemoryStore {
19981
19992
  return id;
19982
19993
  }
19983
19994
  async getPersonalPrimer() {
19984
- const { memories } = await this.getGlobal();
19985
- const primer = memories.get(PERSONAL_PRIMER_ID);
19986
- if (!primer) {
19995
+ const { primer } = await this.getGlobal();
19996
+ const record = primer.get(PERSONAL_PRIMER_ID);
19997
+ if (!record) {
19987
19998
  return null;
19988
19999
  }
19989
20000
  return {
19990
- content: primer.content,
19991
- updated: primer.updated
20001
+ content: record.content,
20002
+ updated: record.updated
19992
20003
  };
19993
20004
  }
19994
- async setPersonalPrimer(content) {
19995
- const { memories } = await this.getGlobal();
19996
- const existing = memories.get(PERSONAL_PRIMER_ID);
20005
+ async setPersonalPrimer(content, sessionNumber, updatedBy = "user") {
20006
+ const { primer } = await this.getGlobal();
20007
+ const existing = primer.get(PERSONAL_PRIMER_ID);
19997
20008
  if (existing) {
19998
- memories.update(PERSONAL_PRIMER_ID, { content });
20009
+ primer.update(PERSONAL_PRIMER_ID, {
20010
+ content,
20011
+ session_updated: sessionNumber ?? existing.session_updated,
20012
+ updated_by: updatedBy
20013
+ });
19999
20014
  } else {
20000
- memories.insert({
20015
+ primer.insert({
20001
20016
  id: PERSONAL_PRIMER_ID,
20002
20017
  content,
20003
- reasoning: "Personal relationship context injected at session start",
20004
- importance_weight: 1,
20005
- confidence_score: 1,
20006
- context_type: "personal",
20007
- temporal_relevance: "persistent",
20008
- knowledge_domain: "personal",
20009
- emotional_resonance: "neutral",
20010
- action_required: false,
20011
- problem_solution_pair: false,
20012
- semantic_tags: ["personal", "primer", "relationship"],
20013
- trigger_phrases: [],
20014
- question_types: [],
20015
- session_id: "system",
20016
- project_id: "global",
20017
- embedding: null
20018
+ session_updated: sessionNumber ?? 0,
20019
+ updated_by: updatedBy
20018
20020
  });
20019
20021
  }
20020
20022
  }
@@ -20034,6 +20036,7 @@ class MemoryStore {
20034
20036
  success: entry.success,
20035
20037
  duration_ms: entry.durationMs,
20036
20038
  summary: entry.summary,
20039
+ full_report: entry.fullReport ?? "",
20037
20040
  error: entry.error ?? "",
20038
20041
  details: entry.details ? JSON.stringify(entry.details) : ""
20039
20042
  });
@@ -20397,7 +20400,14 @@ var sym = {
20397
20400
  fire: "\uD83D\uDD25",
20398
20401
  target: "\uD83C\uDFAF"
20399
20402
  };
20403
+ var _verbose = false;
20400
20404
  var logger = {
20405
+ setVerbose(enabled) {
20406
+ _verbose = enabled;
20407
+ },
20408
+ isVerbose() {
20409
+ return _verbose;
20410
+ },
20401
20411
  info(message) {
20402
20412
  console.log(`${timestamp()} ${style("cyan", sym.info)} ${message}`);
20403
20413
  },
@@ -20469,7 +20479,14 @@ var logger = {
20469
20479
  problem_solution: "✅",
20470
20480
  project_context: "\uD83D\uDCE6",
20471
20481
  milestone: "\uD83C\uDFC6",
20472
- general: "\uD83D\uDCDD"
20482
+ general: "\uD83D\uDCDD",
20483
+ project_state: "\uD83D\uDCCD",
20484
+ pending_task: "⏳",
20485
+ work_in_progress: "\uD83D\uDD28",
20486
+ system_feedback: "\uD83D\uDCE3",
20487
+ project_milestone: "\uD83C\uDFC6",
20488
+ architectural_insight: "\uD83C\uDFDB️",
20489
+ architectural_direction: "\uD83E\uDDED"
20473
20490
  };
20474
20491
  console.log();
20475
20492
  console.log(`${timestamp()} ${style("cyan", sym.sparkles)} ${style("bold", `SURFACING ${memories.length} MEMORIES`)}`);
@@ -20481,11 +20498,12 @@ var logger = {
20481
20498
  return;
20482
20499
  }
20483
20500
  memories.forEach((m, i) => {
20484
- const score = style("green", `${(m.score * 100).toFixed(0)}%`);
20501
+ const signalCount = Math.round(m.score * 6);
20502
+ const signalStr = style("green", `${signalCount}sig`);
20485
20503
  const emoji = emojiMap[m.context_type?.toLowerCase()] ?? "\uD83D\uDCDD";
20486
20504
  const num = style("dim", `${i + 1}.`);
20487
20505
  const preview = m.content.length > 55 ? m.content.slice(0, 55) + style("dim", "...") : m.content;
20488
- console.log(` ${num} [${score}] ${emoji}`);
20506
+ console.log(` ${num} [${signalStr}] ${emoji}`);
20489
20507
  console.log(` ${preview}`);
20490
20508
  });
20491
20509
  console.log();
@@ -20533,44 +20551,124 @@ var logger = {
20533
20551
  console.log(` ${style("dim", "processing:")} ${memoriesCount} new memories`);
20534
20552
  },
20535
20553
  logManagementComplete(result) {
20554
+ const formatAction = (action, truncate = true) => {
20555
+ let icon = " •";
20556
+ if (action.startsWith("READ OK"))
20557
+ icon = style("dim", " \uD83D\uDCD6");
20558
+ else if (action.startsWith("READ FAILED"))
20559
+ icon = style("red", " ❌");
20560
+ else if (action.startsWith("WRITE OK"))
20561
+ icon = style("green", " ✏️");
20562
+ else if (action.startsWith("WRITE FAILED"))
20563
+ icon = style("red", " ❌");
20564
+ else if (action.startsWith("RECEIVED"))
20565
+ icon = style("cyan", " \uD83D\uDCE5");
20566
+ else if (action.startsWith("CREATED"))
20567
+ icon = style("green", " ✨");
20568
+ else if (action.startsWith("UPDATED"))
20569
+ icon = style("blue", " \uD83D\uDCDD");
20570
+ else if (action.startsWith("SUPERSEDED"))
20571
+ icon = style("yellow", " \uD83D\uDD04");
20572
+ else if (action.startsWith("RESOLVED"))
20573
+ icon = style("green", " ✅");
20574
+ else if (action.startsWith("LINKED"))
20575
+ icon = style("cyan", " \uD83D\uDD17");
20576
+ else if (action.startsWith("PRIMER"))
20577
+ icon = style("magenta", " \uD83D\uDC9C");
20578
+ else if (action.startsWith("SKIPPED"))
20579
+ icon = style("dim", " ⏭️");
20580
+ else if (action.startsWith("NO_ACTION"))
20581
+ icon = style("dim", " ◦");
20582
+ const text = truncate && action.length > 70 ? action.slice(0, 67) + "..." : action;
20583
+ return `${icon} ${style("dim", text)}`;
20584
+ };
20536
20585
  if (result.success) {
20537
20586
  console.log(` ${style("green", sym.check)} ${style("bold", "Completed")}`);
20538
- const stats = [];
20539
- if (result.superseded && result.superseded > 0) {
20540
- stats.push(`${result.superseded} superseded`);
20541
- }
20542
- if (result.resolved && result.resolved > 0) {
20543
- stats.push(`${result.resolved} resolved`);
20544
- }
20545
- if (result.linked && result.linked > 0) {
20546
- stats.push(`${result.linked} linked`);
20547
- }
20548
- if (result.primerUpdated) {
20549
- stats.push("primer updated");
20550
- }
20551
- if (stats.length > 0) {
20552
- console.log(` ${style("dim", "changes:")} ${stats.join(style("dim", ", "))}`);
20587
+ if (_verbose) {
20588
+ console.log(` ${style("dim", "─".repeat(50))}`);
20589
+ console.log(` ${style("cyan", "\uD83D\uDCCA")} ${style("bold", "Statistics")}`);
20590
+ const filesRead = result.filesRead ?? 0;
20591
+ const filesWritten = result.filesWritten ?? 0;
20592
+ console.log(` ${style("dim", "Files read:")} ${filesRead > 0 ? style("green", String(filesRead)) : style("dim", "0")}`);
20593
+ console.log(` ${style("dim", "Files written:")} ${filesWritten > 0 ? style("green", String(filesWritten)) : style("dim", "0")}`);
20594
+ const superseded = result.superseded ?? 0;
20595
+ const resolved = result.resolved ?? 0;
20596
+ const linked = result.linked ?? 0;
20597
+ console.log(` ${style("dim", "Superseded:")} ${superseded > 0 ? style("yellow", String(superseded)) : style("dim", "0")}`);
20598
+ console.log(` ${style("dim", "Resolved:")} ${resolved > 0 ? style("green", String(resolved)) : style("dim", "0")}`);
20599
+ console.log(` ${style("dim", "Linked:")} ${linked > 0 ? style("cyan", String(linked)) : style("dim", "0")}`);
20600
+ console.log(` ${style("dim", "Primer:")} ${result.primerUpdated ? style("magenta", "updated") : style("dim", "unchanged")}`);
20601
+ if (result.actions && result.actions.length > 0) {
20602
+ console.log(` ${style("dim", "─".repeat(50))}`);
20603
+ console.log(` ${style("cyan", "\uD83C\uDFAC")} ${style("bold", "Actions")} ${style("dim", `(${result.actions.length} total)`)}`);
20604
+ for (const action of result.actions) {
20605
+ console.log(` ${formatAction(action, false)}`);
20606
+ }
20607
+ }
20608
+ if (result.fullReport) {
20609
+ console.log(` ${style("dim", "─".repeat(50))}`);
20610
+ console.log(` ${style("cyan", "\uD83D\uDCCB")} ${style("bold", "Full Report")}`);
20611
+ const reportLines = result.fullReport.split(`
20612
+ `);
20613
+ for (const line of reportLines) {
20614
+ if (line.includes("===")) {
20615
+ console.log(` ${style("bold", line)}`);
20616
+ } else if (line.match(/^[A-Z_]+:/)) {
20617
+ console.log(` ${style("cyan", line)}`);
20618
+ } else {
20619
+ console.log(` ${style("dim", line)}`);
20620
+ }
20621
+ }
20622
+ }
20623
+ console.log(` ${style("dim", "─".repeat(50))}`);
20553
20624
  } else {
20554
- console.log(` ${style("dim", "changes:")} none (memories are current)`);
20555
- }
20556
- if (result.summary) {
20557
- const shortSummary = result.summary.length > 60 ? result.summary.slice(0, 60) + "..." : result.summary;
20558
- console.log(` ${style("dim", "summary:")} ${shortSummary}`);
20625
+ const stats = [];
20626
+ if (result.superseded && result.superseded > 0)
20627
+ stats.push(`${result.superseded} superseded`);
20628
+ if (result.resolved && result.resolved > 0)
20629
+ stats.push(`${result.resolved} resolved`);
20630
+ if (result.linked && result.linked > 0)
20631
+ stats.push(`${result.linked} linked`);
20632
+ if (result.primerUpdated)
20633
+ stats.push("primer updated");
20634
+ if (stats.length > 0) {
20635
+ console.log(` ${style("dim", "changes:")} ${stats.join(style("dim", ", "))}`);
20636
+ } else {
20637
+ console.log(` ${style("dim", "changes:")} none (memories are current)`);
20638
+ }
20639
+ if (result.actions && result.actions.length > 0) {
20640
+ console.log(` ${style("dim", "actions:")}`);
20641
+ for (const action of result.actions.slice(0, 10)) {
20642
+ console.log(` ${formatAction(action, true)}`);
20643
+ }
20644
+ if (result.actions.length > 10) {
20645
+ console.log(` ${style("dim", ` ... and ${result.actions.length - 10} more actions`)}`);
20646
+ }
20647
+ }
20559
20648
  }
20560
20649
  } else {
20561
20650
  console.log(` ${style("yellow", sym.warning)} ${style("bold", "Failed")}`);
20562
20651
  if (result.error) {
20563
- console.log(` ${style("dim", "error:")} ${result.error.slice(0, 80)}`);
20652
+ console.log(` ${style("red", "error:")} ${result.error}`);
20653
+ }
20654
+ if (result.fullReport) {
20655
+ console.log(` ${style("dim", "─".repeat(50))}`);
20656
+ console.log(` ${style("red", "\uD83D\uDCCB")} ${style("bold", "Error Report:")}`);
20657
+ const reportLines = result.fullReport.split(`
20658
+ `);
20659
+ for (const line of reportLines) {
20660
+ console.log(` ${style("dim", line)}`);
20661
+ }
20564
20662
  }
20565
20663
  }
20566
20664
  console.log();
20567
20665
  },
20568
20666
  logRetrievalScoring(params) {
20569
- const { totalMemories, currentMessage, alreadyInjected, preFiltered, globalCount, projectCount, finalCount, selectedMemories } = params;
20667
+ const { totalMemories, currentMessage, alreadyInjected, preFiltered, globalCount, projectCount, finalCount, durationMs, selectedMemories } = params;
20668
+ const timeStr = durationMs !== undefined ? style("cyan", `${durationMs.toFixed(1)}ms`) : "";
20570
20669
  console.log();
20571
- console.log(`${timestamp()} ${style("magenta", sym.brain)} ${style("bold", "MULTI-DIMENSIONAL RETRIEVAL")}`);
20572
- console.log(` ${style("dim", "total:")} ${totalMemories} memories`);
20573
- console.log(` ${style("dim", "pre-filtered:")} ${preFiltered} (inactive/excluded/scope)`);
20670
+ console.log(`${timestamp()} ${style("magenta", sym.brain)} ${style("bold", "RETRIEVAL")} ${timeStr}`);
20671
+ console.log(` ${style("dim", "total:")} ${totalMemories} → ${style("dim", "filtered:")} ${preFiltered} → ${style("dim", "candidates:")} ${totalMemories - preFiltered}`);
20574
20672
  console.log(` ${style("dim", "already injected:")} ${alreadyInjected}`);
20575
20673
  const msgPreview = currentMessage.length > 60 ? currentMessage.slice(0, 60) + "..." : currentMessage;
20576
20674
  console.log(` ${style("dim", "message:")} "${msgPreview}"`);
@@ -20589,347 +20687,471 @@ var logger = {
20589
20687
  console.log();
20590
20688
  selectedMemories.forEach((m, i) => {
20591
20689
  const num = style("dim", `${i + 1}.`);
20592
- const score = style("green", `${(m.score * 100).toFixed(0)}%`);
20593
- const relevance = style("cyan", `rel:${(m.relevance_score * 100).toFixed(0)}%`);
20594
- const corr = style("magenta", `corr:${(m.corroboration_score * 100).toFixed(0)}%`);
20690
+ const signalsStr = style("green", `${m.signalCount} signals`);
20691
+ const imp = style("magenta", `imp:${(m.importance_weight * 100).toFixed(0)}%`);
20595
20692
  const type = style("yellow", m.context_type.toUpperCase());
20596
- const scope = m.isGlobal ? style("blue", "[G]") : "";
20597
- console.log(` ${num} [${score} ${relevance} ${corr}] ${type} ${scope}`);
20693
+ const scope = m.isGlobal ? style("blue", " [G]") : "";
20694
+ console.log(` ${num} [${signalsStr} ${imp}] ${type}${scope}`);
20598
20695
  const preview = m.content.length > 60 ? m.content.slice(0, 60) + style("dim", "...") : m.content;
20599
20696
  console.log(` ${style("white", preview)}`);
20600
- 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(", ");
20601
- if (components) {
20602
- console.log(` ${style("dim", "scores:")} ${components}`);
20697
+ const firedSignals = [];
20698
+ if (m.signals.trigger) {
20699
+ firedSignals.push(`trigger:${(m.signals.triggerStrength * 100).toFixed(0)}%`);
20700
+ }
20701
+ if (m.signals.tags) {
20702
+ firedSignals.push(`tags:${m.signals.tagCount}`);
20603
20703
  }
20604
- if (m.reasoning) {
20605
- console.log(` ${style("dim", m.reasoning)}`);
20704
+ if (m.signals.domain)
20705
+ firedSignals.push("domain");
20706
+ if (m.signals.feature)
20707
+ firedSignals.push("feature");
20708
+ if (m.signals.content)
20709
+ firedSignals.push("content");
20710
+ if (m.signals.vector) {
20711
+ firedSignals.push(`vector:${(m.signals.vectorSimilarity * 100).toFixed(0)}%`);
20712
+ }
20713
+ if (firedSignals.length > 0) {
20714
+ console.log(` ${style("cyan", "signals:")} ${firedSignals.join(", ")}`);
20606
20715
  }
20607
20716
  console.log();
20608
20717
  });
20718
+ },
20719
+ logScoreDistribution(params) {
20720
+ const { totalCandidates, passedGatekeeper, rejectedByGatekeeper, buckets, stats, signalBreakdown } = params;
20721
+ console.log();
20722
+ console.log(style("dim", " ─".repeat(30)));
20723
+ console.log(` ${style("bold", "ACTIVATION SIGNALS")}`);
20724
+ console.log();
20725
+ const passRate = totalCandidates > 0 ? (passedGatekeeper / totalCandidates * 100).toFixed(0) : "0";
20726
+ console.log(` ${style("dim", "Activated:")} ${style("green", String(passedGatekeeper))}/${totalCandidates} (${passRate}%)`);
20727
+ console.log(` ${style("dim", "Rejected:")} ${rejectedByGatekeeper} (< 2 signals)`);
20728
+ console.log();
20729
+ if (signalBreakdown && signalBreakdown.total > 0) {
20730
+ console.log(` ${style("cyan", "Signal Breakdown:")}`);
20731
+ const signals = [
20732
+ { name: "trigger", count: signalBreakdown.trigger },
20733
+ { name: "tags", count: signalBreakdown.tags },
20734
+ { name: "domain", count: signalBreakdown.domain },
20735
+ { name: "feature", count: signalBreakdown.feature },
20736
+ { name: "content", count: signalBreakdown.content },
20737
+ { name: "vector", count: signalBreakdown.vector }
20738
+ ];
20739
+ for (const sig of signals) {
20740
+ const pct = (sig.count / signalBreakdown.total * 100).toFixed(0);
20741
+ const bar = "█".repeat(Math.round(sig.count / signalBreakdown.total * 20));
20742
+ console.log(` ${sig.name.padEnd(8)} ${bar.padEnd(20)} ${sig.count} (${pct}%)`);
20743
+ }
20744
+ console.log();
20745
+ }
20746
+ if (stats.max > 0) {
20747
+ console.log(` ${style("cyan", "Signals:")} min=${stats.min} max=${stats.max} mean=${stats.mean}`);
20748
+ console.log();
20749
+ }
20750
+ if (Object.keys(buckets).length > 0) {
20751
+ console.log(` ${style("bold", "Distribution:")}`);
20752
+ const maxBucketCount = Math.max(...Object.values(buckets), 1);
20753
+ const bucketOrder = ["2 signals", "3 signals", "4 signals", "5 signals", "6 signals"];
20754
+ for (const bucket of bucketOrder) {
20755
+ const count = buckets[bucket] ?? 0;
20756
+ if (count > 0 || bucket === "2 signals") {
20757
+ const barLen = Math.round(count / maxBucketCount * 25);
20758
+ const bar = "█".repeat(barLen) + style("dim", "░".repeat(25 - barLen));
20759
+ const countStr = count.toString().padStart(3);
20760
+ console.log(` ${style("dim", bucket.padEnd(10))} ${bar} ${style("cyan", countStr)}`);
20761
+ }
20762
+ }
20763
+ console.log();
20764
+ }
20609
20765
  }
20610
20766
  };
20611
20767
 
20612
20768
  // src/core/retrieval.ts
20613
- var TYPE_KEYWORDS = {
20614
- debug: ["bug", "error", "fix", "broken", "crash", "fails", "exception", "stack trace", "debugging"],
20615
- unresolved: ["issue", "problem", "stuck", "blocked", "help", "question", "unsure", "unclear"],
20616
- decision: ["decide", "choice", "option", "should we", "which", "alternative", "tradeoff"],
20617
- architecture: ["structure", "design", "pattern", "approach", "system", "layer", "architecture"],
20618
- breakthrough: ["discovered", "realized", "insight", "found that", "aha", "finally", "key insight"],
20619
- todo: ["need to", "should", "must", "will", "later", "next", "todo"],
20620
- personal: ["family", "children", "friend", "relationship", "feel", "appreciate", "thank"],
20621
- philosophy: ["meaning", "consciousness", "existence", "purpose", "believe", "philosophy"],
20622
- technical: ["implement", "code", "function", "class", "module", "api", "interface"]
20623
- };
20624
- var TEMPORAL_CLASS_SCORES = {
20625
- eternal: 1,
20626
- long_term: 0.9,
20627
- medium_term: 0.7,
20628
- short_term: 0.5,
20629
- ephemeral: 0.3
20769
+ var GLOBAL_TYPE_PRIORITY = {
20770
+ technical: 1,
20771
+ preference: 2,
20772
+ architectural: 3,
20773
+ workflow: 4,
20774
+ decision: 5,
20775
+ breakthrough: 6,
20776
+ philosophy: 7,
20777
+ personal: 8
20630
20778
  };
20779
+ var MIN_ACTIVATION_SIGNALS = 2;
20780
+ var STOPWORDS = new Set([
20781
+ "the",
20782
+ "is",
20783
+ "are",
20784
+ "was",
20785
+ "were",
20786
+ "to",
20787
+ "a",
20788
+ "an",
20789
+ "and",
20790
+ "or",
20791
+ "but",
20792
+ "in",
20793
+ "on",
20794
+ "at",
20795
+ "for",
20796
+ "with",
20797
+ "about",
20798
+ "when",
20799
+ "how",
20800
+ "what",
20801
+ "why",
20802
+ "where",
20803
+ "this",
20804
+ "that",
20805
+ "it",
20806
+ "of",
20807
+ "be",
20808
+ "have",
20809
+ "do",
20810
+ "does",
20811
+ "did",
20812
+ "will",
20813
+ "would",
20814
+ "could",
20815
+ "should",
20816
+ "can",
20817
+ "may",
20818
+ "might",
20819
+ "must",
20820
+ "shall",
20821
+ "has",
20822
+ "had",
20823
+ "been",
20824
+ "being",
20825
+ "i",
20826
+ "you",
20827
+ "we",
20828
+ "they",
20829
+ "he",
20830
+ "she",
20831
+ "my",
20832
+ "your",
20833
+ "our",
20834
+ "its",
20835
+ "his",
20836
+ "her",
20837
+ "their",
20838
+ "if",
20839
+ "then",
20840
+ "else",
20841
+ "so",
20842
+ "as",
20843
+ "from",
20844
+ "by",
20845
+ "into",
20846
+ "through",
20847
+ "during",
20848
+ "before",
20849
+ "after",
20850
+ "also",
20851
+ "now",
20852
+ "back",
20853
+ "get",
20854
+ "go",
20855
+ "come",
20856
+ "let",
20857
+ "like",
20858
+ "just",
20859
+ "know",
20860
+ "think",
20861
+ "see",
20862
+ "look",
20863
+ "make",
20864
+ "take",
20865
+ "want",
20866
+ "need"
20867
+ ]);
20631
20868
 
20632
20869
  class SmartVectorRetrieval {
20633
20870
  _extractSignificantWords(text) {
20634
- const stopWords = new Set([
20635
- "the",
20636
- "is",
20637
- "are",
20638
- "was",
20639
- "were",
20640
- "to",
20641
- "a",
20642
- "an",
20643
- "and",
20644
- "or",
20645
- "but",
20646
- "in",
20647
- "on",
20648
- "at",
20649
- "for",
20650
- "with",
20651
- "about",
20652
- "when",
20653
- "how",
20654
- "what",
20655
- "why",
20656
- "where",
20657
- "this",
20658
- "that",
20659
- "it",
20660
- "of",
20661
- "be",
20662
- "have",
20663
- "do",
20664
- "does",
20665
- "did",
20666
- "will",
20667
- "would",
20668
- "could",
20669
- "should",
20670
- "can",
20671
- "may",
20672
- "might",
20673
- "must",
20674
- "shall",
20675
- "has",
20676
- "had",
20677
- "been",
20678
- "being",
20679
- "i",
20680
- "you",
20681
- "we",
20682
- "they",
20683
- "he",
20684
- "she",
20685
- "my",
20686
- "your",
20687
- "our",
20688
- "its",
20689
- "his",
20690
- "her",
20691
- "their",
20692
- "if",
20693
- "then",
20694
- "else",
20695
- "so",
20696
- "as",
20697
- "from",
20698
- "by",
20699
- "into",
20700
- "through",
20701
- "during",
20702
- "before",
20703
- "after",
20704
- "above",
20705
- "below",
20706
- "up",
20707
- "down",
20708
- "out",
20709
- "off",
20710
- "over",
20711
- "under",
20712
- "again",
20713
- "further",
20714
- "once",
20715
- "here",
20716
- "there",
20717
- "all",
20718
- "each",
20719
- "few",
20720
- "more",
20721
- "most",
20722
- "other",
20723
- "some",
20724
- "such",
20725
- "no",
20726
- "nor",
20727
- "not",
20728
- "only",
20729
- "own",
20730
- "same",
20731
- "than",
20732
- "too",
20733
- "very",
20734
- "just",
20735
- "also",
20736
- "now",
20737
- "back",
20738
- "get",
20739
- "got",
20740
- "go",
20741
- "going",
20742
- "gone",
20743
- "come",
20744
- "came",
20745
- "let",
20746
- "lets",
20747
- "hey",
20748
- "hi",
20749
- "hello",
20750
- "ok",
20751
- "okay"
20752
- ]);
20753
- const words = text.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !stopWords.has(w));
20871
+ const words = text.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !STOPWORDS.has(w));
20754
20872
  return new Set(words);
20755
20873
  }
20756
- _detectContextTypes(message) {
20757
- const messageLower = message.toLowerCase();
20758
- const detected = new Set;
20759
- for (const [type, keywords] of Object.entries(TYPE_KEYWORDS)) {
20760
- for (const keyword of keywords) {
20761
- if (messageLower.includes(keyword)) {
20762
- detected.add(type);
20763
- break;
20764
- }
20765
- }
20766
- }
20767
- return detected;
20768
- }
20769
20874
  _preFilter(memories, currentProjectId, messageLower) {
20770
20875
  return memories.filter((memory) => {
20771
- if (memory.status && memory.status !== "active") {
20876
+ if (memory.status && memory.status !== "active")
20772
20877
  return false;
20773
- }
20774
- if (memory.exclude_from_retrieval === true) {
20878
+ if (memory.exclude_from_retrieval === true)
20775
20879
  return false;
20776
- }
20777
- if (memory.superseded_by) {
20880
+ if (memory.superseded_by)
20778
20881
  return false;
20779
- }
20780
20882
  const isGlobal = memory.scope === "global" || memory.project_id === "global";
20781
- if (!isGlobal && memory.project_id !== currentProjectId) {
20883
+ if (!isGlobal && memory.project_id !== currentProjectId)
20782
20884
  return false;
20783
- }
20784
20885
  if (memory.anti_triggers?.length) {
20785
20886
  for (const antiTrigger of memory.anti_triggers) {
20786
- if (messageLower.includes(antiTrigger.toLowerCase())) {
20887
+ if (messageLower.includes(antiTrigger.toLowerCase()))
20787
20888
  return false;
20788
- }
20789
20889
  }
20790
20890
  }
20791
20891
  return true;
20792
20892
  });
20793
20893
  }
20794
- _calculateCorroboration(memory, messageWords, detectedTypes, messageLower) {
20795
- const signals = [];
20894
+ _checkTriggerActivation(messageLower, messageWords, triggerPhrases) {
20895
+ if (!triggerPhrases.length)
20896
+ return { activated: false, strength: 0 };
20897
+ let maxStrength = 0;
20898
+ for (const phrase of triggerPhrases) {
20899
+ const phraseLower = phrase.trim().toLowerCase();
20900
+ const phraseWords = phraseLower.split(/\s+/).filter((w) => !STOPWORDS.has(w) && w.length > 2);
20901
+ if (!phraseWords.length)
20902
+ continue;
20903
+ let matches = 0;
20904
+ for (const word of phraseWords) {
20905
+ if (messageWords.has(word) || messageLower.includes(word)) {
20906
+ matches++;
20907
+ } else if (messageWords.has(word.replace(/s$/, "")) || messageWords.has(word + "s") || messageLower.includes(word.replace(/s$/, "")) || messageLower.includes(word + "s")) {
20908
+ matches += 0.8;
20909
+ }
20910
+ }
20911
+ const strength = phraseWords.length > 0 ? matches / phraseWords.length : 0;
20912
+ maxStrength = Math.max(maxStrength, strength);
20913
+ }
20914
+ return { activated: maxStrength >= 0.5, strength: maxStrength };
20915
+ }
20916
+ _checkTagActivation(messageLower, messageWords, tags) {
20917
+ if (!tags.length)
20918
+ return { activated: false, count: 0 };
20919
+ let matchCount = 0;
20920
+ for (const tag of tags) {
20921
+ const tagLower = tag.trim().toLowerCase();
20922
+ if (messageWords.has(tagLower) || messageLower.includes(tagLower)) {
20923
+ matchCount++;
20924
+ }
20925
+ }
20926
+ const threshold = tags.length <= 2 ? 1 : 2;
20927
+ return { activated: matchCount >= threshold, count: matchCount };
20928
+ }
20929
+ _checkDomainActivation(messageLower, messageWords, domain) {
20930
+ if (!domain)
20931
+ return false;
20932
+ const domainLower = domain.trim().toLowerCase();
20933
+ return messageWords.has(domainLower) || messageLower.includes(domainLower);
20934
+ }
20935
+ _checkFeatureActivation(messageLower, messageWords, feature) {
20936
+ if (!feature)
20937
+ return false;
20938
+ const featureLower = feature.trim().toLowerCase();
20939
+ return messageWords.has(featureLower) || messageLower.includes(featureLower);
20940
+ }
20941
+ _checkContentActivation(messageWords, memory) {
20942
+ const contentPreview = memory.content.slice(0, 200);
20943
+ const contentWords = this._extractSignificantWords(contentPreview);
20944
+ let overlap = 0;
20945
+ for (const word of messageWords) {
20946
+ if (contentWords.has(word))
20947
+ overlap++;
20948
+ }
20949
+ return overlap >= 3;
20950
+ }
20951
+ _vectorDebugSamples = [];
20952
+ _calculateVectorSimilarity(vec1, vec2) {
20953
+ if (!vec1 || !vec2) {
20954
+ return 0;
20955
+ }
20956
+ const v1 = vec1 instanceof Float32Array ? vec1 : new Float32Array(vec1);
20957
+ const v2 = vec2 instanceof Float32Array ? vec2 : new Float32Array(vec2);
20958
+ const similarity = cosineSimilarity(v1, v2);
20959
+ if (this._vectorDebugSamples.length < 20) {
20960
+ this._vectorDebugSamples.push(similarity);
20961
+ }
20962
+ return similarity;
20963
+ }
20964
+ _logVectorStats() {
20965
+ if (this._vectorDebugSamples.length === 0)
20966
+ return;
20967
+ const samples = this._vectorDebugSamples;
20968
+ const min = Math.min(...samples);
20969
+ const max = Math.max(...samples);
20970
+ const avg = samples.reduce((a, b) => a + b, 0) / samples.length;
20971
+ console.log(`[DEBUG] Vector similarities: min=${(min * 100).toFixed(1)}% max=${(max * 100).toFixed(1)}% avg=${(avg * 100).toFixed(1)}% (${samples.length} samples)`);
20972
+ this._vectorDebugSamples = [];
20973
+ }
20974
+ _calculateImportanceScore(memory, signalCount, messageLower, messageWords) {
20796
20975
  let score = 0;
20797
- let reasoningMatch = 0;
20798
- const tagOverlap = (memory.semantic_tags ?? []).filter((tag) => messageWords.has(tag.toLowerCase()) || messageLower.includes(tag.toLowerCase()));
20799
- if (tagOverlap.length > 0) {
20800
- score += Math.min(0.4, tagOverlap.length * 0.15);
20801
- signals.push("tags:" + tagOverlap.join(","));
20802
- }
20803
- if (memory.reasoning) {
20804
- const reasoningWords = this._extractSignificantWords(memory.reasoning);
20805
- const reasoningOverlap = [...messageWords].filter((w) => reasoningWords.has(w));
20806
- if (reasoningOverlap.length > 0) {
20807
- reasoningMatch = Math.min(0.4, reasoningOverlap.length * 0.1);
20808
- score += reasoningMatch;
20809
- signals.push("reasoning:" + reasoningOverlap.slice(0, 3).join(","));
20810
- }
20811
- }
20812
- if (memory.domain) {
20813
- const domainLower = memory.domain.toLowerCase();
20814
- if (messageLower.includes(domainLower) || messageWords.has(domainLower)) {
20815
- score += 0.3;
20816
- signals.push("domain:" + memory.domain);
20817
- }
20818
- }
20819
- if (memory.knowledge_domain) {
20820
- const kdLower = memory.knowledge_domain.toLowerCase();
20821
- if (messageLower.includes(kdLower) || messageWords.has(kdLower)) {
20822
- score += 0.2;
20823
- signals.push("knowledge:" + memory.knowledge_domain);
20824
- }
20825
- }
20826
- if (memory.context_type && detectedTypes.has(memory.context_type)) {
20827
- score += 0.12;
20828
- signals.push("type:" + memory.context_type);
20829
- }
20830
- const triggerMatch = this._scoreTriggerPhrases(messageLower, memory.trigger_phrases ?? []);
20831
- if (triggerMatch > 0.3) {
20832
- score += Math.min(0.3, triggerMatch * 0.4);
20833
- signals.push("trigger:" + triggerMatch.toFixed(2));
20834
- }
20835
- if (memory.feature) {
20836
- const featureLower = memory.feature.toLowerCase();
20837
- if (messageLower.includes(featureLower) || messageWords.has(featureLower)) {
20838
- score += 0.2;
20839
- signals.push("feature:" + memory.feature);
20840
- }
20841
- }
20842
- if (memory.related_files?.length) {
20843
- for (const file of memory.related_files) {
20844
- const filename = file.split("/").pop()?.toLowerCase() ?? "";
20845
- if (filename && messageLower.includes(filename)) {
20846
- score += 0.25;
20847
- signals.push("file:" + filename);
20976
+ score += memory.importance_weight ?? 0.5;
20977
+ if (signalCount >= 4)
20978
+ score += 0.2;
20979
+ else if (signalCount >= 3)
20980
+ score += 0.1;
20981
+ if (memory.awaiting_implementation)
20982
+ score += 0.15;
20983
+ if (memory.awaiting_decision)
20984
+ score += 0.1;
20985
+ const contextType = memory.context_type?.toLowerCase() ?? "";
20986
+ const contextKeywords = {
20987
+ debugging: ["debug", "bug", "error", "fix", "issue", "problem", "broken"],
20988
+ decision: ["decide", "decision", "choose", "choice", "option", "should"],
20989
+ architectural: ["architect", "design", "structure", "pattern", "how"],
20990
+ breakthrough: ["insight", "realize", "understand", "discover", "why"],
20991
+ technical: ["implement", "code", "function", "method", "api"],
20992
+ workflow: ["process", "workflow", "step", "flow", "pipeline"],
20993
+ philosophy: ["philosophy", "principle", "belief", "approach", "think"]
20994
+ };
20995
+ const keywords = contextKeywords[contextType] ?? [];
20996
+ for (const kw of keywords) {
20997
+ if (messageWords.has(kw) || messageLower.includes(kw)) {
20998
+ score += 0.1;
20999
+ break;
21000
+ }
21001
+ }
21002
+ if (memory.problem_solution_pair) {
21003
+ const problemWords = ["error", "bug", "issue", "problem", "wrong", "fail", "broken", "help", "stuck"];
21004
+ for (const pw of problemWords) {
21005
+ if (messageWords.has(pw) || messageLower.includes(pw)) {
21006
+ score += 0.1;
20848
21007
  break;
20849
21008
  }
20850
21009
  }
20851
21010
  }
20852
- return { score: Math.min(1, score), signals, reasoningMatch };
21011
+ const temporalClass = memory.temporal_class ?? "medium_term";
21012
+ if (temporalClass === "eternal")
21013
+ score += 0.1;
21014
+ else if (temporalClass === "long_term")
21015
+ score += 0.05;
21016
+ else if (temporalClass === "ephemeral") {
21017
+ if ((memory.sessions_since_surfaced ?? 0) <= 1)
21018
+ score += 0.1;
21019
+ }
21020
+ const confidence = memory.confidence_score ?? 0.7;
21021
+ if (confidence < 0.5)
21022
+ score -= 0.1;
21023
+ const emotionalKeywords = {
21024
+ frustration: ["frustrated", "annoying", "stuck", "ugh", "damn", "hate"],
21025
+ excitement: ["excited", "awesome", "amazing", "love", "great", "wow"],
21026
+ curiosity: ["wonder", "curious", "interesting", "how", "why", "what if"],
21027
+ satisfaction: ["done", "finished", "complete", "works", "solved", "finally"],
21028
+ discovery: ["found", "realized", "understand", "insight", "breakthrough"]
21029
+ };
21030
+ const emotion = memory.emotional_resonance?.toLowerCase() ?? "";
21031
+ const emotionKws = emotionalKeywords[emotion] ?? [];
21032
+ for (const ew of emotionKws) {
21033
+ if (messageWords.has(ew) || messageLower.includes(ew)) {
21034
+ score += 0.05;
21035
+ break;
21036
+ }
21037
+ }
21038
+ return score;
20853
21039
  }
20854
21040
  retrieveRelevantMemories(allMemories, currentMessage, queryEmbedding, sessionContext, maxMemories = 5, alreadyInjectedCount = 0, maxGlobalMemories = 2) {
21041
+ const startTime = performance.now();
20855
21042
  if (!allMemories.length) {
20856
21043
  return [];
20857
21044
  }
20858
21045
  const messageLower = currentMessage.toLowerCase();
20859
21046
  const messageWords = this._extractSignificantWords(currentMessage);
20860
- const detectedTypes = this._detectContextTypes(currentMessage);
20861
21047
  const candidates = this._preFilter(allMemories, sessionContext.project_id, messageLower);
20862
21048
  if (!candidates.length) {
20863
21049
  return [];
20864
21050
  }
20865
- const scoredMemories = [];
21051
+ const activatedMemories = [];
21052
+ let rejectedCount = 0;
20866
21053
  for (const memory of candidates) {
20867
21054
  const isGlobal = memory.scope === "global" || memory.project_id === "global";
20868
- const vectorScore = this._calculateVectorSimilarity(queryEmbedding, memory.embedding);
20869
- const { score: corroborationScore, signals: corroborationSignals, reasoningMatch } = this._calculateCorroboration(memory, messageWords, detectedTypes, messageLower);
20870
- const retrievalWeight = memory.retrieval_weight ?? memory.importance_weight ?? 0.5;
20871
- const temporalScore = memory.temporal_class ? TEMPORAL_CLASS_SCORES[memory.temporal_class] ?? 0.7 : this._scoreTemporalRelevance(memory.temporal_relevance ?? "persistent");
20872
- const contextScore = this._scoreContextAlignment(currentMessage, memory.context_type ?? "general");
20873
- const tagScore = this._scoreSemanticTags(currentMessage, memory.semantic_tags ?? []);
20874
- const triggerScore = this._scoreTriggerPhrases(messageLower, memory.trigger_phrases ?? []);
20875
- const domainScore = this._scoreDomain(messageWords, messageLower, memory);
20876
- const questionScore = this._scoreQuestionTypes(currentMessage, memory.question_types ?? []);
20877
- const emotionScore = this._scoreEmotionalContext(currentMessage, memory.emotional_resonance ?? "");
20878
- const problemScore = this._scoreProblemSolution(currentMessage, memory.problem_solution_pair ?? false);
20879
- const actionBoost = memory.action_required ? 0.15 : 0;
20880
- const relevanceScore = vectorScore * 0.1 + corroborationScore * 0.14 + tagScore * 0.04 + triggerScore * 0.02;
20881
- 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;
20882
- const finalScore = valueScore + relevanceScore;
20883
- if (relevanceScore < 0.05 || finalScore < 0.3) {
21055
+ const triggerResult = this._checkTriggerActivation(messageLower, messageWords, memory.trigger_phrases ?? []);
21056
+ const tagResult = this._checkTagActivation(messageLower, messageWords, memory.semantic_tags ?? []);
21057
+ const domainActivated = this._checkDomainActivation(messageLower, messageWords, memory.domain);
21058
+ const featureActivated = this._checkFeatureActivation(messageLower, messageWords, memory.feature);
21059
+ const contentActivated = this._checkContentActivation(messageWords, memory);
21060
+ const vectorSimilarity = this._calculateVectorSimilarity(queryEmbedding, memory.embedding);
21061
+ let signalCount = 0;
21062
+ if (triggerResult.activated)
21063
+ signalCount++;
21064
+ if (tagResult.activated)
21065
+ signalCount++;
21066
+ if (domainActivated)
21067
+ signalCount++;
21068
+ if (featureActivated)
21069
+ signalCount++;
21070
+ if (contentActivated)
21071
+ signalCount++;
21072
+ if (vectorSimilarity >= 0.4)
21073
+ signalCount++;
21074
+ const signals = {
21075
+ trigger: triggerResult.activated,
21076
+ tags: tagResult.activated,
21077
+ domain: domainActivated,
21078
+ feature: featureActivated,
21079
+ content: contentActivated,
21080
+ count: signalCount,
21081
+ triggerStrength: triggerResult.strength,
21082
+ tagCount: tagResult.count,
21083
+ vectorSimilarity
21084
+ };
21085
+ if (signalCount < MIN_ACTIVATION_SIGNALS) {
21086
+ rejectedCount++;
20884
21087
  continue;
20885
21088
  }
20886
- const components = {
20887
- vector: vectorScore,
20888
- corroboration: corroborationScore,
20889
- reasoning_match: reasoningMatch,
20890
- retrieval_weight: retrievalWeight,
20891
- temporal: temporalScore,
20892
- context: contextScore,
20893
- tags: tagScore,
20894
- trigger: triggerScore,
20895
- domain: domainScore,
20896
- question: questionScore,
20897
- emotion: emotionScore,
20898
- problem: problemScore,
20899
- action: actionBoost
20900
- };
20901
- const reasoning = this._generateSelectionReasoning(components, corroborationSignals);
20902
- scoredMemories.push({
21089
+ const importanceScore = this._calculateImportanceScore(memory, signalCount, messageLower, messageWords);
21090
+ activatedMemories.push({
20903
21091
  memory,
20904
- score: finalScore,
20905
- relevance_score: relevanceScore,
20906
- value_score: valueScore,
20907
- corroboration_score: corroborationScore,
20908
- reasoning,
20909
- components,
21092
+ signals,
21093
+ importanceScore,
20910
21094
  isGlobal
20911
21095
  });
20912
21096
  }
20913
- scoredMemories.sort((a, b) => b.score - a.score);
21097
+ this._logActivationDistribution(activatedMemories, candidates.length, rejectedCount);
21098
+ this._logVectorStats();
21099
+ if (!activatedMemories.length) {
21100
+ const durationMs2 = performance.now() - startTime;
21101
+ logger.logRetrievalScoring({
21102
+ totalMemories: allMemories.length,
21103
+ currentMessage,
21104
+ alreadyInjected: alreadyInjectedCount,
21105
+ preFiltered: allMemories.length - candidates.length,
21106
+ globalCount: 0,
21107
+ projectCount: 0,
21108
+ finalCount: 0,
21109
+ durationMs: durationMs2,
21110
+ selectedMemories: []
21111
+ });
21112
+ return [];
21113
+ }
21114
+ activatedMemories.sort((a, b) => {
21115
+ if (b.signals.count !== a.signals.count) {
21116
+ return b.signals.count - a.signals.count;
21117
+ }
21118
+ return b.importanceScore - a.importanceScore;
21119
+ });
20914
21120
  const selected = [];
20915
21121
  const selectedIds = new Set;
20916
- const globalMemories = scoredMemories.filter((m) => m.isGlobal);
20917
- const projectMemories = scoredMemories.filter((m) => !m.isGlobal);
20918
- const globalSorted = globalMemories.sort((a, b) => {
20919
- const aIsPersonal = a.memory.context_type === "personal" || a.memory.context_type === "philosophy";
20920
- const bIsPersonal = b.memory.context_type === "personal" || b.memory.context_type === "philosophy";
20921
- if (aIsPersonal !== bIsPersonal) {
20922
- return aIsPersonal ? 1 : -1;
20923
- }
20924
- return b.score - a.score;
21122
+ const globalMemories = activatedMemories.filter((m) => m.isGlobal);
21123
+ const projectMemories = activatedMemories.filter((m) => !m.isGlobal);
21124
+ const globalsSorted = globalMemories.sort((a, b) => {
21125
+ const aPriority = GLOBAL_TYPE_PRIORITY[a.memory.context_type ?? "personal"] ?? 8;
21126
+ const bPriority = GLOBAL_TYPE_PRIORITY[b.memory.context_type ?? "personal"] ?? 8;
21127
+ if (aPriority !== bPriority)
21128
+ return aPriority - bPriority;
21129
+ if (b.signals.count !== a.signals.count)
21130
+ return b.signals.count - a.signals.count;
21131
+ return b.importanceScore - a.importanceScore;
20925
21132
  });
20926
- for (const item of globalSorted.slice(0, maxGlobalMemories)) {
21133
+ for (const item of globalsSorted.slice(0, maxGlobalMemories)) {
20927
21134
  if (!selectedIds.has(item.memory.id)) {
20928
21135
  selected.push(item);
20929
21136
  selectedIds.add(item.memory.id);
20930
21137
  }
20931
21138
  }
20932
- for (const item of projectMemories) {
21139
+ const projectsSorted = [...projectMemories].sort((a, b) => {
21140
+ const aAction = a.memory.action_required ? 1 : 0;
21141
+ const bAction = b.memory.action_required ? 1 : 0;
21142
+ if (bAction !== aAction)
21143
+ return bAction - aAction;
21144
+ if (b.signals.count !== a.signals.count)
21145
+ return b.signals.count - a.signals.count;
21146
+ return b.importanceScore - a.importanceScore;
21147
+ });
21148
+ console.log(`[DEBUG] Top 15 candidates (sorted):`);
21149
+ for (let i = 0;i < Math.min(15, projectsSorted.length); i++) {
21150
+ const m = projectsSorted[i];
21151
+ const action = m.memory.action_required ? "⚡" : "";
21152
+ console.log(` ${i + 1}. [${m.signals.count}sig] score=${m.importanceScore.toFixed(2)} ${action} ${m.memory.content.slice(0, 45)}...`);
21153
+ }
21154
+ for (const item of projectsSorted) {
20933
21155
  if (selected.length >= maxMemories)
20934
21156
  break;
20935
21157
  if (selectedIds.has(item.memory.id))
@@ -20937,7 +21159,27 @@ class SmartVectorRetrieval {
20937
21159
  selected.push(item);
20938
21160
  selectedIds.add(item.memory.id);
20939
21161
  }
20940
- selected.sort((a, b) => b.score - a.score);
21162
+ if (selected.length < maxMemories) {
21163
+ const relatedIds = new Set;
21164
+ for (const item of selected) {
21165
+ for (const relatedId of item.memory.related_to ?? []) {
21166
+ if (!selectedIds.has(relatedId)) {
21167
+ relatedIds.add(relatedId);
21168
+ }
21169
+ }
21170
+ }
21171
+ for (const item of activatedMemories) {
21172
+ if (selected.length >= maxMemories)
21173
+ break;
21174
+ if (selectedIds.has(item.memory.id))
21175
+ continue;
21176
+ if (relatedIds.has(item.memory.id)) {
21177
+ selected.push(item);
21178
+ selectedIds.add(item.memory.id);
21179
+ }
21180
+ }
21181
+ }
21182
+ const durationMs = performance.now() - startTime;
20941
21183
  logger.logRetrievalScoring({
20942
21184
  totalMemories: allMemories.length,
20943
21185
  currentMessage,
@@ -20946,202 +21188,102 @@ class SmartVectorRetrieval {
20946
21188
  globalCount: globalMemories.length,
20947
21189
  projectCount: projectMemories.length,
20948
21190
  finalCount: selected.length,
21191
+ durationMs,
20949
21192
  selectedMemories: selected.map((item) => ({
20950
21193
  content: item.memory.content,
20951
- reasoning: item.reasoning,
20952
- score: item.score,
20953
- relevance_score: item.relevance_score,
20954
- corroboration_score: item.corroboration_score,
21194
+ reasoning: this._generateActivationReasoning(item.signals),
21195
+ signalCount: item.signals.count,
20955
21196
  importance_weight: item.memory.importance_weight ?? 0.5,
20956
21197
  context_type: item.memory.context_type ?? "general",
20957
21198
  semantic_tags: item.memory.semantic_tags ?? [],
20958
21199
  isGlobal: item.isGlobal,
20959
- components: item.components
21200
+ signals: {
21201
+ trigger: item.signals.trigger,
21202
+ triggerStrength: item.signals.triggerStrength,
21203
+ tags: item.signals.tags,
21204
+ tagCount: item.signals.tagCount,
21205
+ domain: item.signals.domain,
21206
+ feature: item.signals.feature,
21207
+ content: item.signals.content,
21208
+ vector: item.signals.vectorSimilarity >= 0.4,
21209
+ vectorSimilarity: item.signals.vectorSimilarity
21210
+ }
20960
21211
  }))
20961
21212
  });
20962
21213
  return selected.map((item) => ({
20963
21214
  ...item.memory,
20964
- score: item.score,
20965
- relevance_score: item.relevance_score,
20966
- value_score: item.value_score
21215
+ score: item.signals.count / 6,
21216
+ relevance_score: item.signals.count / 6,
21217
+ value_score: item.importanceScore
20967
21218
  }));
20968
21219
  }
20969
- _calculateVectorSimilarity(vec1, vec2) {
20970
- if (!vec1 || !vec2)
20971
- return 0;
20972
- const v1 = vec1 instanceof Float32Array ? vec1 : new Float32Array(vec1);
20973
- return cosineSimilarity(v1, vec2);
20974
- }
20975
- _scoreTemporalRelevance(temporalType) {
20976
- const scores = {
20977
- persistent: 0.8,
20978
- session: 0.6,
20979
- temporary: 0.3,
20980
- archived: 0.1
20981
- };
20982
- return scores[temporalType] ?? 0.5;
20983
- }
20984
- _scoreContextAlignment(message, contextType) {
20985
- const messageLower = message.toLowerCase();
20986
- const keywords = TYPE_KEYWORDS[contextType] ?? [];
20987
- const matches = keywords.filter((kw) => messageLower.includes(kw)).length;
20988
- if (matches > 0) {
20989
- return Math.min(0.2 + matches * 0.15, 0.7);
20990
- }
20991
- return 0.1;
20992
- }
20993
- _scoreSemanticTags(message, tags) {
20994
- if (!tags.length)
20995
- return 0;
20996
- const messageLower = message.toLowerCase();
20997
- const matches = tags.filter((tag) => messageLower.includes(tag.trim().toLowerCase())).length;
20998
- if (matches > 0) {
20999
- return Math.min(0.3 + matches * 0.25, 1);
21000
- }
21001
- return 0;
21002
- }
21003
- _scoreTriggerPhrases(messageLower, triggerPhrases) {
21004
- if (!triggerPhrases.length)
21005
- return 0;
21006
- const stopWords = new Set([
21007
- "the",
21008
- "is",
21009
- "are",
21010
- "was",
21011
- "were",
21012
- "to",
21013
- "a",
21014
- "an",
21015
- "and",
21016
- "or",
21017
- "but",
21018
- "in",
21019
- "on",
21020
- "at",
21021
- "for",
21022
- "with",
21023
- "about",
21024
- "when",
21025
- "how",
21026
- "what",
21027
- "why"
21028
- ]);
21029
- let maxScore = 0;
21030
- for (const pattern of triggerPhrases) {
21031
- const patternLower = pattern.trim().toLowerCase();
21032
- const patternWords = patternLower.split(/\s+/).filter((w) => !stopWords.has(w) && w.length > 2);
21033
- if (patternWords.length) {
21034
- let matches = 0;
21035
- for (const word of patternWords) {
21036
- if (messageLower.includes(word)) {
21037
- matches += 1;
21038
- } else if (messageLower.includes(word.replace(/s$/, "")) || messageLower.includes(word + "s")) {
21039
- matches += 0.9;
21040
- }
21041
- }
21042
- const conceptScore = matches / patternWords.length;
21043
- maxScore = Math.max(maxScore, conceptScore);
21044
- }
21045
- }
21046
- return Math.min(maxScore, 1);
21047
- }
21048
- _scoreDomain(messageWords, messageLower, memory) {
21049
- let score = 0;
21050
- if (memory.domain) {
21051
- const domainLower = memory.domain.toLowerCase();
21052
- if (messageWords.has(domainLower) || messageLower.includes(domainLower)) {
21053
- score += 0.5;
21054
- }
21055
- }
21056
- if (memory.feature) {
21057
- const featureLower = memory.feature.toLowerCase();
21058
- if (messageWords.has(featureLower) || messageLower.includes(featureLower)) {
21059
- score += 0.3;
21060
- }
21061
- }
21062
- if (memory.component) {
21063
- const componentLower = memory.component.toLowerCase();
21064
- if (messageWords.has(componentLower) || messageLower.includes(componentLower)) {
21065
- score += 0.2;
21066
- }
21067
- }
21068
- return Math.min(score, 1);
21069
- }
21070
- _scoreQuestionTypes(message, questionTypes) {
21071
- if (!questionTypes.length)
21072
- return 0;
21073
- const messageLower = message.toLowerCase();
21074
- const questionWords = ["how", "why", "what", "when", "where"];
21075
- for (const qtype of questionTypes) {
21076
- const qtypeLower = qtype.trim().toLowerCase();
21077
- if (messageLower.includes(qtypeLower)) {
21078
- return 0.8;
21079
- }
21080
- const messageHasQuestion = questionWords.some((qw) => messageLower.includes(qw));
21081
- const typeHasQuestion = questionWords.some((qw) => qtypeLower.includes(qw));
21082
- if (messageHasQuestion && typeHasQuestion) {
21083
- return 0.5;
21084
- }
21085
- }
21086
- return 0;
21087
- }
21088
- _scoreEmotionalContext(message, emotion) {
21089
- if (!emotion)
21090
- return 0;
21091
- const messageLower = message.toLowerCase();
21092
- const emotionPatterns = {
21093
- joy: ["happy", "excited", "love", "wonderful", "great", "awesome"],
21094
- frustration: ["stuck", "confused", "help", "issue", "problem", "why"],
21095
- discovery: ["realized", "found", "discovered", "aha", "insight"],
21096
- gratitude: ["thank", "appreciate", "grateful", "dear friend"]
21097
- };
21098
- const patterns = emotionPatterns[emotion.toLowerCase()] ?? [];
21099
- if (patterns.some((pattern) => messageLower.includes(pattern))) {
21100
- return 0.7;
21101
- }
21102
- return 0;
21103
- }
21104
- _scoreProblemSolution(message, isProblemSolution) {
21105
- if (!isProblemSolution)
21106
- return 0;
21107
- const messageLower = message.toLowerCase();
21108
- const problemWords = ["error", "issue", "problem", "stuck", "help", "fix", "solve", "debug"];
21109
- if (problemWords.some((word) => messageLower.includes(word))) {
21110
- return 0.8;
21111
- }
21112
- return 0;
21113
- }
21114
- _generateSelectionReasoning(components, corroborationSignals) {
21115
- const scores = [
21116
- ["vector", components.vector],
21117
- ["corroboration", components.corroboration],
21118
- ["reasoning", components.reasoning_match],
21119
- ["weight", components.retrieval_weight],
21120
- ["context", components.context],
21121
- ["temporal", components.temporal],
21122
- ["tags", components.tags],
21123
- ["trigger", components.trigger],
21124
- ["domain", components.domain],
21125
- ["question", components.question],
21126
- ["emotion", components.emotion],
21127
- ["problem", components.problem],
21128
- ["action", components.action]
21129
- ];
21130
- scores.sort((a, b) => b[1] - a[1]);
21220
+ _generateActivationReasoning(signals) {
21131
21221
  const reasons = [];
21132
- const primary = scores[0];
21133
- if (primary[1] > 0.2) {
21134
- reasons.push(primary[0] + ":" + primary[1].toFixed(2));
21135
- }
21136
- for (const [reason, score] of scores.slice(1, 3)) {
21137
- if (score > 0.15) {
21138
- reasons.push(reason + ":" + score.toFixed(2));
21222
+ if (signals.trigger)
21223
+ reasons.push(`trigger:${(signals.triggerStrength * 100).toFixed(0)}%`);
21224
+ if (signals.tags)
21225
+ reasons.push(`tags:${signals.tagCount}`);
21226
+ if (signals.domain)
21227
+ reasons.push("domain");
21228
+ if (signals.feature)
21229
+ reasons.push("feature");
21230
+ if (signals.content)
21231
+ reasons.push("content");
21232
+ if (signals.vectorSimilarity >= 0.4)
21233
+ reasons.push(`vector:${(signals.vectorSimilarity * 100).toFixed(0)}%`);
21234
+ return reasons.length ? `Activated: ${reasons.join(", ")} (${signals.count} signals)` : "No signals";
21235
+ }
21236
+ _logActivationDistribution(activated, totalCandidates, rejectedCount) {
21237
+ const signalBuckets = {
21238
+ "2 signals": 0,
21239
+ "3 signals": 0,
21240
+ "4 signals": 0,
21241
+ "5 signals": 0,
21242
+ "6 signals": 0
21243
+ };
21244
+ for (const mem of activated) {
21245
+ const key = `${Math.min(mem.signals.count, 6)} signals`;
21246
+ signalBuckets[key] = (signalBuckets[key] ?? 0) + 1;
21247
+ }
21248
+ let triggerCount = 0, tagCount = 0, domainCount = 0, featureCount = 0, contentCount = 0, vectorCount = 0;
21249
+ for (const mem of activated) {
21250
+ if (mem.signals.trigger)
21251
+ triggerCount++;
21252
+ if (mem.signals.tags)
21253
+ tagCount++;
21254
+ if (mem.signals.domain)
21255
+ domainCount++;
21256
+ if (mem.signals.feature)
21257
+ featureCount++;
21258
+ if (mem.signals.content)
21259
+ contentCount++;
21260
+ if (mem.signals.vectorSimilarity >= 0.4)
21261
+ vectorCount++;
21262
+ }
21263
+ logger.logScoreDistribution({
21264
+ totalCandidates,
21265
+ passedGatekeeper: activated.length,
21266
+ rejectedByGatekeeper: rejectedCount,
21267
+ buckets: signalBuckets,
21268
+ stats: {
21269
+ min: activated.length ? Math.min(...activated.map((m) => m.signals.count)) : 0,
21270
+ max: activated.length ? Math.max(...activated.map((m) => m.signals.count)) : 0,
21271
+ mean: activated.length ? Math.round(activated.reduce((s, m) => s + m.signals.count, 0) / activated.length * 10) / 10 : 0,
21272
+ stdev: 0,
21273
+ spread: activated.length ? Math.max(...activated.map((m) => m.signals.count)) - Math.min(...activated.map((m) => m.signals.count)) : 0
21274
+ },
21275
+ percentiles: {},
21276
+ compressionWarning: false,
21277
+ signalBreakdown: {
21278
+ trigger: triggerCount,
21279
+ tags: tagCount,
21280
+ domain: domainCount,
21281
+ feature: featureCount,
21282
+ content: contentCount,
21283
+ vector: vectorCount,
21284
+ total: activated.length
21139
21285
  }
21140
- }
21141
- if (corroborationSignals.length > 0) {
21142
- reasons.push("signals:[" + corroborationSignals.slice(0, 2).join(",") + "]");
21143
- }
21144
- return reasons.length ? "Selected: " + reasons.join(", ") : "Combined factors";
21286
+ });
21145
21287
  }
21146
21288
  }
21147
21289
  function createRetrieval() {
@@ -21160,7 +21302,8 @@ class MemoryEngine {
21160
21302
  centralPath: config.centralPath ?? join2(homedir2(), ".local", "share", "memory"),
21161
21303
  localFolder: config.localFolder ?? ".memory",
21162
21304
  maxMemories: config.maxMemories ?? 5,
21163
- embedder: config.embedder
21305
+ embedder: config.embedder,
21306
+ personalMemoriesEnabled: config.personalMemoriesEnabled ?? true
21164
21307
  };
21165
21308
  this._retrieval = createRetrieval();
21166
21309
  }
@@ -21289,7 +21432,7 @@ class MemoryEngine {
21289
21432
  }
21290
21433
  async _generateSessionPrimer(store, projectId) {
21291
21434
  let personalContext;
21292
- if (store.isPersonalMemoriesEnabled()) {
21435
+ if (this._config.personalMemoriesEnabled) {
21293
21436
  const personalPrimer = await store.getPersonalPrimer();
21294
21437
  personalContext = personalPrimer?.content;
21295
21438
  }
@@ -21377,12 +21520,28 @@ ${primer.personal_context}`);
21377
21520
  **Project status**: ${primer.project_status}`);
21378
21521
  }
21379
21522
  parts.push(`
21380
- **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`);
21523
+ **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`);
21381
21524
  parts.push(`
21382
21525
  *Memories will surface naturally as we converse.*`);
21383
21526
  return parts.join(`
21384
21527
  `);
21385
21528
  }
21529
+ _formatAge(createdAt) {
21530
+ const now = Date.now();
21531
+ const diffMs = now - createdAt;
21532
+ const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
21533
+ if (diffDays === 0)
21534
+ return "today";
21535
+ if (diffDays === 1)
21536
+ return "1d";
21537
+ if (diffDays < 7)
21538
+ return `${diffDays}d`;
21539
+ if (diffDays < 30)
21540
+ return `${Math.floor(diffDays / 7)}w`;
21541
+ if (diffDays < 365)
21542
+ return `${Math.floor(diffDays / 30)}mo`;
21543
+ return `${Math.floor(diffDays / 365)}y`;
21544
+ }
21386
21545
  _formatMemories(memories) {
21387
21546
  if (!memories.length)
21388
21547
  return "";
@@ -21393,11 +21552,40 @@ ${primer.personal_context}`);
21393
21552
  const tags = memory.semantic_tags?.join(", ") || "";
21394
21553
  const importance = memory.importance_weight?.toFixed(1) || "0.5";
21395
21554
  const emoji = getMemoryEmoji(memory.context_type || "general");
21396
- parts.push(`[${emoji} ${importance}] [${tags}] ${memory.content}`);
21555
+ const actionFlag = memory.action_required ? " ⚡ACTION" : "";
21556
+ const age = memory.updated_at ? this._formatAge(memory.updated_at) : memory.created_at ? this._formatAge(memory.created_at) : "";
21557
+ parts.push(`[${emoji} • ${importance} • ${age}${actionFlag}] [${tags}] ${memory.content}`);
21558
+ const related = memory.related_to;
21559
+ if (related && related.length > 0) {
21560
+ const moreCount = related.length - 1;
21561
+ const moreSuffix = moreCount > 0 ? ` +${moreCount} more` : "";
21562
+ parts.push(` ↳ ${related[0]}${moreSuffix}`);
21563
+ }
21397
21564
  }
21398
21565
  return parts.join(`
21399
21566
  `);
21400
21567
  }
21568
+ getStoragePaths(projectId, projectPath) {
21569
+ const globalPath = join2(homedir2(), ".local", "share", "memory", "global");
21570
+ const globalMemoriesPath = join2(globalPath, "memories");
21571
+ const personalPrimerPath = join2(globalPath, "primer", "personal-primer.md");
21572
+ let storeBasePath;
21573
+ if (this._config.storageMode === "local" && projectPath) {
21574
+ storeBasePath = join2(projectPath, this._config.localFolder);
21575
+ } else {
21576
+ storeBasePath = this._config.centralPath;
21577
+ }
21578
+ const projectRootPath = join2(storeBasePath, projectId);
21579
+ const projectMemoriesPath = join2(projectRootPath, "memories");
21580
+ return {
21581
+ projectPath: projectRootPath,
21582
+ globalPath,
21583
+ projectMemoriesPath,
21584
+ globalMemoriesPath,
21585
+ personalPrimerPath,
21586
+ storageMode: this._config.storageMode
21587
+ };
21588
+ }
21401
21589
  close() {
21402
21590
  for (const store of this._stores.values()) {
21403
21591
  store.close();
@@ -21432,11 +21620,12 @@ class Curator {
21432
21620
  this._config = {
21433
21621
  apiKey: config.apiKey ?? "",
21434
21622
  cliCommand,
21435
- cliType: config.cliType ?? "claude-code"
21623
+ cliType: config.cliType ?? "claude-code",
21624
+ personalMemoriesEnabled: config.personalMemoriesEnabled ?? true
21436
21625
  };
21437
21626
  }
21438
21627
  buildCurationPrompt(triggerType = "session_end") {
21439
- return `You have just had a conversation. As this session is ending (${triggerType}), please curate memories for the Claude Tools Memory System.
21628
+ const basePrompt = `You have just had a conversation. As this session is ending (${triggerType}), please curate memories for the Claude Tools Memory System.
21440
21629
 
21441
21630
  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.
21442
21631
 
@@ -21473,27 +21662,82 @@ Each memory should stand alone.
21473
21662
  - Craft language that activates rather than just informs
21474
21663
  - Test: 'What state will this restore when Claude encounters it?'
21475
21664
 
21476
- **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.
21665
+ **HOW RETRIEVAL WORKS - ACTIVATION SIGNAL ALGORITHM**
21666
+
21667
+ Understanding the algorithm helps you craft metadata that surfaces memories at the right moments.
21668
+
21669
+ **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.
21670
+
21671
+ **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.
21672
+
21673
+ **6 ACTIVATION SIGNALS** (each is binary - fires or doesn't):
21674
+
21675
+ 1. **TRIGGER** - Words from trigger_phrases found in user's message (≥50% match)
21676
+ - THE MOST IMPORTANT SIGNAL. Handcrafted activation patterns.
21677
+ - Example: "when debugging retrieval" fires if user says "I'm debugging the retrieval algorithm"
21678
+
21679
+ 2. **TAGS** - 2+ semantic_tags found in user's message
21680
+ - Use words users would ACTUALLY TYPE, not generic descriptors
21681
+ - GOOD: ["retrieval", "embeddings", "curator", "scoring"]
21682
+ - WEAK: ["technical", "important", "system"]
21683
+
21684
+ 3. **DOMAIN** - The domain word appears in user's message
21685
+ - Be specific: "retrieval", "embeddings", "auth", "ui"
21686
+ - NOT: "technical", "code", "implementation"
21687
+
21688
+ 4. **FEATURE** - The feature word appears in user's message
21689
+ - Be specific: "scoring-weights", "gpu-acceleration", "login-flow"
21690
+
21691
+ 5. **CONTENT** - 3+ significant words from memory content overlap with message
21692
+ - Automatic - based on the memory's content text
21693
+
21694
+ 6. **VECTOR** - Semantic similarity ≥ 40% (embedding cosine distance)
21695
+ - Automatic - based on embeddings generated from content
21696
+
21697
+ **RELEVANCE GATE**: A memory must have ≥2 signals to be considered relevant.
21698
+ If only 1 signal fires, the memory is REJECTED. This prevents noise.
21477
21699
 
21478
- **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:
21700
+ **RANKING AMONG RELEVANT**: Once a memory passes the gate:
21701
+ 1. Sort by SIGNAL COUNT (more signals = more certainly relevant)
21702
+ 2. Then by IMPORTANCE WEIGHT (your assessment of how important this memory is)
21479
21703
 
21480
- 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.
21481
- - GOOD: ["embeddings", "retrieval", "curator", "fsdb", "memory-system"]
21482
- - WEAK: ["technical", "implementation", "code"]
21704
+ **SELECTION**:
21705
+ - Global memories (scope='global'): Max 2 selected, tech types prioritized over personal
21706
+ - Project memories: Fill remaining slots, action_required prioritized
21707
+ - Related memories (related_to field): May be included if they also passed the gate
21483
21708
 
21484
- 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.
21709
+ **WHY THIS MATTERS FOR YOU**:
21710
+ - If you don't fill trigger_phrases well → trigger signal never fires
21711
+ - If you use generic tags → tags signal rarely fires
21712
+ - If you leave domain/feature empty → those signals can't fire
21713
+ - A memory with poor metadata may NEVER surface because it can't reach 2 signals
21485
21714
 
21486
- 3. **Domain field** (12% weight) - The specific area this relates to. Be precise: "embeddings", "gpu-compute", "authentication", not "technical" or "backend".
21715
+ **CRAFTING EFFECTIVE METADATA** (CRITICAL FOR RETRIEVAL):
21487
21716
 
21488
- 4. **Feature field** - Even more specific: "vector-search", "login-flow", "curation-prompt".
21717
+ 1. **trigger_phrases** (MOST IMPORTANT) - Activation patterns describing WHEN to surface:
21718
+ - Include 2-4 specific patterns per memory
21719
+ - Use words the user would actually type
21720
+ - GOOD: ["debugging retrieval", "working on embeddings", "memory system performance"]
21721
+ - WEAK: ["when relevant", "if needed", "technical work"]
21489
21722
 
21490
- **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.
21723
+ 2. **semantic_tags** - Words users would type (need 2+ to fire):
21724
+ - Be specific and searchable
21725
+ - GOOD: ["retrieval", "embeddings", "fsdb", "curator", "scoring"]
21726
+ - WEAK: ["technical", "important", "system", "implementation"]
21491
21727
 
21492
- **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.
21728
+ 3. **domain** (NEW - FILL THIS) - Single specific area word:
21729
+ - GOOD: "retrieval", "embeddings", "curator", "signals", "fsdb"
21730
+ - WEAK: "technical", "code", "memory" (too generic)
21493
21731
 
21494
- **YOUR IMPORTANCE ASSESSMENT MATTERS** (18% weight): The importance_weight you assign is the single most influential factor. Use it wisely.
21732
+ 4. **feature** (NEW - FILL THIS) - Specific feature within domain:
21733
+ - GOOD: "scoring-algorithm", "activation-signals", "vector-search"
21734
+ - WEAK: "implementation", "code", "logic"
21495
21735
 
21496
- **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.
21736
+ 5. **importance_weight** - Only affects ranking AMONG relevant memories:
21737
+ - 0.9+ = Critical breakthrough, must surface if relevant
21738
+ - 0.7-0.8 = Important insight, should surface if relevant
21739
+ - 0.5-0.6 = Useful context, nice to have if relevant
21740
+ - NOTE: This does NOT affect whether the memory passes the relevance gate!
21497
21741
 
21498
21742
  **SCOPE DETERMINES WHERE MEMORIES SURFACE**:
21499
21743
  - scope: 'global' → surfaces in ALL projects (personal facts, philosophy, preferences)
@@ -21561,6 +21805,21 @@ Return ONLY this JSON structure:
21561
21805
  }
21562
21806
  ]
21563
21807
  }`;
21808
+ if (!this._config.personalMemoriesEnabled) {
21809
+ return basePrompt + `
21810
+
21811
+ ---
21812
+
21813
+ **IMPORTANT: PERSONAL MEMORIES DISABLED**
21814
+
21815
+ The user has disabled personal memory extraction. Do NOT extract any memories with:
21816
+ - context_type: "personal"
21817
+ - scope: "global" when the content is about the user's personal life, relationships, family, or emotional states
21818
+ - Content about the user's preferences, feelings, personal opinions, or relationship dynamics
21819
+
21820
+ Focus ONLY on technical, architectural, debugging, decision, workflow, and project-related memories. Skip any content that would reveal personal information about the user.`;
21821
+ }
21822
+ return basePrompt;
21564
21823
  }
21565
21824
  parseCurationResponse(responseJson) {
21566
21825
  try {
@@ -21651,33 +21910,35 @@ Return ONLY this JSON structure:
21651
21910
  _clamp(value, min, max) {
21652
21911
  return Math.max(min, Math.min(max, value));
21653
21912
  }
21654
- async curateWithSDK(conversationContext, triggerType = "session_end") {
21913
+ async curateWithSDK(messages, triggerType = "session_end") {
21655
21914
  if (!this._config.apiKey) {
21656
- throw new Error("API key required for SDK mode");
21915
+ throw new Error("API key required for SDK mode. Set ANTHROPIC_API_KEY environment variable.");
21657
21916
  }
21658
21917
  const { default: Anthropic2 } = await Promise.resolve().then(() => (init_sdk(), exports_sdk));
21659
21918
  const client = new Anthropic2({ apiKey: this._config.apiKey });
21660
- const prompt = this.buildCurationPrompt(triggerType);
21919
+ const systemPrompt = this.buildCurationPrompt(triggerType);
21920
+ const conversationMessages = [
21921
+ ...messages,
21922
+ {
21923
+ role: "user",
21924
+ 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."
21925
+ }
21926
+ ];
21661
21927
  const response = await client.messages.create({
21662
21928
  model: "claude-sonnet-4-20250514",
21663
21929
  max_tokens: 8192,
21664
- messages: [
21665
- {
21666
- role: "user",
21667
- content: `${conversationContext}
21668
-
21669
- ---
21670
-
21671
- ${prompt}`
21672
- }
21673
- ]
21930
+ system: systemPrompt,
21931
+ messages: conversationMessages
21674
21932
  });
21675
21933
  const content = response.content[0];
21676
21934
  if (content.type !== "text") {
21677
- throw new Error("Unexpected response type");
21935
+ throw new Error("Unexpected response type from Claude API");
21678
21936
  }
21679
21937
  return this.parseCurationResponse(content.text);
21680
21938
  }
21939
+ async curateFromSegment(segment, triggerType = "session_end") {
21940
+ return this.curateWithSDK(segment.messages, triggerType);
21941
+ }
21681
21942
  async curateWithCLI(sessionId, triggerType = "session_end", cwd, cliTypeOverride) {
21682
21943
  const type = cliTypeOverride ?? this._config.cliType;
21683
21944
  const systemPrompt = this.buildCurationPrompt(triggerType);
@@ -46256,8 +46517,9 @@ class Manager {
46256
46517
  _config;
46257
46518
  constructor(config = {}) {
46258
46519
  this._config = {
46520
+ enabled: config.enabled ?? true,
46259
46521
  cliCommand: config.cliCommand ?? getClaudeCommand2(),
46260
- maxTurns: config.maxTurns ?? 50
46522
+ maxTurns: config.maxTurns
46261
46523
  };
46262
46524
  }
46263
46525
  async buildManagementPrompt() {
@@ -46278,14 +46540,31 @@ class Manager {
46278
46540
  }
46279
46541
  return null;
46280
46542
  }
46281
- buildUserMessage(projectId, sessionNumber, result) {
46543
+ buildUserMessage(projectId, sessionNumber, result, storagePaths) {
46282
46544
  const today = new Date().toISOString().split("T")[0];
46545
+ const pathsSection = storagePaths ? `
46546
+ ## Storage Paths (ACTUAL - use these exact paths)
46547
+
46548
+ **Storage Mode:** ${storagePaths.storageMode}
46549
+
46550
+ ### Project Storage
46551
+ - **Project Root:** ${storagePaths.projectPath}
46552
+ - **Project Memories:** ${storagePaths.projectMemoriesPath}
46553
+
46554
+ ### Global Storage (shared across all projects)
46555
+ - **Global Root:** ${storagePaths.globalPath}
46556
+ - **Global Memories:** ${storagePaths.globalMemoriesPath}
46557
+ - **Personal Primer:** ${storagePaths.personalPrimerPath}
46558
+
46559
+ > ⚠️ These paths are resolved from the running server configuration. Use them exactly as provided.
46560
+ > Memories are stored as individual markdown files in the memories directories.
46561
+ ` : "";
46283
46562
  return `## Curation Data
46284
46563
 
46285
46564
  **Project ID:** ${projectId}
46286
46565
  **Session Number:** ${sessionNumber}
46287
46566
  **Date:** ${today}
46288
-
46567
+ ${pathsSection}
46289
46568
  ### Session Summary
46290
46569
  ${result.session_summary || "No summary provided"}
46291
46570
 
@@ -46311,56 +46590,105 @@ ${result.memories.map((m2, i2) => `
46311
46590
 
46312
46591
  ---
46313
46592
 
46314
- Please process these memories according to your management procedure. Update, supersede, or link existing memories as needed. Update the personal primer if any personal memories warrant it.`;
46593
+ Please process these memories according to your management procedure. Use the exact storage paths provided above to read and write memory files. Update, supersede, or link existing memories as needed. Update the personal primer if any personal memories warrant it.`;
46315
46594
  }
46316
46595
  parseManagementResponse(responseJson) {
46596
+ const emptyResult = (error) => ({
46597
+ success: !error,
46598
+ superseded: 0,
46599
+ resolved: 0,
46600
+ linked: 0,
46601
+ filesRead: 0,
46602
+ filesWritten: 0,
46603
+ primerUpdated: false,
46604
+ actions: [],
46605
+ summary: error ? "" : "No actions taken",
46606
+ fullReport: error ? `Error: ${error}` : "",
46607
+ error
46608
+ });
46317
46609
  try {
46318
46610
  const cliOutput = JSON.parse(responseJson);
46319
46611
  if (cliOutput.type === "error" || cliOutput.is_error === true) {
46320
- return {
46321
- success: false,
46322
- superseded: 0,
46323
- resolved: 0,
46324
- linked: 0,
46325
- primerUpdated: false,
46326
- summary: "",
46327
- error: cliOutput.error || "Unknown error"
46328
- };
46612
+ return emptyResult(cliOutput.error || "Unknown error");
46329
46613
  }
46330
46614
  const resultText = typeof cliOutput.result === "string" ? cliOutput.result : "";
46331
- const supersededMatch = resultText.match(/superseded[:\s]+(\d+)/i);
46332
- const resolvedMatch = resultText.match(/resolved[:\s]+(\d+)/i);
46333
- const linkedMatch = resultText.match(/linked[:\s]+(\d+)/i);
46334
- const primerUpdated = /primer.*updated|updated.*primer/i.test(resultText);
46615
+ const reportMatch = resultText.match(/(=== MANAGEMENT ACTIONS ===[\s\S]*)/);
46616
+ const fullReport = reportMatch ? reportMatch[1].trim() : resultText;
46617
+ const actionsMatch = resultText.match(/=== MANAGEMENT ACTIONS ===([\s\S]*?)(?:=== SUMMARY ===|$)/);
46618
+ const actions = [];
46619
+ if (actionsMatch) {
46620
+ const actionsText = actionsMatch[1];
46621
+ const actionLines = actionsText.split(`
46622
+ `).map((line) => line.trim()).filter((line) => /^(READ|WRITE|RECEIVED|CREATED|UPDATED|SUPERSEDED|RESOLVED|LINKED|PRIMER|SKIPPED|NO_ACTION)/.test(line));
46623
+ actions.push(...actionLines);
46624
+ }
46625
+ const supersededMatch = resultText.match(/memories_superseded[:\s]+(\d+)/i) || resultText.match(/superseded[:\s]+(\d+)/i);
46626
+ const resolvedMatch = resultText.match(/memories_resolved[:\s]+(\d+)/i) || resultText.match(/resolved[:\s]+(\d+)/i);
46627
+ const linkedMatch = resultText.match(/memories_linked[:\s]+(\d+)/i) || resultText.match(/linked[:\s]+(\d+)/i);
46628
+ const filesReadMatch = resultText.match(/files_read[:\s]+(\d+)/i);
46629
+ const filesWrittenMatch = resultText.match(/files_written[:\s]+(\d+)/i);
46630
+ const primerUpdated = /primer_updated[:\s]+true/i.test(resultText) || /PRIMER\s+OK/i.test(resultText);
46631
+ const readActions = actions.filter((a2) => a2.startsWith("READ OK")).length;
46632
+ const writeActions = actions.filter((a2) => a2.startsWith("WRITE OK")).length;
46335
46633
  return {
46336
46634
  success: true,
46337
46635
  superseded: supersededMatch ? parseInt(supersededMatch[1]) : 0,
46338
46636
  resolved: resolvedMatch ? parseInt(resolvedMatch[1]) : 0,
46339
46637
  linked: linkedMatch ? parseInt(linkedMatch[1]) : 0,
46638
+ filesRead: filesReadMatch ? parseInt(filesReadMatch[1]) : readActions,
46639
+ filesWritten: filesWrittenMatch ? parseInt(filesWrittenMatch[1]) : writeActions,
46340
46640
  primerUpdated,
46341
- summary: resultText.slice(0, 500)
46641
+ actions,
46642
+ summary: resultText.slice(0, 500),
46643
+ fullReport
46342
46644
  };
46343
46645
  } catch {
46344
- return {
46345
- success: false,
46346
- superseded: 0,
46347
- resolved: 0,
46348
- linked: 0,
46349
- primerUpdated: false,
46350
- summary: "",
46351
- error: "Failed to parse response"
46352
- };
46353
- }
46646
+ return emptyResult("Failed to parse response");
46647
+ }
46648
+ }
46649
+ async _buildSettingsFile(storagePaths) {
46650
+ const allowRules = [];
46651
+ const globalPath = storagePaths?.globalPath ?? join4(homedir4(), ".local", "share", "memory", "global");
46652
+ const projectPath = storagePaths?.projectPath ?? join4(homedir4(), ".local", "share", "memory");
46653
+ allowRules.push("Glob");
46654
+ allowRules.push("Grep");
46655
+ const formatPath = (p2) => p2.startsWith("/") ? "/" + p2 : "//" + p2;
46656
+ allowRules.push(`Read(${formatPath(globalPath)}/**)`);
46657
+ allowRules.push(`Write(${formatPath(globalPath)}/**)`);
46658
+ allowRules.push(`Edit(${formatPath(globalPath)}/**)`);
46659
+ allowRules.push(`Read(${formatPath(projectPath)}/**)`);
46660
+ allowRules.push(`Write(${formatPath(projectPath)}/**)`);
46661
+ allowRules.push(`Edit(${formatPath(projectPath)}/**)`);
46662
+ const settings = {
46663
+ permissions: {
46664
+ allow: allowRules,
46665
+ deny: [
46666
+ "Read(/etc/**)",
46667
+ "Read(~/.ssh/**)",
46668
+ "Read(~/.aws/**)",
46669
+ "Read(~/.gnupg/**)",
46670
+ "Read(.env)",
46671
+ "Read(.env.*)"
46672
+ ]
46673
+ }
46674
+ };
46675
+ const tempPath = join4(homedir4(), ".local", "share", "memory", ".manager-settings.json");
46676
+ await Bun.write(tempPath, JSON.stringify(settings, null, 2));
46677
+ return tempPath;
46354
46678
  }
46355
- async manageWithCLI(projectId, sessionNumber, result) {
46356
- if (process.env.MEMORY_MANAGER_DISABLED === "1") {
46679
+ async manageWithCLI(projectId, sessionNumber, result, storagePaths) {
46680
+ if (!this._config.enabled || process.env.MEMORY_MANAGER_DISABLED === "1") {
46357
46681
  return {
46358
46682
  success: true,
46359
46683
  superseded: 0,
46360
46684
  resolved: 0,
46361
46685
  linked: 0,
46686
+ filesRead: 0,
46687
+ filesWritten: 0,
46362
46688
  primerUpdated: false,
46363
- summary: "Management agent disabled"
46689
+ actions: [],
46690
+ summary: "Management agent disabled",
46691
+ fullReport: "Management agent disabled via configuration"
46364
46692
  };
46365
46693
  }
46366
46694
  if (result.memories.length === 0) {
@@ -46369,8 +46697,12 @@ Please process these memories according to your management procedure. Update, su
46369
46697
  superseded: 0,
46370
46698
  resolved: 0,
46371
46699
  linked: 0,
46700
+ filesRead: 0,
46701
+ filesWritten: 0,
46372
46702
  primerUpdated: false,
46373
- summary: "No memories to process"
46703
+ actions: [],
46704
+ summary: "No memories to process",
46705
+ fullReport: "No memories to process - skipped"
46374
46706
  };
46375
46707
  }
46376
46708
  const systemPrompt = await this.buildManagementPrompt();
@@ -46380,12 +46712,17 @@ Please process these memories according to your management procedure. Update, su
46380
46712
  superseded: 0,
46381
46713
  resolved: 0,
46382
46714
  linked: 0,
46715
+ filesRead: 0,
46716
+ filesWritten: 0,
46383
46717
  primerUpdated: false,
46718
+ actions: [],
46384
46719
  summary: "",
46720
+ fullReport: "Error: Management skill file not found",
46385
46721
  error: "Management skill not found"
46386
46722
  };
46387
46723
  }
46388
- const userMessage = this.buildUserMessage(projectId, sessionNumber, result);
46724
+ const userMessage = this.buildUserMessage(projectId, sessionNumber, result, storagePaths);
46725
+ const settingsPath = await this._buildSettingsFile(storagePaths);
46389
46726
  const args = [
46390
46727
  "-p",
46391
46728
  userMessage,
@@ -46393,9 +46730,12 @@ Please process these memories according to your management procedure. Update, su
46393
46730
  systemPrompt,
46394
46731
  "--output-format",
46395
46732
  "json",
46396
- "--max-turns",
46397
- String(this._config.maxTurns)
46733
+ "--settings",
46734
+ settingsPath
46398
46735
  ];
46736
+ if (this._config.maxTurns !== undefined) {
46737
+ args.push("--max-turns", String(this._config.maxTurns));
46738
+ }
46399
46739
  const proc = Bun.spawn([this._config.cliCommand, ...args], {
46400
46740
  env: {
46401
46741
  ...process.env,
@@ -46409,14 +46749,20 @@ Please process these memories according to your management procedure. Update, su
46409
46749
  ]);
46410
46750
  const exitCode = await proc.exited;
46411
46751
  if (exitCode !== 0) {
46752
+ const errorMsg = stderr || `Exit code ${exitCode}`;
46412
46753
  return {
46413
46754
  success: false,
46414
46755
  superseded: 0,
46415
46756
  resolved: 0,
46416
46757
  linked: 0,
46758
+ filesRead: 0,
46759
+ filesWritten: 0,
46417
46760
  primerUpdated: false,
46761
+ actions: [],
46418
46762
  summary: "",
46419
- error: stderr || `Exit code ${exitCode}`
46763
+ fullReport: `Error: CLI failed with exit code ${exitCode}
46764
+ ${stderr}`,
46765
+ error: errorMsg
46420
46766
  };
46421
46767
  }
46422
46768
  return this.parseManagementResponse(stdout);
@@ -46433,17 +46779,28 @@ async function createServer(config = {}) {
46433
46779
  host = "localhost",
46434
46780
  curator: curatorConfig,
46435
46781
  manager: managerConfig,
46782
+ managerEnabled,
46783
+ personalMemoriesEnabled,
46436
46784
  ...engineConfig
46437
46785
  } = config;
46438
46786
  const embeddings = createEmbeddings();
46439
46787
  logger.info("Initializing embedding model (this may take a moment on first run)...");
46440
46788
  await embeddings.initialize();
46789
+ const finalCuratorConfig = {
46790
+ ...curatorConfig,
46791
+ personalMemoriesEnabled: personalMemoriesEnabled ?? curatorConfig?.personalMemoriesEnabled
46792
+ };
46793
+ const finalManagerConfig = {
46794
+ ...managerConfig,
46795
+ enabled: managerEnabled ?? managerConfig?.enabled
46796
+ };
46441
46797
  const engine = createEngine({
46442
46798
  ...engineConfig,
46443
- embedder: embeddings.createEmbedder()
46799
+ embedder: embeddings.createEmbedder(),
46800
+ personalMemoriesEnabled: finalCuratorConfig.personalMemoriesEnabled
46444
46801
  });
46445
- const curator = createCurator(curatorConfig);
46446
- const manager = createManager(managerConfig);
46802
+ const curator = createCurator(finalCuratorConfig);
46803
+ const manager = createManager(finalManagerConfig);
46447
46804
  const server = Bun.serve({
46448
46805
  port,
46449
46806
  hostname: host,
@@ -46512,18 +46869,22 @@ async function createServer(config = {}) {
46512
46869
  logger.logCurationComplete(result.memories.length, result.session_summary);
46513
46870
  logger.logCuratedMemories(result.memories);
46514
46871
  const sessionNumber = await engine.getSessionNumber(body.project_id, body.project_path);
46872
+ const storagePaths = engine.getStoragePaths(body.project_id, body.project_path);
46515
46873
  setImmediate(async () => {
46516
46874
  try {
46517
46875
  logger.logManagementStart(result.memories.length);
46518
46876
  const startTime = Date.now();
46519
- const managementResult = await manager.manageWithCLI(body.project_id, sessionNumber, result);
46877
+ const managementResult = await manager.manageWithCLI(body.project_id, sessionNumber, result, storagePaths);
46520
46878
  logger.logManagementComplete({
46521
46879
  success: managementResult.success,
46522
46880
  superseded: managementResult.superseded || undefined,
46523
46881
  resolved: managementResult.resolved || undefined,
46524
46882
  linked: managementResult.linked || undefined,
46883
+ filesRead: managementResult.filesRead || undefined,
46884
+ filesWritten: managementResult.filesWritten || undefined,
46525
46885
  primerUpdated: managementResult.primerUpdated,
46526
- summary: managementResult.summary.slice(0, 100),
46886
+ actions: managementResult.actions,
46887
+ fullReport: managementResult.fullReport,
46527
46888
  error: managementResult.error
46528
46889
  });
46529
46890
  await engine.storeManagementLog({
@@ -46537,7 +46898,13 @@ async function createServer(config = {}) {
46537
46898
  success: managementResult.success,
46538
46899
  durationMs: Date.now() - startTime,
46539
46900
  summary: managementResult.summary,
46540
- error: managementResult.error
46901
+ fullReport: managementResult.fullReport,
46902
+ error: managementResult.error,
46903
+ details: {
46904
+ actions: managementResult.actions,
46905
+ filesRead: managementResult.filesRead,
46906
+ filesWritten: managementResult.filesWritten
46907
+ }
46541
46908
  });
46542
46909
  } catch (error) {
46543
46910
  logger.error(`Management failed: ${error}`);
@@ -46586,11 +46953,15 @@ if (__require.main == __require.module) {
46586
46953
  const host = process.env.MEMORY_HOST ?? "localhost";
46587
46954
  const storageMode = process.env.MEMORY_STORAGE_MODE ?? "central";
46588
46955
  const apiKey = process.env.ANTHROPIC_API_KEY;
46956
+ const managerEnabled = !["0", "false"].includes(process.env.MEMORY_MANAGER_ENABLED?.toLowerCase() ?? "");
46957
+ const personalMemoriesEnabled = !["0", "false"].includes(process.env.MEMORY_PERSONAL_ENABLED?.toLowerCase() ?? "");
46589
46958
  (async () => {
46590
46959
  await createServer({
46591
46960
  port,
46592
46961
  host,
46593
46962
  storageMode,
46963
+ managerEnabled,
46964
+ personalMemoriesEnabled,
46594
46965
  curator: { apiKey }
46595
46966
  });
46596
46967
  })();