claude-memory-layer 1.0.23 → 1.0.25

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 (58) hide show
  1. package/.claude/settings.local.json +25 -0
  2. package/README.md +2 -0
  3. package/dist/cli/index.js +229 -978
  4. package/dist/cli/index.js.map +4 -4
  5. package/dist/core/index.js +59 -71
  6. package/dist/core/index.js.map +3 -3
  7. package/dist/hooks/post-tool-use.js +287 -976
  8. package/dist/hooks/post-tool-use.js.map +4 -4
  9. package/dist/hooks/semantic-daemon.js +6520 -0
  10. package/dist/hooks/semantic-daemon.js.map +7 -0
  11. package/dist/hooks/session-end.js +209 -973
  12. package/dist/hooks/session-end.js.map +4 -4
  13. package/dist/hooks/session-start.js +293 -978
  14. package/dist/hooks/session-start.js.map +4 -4
  15. package/dist/hooks/stop.js +247 -975
  16. package/dist/hooks/stop.js.map +4 -4
  17. package/dist/hooks/user-prompt-submit.js +406 -1036
  18. package/dist/hooks/user-prompt-submit.js.map +4 -4
  19. package/dist/server/api/index.js +209 -973
  20. package/dist/server/api/index.js.map +4 -4
  21. package/dist/server/index.js +209 -973
  22. package/dist/server/index.js.map +4 -4
  23. package/dist/services/memory-service.js +209 -973
  24. package/dist/services/memory-service.js.map +4 -4
  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 +1314 -1
  29. package/memory/session_summary/uncategorized/2026-03-04.md +50 -0
  30. package/memory/tool_observation/uncategorized/2026-03-04.md +969 -1
  31. package/memory/user_prompt/uncategorized/2026-03-04.md +555 -1
  32. package/package.json +1 -2
  33. package/scripts/build.ts +2 -1
  34. package/specs/memory-utilization-improvements/context.md +145 -0
  35. package/specs/memory-utilization-improvements/plan.md +361 -0
  36. package/specs/memory-utilization-improvements/spec.md +308 -0
  37. package/specs/optional-duckdb/context.md +77 -0
  38. package/specs/optional-duckdb/plan.md +142 -0
  39. package/specs/optional-duckdb/spec.md +35 -0
  40. package/specs/selective-tool-observation/context.md +100 -0
  41. package/specs/selective-tool-observation/plan.md +158 -0
  42. package/specs/selective-tool-observation/spec.md +127 -0
  43. package/src/cli/index.ts +1 -0
  44. package/src/core/db-wrapper.ts +18 -73
  45. package/src/core/embedder.ts +13 -4
  46. package/src/core/sqlite-event-store.ts +40 -0
  47. package/src/core/turn-state.ts +48 -0
  48. package/src/core/types.ts +1 -0
  49. package/src/hooks/post-tool-use.ts +72 -2
  50. package/src/hooks/semantic-daemon-client.ts +208 -0
  51. package/src/hooks/semantic-daemon.ts +276 -0
  52. package/src/hooks/session-start.ts +11 -0
  53. package/src/hooks/stop.ts +33 -4
  54. package/src/hooks/user-prompt-submit.ts +48 -40
  55. package/src/services/memory-service.ts +112 -65
  56. package/src/services/session-history-importer.ts +18 -0
  57. package/src/ui/app.js +48 -1
  58. package/src/ui/index.html +11 -3
