claude-memory-layer 1.0.22 → 1.0.24

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.
Files changed (51) hide show
  1. package/.claude/settings.local.json +11 -0
  2. package/README.md +2 -0
  3. package/dist/cli/index.js +87 -17
  4. package/dist/cli/index.js.map +2 -2
  5. package/dist/core/index.js +30 -5
  6. package/dist/core/index.js.map +2 -2
  7. package/dist/hooks/post-tool-use.js +117 -18
  8. package/dist/hooks/post-tool-use.js.map +2 -2
  9. package/dist/hooks/semantic-daemon.js +7337 -0
  10. package/dist/hooks/semantic-daemon.js.map +7 -0
  11. package/dist/hooks/session-end.js +71 -16
  12. package/dist/hooks/session-end.js.map +2 -2
  13. package/dist/hooks/session-start.js +156 -24
  14. package/dist/hooks/session-start.js.map +4 -4
  15. package/dist/hooks/stop.js +101 -18
  16. package/dist/hooks/stop.js.map +2 -2
  17. package/dist/hooks/user-prompt-submit.js +291 -102
  18. package/dist/hooks/user-prompt-submit.js.map +4 -4
  19. package/dist/server/api/index.js +71 -16
  20. package/dist/server/api/index.js.map +2 -2
  21. package/dist/server/index.js +71 -16
  22. package/dist/server/index.js.map +2 -2
  23. package/dist/services/memory-service.js +71 -16
  24. package/dist/services/memory-service.js.map +2 -2
  25. package/dist/ui/app.js +48 -1
  26. package/dist/ui/index.html +11 -3
  27. package/memory/_index.md +1 -0
  28. package/memory/agent_response/uncategorized/2026-03-04.md +1138 -1
  29. package/memory/session_summary/uncategorized/2026-03-04.md +31 -0
  30. package/memory/tool_observation/uncategorized/2026-03-04.md +785 -1
  31. package/memory/user_prompt/uncategorized/2026-03-04.md +438 -1
  32. package/package.json +1 -1
  33. package/scripts/build.ts +2 -1
  34. package/specs/selective-tool-observation/context.md +100 -0
  35. package/specs/selective-tool-observation/plan.md +158 -0
  36. package/specs/selective-tool-observation/spec.md +127 -0
  37. package/src/cli/index.ts +1 -0
  38. package/src/core/embedder.ts +15 -4
  39. package/src/core/sqlite-event-store.ts +16 -0
  40. package/src/core/turn-state.ts +48 -0
  41. package/src/core/types.ts +1 -0
  42. package/src/hooks/post-tool-use.ts +47 -2
  43. package/src/hooks/semantic-daemon-client.ts +208 -0
  44. package/src/hooks/semantic-daemon.ts +276 -0
  45. package/src/hooks/session-start.ts +7 -0
  46. package/src/hooks/stop.ts +19 -4
  47. package/src/hooks/user-prompt-submit.ts +48 -40
  48. package/src/services/memory-service.ts +59 -16
  49. package/src/services/session-history-importer.ts +18 -0
  50. package/src/ui/app.js +48 -1
  51. package/src/ui/index.html +11 -3
@@ -0,0 +1,11 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(curl -s http://localhost:37777/api/stats | python3 -m json.tool 2>/dev/null || curl -s http://localhost:37777/api/stats)",
5
+ "Bash(curl -s http://localhost:37777/api/projects | python3 -m json.tool 2>/dev/null | head -60)",
6
+ "Bash(curl -s \"http://localhost:37777/api/retrievals?project=f4d5c120&limit=10\" | python3 -m json.tool 2>/dev/null | head -60)",
7
+ "Bash(curl -s \"http://localhost:37777/api/stats/retrieval-traces?project=f4d5c120&limit=5\" | python3 -m json.tool 2>/dev/null | head -30)",
8
+ "Bash(curl -s \"https://be2f-222-112-203-51.ngrok-free.app/api/health?project=f4d5c120\" | python3 -m json.tool)"
9
+ ]
10
+ }
11
+ }
package/README.md CHANGED
@@ -72,6 +72,8 @@ npx claude-memory-layer search "배포 이슈"
72
72
  - `GET /api/health` (outbox pending/failed 포함 상세 헬스)
73
73
  - `GET /api/stats/retrieval-traces` (검색→컨텍스트 채택 추적)
74
74
  - 주입 임계값 튜닝(환경변수):
75
+ - `CLAUDE_MEMORY_RETRIEVAL_MODE` (기본 `hybrid`, `keyword`/`hybrid`/`semantic`)
76
+ - `CLAUDE_MEMORY_SEMANTIC_DAEMON_IDLE_MS` (기본 `600000`, semantic daemon 유휴 종료 시간)
75
77
  - `CLAUDE_MEMORY_MIN_SCORE` (기본 0.4)
76
78
  - `CLAUDE_MEMORY_FALLBACK_MIN_SCORE` (기본 0.3, 결과 0건일 때 재시도)
77
79
 
package/dist/cli/index.js CHANGED
@@ -1848,6 +1848,21 @@ var SQLiteEventStore = class {
1848
1848
  [id, eventId, sessionId, score, query.slice(0, 100)]
1849
1849
  );
1850
1850
  }
1851
+ /**
1852
+ * Get session IDs that have unevaluated retrievals (measured_at IS NULL).
1853
+ * Excludes the current session. Used to backfill sessions that ended without Stop hook.
1854
+ */
1855
+ async getUnevaluatedSessions(currentSessionId, limit = 5) {
1856
+ await this.initialize();
1857
+ const rows = sqliteAll(
1858
+ this.db,
1859
+ `SELECT DISTINCT session_id FROM memory_helpfulness
1860
+ WHERE measured_at IS NULL AND session_id != ?
1861
+ ORDER BY created_at DESC LIMIT ?`,
1862
+ [currentSessionId, limit]
1863
+ );
1864
+ return rows.map((r) => r.session_id);
1865
+ }
1851
1866
  /**
1852
1867
  * Evaluate helpfulness for all retrievals in a session
1853
1868
  * Called at session end - uses behavioral signals to compute score
@@ -2635,7 +2650,7 @@ var VectorStore = class {
2635
2650
 
2636
2651
  // src/core/embedder.ts
2637
2652
  import { pipeline } from "@huggingface/transformers";
2638
- var Embedder = class {
2653
+ var Embedder = class _Embedder {
2639
2654
  pipeline = null;
2640
2655
  modelName;
2641
2656
  activeModelName;
@@ -2666,6 +2681,11 @@ var Embedder = class {
2666
2681
  this.initialized = true;
2667
2682
  }
2668
2683
  }
2684
+ // ~4 chars per token; 512 tokens * 4 = 2048, use 2000 to be safe
2685
+ static MAX_CHARS = 2e3;
2686
+ truncate(text) {
2687
+ return text.length > _Embedder.MAX_CHARS ? text.slice(0, _Embedder.MAX_CHARS) : text;
2688
+ }
2669
2689
  /**
2670
2690
  * Generate embedding for a single text
2671
2691
  */