@@ -124,6 +124,7 @@ var ConfigSchema = z.object({
124
124
  toolObservation: z.object({
125
125
  enabled: z.boolean().default(true),
126
126
  excludedTools: z.array(z.string()).default(["TodoWrite", "TodoRead"]),
127
+ minOutputLength: z.number().default(100),
127
128
  maxOutputLength: z.number().default(1e4),
128
129
  maxOutputLines: z.number().default(100),
129
130
  storeOnlyOnSuccess: z.boolean().default(false)
@@ -620,25 +621,7 @@ function parseEntityCanonicalKey(canonicalKey) {
620
621
  import { randomUUID } from "crypto";
621
622
 
622
623
  // src/core/db-wrapper.ts
623
- import duckdb from "duckdb";
624
- function convertBigInts(obj) {
625
- if (obj === null || obj === void 0)
626
- return obj;
627
- if (typeof obj === "bigint")
628
- return Number(obj);
629
- if (obj instanceof Date)
630
- return obj;
631
- if (Array.isArray(obj))
632
- return obj.map(convertBigInts);
633
- if (typeof obj === "object") {
634
- const result = {};
635
- for (const [key, value] of Object.entries(obj)) {
636
- result[key] = convertBigInts(value);
637
- }
638
- return result;
639
- }
640
- return obj;
641
- }
624
+ import BetterSqlite3 from "better-sqlite3";
642
625
  function toDate(value) {
643
626
  if (value instanceof Date)
644
627
  return value;
@@ -648,59 +631,19 @@ function toDate(value) {
648
631
  return new Date(value);
649
632
  return new Date(String(value));
650
633
  }
651
- function createDatabase(path2, options) {
652
- if (options?.readOnly) {
653
- return new duckdb.Database(path2, { access_mode: "READ_ONLY" });
654
- }
655
- return new duckdb.Database(path2);
634
+ function createDatabase(dbPath, options) {
635
+ return new BetterSqlite3(dbPath, { readonly: options?.readOnly });
656
636
  }
657
637
  function dbRun(db, sql, params = []) {
658
- return new Promise((resolve, reject) => {
659
- if (params.length === 0) {
660
- db.run(sql, (err) => {
661
- if (err)
662
- reject(err);
663
- else
664
- resolve();
665
- });
666
- } else {
667
- db.run(sql, ...params, (err) => {
668
- if (err)
669
- reject(err);
670
- else
671
- resolve();
672
- });
673
- }
674
- });
638
+ db.prepare(sql).run(...params);
639
+ return Promise.resolve();
675
640
  }
676
641
  function dbAll(db, sql, params = []) {
677
- return new Promise((resolve, reject) => {
678
- if (params.length === 0) {
679
- db.all(sql, (err, rows) => {
680
- if (err)
681
- reject(err);
682
- else
683
- resolve(convertBigInts(rows || []));
684
- });
685
- } else {
686
- db.all(sql, ...params, (err, rows) => {
687
- if (err)
688
- reject(err);
689
- else
690
- resolve(convertBigInts(rows || []));
691
- });
692
- }
693
- });
642
+ return Promise.resolve(db.prepare(sql).all(...params));
694
643
  }
695
644
  function dbClose(db) {
696
- return new Promise((resolve, reject) => {
697
- db.close((err) => {
698
- if (err)
699
- reject(err);
700
- else
701
- resolve();
702
- });
703
- });
645
+ db.close();
646
+ return Promise.resolve();
704
647
  }
705
648
 
706
649
  // src/core/event-store.ts
@@ -1902,6 +1845,29 @@ var SQLiteEventStore = class {
1902
1845
  };
1903
1846
  }
1904
1847
  }
1848
+ /**
1849
+ * Get session IDs that have events but no session_summary event.
1850
+ * Used to backfill summaries for sessions that ended without Stop hook.
1851
+ */
1852
+ async getSessionsWithoutSummary(currentSessionId, limit = 5) {
1853
+ await this.initialize();
1854
+ const rows = sqliteAll(
1855
+ this.db,
1856
+ `SELECT DISTINCT e.session_id
1857
+ FROM events e
1858
+ WHERE e.session_id != ?
1859
+ AND e.event_type != 'session_summary'
1860
+ AND e.session_id NOT IN (
1861
+ SELECT DISTINCT session_id FROM events WHERE event_type = 'session_summary'
1862
+ )
1863
+ GROUP BY e.session_id
1864
+ HAVING COUNT(*) >= 3
1865
+ ORDER BY MAX(e.timestamp) DESC
1866
+ LIMIT ?`,
1867
+ [currentSessionId, limit]
1868
+ );
1869
+ return rows.map((r) => r.session_id);
1870
+ }
1905
1871
  /**
1906
1872
  * Get events by session ID
1907
1873
  */
@@ -2418,6 +2384,21 @@ var SQLiteEventStore = class {
2418
2384
  [id, eventId, sessionId, score, query.slice(0, 100)]
2419
2385
  );
2420
2386
  }
2387
+ /**
2388
+ * Get session IDs that have unevaluated retrievals (measured_at IS NULL).
2389
+ * Excludes the current session. Used to backfill sessions that ended without Stop hook.
2390
+ */
2391
+ async getUnevaluatedSessions(currentSessionId, limit = 5) {
2392
+ await this.initialize();
2393
+ const rows = sqliteAll(
2394
+ this.db,
2395
+ `SELECT DISTINCT session_id FROM memory_helpfulness
2396
+ WHERE measured_at IS NULL AND session_id != ?
2397
+ ORDER BY created_at DESC LIMIT ?`,
2398
+ [currentSessionId, limit]
2399
+ );
2400
+ return rows.map((r) => r.session_id);
2401
+ }
2421
2402
  /**
2422
2403
  * Evaluate helpfulness for all retrievals in a session
2423
2404
  * Called at session end - uses behavioral signals to compute score
@@ -3997,7 +3978,7 @@ var VectorStore = class {
3997
3978
 
3998
3979
  // src/core/embedder.ts
3999
3980
  import { pipeline } from "@huggingface/transformers";
4000
- var Embedder = class {
3981
+ var Embedder = class _Embedder {
4001
3982
  pipeline = null;
4002
3983
  modelName;
4003
3984
  activeModelName;
@@ -4028,6 +4009,11 @@ var Embedder = class {
4028
4009
  this.initialized = true;
4029
4010
  }
4030
4011
  }
4012
+ // ~4 chars per token; 512 tokens * 4 = 2048, use 2000 to be safe
4013
+ static MAX_CHARS = 2e3;
4014
+ truncate(text) {
4015
+ return text.length > _Embedder.MAX_CHARS ? text.slice(0, _Embedder.MAX_CHARS) : text;
4016
+ }
4031
4017
  /**
4032
4018
  * Generate embedding for a single text
4033
4019
  */
@@ -4036,10 +4022,11 @@ var Embedder = class {
4036
4022
  if (!this.pipeline) {
4037
4023
  throw new Error("Embedding pipeline not initialized");
4038
4024
  }
4039
- const output = await this.pipeline(text, {
4025
+ const output = await this.pipeline(this.truncate(text), {
4040
4026
  pooling: "mean",
4041
4027
  normalize: true,
4042
- truncation: true
4028
+ truncation: true,
4029
+ max_length: 512
4043
4030
  });
4044
4031
  const vector = Array.from(output.data);
4045
4032
  return {
@@ -4061,10 +4048,11 @@ var Embedder = class {
4061
4048
  for (let i = 0; i < texts.length; i += batchSize) {
4062
4049
  const batch = texts.slice(i, i + batchSize);
4063
4050
  for (const text of batch) {
4064
- const output = await this.pipeline(text, {
4051
+ const output = await this.pipeline(this.truncate(text), {
4065
4052
  pooling: "mean",
4066
4053
  normalize: true,
4067
- truncation: true
4054
+ truncation: true,
4055
+ max_length: 512
4068
4056
  });
4069
4057
  const vector = Array.from(output.data);
4070
4058
  results.push({