@@ -2674,9 +2694,11 @@ var Embedder = class {
2674
2694
  if (!this.pipeline) {
2675
2695
  throw new Error("Embedding pipeline not initialized");
2676
2696
  }
2677
- const output = await this.pipeline(text, {
2697
+ const output = await this.pipeline(this.truncate(text), {
2678
2698
  pooling: "mean",
2679
- normalize: true
2699
+ normalize: true,
2700
+ truncation: true,
2701
+ max_length: 512
2680
2702
  });
2681
2703
  const vector = Array.from(output.data);
2682
2704
  return {
@@ -2698,9 +2720,11 @@ var Embedder = class {
2698
2720
  for (let i = 0; i < texts.length; i += batchSize) {
2699
2721
  const batch = texts.slice(i, i + batchSize);
2700
2722
  for (const text of batch) {
2701
- const output = await this.pipeline(text, {
2723
+ const output = await this.pipeline(this.truncate(text), {
2702
2724
  pooling: "mean",
2703
- normalize: true
2725
+ normalize: true,
2726
+ truncation: true,
2727
+ max_length: 512
2704
2728
  });
2705
2729
  const vector = Array.from(output.data);
2706
2730
  results.push({
@@ -5991,6 +6015,7 @@ var MemoryService = class {
5991
6015
  projectPath = null;
5992
6016
  readOnly;
5993
6017
  lightweightMode;
6018
+ embeddingOnly;
5994
6019
  mdMirror;
5995
6020
  storagePath;
5996
6021
  constructor(config) {
@@ -5998,6 +6023,7 @@ var MemoryService = class {
5998
6023
  this.storagePath = storagePath;
5999
6024
  this.readOnly = config.readOnly ?? false;
6000
6025
  this.lightweightMode = config.lightweightMode ?? false;
6026
+ this.embeddingOnly = config.embeddingOnly ?? false;
6001
6027
  this.mdMirror = new MarkdownMirror2(process.cwd());
6002
6028
  if (!this.readOnly && !fs4.existsSync(storagePath)) {
6003
6029
  fs4.mkdirSync(storagePath, { recursive: true });
@@ -6071,19 +6097,21 @@ var MemoryService = class {
6071
6097
  this.embedder
6072
6098
  );
6073
6099
  this.vectorWorker.start();
6074
- this.retriever.setGraduationPipeline(this.graduation);
6075
- this.graduationWorker = createGraduationWorker(
6076
- this.sqliteStore,
6077
- this.graduation
6078
- );
6079
- this.graduationWorker.start();
6080
- if (this.analyticsStore) {
6081
- this.syncWorker = new SyncWorker(
6100
+ if (!this.embeddingOnly) {
6101
+ this.retriever.setGraduationPipeline(this.graduation);
6102
+ this.graduationWorker = createGraduationWorker(
6082
6103
  this.sqliteStore,
6083
- this.analyticsStore,
6084
- { intervalMs: 3e4, batchSize: 500 }
6104
+ this.graduation
6085
6105
  );
6086
- this.syncWorker.start();
6106
+ this.graduationWorker.start();
6107
+ if (this.analyticsStore) {
6108
+ this.syncWorker = new SyncWorker(
6109
+ this.sqliteStore,
6110
+ this.analyticsStore,
6111
+ { intervalMs: 3e4, batchSize: 500 }
6112
+ );
6113
+ this.syncWorker.start();
6114
+ }
6087
6115
  }
6088
6116
  const savedMode = await this.sqliteStore.getEndlessConfig("mode");
6089
6117
  if (savedMode === "endless") {
@@ -6807,6 +6835,19 @@ var MemoryService = class {
6807
6835
  await this.initialize();
6808
6836
  await this.sqliteStore.recordRetrieval(eventId, sessionId, score, query);
6809
6837
  }
6838
+ /**
6839
+ * Record a query-level retrieval trace (used by user-prompt-submit hook).
6840
+ * Feeds the retrieval_traces table that powers dashboard stats.
6841
+ */
6842
+ async recordQueryTrace(input) {
6843
+ await this.initialize();
6844
+ await this.sqliteStore.recordRetrievalTrace({
6845
+ ...input,
6846
+ candidateDetails: [],
6847
+ selectedDetails: [],
6848
+ fallbackTrace: []
6849
+ });
6850
+ }
6810
6851
  /**
6811
6852
  * Evaluate helpfulness of retrievals in a session (called at session end)
6812
6853
  */
@@ -6814,6 +6855,20 @@ var MemoryService = class {
6814
6855
  await this.initialize();
6815
6856
  await this.sqliteStore.evaluateSessionHelpfulness(sessionId);
6816
6857
  }
6858
+ /**
6859
+ * Backfill helpfulness evaluation for sessions that ended without Stop hook.
6860
+ * Call on first turn of a new session to catch missed evaluations.
6861
+ */
6862
+ async evaluatePendingSessions(currentSessionId) {
6863
+ await this.initialize();
6864
+ const sessions = await this.sqliteStore.getUnevaluatedSessions(currentSessionId, 5);
6865
+ for (const sid of sessions) {
6866
+ try {
6867
+ await this.sqliteStore.evaluateSessionHelpfulness(sid);
6868
+ } catch {
6869
+ }
6870
+ }
6871
+ }
6817
6872
  /**
6818
6873
  * Get most helpful memories ranked by helpfulness score
6819
6874
  */
@@ -7146,6 +7201,16 @@ import * as path4 from "path";
7146
7201
  import * as os2 from "os";
7147
7202
  import * as readline from "readline";
7148
7203
  import { randomUUID as randomUUID9 } from "crypto";
7204
+ function isWorthStoringPrompt(content) {
7205
+ const trimmed = content.trim();
7206
+ if (trimmed.startsWith("/"))
7207
+ return false;
7208
+ if (trimmed.length < 15)
7209
+ return false;
7210
+ if (!/[a-zA-Z가-힣]{2,}/.test(trimmed))
7211
+ return false;
7212
+ return true;
7213
+ }
7149
7214
  function classifyEntry(entry) {
7150
7215
  if (entry.type !== "user" && entry.type !== "assistant") {
7151
7216
  return "skip";
@@ -7322,6 +7387,10 @@ var SessionHistoryImporter = class {
7322
7387
  const content = this.extractContent(entry);
7323
7388
  if (!content)
7324
7389
  continue;
7390
+ if (!isWorthStoringPrompt(content)) {
7391
+ result.skippedDuplicates++;
7392
+ continue;
7393
+ }
7325
7394
  currentTurnId = randomUUID9();
7326
7395
  const appendResult = await this.memoryService.storeUserPrompt(
7327
7396
  sessionId,
@@ -9709,7 +9778,7 @@ function getHooksConfig(pluginPath) {
9709
9778
  };
9710
9779
  }
9711
9780
  var program = new Command();
9712
- program.name("claude-memory-layer").description("Claude Code Memory Plugin CLI").version("1.0.22");
9781
+ program.name("claude-memory-layer").description("Claude Code Memory Plugin CLI").version("1.0.24");
9713
9782
  program.command("install").description("Install hooks into Claude Code settings").option("--path <path>", "Custom plugin path (defaults to auto-detect)").action(async (options) => {
9714
9783
  try {
9715
9784
  const pluginPath = options.path || getPluginPath();
@@ -9910,6 +9979,7 @@ program.command("process").description("Process pending embeddings").option("-p,
9910
9979
  const projectPath = options.project || process.cwd();
9911
9980
  const service = getMemoryServiceForProject(projectPath);
9912
9981
  try {
9982
+ await service.initialize();
9913
9983
  console.log("\u23F3 Processing pending embeddings...");
9914
9984
  const count = await service.processPendingEmbeddings();
9915
9985
  console.log(`\u2705 Processed ${count} embeddings`);