@ponraaj/dimple-sdk 0.0.0-alpha.4 → 0.0.0-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { createRequire } from "node:module";
1
2
  var __defProp = Object.defineProperty;
2
3
  var __returnValue = (v) => v;
3
4
  function __exportSetter(name, newValue) {
@@ -12,9 +13,10 @@ var __export = (target, all) => {
12
13
  set: __exportSetter.bind(all, name)
13
14
  });
14
15
  };
16
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
15
17
 
16
18
  // src/make.ts
17
- import { Effect as Effect50, Exit as Exit7, Layer as Layer26, ManagedRuntime as ManagedRuntime2, Scope as Scope5 } from "effect";
19
+ import { Effect as Effect50, Exit as Exit7, Layer as Layer26, ManagedRuntime, Scope as Scope5 } from "effect";
18
20
 
19
21
  // ../core/src/core.ts
20
22
  import { Context, Schema } from "effect";
@@ -31,7 +33,7 @@ import { Effect as Effect39, Layer as Layer15, Schema as Schema24 } from "effect
31
33
 
32
34
  // ../store/src/store.ts
33
35
  import { Config as Config4, Effect as Effect26, Layer as Layer10, Schema as Schema19 } from "effect";
34
- import { eq as eq8, and as and8, desc as desc2, asc as asc5, inArray as inArray4, ne as ne4, or, isNull as isNull2, sql as sql7 } from "drizzle-orm";
36
+ import { eq as eq8, and as and8, desc as desc2, asc as asc5, inArray as inArray4, ne as ne4, or, isNull as isNull2, like, sql as sql7 } from "drizzle-orm";
35
37
 
36
38
  // ../store/src/db-backend.ts
37
39
  import { Context as Context2 } from "effect";
@@ -44,9 +46,6 @@ import { Effect as Effect9, Layer as Layer3, Config as Config2 } from "effect";
44
46
  import { createClient } from "@libsql/client";
45
47
  import { drizzle } from "drizzle-orm/libsql";
46
48
  import { sql as sql2 } from "drizzle-orm";
47
- import * as fs2 from "node:fs";
48
- import { dirname as dirname2 } from "node:path";
49
- import { migrate } from "drizzle-orm/libsql/migrator";
50
49
 
51
50
  // ../store/src/error.ts
52
51
  import { Schema as Schema2 } from "effect";
@@ -109,6 +108,11 @@ var EMBEDDED_MIGRATIONS = [
109
108
  name: "20260804035154_dream_state",
110
109
  hash: "376f6768a43edf8941614b92720222c895e258411504fb5857569dff5990b62f",
111
110
  sql: "CREATE TABLE `dream_state` (\n\t`model_key` text PRIMARY KEY,\n\t`last_dream_at` integer NOT NULL,\n\t`last_dream_memory_count` integer NOT NULL\n);\n"
111
+ },
112
+ {
113
+ name: "20260807074034_ttl_secs_nullable",
114
+ hash: "be3a8d0e64b0224021d869c1c7e217e40b6eb868615cc4c86a6717f5b7211721",
115
+ sql: "PRAGMA foreign_keys=OFF;--> statement-breakpoint\nCREATE TABLE `__new_memories` (\n\t`id` text PRIMARY KEY,\n\t`logical_key` text NOT NULL,\n\t`content` text NOT NULL,\n\t`content_type` text DEFAULT 'text/plain' NOT NULL,\n\t`state` text DEFAULT 'active' NOT NULL,\n\t`created_at` integer NOT NULL,\n\t`ttl_secs` integer DEFAULT 604800,\n\t`namespace` text DEFAULT 'default' NOT NULL,\n\t`metadata` text,\n\t`embedding` F32_BLOB(384),\n\t`embedding_model` text\n);\n--> statement-breakpoint\nINSERT INTO `__new_memories`(`id`, `logical_key`, `content`, `content_type`, `state`, `created_at`, `ttl_secs`, `namespace`, `metadata`, `embedding`, `embedding_model`) SELECT `id`, `logical_key`, `content`, `content_type`, `state`, `created_at`, `ttl_secs`, `namespace`, `metadata`, `embedding`, `embedding_model` FROM `memories`;--> statement-breakpoint\nDROP TABLE `memories`;--> statement-breakpoint\nALTER TABLE `__new_memories` RENAME TO `memories`;--> statement-breakpoint\nPRAGMA foreign_keys=ON;--> statement-breakpoint\nCREATE INDEX `idx_memories_ns_created` ON `memories` (`namespace`,`created_at`);--> statement-breakpoint\nCREATE INDEX `idx_memories_state_created` ON `memories` (`state`,`created_at`);--> statement-breakpoint\nCREATE UNIQUE INDEX `idx_memories_active_key` ON `memories` (`namespace`,`logical_key`) WHERE state = 'active';"
112
116
  }
113
117
  ];
114
118
 
@@ -148,7 +152,7 @@ var memories = sqliteTable("memories", {
148
152
  contentType: text("content_type").notNull().default("text/plain"),
149
153
  state: text("state", { enum: ["active", "superseded", "archived", "forgotten"] }).notNull().default("active"),
150
154
  createdAt: integer("created_at").notNull(),
151
- ttlSecs: integer("ttl_secs").notNull().default(604800),
155
+ ttlSecs: integer("ttl_secs").default(604800),
152
156
  namespace: text("namespace").notNull().default("default"),
153
157
  metadata: text("metadata"),
154
158
  embedding: float32Array("embedding", { dimensions: 384 }),
@@ -326,7 +330,7 @@ var artifactEdges = sqliteTable3("artifact_edges", {
326
330
  fromMemoryId: text3("from_memory_id").notNull().references(() => memories.id, { onDelete: "cascade" }),
327
331
  toMemoryId: text3("to_memory_id").notNull().references(() => memories.id, { onDelete: "cascade" }),
328
332
  edgeKind: text3("edge_kind", {
329
- enum: ["memory_supports", "memory_supersedes", "memory_contradicts"]
333
+ enum: ["memory_supports", "memory_supersedes", "memory_contradicts", "code_calls"]
330
334
  }).notNull(),
331
335
  weight: real3("weight").notNull().default(1),
332
336
  status: text3("status", { enum: ["pending", "confirmed", "rejected"] }).notNull().default("pending"),
@@ -378,7 +382,6 @@ import { Effect as Effect6, Schema as Schema6 } from "effect";
378
382
 
379
383
  // ../logger/src/service.ts
380
384
  import { Config, Context as Context3, Effect as Effect5, Layer, Logger, References } from "effect";
381
- import { closeSync, fsyncSync, openSync, writeSync } from "node:fs";
382
385
 
383
386
  // ../logger/src/format.ts
384
387
  function renderJson(entry) {
@@ -498,13 +501,14 @@ var consoleSink = (pretty) => {
498
501
  const render = pretty ? renderPretty : renderJson;
499
502
  return {
500
503
  write: (entry) => {
501
- process.stdout.write(render(entry) + `
504
+ process.stderr.write(render(entry) + `
502
505
  `);
503
506
  },
504
507
  dispose: () => {}
505
508
  };
506
509
  };
507
- var fileSink = (path) => {
510
+ var fileSink = (path) => Effect5.tryPromise(async () => {
511
+ const { openSync, closeSync, fsyncSync, writeSync } = await import("node:fs");
508
512
  const fd = openSync(path, "a");
509
513
  let disposed = false;
510
514
  const flush = () => {
@@ -537,11 +541,15 @@ var fileSink = (path) => {
537
541
  closeSync(fd);
538
542
  }
539
543
  };
540
- };
544
+ });
541
545
 
542
546
  class Service3 extends Context3.Service()("@dimple/logger/Logging") {
543
547
  }
544
- var make = (config) => {
548
+ var makeEffect = (config) => Effect5.gen(function* () {
549
+ const file = config.file === undefined ? undefined : yield* fileSink(config.file);
550
+ const sinks = [consoleSink((config.format ?? "jsonl") === "pretty")];
551
+ if (file !== undefined)
552
+ sinks.push(file);
545
553
  const level = config.level ?? "info";
546
554
  const floor = LEVEL_ORDER[level];
547
555
  const overrides = {};
@@ -551,9 +559,6 @@ var make = (config) => {
551
559
  overrides[scope] = order;
552
560
  }
553
561
  const ring = new RingBuffer(config.ringSize ?? 4096);
554
- const sinks = [consoleSink((config.format ?? "jsonl") === "pretty")];
555
- if (config.file !== undefined)
556
- sinks.push(fileSink(config.file));
557
562
  return Service3.of({
558
563
  ring,
559
564
  emit: (entry) => {
@@ -572,7 +577,7 @@ var make = (config) => {
572
577
  sink.dispose();
573
578
  }
574
579
  });
575
- };
580
+ });
576
581
  var LEVEL_MAP = {
577
582
  FATAL: "error",
578
583
  ERROR: "error",
@@ -637,7 +642,7 @@ var EFFECT_LEVEL = {
637
642
  };
638
643
  var minLevelLayer = (minLevel) => Layer.succeed(References.MinimumLogLevel, minLevel === "None" ? "None" : EFFECT_LEVEL[minLevel]);
639
644
  var layer = (config) => {
640
- const loggingLayer = Layer.effect(Service3, Effect5.acquireRelease(Effect5.sync(() => make(config)), (logging) => Effect5.sync(() => logging.dispose())));
645
+ const loggingLayer = Layer.effect(Service3, Effect5.acquireRelease(makeEffect(config), (logging) => Effect5.sync(() => logging.dispose())));
641
646
  const minLevel = config.enabled === false ? "None" : lowestLevel(config);
642
647
  const withLogger = Layer.provideMerge(loggingLayer)(loggerReplacement());
643
648
  return Layer.provideMerge(minLevelLayer(minLevel))(withLogger);
@@ -688,7 +693,8 @@ var EmbeddingModelConfig = Schema6.Struct({
688
693
  ])),
689
694
  pooling: Schema6.optional(Schema6.Literals(["mean", "cls", "none"])),
690
695
  normalize: Schema6.optional(Schema6.Boolean),
691
- cacheDir: Schema6.optional(Schema6.String)
696
+ cacheDir: Schema6.optional(Schema6.String),
697
+ autoInstall: Schema6.optional(Schema6.Boolean)
692
698
  });
693
699
  var EmbeddingConfig = Schema6.Struct({
694
700
  defaultModel: Schema6.String,
@@ -755,8 +761,6 @@ import { Context as Context4, Effect as Effect8, Layer as Layer2 } from "effect"
755
761
  // ../config/src/loader.ts
756
762
  import { Effect as Effect7, Schema as Schema7 } from "effect";
757
763
  import { parse, parseTree, printParseErrorCode } from "jsonc-parser";
758
- import * as fs from "node:fs";
759
- import * as path from "node:path";
760
764
  var CONFIG_FILE_NAMES = ["dimple.jsonc", "dimple.json"];
761
765
  var GLOBAL_DIR = ".config/dimple";
762
766
  var defaults = {
@@ -856,11 +860,14 @@ var parseJsoncEffect = (text4, formatError) => {
856
860
  return Effect7.fail(formatError(result.error));
857
861
  return Effect7.succeed(result.value);
858
862
  };
859
- var readFile = (filePath) => Effect7.try({
860
- try: () => fs.readFileSync(filePath, "utf-8"),
863
+ var readFile = (filePath) => Effect7.tryPromise({
864
+ try: async () => (await import("node:fs")).promises.readFile(filePath, "utf8"),
861
865
  catch: (cause) => new Error(`failed to read config file ${filePath}: ${cause instanceof Error ? cause.message : String(cause)}`)
862
866
  }).pipe(Effect7.flatMap((content) => parseJsoncEffect(content, (cause) => new Error(`failed to read config file ${filePath}: ${cause.message}`))));
863
- var findProjectConfig = () => {
867
+ var loadFs = () => Effect7.tryPromise(() => import("node:fs"));
868
+ var findProjectConfig = () => Effect7.gen(function* () {
869
+ const fs = yield* loadFs();
870
+ const path = yield* Effect7.tryPromise(() => import("node:path"));
864
871
  let dir = process.cwd();
865
872
  for (let i = 0;i < 20; i++) {
866
873
  for (const name of CONFIG_FILE_NAMES) {
@@ -874,37 +881,40 @@ var findProjectConfig = () => {
874
881
  dir = parent;
875
882
  }
876
883
  return;
877
- };
878
- var getGlobalConfigPath = () => {
884
+ });
885
+ var getGlobalConfigPath = () => Effect7.gen(function* () {
879
886
  const home = process.env["HOME"] ?? process.env["USERPROFILE"];
880
887
  if (!home)
881
888
  return;
889
+ const fs = yield* loadFs();
890
+ const path = yield* Effect7.tryPromise(() => import("node:path"));
882
891
  for (const name of CONFIG_FILE_NAMES) {
883
892
  const p = path.join(home, GLOBAL_DIR, name);
884
893
  if (fs.existsSync(p))
885
894
  return p;
886
895
  }
887
896
  return;
888
- };
889
- var loadFromFiles = () => {
897
+ });
898
+ var loadFromFiles = () => Effect7.gen(function* () {
890
899
  const sources = [];
891
- const globalPath = getGlobalConfigPath();
900
+ const globalPath = yield* getGlobalConfigPath();
892
901
  if (globalPath)
893
902
  sources.push({ path: globalPath });
894
- const projectPath = findProjectConfig();
903
+ const projectPath = yield* findProjectConfig();
895
904
  if (projectPath)
896
905
  sources.push({ path: projectPath });
897
906
  const envPath = process.env["DIMPLE_CONFIG"];
898
907
  if (envPath) {
908
+ const fs = yield* loadFs();
899
909
  if (!fs.existsSync(envPath)) {
900
- return Effect7.fail(new Error(`DIMPLE_CONFIG path not found: ${envPath}`));
910
+ return yield* Effect7.fail(new Error(`DIMPLE_CONFIG path not found: ${envPath}`));
901
911
  }
902
912
  sources.push({ path: envPath });
903
913
  }
904
914
  if (sources.length === 0)
905
- return Effect7.succeed([]);
906
- return Effect7.forEach(sources, (s) => readFile(s.path));
907
- };
915
+ return [];
916
+ return yield* Effect7.forEach(sources, (s) => readFile(s.path));
917
+ });
908
918
  var loadFromEnvContent = () => {
909
919
  const content = process.env["DIMPLE_CONFIG_CONTENT"];
910
920
  if (!content)
@@ -935,15 +945,16 @@ var deepMerge = (base, over) => {
935
945
  }
936
946
  return out;
937
947
  };
938
- var loadConfigFromFile = (path2) => Effect7.gen(function* () {
948
+ var loadConfigFromFile = (path) => Effect7.gen(function* () {
949
+ const fs = yield* loadFs();
939
950
  const content = yield* Effect7.tryPromise({
940
- try: () => fs.promises.readFile(path2, "utf8"),
941
- catch: (cause) => new Error(`config read ${path2}: ${cause}`)
951
+ try: () => fs.promises.readFile(path, "utf8"),
952
+ catch: (cause) => new Error(`config read ${path}: ${cause}`)
942
953
  });
943
- const parsed = yield* parseJsoncEffect(content, (cause) => new Error(`config parse ${path2}: ${cause.message}`));
954
+ const parsed = yield* parseJsoncEffect(content, (cause) => new Error(`config parse ${path}: ${cause.message}`));
944
955
  const merged = deepMerge(defaults, parsed);
945
956
  const resolved = resolveEnvVars(merged);
946
- return yield* Schema7.decodeUnknownEffect(DimpleConfig)(resolved).pipe(Effect7.mapError((e) => new Error(`config validation: ${e.message}`)));
957
+ return yield* Schema7.decodeUnknownEffect(DimpleConfig, { errors: "all" })(resolved).pipe(Effect7.mapError((e) => new Error(`config validation: ${e.message}`)));
947
958
  });
948
959
  var loadConfig = (overrides) => Effect7.gen(function* () {
949
960
  const fileParts = yield* loadFromFiles();
@@ -956,7 +967,7 @@ var loadConfig = (overrides) => Effect7.gen(function* () {
956
967
  if (overrides)
957
968
  merged = deepMerge(merged, overrides);
958
969
  const resolved = resolveEnvVars(merged);
959
- return yield* Schema7.decodeUnknownEffect(DimpleConfig)(resolved).pipe(Effect7.mapError((e) => new Error(`config validation: ${e.message}`)));
970
+ return yield* Schema7.decodeUnknownEffect(DimpleConfig, { errors: "all" })(resolved).pipe(Effect7.mapError((e) => new Error(`config validation: ${e.message}`)));
960
971
  });
961
972
 
962
973
  // ../config/src/service.ts
@@ -1049,9 +1060,6 @@ var runEmbeddedMigrations = async (client) => {
1049
1060
  }
1050
1061
  };
1051
1062
  var makeClientDb = (url) => {
1052
- if (url.startsWith("file:")) {
1053
- fs2.mkdirSync(dirname2(url.slice("file:".length)), { recursive: true });
1054
- }
1055
1063
  const client = createClient({ url });
1056
1064
  const db = drizzle({ client });
1057
1065
  const rawTransaction = client.transaction.bind(client);
@@ -1114,25 +1122,29 @@ var makeClientDb = (url) => {
1114
1122
  };
1115
1123
  return { client, db };
1116
1124
  };
1125
+ var ensureFileDir = (url) => url.startsWith("file:") ? import("node:fs").then((fs) => import("node:path").then(({ dirname }) => {
1126
+ fs.mkdirSync(dirname(url.slice("file:".length)), { recursive: true });
1127
+ })) : Promise.resolve();
1117
1128
  var acquireMigrationLock = async (url) => {
1118
1129
  if (!url.startsWith("file:"))
1119
1130
  return;
1131
+ const fs = await import("node:fs");
1120
1132
  const dbPath = url.slice("file:".length);
1121
1133
  const lockPath = `${dbPath}.migrate-lock`;
1122
1134
  const deadline = Date.now() + 30000;
1123
1135
  for (;; ) {
1124
1136
  try {
1125
- const fd = fs2.openSync(lockPath, "wx");
1137
+ const fd = fs.openSync(lockPath, "wx");
1126
1138
  return () => {
1127
1139
  try {
1128
- fs2.closeSync(fd);
1129
- fs2.unlinkSync(lockPath);
1140
+ fs.closeSync(fd);
1141
+ fs.unlinkSync(lockPath);
1130
1142
  } catch {}
1131
1143
  };
1132
1144
  } catch {
1133
1145
  try {
1134
- if (Date.now() - fs2.statSync(lockPath).mtimeMs > 5 * 60000)
1135
- fs2.unlinkSync(lockPath);
1146
+ if (Date.now() - fs.statSync(lockPath).mtimeMs > 300000)
1147
+ fs.unlinkSync(lockPath);
1136
1148
  } catch {}
1137
1149
  if (Date.now() > deadline) {
1138
1150
  return Promise.reject(new Error(`timed out waiting for migration lock ${lockPath}`));
@@ -1142,6 +1154,10 @@ var acquireMigrationLock = async (url) => {
1142
1154
  }
1143
1155
  };
1144
1156
  var topicFilter = (topicKeys) => topicKeys && topicKeys.length > 0 ? ` AND m.id IN (SELECT memory_id FROM topic_memberships WHERE topic_key IN (${topicKeys.map(() => "?").join(", ")}) AND model_key = ?)` : "";
1157
+ var contentTypeFilter = (contentType) => {
1158
+ const kinds = typeof contentType === "string" ? [contentType] : contentType ?? [];
1159
+ return kinds.length === 0 ? "" : ` AND m.content_type IN (${kinds.map(() => "?").join(", ")})`;
1160
+ };
1145
1161
  var makeBackend = (url, client, db, migrationFolder) => ({
1146
1162
  listIsolatedMemories: (limit, modelKey) => Effect9.tryPromise({
1147
1163
  try: () => client.execute({
@@ -1177,6 +1193,7 @@ var makeBackend = (url, client, db, migrationFolder) => ({
1177
1193
  try: async () => {
1178
1194
  try {
1179
1195
  if (migrationFolder !== undefined) {
1196
+ const { migrate } = await import("drizzle-orm/libsql/migrator");
1180
1197
  await migrate(db, { migrationsFolder: migrationFolder });
1181
1198
  } else {
1182
1199
  await runEmbeddedMigrations(client);
@@ -1207,25 +1224,25 @@ var makeBackend = (url, client, db, migrationFolder) => ({
1207
1224
  topics,
1208
1225
  artifactEdges
1209
1226
  },
1210
- ftsSearch: (query, namespace, limit, topicKeys, modelKey) => Effect9.tryPromise({
1227
+ ftsSearch: (query, namespace, limit, topicKeys, modelKey, contentType) => Effect9.tryPromise({
1211
1228
  try: () => client.execute({
1212
1229
  sql: `SELECT m.*, f.rank
1213
1230
  FROM memories_fts f
1214
1231
  JOIN memories m ON m.rowid = f.rowid
1215
- WHERE f.content MATCH ? AND m.namespace = ? AND m.state = 'active'${topicFilter(topicKeys)}
1232
+ WHERE f.content MATCH ? AND m.namespace = ? AND m.state = 'active'${topicFilter(topicKeys)}${contentTypeFilter(contentType)}
1216
1233
  ORDER BY f.rank
1217
1234
  LIMIT ?`,
1218
- args: topicKeys && topicKeys.length > 0 ? [
1235
+ args: [
1219
1236
  `"${query.replaceAll('"', '""')}"`,
1220
1237
  namespace,
1221
- ...topicKeys,
1222
- modelKey ?? "",
1238
+ ...topicKeys && topicKeys.length > 0 ? [...topicKeys, modelKey ?? ""] : [],
1239
+ ...typeof contentType === "string" ? [contentType] : contentType ?? [],
1223
1240
  limit
1224
- ] : [`"${query.replaceAll('"', '""')}"`, namespace, limit]
1241
+ ]
1225
1242
  }).then((rs) => rs.rows),
1226
1243
  catch: (cause) => new StoreError({ message: `DbBackend.ftsSearch: ${cause}` })
1227
1244
  }),
1228
- vectorSearch: (query, namespace, limit, model, topicKeys) => Effect9.gen(function* () {
1245
+ vectorSearch: (query, namespace, limit, model, topicKeys, contentType) => Effect9.gen(function* () {
1229
1246
  for (const n of query) {
1230
1247
  if (!Number.isFinite(n)) {
1231
1248
  yield* new StoreError({
@@ -1236,7 +1253,13 @@ var makeBackend = (url, client, db, migrationFolder) => ({
1236
1253
  const tableName = vectorTableName(model);
1237
1254
  const vecJson = JSON.stringify([...query]);
1238
1255
  const convertFn = VECTOR_CONVERT_FNS[model.vectorType ?? "float32"] ?? "vector32";
1239
- const args = topicKeys && topicKeys.length > 0 ? [vecJson, namespace, ...topicKeys, model.id, limit] : [vecJson, namespace, limit];
1256
+ const args = [
1257
+ vecJson,
1258
+ namespace,
1259
+ ...topicKeys && topicKeys.length > 0 ? [...topicKeys, model.id] : [],
1260
+ ...typeof contentType === "string" ? [contentType] : contentType ?? [],
1261
+ limit
1262
+ ];
1240
1263
  const rows = yield* Effect9.tryPromise({
1241
1264
  try: () => client.execute({
1242
1265
  sql: `
@@ -1244,7 +1267,7 @@ var makeBackend = (url, client, db, migrationFolder) => ({
1244
1267
  vector_distance_cos(v.embedding, ${convertFn}(?)) AS distance
1245
1268
  FROM memories m
1246
1269
  JOIN ${tableName} v ON v.memory_id = m.id
1247
- WHERE m.namespace = ? AND m.state = 'active'${topicFilter(topicKeys)}
1270
+ WHERE m.namespace = ? AND m.state = 'active'${topicFilter(topicKeys)}${contentTypeFilter(contentType)}
1248
1271
  ORDER BY distance
1249
1272
  LIMIT ?
1250
1273
  `,
@@ -1282,6 +1305,23 @@ var makeBackend = (url, client, db, migrationFolder) => ({
1282
1305
  }),
1283
1306
  catch: (cause) => new StoreError({ message: `DbBackend.sweep: ${cause}` })
1284
1307
  }),
1308
+ listSchema: () => Effect9.tryPromise({
1309
+ try: async () => {
1310
+ const tables = (await client.execute({
1311
+ sql: `SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name`
1312
+ })).rows;
1313
+ const out = [];
1314
+ for (const t of tables) {
1315
+ const cols = (await client.execute({ sql: `PRAGMA table_info(${t.name})` })).rows;
1316
+ out.push({
1317
+ table: t.name,
1318
+ columns: cols.map((c) => ({ name: c.name, type: c.type }))
1319
+ });
1320
+ }
1321
+ return out;
1322
+ },
1323
+ catch: (cause) => new StoreError({ message: `DbBackend.listSchema: ${cause}` })
1324
+ }),
1285
1325
  writeAuditRows: (rows) => rows.length === 0 ? Effect9.void : Effect9.tryPromise({
1286
1326
  try: () => db.insert(retrievalLog).values([...rows]),
1287
1327
  catch: (cause) => new StoreError({ message: `DbBackend.writeAuditRows: ${cause}` })
@@ -1330,10 +1370,12 @@ var makeBackend = (url, client, db, migrationFolder) => ({
1330
1370
  const maxTotal = opts?.maxTotal ?? 80;
1331
1371
  const includePending = opts?.includePending ?? true;
1332
1372
  const modelKey = opts?.modelKey;
1333
- const statusClause = includePending ? `AND e.status IN ('active', 'confirmed', 'pending')` : `AND e.status IN ('active', 'confirmed')`;
1373
+ const statusClause = includePending ? `AND e.expired_at IS NULL AND e.status IN ('active', 'confirmed', 'pending')` : `AND e.expired_at IS NULL AND e.status IN ('active', 'confirmed')`;
1334
1374
  const modelClause = modelKey === undefined ? "" : `AND (e.from_kind <> 'topic'
1335
1375
  OR EXISTS (SELECT 1 FROM topic_memberships tm WHERE tm.topic_key = e.from_id AND tm.memory_id = e.to_id AND tm.model_key = ?)
1336
1376
  OR EXISTS (SELECT 1 FROM topic_relations tr WHERE tr.parent_key = e.from_id AND tr.child_key = e.to_id AND tr.model_key = ?))`;
1377
+ const kinds = opts?.edgeKinds;
1378
+ const kindClause = kinds === undefined || kinds.length === 0 ? "" : `AND e.edge_kind IN (${kinds.map(() => "?").join(",")})`;
1337
1379
  const results = [];
1338
1380
  const seenEdges = new Set;
1339
1381
  const visited = new Set([`${kind}:${id}`]);
@@ -1346,6 +1388,8 @@ var makeBackend = (url, client, db, migrationFolder) => ({
1346
1388
  args.push(node.id, node.kind, node.id, node.kind);
1347
1389
  if (modelKey !== undefined)
1348
1390
  args.push(modelKey, modelKey);
1391
+ if (kinds !== undefined && kinds.length > 0)
1392
+ args.push(...kinds);
1349
1393
  const rows = yield* Effect9.tryPromise({
1350
1394
  try: () => client.execute({
1351
1395
  sql: `SELECT e.from_kind, e.from_id, e.to_kind, e.to_id, e.edge_kind, e.weight, e.status, e.source, e.valid_at, e.expired_at, ? AS depth
@@ -1353,6 +1397,7 @@ var makeBackend = (url, client, db, migrationFolder) => ({
1353
1397
  WHERE (${touches})
1354
1398
  ${statusClause}
1355
1399
  ${modelClause}
1400
+ ${kindClause}
1356
1401
  ORDER BY e.from_id, e.edge_kind, e.to_id, e.to_kind`,
1357
1402
  args
1358
1403
  }).then((rs) => rs.rows),
@@ -1524,13 +1569,20 @@ var makeBackend = (url, client, db, migrationFolder) => ({
1524
1569
  })
1525
1570
  });
1526
1571
  var layer3 = (url, options) => {
1527
- const effect = options?.releaseClient === true ? Effect9.acquireRelease(Effect9.sync(() => makeClientDb(url)), ({ client }) => Effect9.tryPromise({
1572
+ const effect = options?.releaseClient === true ? Effect9.acquireRelease(Effect9.tryPromise({
1573
+ try: () => ensureFileDir(url).then(() => makeClientDb(url)),
1574
+ catch: (cause) => new StoreError({ message: `DbBackend.client: ${cause}` })
1575
+ }), ({ client }) => Effect9.tryPromise({
1528
1576
  try: async () => client.close(),
1529
1577
  catch: () => new Error("DbBackend.close failed")
1530
1578
  }).pipe(Effect9.ignore)).pipe(Effect9.flatMap(({ client, db }) => {
1531
1579
  const backend = Service2.of(makeBackend(url, client, db, options?.migrationFolder));
1532
1580
  return backend.runMigrations().pipe(Effect9.as(backend));
1533
1581
  })) : Effect9.gen(function* () {
1582
+ yield* Effect9.tryPromise({
1583
+ try: () => ensureFileDir(url),
1584
+ catch: (cause) => new StoreError({ message: `DbBackend.client: ${cause}` })
1585
+ });
1534
1586
  const { client, db } = yield* Effect9.sync(() => makeClientDb(url));
1535
1587
  const backend = Service2.of(makeBackend(url, client, db, options?.migrationFolder));
1536
1588
  yield* backend.runMigrations();
@@ -1557,6 +1609,7 @@ var MemoryMetadata = Schema8.Struct({
1557
1609
  confidence: Schema8.Number,
1558
1610
  affordances: Schema8.Array(Schema8.String),
1559
1611
  language: Schema8.optional(Schema8.String),
1612
+ codeVersion: Schema8.optional(Schema8.String),
1560
1613
  pinned: Schema8.optional(Schema8.Boolean)
1561
1614
  });
1562
1615
  var parseMetadata = (blob) => {
@@ -2423,7 +2476,6 @@ import {
2423
2476
  } from "effect";
2424
2477
  import { PersistedQueue } from "effect/unstable/persistence";
2425
2478
  import { SqlClient } from "effect/unstable/sql";
2426
- import { randomUUID } from "node:crypto";
2427
2479
 
2428
2480
  class QueueKey extends Data.Class {
2429
2481
  }
@@ -2434,7 +2486,7 @@ var makeStore = Effect15.fnUntraced(function* (options) {
2434
2486
  const pollInterval = Duration.max(options?.pollInterval ? Duration.fromInputUnsafe(options.pollInterval) : Duration.millis(1000), Duration.millis(1));
2435
2487
  const lockRefreshInterval = Duration.max(options?.lockRefreshInterval ? Duration.fromInputUnsafe(options.lockRefreshInterval) : Duration.seconds(30), Duration.millis(1));
2436
2488
  const lockExpiration = Duration.max(options?.lockExpiration ? Duration.fromInputUnsafe(options.lockExpiration) : Duration.minutes(2), Duration.millis(1));
2437
- const workerId = randomUUID();
2489
+ const workerId = globalThis.crypto.randomUUID();
2438
2490
  const sqlNow = sql3.literal("CURRENT_TIMESTAMP");
2439
2491
  const expiresAt = sql3.literal(`datetime(CURRENT_TIMESTAMP, '-${Math.ceil(Duration.toSeconds(lockExpiration))} seconds')`);
2440
2492
  const workerIdSql = sql3.literal(`'${workerId}'`);
@@ -2574,7 +2626,7 @@ var storeSqliteLayer = Layer4.effect(PersistedQueue.PersistedQueueStore, makeSto
2574
2626
  // ../../node_modules/.bun/@effect+sql-libsql@4.0.0-beta.103+10c4c6971de0179f/node_modules/@effect/sql-libsql/dist/LibsqlClient.js
2575
2627
  var exports_LibsqlClient = {};
2576
2628
  __export(exports_LibsqlClient, {
2577
- make: () => make5,
2629
+ make: () => make4,
2578
2630
  layerConfig: () => layerConfig2,
2579
2631
  layer: () => layer5,
2580
2632
  TypeId: () => TypeId,
@@ -2603,7 +2655,7 @@ var classifyError = (cause, message, operation) => classifySqliteError(cause, {
2603
2655
  var TypeId = "~@effect/sql-libsql/LibsqlClient";
2604
2656
  var LibsqlClient = /* @__PURE__ */ Context6.Service("@effect/sql-libsql/LibsqlClient");
2605
2657
  var LibsqlTransaction = /* @__PURE__ */ Context6.Service("@effect/sql-libsql/LibsqlClient/LibsqlTransaction");
2606
- var make5 = (options) => Effect16.gen(function* () {
2658
+ var make4 = (options) => Effect16.gen(function* () {
2607
2659
  const compiler = Statement.makeCompilerSqlite(options.transformQueryNames);
2608
2660
  const transformRows = options.transformResultNames ? Statement.defaultTransforms(options.transformResultNames).array : undefined;
2609
2661
  const spanAttributes = [...options.spanAttributes ? Object.entries(options.spanAttributes) : [], [ATTR_DB_SYSTEM_NAME, "sqlite"]];
@@ -2718,11 +2770,9 @@ var make5 = (options) => Effect16.gen(function* () {
2718
2770
  sdk: connection.sdk
2719
2771
  });
2720
2772
  });
2721
- var layerConfig2 = (config) => Layer5.effectContext(Config3.unwrap(config).pipe(Effect16.flatMap(make5), Effect16.map((client) => Context6.make(LibsqlClient, client).pipe(Context6.add(Client.SqlClient, client))))).pipe(Layer5.provide(Reactivity.layer));
2722
- var layer5 = (config) => Layer5.effectContext(Effect16.map(make5(config), (client) => Context6.make(LibsqlClient, client).pipe(Context6.add(Client.SqlClient, client)))).pipe(Layer5.provide(Reactivity.layer));
2773
+ var layerConfig2 = (config) => Layer5.effectContext(Config3.unwrap(config).pipe(Effect16.flatMap(make4), Effect16.map((client) => Context6.make(LibsqlClient, client).pipe(Context6.add(Client.SqlClient, client))))).pipe(Layer5.provide(Reactivity.layer));
2774
+ var layer5 = (config) => Layer5.effectContext(Effect16.map(make4(config), (client) => Context6.make(LibsqlClient, client).pipe(Context6.add(Client.SqlClient, client)))).pipe(Layer5.provide(Reactivity.layer));
2723
2775
  // ../workflow/src/engine.ts
2724
- import { mkdirSync as mkdirSync2 } from "node:fs";
2725
- import { dirname as dirname3 } from "node:path";
2726
2776
  var Persistence = PersistenceNamespace2.Persistence;
2727
2777
  var PersistedQueueFactory = PersistedQueue2.PersistedQueueFactory;
2728
2778
  var layerDurable = Layer6.effect(WorkflowEngine2.WorkflowEngine)(Effect17.gen(function* () {
@@ -2923,7 +2973,9 @@ var workflowTagFor = (workflows, store, executionId) => Effect17.gen(function* (
2923
2973
  });
2924
2974
  var ensureDbDir = (url) => {
2925
2975
  if (url.startsWith("file:")) {
2926
- mkdirSync2(dirname3(url.slice("file:".length)), { recursive: true });
2976
+ import("node:fs").then((fs) => import("node:path").then(({ dirname }) => {
2977
+ fs.mkdirSync(dirname(url.slice("file:".length)), { recursive: true });
2978
+ }));
2927
2979
  }
2928
2980
  };
2929
2981
  var libsqlStack = (options) => {
@@ -4314,6 +4366,11 @@ var sameLogicalKeyFamily = (a, b) => {
4314
4366
  const fb = familyOf(b);
4315
4367
  return fa !== "" && fa === fb;
4316
4368
  };
4369
+ var sameFileChunkFamily = (a, b) => {
4370
+ if (a === undefined || b === undefined || a === "" || b === "")
4371
+ return false;
4372
+ return a.startsWith(`${b}#`) || b.startsWith(`${a}#`);
4373
+ };
4317
4374
  var topicsOf = (blob) => {
4318
4375
  if (blob === undefined || blob === null)
4319
4376
  return new Set;
@@ -4358,7 +4415,7 @@ var leafLocalCandidates = (newId, newVector, newCreatedAt, newLogicalKey, newMet
4358
4415
  });
4359
4416
  continue;
4360
4417
  }
4361
- if (cosine > bands.nearDuplicate) {
4418
+ if (cosine > bands.nearDuplicate && !sameFileChunkFamily(newLogicalKey, member.logicalKey)) {
4362
4419
  const dir = direction(member, newId, newCreatedAt);
4363
4420
  out.push({
4364
4421
  ...dir,
@@ -4370,7 +4427,7 @@ var leafLocalCandidates = (newId, newVector, newCreatedAt, newLogicalKey, newMet
4370
4427
  });
4371
4428
  continue;
4372
4429
  }
4373
- if (cosine > bands.conflictLow && (sameLogicalKeyFamily(newLogicalKey, member.logicalKey) || sharedMetadataTopics(newMetadata, member.metadata))) {
4430
+ if (cosine > bands.conflictLow && !sameFileChunkFamily(newLogicalKey, member.logicalKey) && (sameLogicalKeyFamily(newLogicalKey, member.logicalKey) || sharedMetadataTopics(newMetadata, member.metadata))) {
4374
4431
  const dir = direction(member, newId, newCreatedAt);
4375
4432
  out.push({
4376
4433
  ...dir,
@@ -4505,7 +4562,12 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
4505
4562
  supersedeDemotion: GRAPH_SUPERSEDE_DEMOTION
4506
4563
  });
4507
4564
  });
4508
- const EDGE_KINDS = ["memory_supports", "memory_supersedes", "memory_contradicts"];
4565
+ const EDGE_KINDS = [
4566
+ "memory_supports",
4567
+ "memory_supersedes",
4568
+ "memory_contradicts",
4569
+ "code_calls"
4570
+ ];
4509
4571
  const EDGE_SOURCES = ["rule", "llm", "agent"];
4510
4572
  const write = Effect26.fn("Store.write")(function* (input) {
4511
4573
  if (input.content.includes("\x00")) {
@@ -4524,7 +4586,7 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
4524
4586
  content: input.content,
4525
4587
  contentType: input.contentType ?? "text/plain",
4526
4588
  createdAt: now,
4527
- ttlSecs: input.ttlSecs ?? defaultTtlSecs,
4589
+ ttlSecs: input.ttlSecs === null ? null : input.ttlSecs ?? defaultTtlSecs,
4528
4590
  namespace: input.namespace ?? "default",
4529
4591
  metadata: metadataBlob
4530
4592
  });
@@ -4542,7 +4604,7 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
4542
4604
  contentType: input.contentType ?? "text/plain",
4543
4605
  state: "active",
4544
4606
  createdAt: now,
4545
- ttlSecs: input.ttlSecs ?? defaultTtlSecs,
4607
+ ttlSecs: input.ttlSecs === null ? null : input.ttlSecs ?? defaultTtlSecs,
4546
4608
  namespace: input.namespace ?? "default",
4547
4609
  metadata: metadataBlob ?? undefined,
4548
4610
  embedding: undefined,
@@ -4586,7 +4648,7 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
4586
4648
  });
4587
4649
  const searchFts5 = Effect26.fn("Store.searchFts5")(function* (query, namespace, limit, opts) {
4588
4650
  const start = Date.now();
4589
- const rows = yield* backend.ftsSearch(query, namespace, limit, opts?.topicKeys, modelConfig?.id);
4651
+ const rows = yield* backend.ftsSearch(query, namespace, limit, opts?.topicKeys, modelConfig?.id, opts?.contentType);
4590
4652
  if (opts?.audit !== false) {
4591
4653
  yield* logRetrieval(query, namespace, "fts", rows.length, start);
4592
4654
  }
@@ -4629,7 +4691,7 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
4629
4691
  });
4630
4692
  allowedMemoryIds = new Set(conceptRows.map((r) => r.memoryId));
4631
4693
  }
4632
- const rows = yield* backend.vectorSearch(query, namespace, limit, modelConfig, opts?.topicKeys);
4694
+ const rows = yield* backend.vectorSearch(query, namespace, limit, modelConfig, opts?.topicKeys, opts?.contentType);
4633
4695
  const allowed = allowedMemoryIds;
4634
4696
  const scopedRows = allowed !== undefined ? rows.filter((row) => allowed.has(row.id)) : rows;
4635
4697
  if (opts?.audit !== false) {
@@ -4964,7 +5026,7 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
4964
5026
  });
4965
5027
  return rows.map((row) => row.id);
4966
5028
  });
4967
- const flipToSupersededById = async (tx, memoryId, now) => {
5029
+ const flipToSupersededById = async (tx, memoryId, now, reason = "superseded") => {
4968
5030
  const rs = await tx.update(memories2).set({ state: "superseded" }).where(and8(eq8(memories2.id, memoryId), eq8(memories2.state, "active")));
4969
5031
  if (rs.rowsAffected === 0)
4970
5032
  return null;
@@ -4973,11 +5035,106 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
4973
5035
  eventType: "superseded",
4974
5036
  fromState: "active",
4975
5037
  toState: "superseded",
4976
- reason: "superseded",
5038
+ reason,
4977
5039
  createdAt: now
4978
5040
  });
4979
5041
  return memoryId;
4980
5042
  };
5043
+ const reconcile = Effect26.fn("Store.reconcile")(function* (namespace, keyPrefix, keyRoot, walkedRoots, keptKeys) {
5044
+ const inDomain = (key) => {
5045
+ const rel = key.slice(keyPrefix.length).split("#")[0];
5046
+ const filePath = keyRoot === "/" ? `/${rel}` : `${keyRoot}/${rel}`;
5047
+ return walkedRoots.some((root) => filePath === root || filePath.startsWith(`${root}/`));
5048
+ };
5049
+ const owned = (key) => keptKeys.includes(key);
5050
+ const rows = yield* Effect26.tryPromise({
5051
+ try: () => db.select({
5052
+ id: memories2.id,
5053
+ logicalKey: memories2.logicalKey,
5054
+ embedding: memories2.embedding
5055
+ }).from(memories2).where(and8(eq8(memories2.namespace, namespace), eq8(memories2.state, "active"), like(memories2.logicalKey, `${keyPrefix}%`))),
5056
+ catch: (cause) => new StoreError({ message: `Store.reconcile: ${cause}` })
5057
+ });
5058
+ const vanished = rows.filter((r) => inDomain(r.logicalKey) && !owned(r.logicalKey));
5059
+ if (vanished.length > 0) {
5060
+ const now = Date.now();
5061
+ yield* Effect26.tryPromise({
5062
+ try: () => db.transaction(async (tx) => {
5063
+ for (const row of vanished) {
5064
+ await flipToSupersededById(tx, row.id, now, "source_removed");
5065
+ }
5066
+ }),
5067
+ catch: (cause) => new StoreError({ message: `Store.reconcile: ${cause}` })
5068
+ });
5069
+ }
5070
+ return vanished;
5071
+ });
5072
+ const writeSuperseding = Effect26.fn("Store.writeSuperseding")(function* (input) {
5073
+ if (input.content.includes("\x00")) {
5074
+ return yield* new StoreError({
5075
+ message: "Store.writeSuperseding: content contains a NUL byte (libSQL would silently truncate it)"
5076
+ });
5077
+ }
5078
+ const id = crypto.randomUUID();
5079
+ const now = Date.now();
5080
+ const metadataBlob = input.typedMetadata !== undefined ? mergeMetadataBlob(input.metadata, input.typedMetadata) : input.metadata ?? null;
5081
+ const namespace = input.namespace ?? "default";
5082
+ let supersededId;
5083
+ yield* Effect26.tryPromise({
5084
+ try: () => db.transaction(async (tx) => {
5085
+ const existing = await tx.select({ id: memories2.id }).from(memories2).where(and8(eq8(memories2.logicalKey, input.logicalKey), eq8(memories2.namespace, namespace), eq8(memories2.state, "active"))).limit(1);
5086
+ const old = existing[0];
5087
+ if (old !== undefined) {
5088
+ await flipToSupersededById(tx, old.id, now);
5089
+ supersededId = old.id;
5090
+ }
5091
+ await tx.insert(memories2).values({
5092
+ id,
5093
+ logicalKey: input.logicalKey,
5094
+ content: input.content,
5095
+ contentType: input.contentType ?? "text/plain",
5096
+ createdAt: now,
5097
+ ttlSecs: input.ttlSecs === null ? null : input.ttlSecs ?? defaultTtlSecs,
5098
+ namespace,
5099
+ metadata: metadataBlob
5100
+ });
5101
+ await tx.insert(memoryEvents2).values({ memoryId: id, eventType: "created", createdAt: now });
5102
+ if (old !== undefined) {
5103
+ await tx.insert(artifactEdges).values({
5104
+ id: crypto.randomUUID(),
5105
+ fromMemoryId: old.id,
5106
+ toMemoryId: id,
5107
+ edgeKind: "memory_supersedes",
5108
+ weight: 1,
5109
+ status: "confirmed",
5110
+ source: "rule",
5111
+ confidence: 1,
5112
+ validAt: now,
5113
+ expiredAt: null,
5114
+ createdAt: now,
5115
+ updatedAt: now
5116
+ });
5117
+ }
5118
+ }),
5119
+ catch: (cause) => backend.isUniqueViolation(cause) ? new DuplicateActiveKey({ logicalKey: input.logicalKey, namespace }) : new StoreError({ message: `Store.writeSuperseding: ${cause}` })
5120
+ });
5121
+ return {
5122
+ memory: {
5123
+ id,
5124
+ logicalKey: input.logicalKey,
5125
+ content: input.content,
5126
+ contentType: input.contentType ?? "text/plain",
5127
+ state: "active",
5128
+ createdAt: now,
5129
+ ttlSecs: input.ttlSecs === null ? null : input.ttlSecs ?? defaultTtlSecs,
5130
+ namespace,
5131
+ metadata: metadataBlob ?? undefined,
5132
+ embedding: undefined,
5133
+ embeddingModel: undefined
5134
+ },
5135
+ ...supersededId !== undefined ? { supersededId } : {}
5136
+ };
5137
+ });
4981
5138
  const addEdge = Effect26.fn("Store.addEdge")(function* (input) {
4982
5139
  if (input.fromMemoryId === input.toMemoryId) {
4983
5140
  return yield* new StoreError({ message: "Store.addEdge: self-loops are not allowed" });
@@ -5030,7 +5187,12 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
5030
5187
  updatedAt: now
5031
5188
  };
5032
5189
  yield* Effect26.tryPromise({
5033
- try: () => db.insert(artifactEdges).values(row),
5190
+ try: () => db.transaction(async (tx) => {
5191
+ await tx.insert(artifactEdges).values(row);
5192
+ if (input.confirmed && input.edgeKind === "memory_supersedes") {
5193
+ await flipToSupersededById(tx, input.fromMemoryId, now);
5194
+ }
5195
+ }),
5034
5196
  catch: (cause) => {
5035
5197
  if (backend.isUniqueViolation(cause)) {
5036
5198
  return new EdgeExists({
@@ -5169,6 +5331,42 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
5169
5331
  }),
5170
5332
  catch: (cause) => new StoreError({ message: `Store.writeCandidateEdge: ${cause}` })
5171
5333
  });
5334
+ const reconcileRenameCandidates = Effect26.fn("Store.reconcileRenameCandidates")(function* (vanished, newUnitIds) {
5335
+ const withVectors = vanished.filter((v) => v.embedding !== null);
5336
+ if (withVectors.length === 0 || newUnitIds.length === 0)
5337
+ return 0;
5338
+ const newRows = yield* Effect26.tryPromise({
5339
+ try: () => db.select({ id: memories2.id, embedding: memories2.embedding }).from(memories2).where(and8(inArray4(memories2.id, newUnitIds))),
5340
+ catch: (cause) => new StoreError({ message: `Store.reconcileRenameCandidates: ${cause}` })
5341
+ });
5342
+ const newWithVectors = newRows.filter((r) => r.embedding !== null);
5343
+ if (newWithVectors.length === 0)
5344
+ return 0;
5345
+ const now = Date.now();
5346
+ let inserted = 0;
5347
+ for (const v of withVectors) {
5348
+ const vVec = Float32Array.from(v.embedding);
5349
+ for (const n of newWithVectors) {
5350
+ if (v.id === n.id)
5351
+ continue;
5352
+ const cosine = cosineSimilarity(vVec, Float32Array.from(n.embedding));
5353
+ if (cosine >= GRAPH_CONFLICT_BANDS.nearDuplicate) {
5354
+ const ok = yield* writeCandidateEdge({
5355
+ fromMemoryId: v.id,
5356
+ toMemoryId: n.id,
5357
+ edgeKind: "memory_supersedes",
5358
+ source: "rule",
5359
+ status: "pending",
5360
+ confidence: cosine,
5361
+ reason: "rename"
5362
+ }, now);
5363
+ if (ok)
5364
+ inserted += 1;
5365
+ }
5366
+ }
5367
+ }
5368
+ return inserted;
5369
+ });
5172
5370
  const scanLeafCandidates = (memoryId, vector, topicKey, model) => Effect26.gen(function* () {
5173
5371
  const memberRows = yield* Effect26.tryPromise({
5174
5372
  try: () => db.select({ memoryId: topicMemberships.memoryId }).from(topicMemberships).where(and8(eq8(topicMemberships.topicKey, topicKey), eq8(topicMemberships.modelKey, model.id))).orderBy(asc5(topicMemberships.memoryId)),
@@ -5369,12 +5567,16 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
5369
5567
  });
5370
5568
  return {
5371
5569
  write,
5570
+ writeSuperseding,
5571
+ reconcile,
5572
+ reconcileRenameCandidates,
5372
5573
  get,
5373
5574
  getByKey,
5374
5575
  recency,
5375
5576
  searchFts5,
5376
5577
  searchVector,
5377
5578
  logAudit,
5579
+ listSchema: () => backend.listSchema(),
5378
5580
  supersede,
5379
5581
  forget,
5380
5582
  sweep,
@@ -5508,6 +5710,7 @@ __export(exports_graph, {
5508
5710
  farthestPointSeeds: () => farthestPointSeeds,
5509
5711
  dreamStateDefaults: () => dreamStateDefaults,
5510
5712
  dreamState: () => dreamState,
5713
+ cosineSimilarity: () => cosineSimilarity,
5511
5714
  conceptConfidence: () => conceptConfidence,
5512
5715
  conceptClusterHandler: () => conceptClusterHandler,
5513
5716
  clusterMembers: () => clusterMembers,
@@ -6261,7 +6464,7 @@ var MemoryUnit = Schema20.Struct({
6261
6464
  contentType: Schema20.String,
6262
6465
  state: MemoryState,
6263
6466
  createdAt: Schema20.Number,
6264
- ttlSecs: Schema20.Number,
6467
+ ttlSecs: Schema20.NullOr(Schema20.Number),
6265
6468
  namespace: Schema20.String,
6266
6469
  metadata: Schema20.optional(Schema20.String),
6267
6470
  embedding: Schema20.optional(Schema20.Array(Schema20.Number)),
@@ -6272,7 +6475,7 @@ var WriteMemoryInput = Schema20.Struct({
6272
6475
  content: Schema20.String,
6273
6476
  contentType: Schema20.optional(Schema20.String),
6274
6477
  namespace: Schema20.optional(Schema20.String),
6275
- ttlSecs: Schema20.optional(Schema20.Number),
6478
+ ttlSecs: Schema20.optional(Schema20.NullOr(Schema20.Number)),
6276
6479
  metadata: Schema20.optional(Schema20.String),
6277
6480
  typedMetadata: Schema20.optional(MemoryMetadata)
6278
6481
  });
@@ -6325,7 +6528,103 @@ class Service9 extends Context9.Service()("@dimple/embedder/EmbeddingModelRegist
6325
6528
  }
6326
6529
  // ../embedder/src/transformers.ts
6327
6530
  import { Config as Config5, Effect as Effect32, Layer as Layer11 } from "effect";
6328
- import { pipeline, env } from "@huggingface/transformers";
6531
+ import { existsSync as existsSync2, readFileSync } from "node:fs";
6532
+ import { join as join2 } from "node:path";
6533
+ import { pathToFileURL } from "node:url";
6534
+
6535
+ // ../embedder/src/auto-install.ts
6536
+ import { execFile } from "node:child_process";
6537
+ import { existsSync } from "node:fs";
6538
+ import { join } from "node:path";
6539
+ var detectPackageManager = (cwd) => {
6540
+ let dir = cwd;
6541
+ while (true) {
6542
+ if (existsSync(join(dir, "bun.lock")) || existsSync(join(dir, "bun.lockb"))) {
6543
+ return { pm: "bun", root: dir };
6544
+ }
6545
+ if (existsSync(join(dir, "pnpm-lock.yaml")))
6546
+ return { pm: "pnpm", root: dir };
6547
+ if (existsSync(join(dir, "yarn.lock")))
6548
+ return { pm: "yarn", root: dir };
6549
+ if (existsSync(join(dir, "package-lock.json")))
6550
+ return { pm: "npm", root: dir };
6551
+ const parent = join(dir, "..");
6552
+ if (parent === dir)
6553
+ return { pm: "bun", root: cwd };
6554
+ dir = parent;
6555
+ }
6556
+ };
6557
+ var installCommand = (pm) => {
6558
+ switch (pm) {
6559
+ case "bun":
6560
+ return ["bun", "add", "@huggingface/transformers"];
6561
+ case "npm":
6562
+ return [
6563
+ "npm",
6564
+ "install",
6565
+ "@huggingface/transformers",
6566
+ "--ignore-scripts",
6567
+ "--no-audit",
6568
+ "--no-fund"
6569
+ ];
6570
+ case "pnpm":
6571
+ return ["pnpm", "add", "@huggingface/transformers", "--ignore-scripts"];
6572
+ case "yarn":
6573
+ return ["yarn", "add", "@huggingface/transformers", "--ignore-scripts"];
6574
+ }
6575
+ };
6576
+ var installTransformers = (cwd) => new Promise((resolve, reject) => {
6577
+ const target = detectPackageManager(cwd);
6578
+ const [cmd, ...args] = installCommand(target.pm);
6579
+ execFile(cmd, args, { cwd: target.root, encoding: "utf8" }, (error) => {
6580
+ if (error) {
6581
+ reject(new Error(`auto-install of @huggingface/transformers failed (${target.pm} add …): ${error.message} — ` + "run it manually or set `autoInstall: false` and configure a hosted embedding provider"));
6582
+ return;
6583
+ }
6584
+ resolve();
6585
+ });
6586
+ });
6587
+
6588
+ // ../embedder/src/transformers.ts
6589
+ var transformersEntryPath = (root) => {
6590
+ let dir = root;
6591
+ while (true) {
6592
+ const pkgJson = join2(dir, "node_modules", "@huggingface", "transformers", "package.json");
6593
+ if (existsSync2(pkgJson)) {
6594
+ let entry;
6595
+ try {
6596
+ const pkg = JSON.parse(readFileSync(pkgJson, "utf8"));
6597
+ const ex = pkg.exports;
6598
+ const dot4 = typeof ex === "object" ? ex["."] : ex;
6599
+ entry = typeof dot4 === "string" ? dot4 : dot4?.import ?? dot4?.default ?? pkg.module ?? pkg.main ?? "index.js";
6600
+ } catch {
6601
+ entry = "index.js";
6602
+ }
6603
+ return join2(dir, "node_modules", "@huggingface", "transformers", entry);
6604
+ }
6605
+ const parent = join2(dir, "..");
6606
+ if (parent === dir)
6607
+ return null;
6608
+ dir = parent;
6609
+ }
6610
+ };
6611
+ var importFromDisk = () => new Promise((resolve, reject) => {
6612
+ try {
6613
+ const root = detectPackageManager(process.cwd()).root;
6614
+ const entry = transformersEntryPath(root);
6615
+ if (entry === null) {
6616
+ reject(new Error("@huggingface/transformers not found on disk after install"));
6617
+ return;
6618
+ }
6619
+ import(pathToFileURL(entry).href).then((mod) => {
6620
+ resolve(mod);
6621
+ }).catch((cause) => {
6622
+ reject(cause);
6623
+ });
6624
+ } catch (cause) {
6625
+ reject(cause);
6626
+ }
6627
+ });
6329
6628
  var defaults2 = {
6330
6629
  modelId: "Xenova/all-MiniLM-L6-v2",
6331
6630
  device: "cpu",
@@ -6333,14 +6632,31 @@ var defaults2 = {
6333
6632
  pooling: "mean",
6334
6633
  normalize: true
6335
6634
  };
6336
- var make6 = (options) => {
6635
+ var make5 = (options) => {
6337
6636
  let pipe = null;
6338
6637
  const getPipeline = Effect32.fnUntraced(function* () {
6339
6638
  if (pipe)
6340
6639
  return pipe;
6640
+ const loadModule = () => Effect32.tryPromise(() => import("@huggingface/transformers"));
6641
+ let transformers = yield* loadModule().pipe(Effect32.catch(() => {
6642
+ if (options.autoInstall === false) {
6643
+ return Effect32.fail(new EmbeddingError({
6644
+ message: "@huggingface/transformers is not installed — run `bun add @huggingface/transformers` " + "to use local embeddings (or configure a hosted embedding provider)"
6645
+ }));
6646
+ }
6647
+ return Effect32.gen(function* () {
6648
+ yield* Effect32.logInfo("auto-installing @huggingface/transformers (first local-embed use — scripts skipped)").pipe(Effect32.annotateLogs({ scope: "@dimple/embedder", event: "embedder.auto-install" }));
6649
+ yield* Effect32.tryPromise(() => installTransformers(process.cwd())).pipe(Effect32.timeout("5 minutes"), Effect32.catch((cause) => Effect32.fail(new EmbeddingError({
6650
+ message: cause._tag === "TimeoutError" ? "auto-install of @huggingface/transformers timed out (5 minutes) — run " + "`bun add @huggingface/transformers` manually" : cause.cause instanceof Error ? cause.cause.message : cause instanceof Error ? cause.message : `auto-install failed: ${String(cause)}`
6651
+ }))));
6652
+ return yield* Effect32.tryPromise(importFromDisk).pipe(Effect32.mapError(() => new EmbeddingError({
6653
+ message: "@huggingface/transformers still unavailable after auto-install — run `bun add " + "@huggingface/transformers` manually (or set `autoInstall: false` and configure a hosted embedding provider)"
6654
+ })));
6655
+ });
6656
+ }));
6341
6657
  if (options.cacheDir)
6342
- env.cacheDir = options.cacheDir;
6343
- pipe = yield* Effect32.promise(() => pipeline("feature-extraction", options.modelId ?? defaults2.modelId, {
6658
+ transformers.env.cacheDir = options.cacheDir;
6659
+ pipe = yield* Effect32.tryPromise(() => transformers.pipeline("feature-extraction", options.modelId ?? defaults2.modelId, {
6344
6660
  device: options.device ?? defaults2.device,
6345
6661
  dtype: options.dtype ?? defaults2.dtype
6346
6662
  })).pipe(Effect32.mapError((cause) => new EmbeddingError({ message: `load model: ${cause}` })));
@@ -6386,7 +6702,7 @@ var make6 = (options) => {
6386
6702
  return { embed, embedSingle };
6387
6703
  };
6388
6704
  var makeTransformersModel = (options = {}) => {
6389
- const impl = make6(options);
6705
+ const impl = make5(options);
6390
6706
  return {
6391
6707
  modelId: options.modelId ?? "local-minilm",
6392
6708
  provider: "transformers",
@@ -6517,7 +6833,7 @@ var layer11 = (options) => Layer13.effect(Service10, Effect35.gen(function* () {
6517
6833
  const namespace = options2?.namespace ?? "default";
6518
6834
  const limit = options2?.limit ?? defaultLimit;
6519
6835
  const k = options2?.k ?? defaultK;
6520
- const profile = typeof options2?.profile === "string" ? {} : options2?.profile ?? {};
6836
+ const profile = typeof options2?.profile === "string" ? options2.profile === "keyword" ? { modeWeights: { vector: 0, fts: 1 } } : options2.profile === "semantic" ? { modeWeights: { vector: 1, fts: 0 } } : {} : options2?.profile ?? {};
6521
6837
  const topicKeys = options2?.topics?.topicKeys;
6522
6838
  const fetchMultiplier = 2;
6523
6839
  const start = Date.now();
@@ -6529,21 +6845,24 @@ var layer11 = (options) => Layer13.effect(Service10, Effect35.gen(function* () {
6529
6845
  vectorOpts.conceptIds = profile.conceptFilter;
6530
6846
  const vectorResults = yield* store.searchVector([...embedding], namespace, limit * fetchMultiplier, {
6531
6847
  ...vectorOpts,
6848
+ ...options2?.contentType !== undefined ? { contentType: options2.contentType } : {},
6532
6849
  audit: false
6533
6850
  }).pipe(wrapError("searchVector"));
6534
6851
  const fts5Results = yield* store.searchFts5(query2, namespace, limit * fetchMultiplier, {
6535
6852
  ...topicKeys !== undefined ? { topicKeys } : {},
6853
+ ...options2?.contentType !== undefined ? { contentType: options2.contentType } : {},
6536
6854
  audit: false
6537
6855
  }).pipe(wrapError("searchFts5"));
6538
6856
  const vWeight = profile.modeWeights?.vector ?? 1;
6539
6857
  const fWeight = profile.modeWeights?.fts ?? 1;
6858
+ const mode = vWeight === 0 && fWeight > 0 ? "keyword" : fWeight === 0 && vWeight > 0 ? "semantic" : "hybrid";
6540
6859
  const denseRanks = vectorResults.length > 0 ? vectorResults.map((s) => [s.memory.id, s.score * vWeight]) : null;
6541
6860
  const fts5Ranks = fts5Results.length > 0 ? fts5Results.map((s) => [s.memory.id, s.score * fWeight]) : null;
6542
6861
  if (!denseRanks && !fts5Ranks) {
6543
6862
  yield* store.logAudit({
6544
6863
  query: query2,
6545
6864
  namespace,
6546
- mode: "hybrid",
6865
+ mode,
6547
6866
  resultCount: 0,
6548
6867
  startTime: start,
6549
6868
  queryProfile: JSON.stringify({
@@ -6551,6 +6870,7 @@ var layer11 = (options) => Layer13.effect(Service10, Effect35.gen(function* () {
6551
6870
  recencyBoost: profile.recencyBoost ?? 0,
6552
6871
  conceptFilter: profile.conceptFilter,
6553
6872
  topicKeys,
6873
+ contentType: options2?.contentType,
6554
6874
  legs: { vector: { count: 0, topScores: [] }, fts: { count: 0, topScores: [] } }
6555
6875
  }),
6556
6876
  sources: JSON.stringify({ vector: [], fts: [] })
@@ -6593,7 +6913,7 @@ var layer11 = (options) => Layer13.effect(Service10, Effect35.gen(function* () {
6593
6913
  yield* store.logAudit({
6594
6914
  query: query2,
6595
6915
  namespace,
6596
- mode: "hybrid",
6916
+ mode,
6597
6917
  resultCount: results.length,
6598
6918
  startTime: start,
6599
6919
  queryProfile: JSON.stringify({
@@ -6601,6 +6921,7 @@ var layer11 = (options) => Layer13.effect(Service10, Effect35.gen(function* () {
6601
6921
  recencyBoost: profile.recencyBoost ?? 0,
6602
6922
  conceptFilter: profile.conceptFilter,
6603
6923
  topicKeys,
6924
+ contentType: options2?.contentType,
6604
6925
  legs: {
6605
6926
  vector: {
6606
6927
  count: vectorResults.length,
@@ -6617,9 +6938,10 @@ var layer11 = (options) => Layer13.effect(Service10, Effect35.gen(function* () {
6617
6938
  fts: fts5Results.map((s) => s.memory.id)
6618
6939
  })
6619
6940
  }).pipe(wrapError("logAudit"));
6620
- yield* Effect35.logDebug("hybrid.search").pipe(Effect35.annotateLogs({
6941
+ yield* Effect35.logInfo("hybrid.search").pipe(Effect35.annotateLogs({
6621
6942
  scope: "@dimple/retriever",
6622
6943
  event: "hybrid.search",
6944
+ mode,
6623
6945
  namespace,
6624
6946
  query: query2,
6625
6947
  resultCount: results.length
@@ -6861,7 +7183,8 @@ var embedderFromConfig = (model) => model.provider === "transformers" ? Effect39
6861
7183
  ...model.dtype !== undefined ? { dtype: model.dtype } : {},
6862
7184
  ...model.pooling !== undefined ? { pooling: model.pooling } : {},
6863
7185
  ...model.normalize !== undefined ? { normalize: model.normalize } : {},
6864
- ...model.cacheDir !== undefined ? { cacheDir: model.cacheDir } : {}
7186
+ ...model.cacheDir !== undefined ? { cacheDir: model.cacheDir } : {},
7187
+ ...model.autoInstall !== undefined ? { autoInstall: model.autoInstall } : {}
6865
7188
  })) : model.provider === "openai" ? model.apiKey === undefined ? Effect39.fail(new EmbeddingError({ message: `OpenAI model ${model.id} requires an apiKey` })) : Effect39.succeed(makeHostedEmbeddingModel({
6866
7189
  modelId: model.id,
6867
7190
  model: model.model,
@@ -7042,7 +7365,8 @@ var ExtractedChunk = Schema25.Struct({
7042
7365
  startLine: Schema25.Number,
7043
7366
  endLine: Schema25.Number,
7044
7367
  language: Schema25.String,
7045
- docstring: Schema25.optional(Schema25.String)
7368
+ docstring: Schema25.optional(Schema25.String),
7369
+ calls: Schema25.optional(Schema25.Array(Schema25.Struct({ name: Schema25.String, line: Schema25.Number })))
7046
7370
  });
7047
7371
  var ImportInfo = Schema25.Struct({
7048
7372
  source: Schema25.String,
@@ -7126,6 +7450,7 @@ function emit(source, semantic, filePath, lang, chunks) {
7126
7450
  }
7127
7451
  function makeChunk(source, _semantic, filePath, lang, kind, name, docstring, parentClass) {
7128
7452
  const id = parentClass ? `${filePath}:${parentClass}.${name}:${source.startPosition.row + 1}` : `${filePath}:${name}:${source.startPosition.row + 1}`;
7453
+ const calls = callsOf(source);
7129
7454
  return {
7130
7455
  id,
7131
7456
  filePath,
@@ -7135,9 +7460,49 @@ function makeChunk(source, _semantic, filePath, lang, kind, name, docstring, par
7135
7460
  startLine: source.startPosition.row + 1,
7136
7461
  endLine: source.endPosition.row + 1,
7137
7462
  language: lang,
7138
- docstring
7463
+ docstring,
7464
+ ...calls.length > 0 ? { calls } : {}
7139
7465
  };
7140
7466
  }
7467
+ function callsOf(node) {
7468
+ const out = [];
7469
+ const calleeName = (n) => {
7470
+ let cur = n;
7471
+ for (let guard = 0;guard < 4; guard++) {
7472
+ if (cur.type === "identifier")
7473
+ return cur.text;
7474
+ if (cur.type === "member_expression") {
7475
+ const prop = cur.childForFieldName("property");
7476
+ if (prop === null)
7477
+ return;
7478
+ cur = prop;
7479
+ continue;
7480
+ }
7481
+ if (cur.type === "non_null_expression" || cur.type === "parenthesized_expression") {
7482
+ const inner = cur.namedChildren[0];
7483
+ if (inner === undefined)
7484
+ return;
7485
+ cur = inner;
7486
+ continue;
7487
+ }
7488
+ return;
7489
+ }
7490
+ return;
7491
+ };
7492
+ const visit = (n) => {
7493
+ if (n.type === "call_expression") {
7494
+ const callee = n.childForFieldName("function");
7495
+ const name = callee === null ? undefined : calleeName(callee);
7496
+ if (name !== undefined && name.length > 0) {
7497
+ out.push({ name, line: n.startPosition.row + 1 });
7498
+ }
7499
+ }
7500
+ for (const child of n.namedChildren)
7501
+ visit(child);
7502
+ };
7503
+ visit(node);
7504
+ return out;
7505
+ }
7141
7506
  function unwrap2(node) {
7142
7507
  for (const child of node.namedChildren) {
7143
7508
  if (child.type !== "decorator" && child.type !== "comment")
@@ -7613,11 +7978,11 @@ class Service15 extends Context15.Service()("@dimple/wasm/WasmDownloader") {
7613
7978
  }
7614
7979
  // ../wasm/src/wasm-cache.ts
7615
7980
  import { Context as Context16, Effect as Effect43, Layer as Layer20 } from "effect";
7616
- import { existsSync as existsSync2, mkdirSync as mkdirSync3, renameSync, writeFileSync } from "node:fs";
7617
- import { join as join2 } from "node:path";
7981
+ import { existsSync as existsSync3, mkdirSync, renameSync, writeFileSync } from "node:fs";
7982
+ import { join as join3 } from "node:path";
7618
7983
 
7619
7984
  // ../../node_modules/.bun/env-paths@4.0.0/node_modules/env-paths/index.js
7620
- import path2 from "node:path";
7985
+ import path from "node:path";
7621
7986
  import os from "node:os";
7622
7987
  import process2 from "node:process";
7623
7988
 
@@ -7657,36 +8022,36 @@ function assertSafeFilename(filename) {
7657
8022
  // ../../node_modules/.bun/env-paths@4.0.0/node_modules/env-paths/index.js
7658
8023
  var homedir = os.homedir();
7659
8024
  var tmpdir = os.tmpdir();
7660
- var { env: env2 } = process2;
8025
+ var { env } = process2;
7661
8026
  var macos = (name) => {
7662
- const library = path2.join(homedir, "Library");
8027
+ const library = path.join(homedir, "Library");
7663
8028
  return {
7664
- data: path2.join(library, "Application Support", name),
7665
- config: path2.join(library, "Preferences", name),
7666
- cache: path2.join(library, "Caches", name),
7667
- log: path2.join(library, "Logs", name),
7668
- temp: path2.join(tmpdir, name)
8029
+ data: path.join(library, "Application Support", name),
8030
+ config: path.join(library, "Preferences", name),
8031
+ cache: path.join(library, "Caches", name),
8032
+ log: path.join(library, "Logs", name),
8033
+ temp: path.join(tmpdir, name)
7669
8034
  };
7670
8035
  };
7671
8036
  var windows = (name) => {
7672
- const appData = env2.APPDATA || path2.join(homedir, "AppData", "Roaming");
7673
- const localAppData = env2.LOCALAPPDATA || path2.join(homedir, "AppData", "Local");
8037
+ const appData = env.APPDATA || path.join(homedir, "AppData", "Roaming");
8038
+ const localAppData = env.LOCALAPPDATA || path.join(homedir, "AppData", "Local");
7674
8039
  return {
7675
- data: path2.join(localAppData, name, "Data"),
7676
- config: path2.join(appData, name, "Config"),
7677
- cache: path2.join(localAppData, name, "Cache"),
7678
- log: path2.join(localAppData, name, "Log"),
7679
- temp: path2.join(tmpdir, name)
8040
+ data: path.join(localAppData, name, "Data"),
8041
+ config: path.join(appData, name, "Config"),
8042
+ cache: path.join(localAppData, name, "Cache"),
8043
+ log: path.join(localAppData, name, "Log"),
8044
+ temp: path.join(tmpdir, name)
7680
8045
  };
7681
8046
  };
7682
8047
  var linux = (name) => {
7683
- const username = path2.basename(homedir);
8048
+ const username = path.basename(homedir);
7684
8049
  return {
7685
- data: path2.join(env2.XDG_DATA_HOME || path2.join(homedir, ".local", "share"), name),
7686
- config: path2.join(env2.XDG_CONFIG_HOME || path2.join(homedir, ".config"), name),
7687
- cache: path2.join(env2.XDG_CACHE_HOME || path2.join(homedir, ".cache"), name),
7688
- log: path2.join(env2.XDG_STATE_HOME || path2.join(homedir, ".local", "state"), name),
7689
- temp: path2.join(tmpdir, username, name)
8050
+ data: path.join(env.XDG_DATA_HOME || path.join(homedir, ".local", "share"), name),
8051
+ config: path.join(env.XDG_CONFIG_HOME || path.join(homedir, ".config"), name),
8052
+ cache: path.join(env.XDG_CACHE_HOME || path.join(homedir, ".cache"), name),
8053
+ log: path.join(env.XDG_STATE_HOME || path.join(homedir, ".local", "state"), name),
8054
+ temp: path.join(tmpdir, username, name)
7690
8055
  };
7691
8056
  };
7692
8057
  function envPaths(name, { suffix = "nodejs" } = {}) {
@@ -7711,19 +8076,19 @@ class Service16 extends Context16.Service()("@dimple/wasm/WasmCache") {
7711
8076
  }
7712
8077
  var layer16 = Layer20.effect(Service16, Effect43.succeed(Service16.of({
7713
8078
  ensure: Effect43.fn("WasmCache.ensure")(function* (lang, version, download) {
7714
- const versionDir = join2(paths.cache, "tree-sitter", lang, version);
7715
- const finalPath = join2(versionDir, "parser.wasm");
8079
+ const versionDir = join3(paths.cache, "tree-sitter", lang, version);
8080
+ const finalPath = join3(versionDir, "parser.wasm");
7716
8081
  const exists = yield* Effect43.try({
7717
- try: () => existsSync2(finalPath),
8082
+ try: () => existsSync3(finalPath),
7718
8083
  catch: (cause) => new WasmCacheError({ message: `Failed to stat ${finalPath}`, cause })
7719
8084
  });
7720
8085
  if (exists)
7721
8086
  return finalPath;
7722
8087
  yield* Effect43.try({
7723
- try: () => mkdirSync3(versionDir, { recursive: true }),
8088
+ try: () => mkdirSync(versionDir, { recursive: true }),
7724
8089
  catch: (cause) => new WasmCacheError({ message: `Failed to create ${versionDir}`, cause })
7725
8090
  });
7726
- const tempPath = join2(versionDir, `.tmp-${process.pid}`);
8091
+ const tempPath = join3(versionDir, `.tmp-${process.pid}`);
7727
8092
  const wasm = yield* download;
7728
8093
  yield* Effect43.try({
7729
8094
  try: () => writeFileSync(tempPath, wasm),
@@ -7738,7 +8103,7 @@ var layer16 = Layer20.effect(Service16, Effect43.succeed(Service16.of({
7738
8103
  }).pipe(Effect43.catchCause(() => Effect43.succeed(false)));
7739
8104
  if (!renamed) {
7740
8105
  const existsAfter = yield* Effect43.try({
7741
- try: () => existsSync2(finalPath),
8106
+ try: () => existsSync3(finalPath),
7742
8107
  catch: (cause) => new WasmCacheError({ message: `Failed to stat ${finalPath}`, cause })
7743
8108
  });
7744
8109
  if (!existsAfter) {
@@ -7769,12 +8134,12 @@ var layer17 = Layer21.effect(Service17, Effect44.gen(function* () {
7769
8134
  message: `Failed to download grammar for: ${lang}`,
7770
8135
  cause
7771
8136
  })));
7772
- const path3 = yield* cache.ensure(lang, metadata.version, Effect44.succeed(wasmBytes)).pipe(Effect44.mapError((cause) => new GrammarProvisionError({
8137
+ const path2 = yield* cache.ensure(lang, metadata.version, Effect44.succeed(wasmBytes)).pipe(Effect44.mapError((cause) => new GrammarProvisionError({
7773
8138
  language: lang,
7774
8139
  message: `Failed to provision grammar for: ${lang}`,
7775
8140
  cause
7776
8141
  })));
7777
- return path3;
8142
+ return path2;
7778
8143
  });
7779
8144
  return Service17.of({ ensure });
7780
8145
  }));
@@ -7893,11 +8258,27 @@ class DuplicateEdgeError extends Schema33.TaggedErrorClass()("Sdk.DuplicateEdge"
7893
8258
  }) {
7894
8259
  }
7895
8260
 
8261
+ class EdgeNotFoundError extends Schema33.TaggedErrorClass()("Sdk.EdgeNotFound", {
8262
+ id: Schema33.optional(Schema33.String),
8263
+ message: Schema33.String
8264
+ }) {
8265
+ }
8266
+
7896
8267
  class StoreFailure extends Schema33.TaggedErrorClass()("Sdk.StoreFailure", {
7897
8268
  message: Schema33.String
7898
8269
  }) {
7899
8270
  }
7900
8271
 
8272
+ class ReloadError extends Schema33.TaggedErrorClass()("Sdk.Reload", {
8273
+ message: Schema33.String
8274
+ }) {
8275
+ }
8276
+
8277
+ class EmbeddingFailure extends Schema33.TaggedErrorClass()("Sdk.Embedding", {
8278
+ message: Schema33.String
8279
+ }) {
8280
+ }
8281
+
7901
8282
  class InputError extends Schema33.TaggedErrorClass()("Sdk.InputError", {
7902
8283
  message: Schema33.String
7903
8284
  }) {
@@ -7919,12 +8300,14 @@ var messageOf = (error) => {
7919
8300
  }
7920
8301
  };
7921
8302
  var remapError = (error) => {
7922
- if (error instanceof ConfigError || error instanceof NotFoundError || error instanceof DuplicateKeyError || error instanceof DuplicateEdgeError || error instanceof LLMNotConfiguredError || error instanceof JobFailedError || error instanceof StoreFailure || error instanceof InputError) {
8303
+ if (error instanceof ConfigError || error instanceof NotFoundError || error instanceof DuplicateKeyError || error instanceof DuplicateEdgeError || error instanceof EdgeNotFoundError || error instanceof LLMNotConfiguredError || error instanceof JobFailedError || error instanceof EmbeddingFailure || error instanceof StoreFailure || error instanceof ReloadError || error instanceof InputError) {
7923
8304
  return error;
7924
8305
  }
7925
8306
  const message = messageOf(error);
7926
8307
  if (error instanceof CoreConfigError)
7927
8308
  return new ConfigError({ message });
8309
+ if (error instanceof EmbeddingError)
8310
+ return new EmbeddingFailure({ message });
7928
8311
  if (error instanceof NotFound) {
7929
8312
  return new NotFoundError({
7930
8313
  ...error.id !== undefined ? { id: error.id } : {},
@@ -7946,6 +8329,12 @@ var remapError = (error) => {
7946
8329
  message: `Edge already exists: ${error.fromMemoryId} → ${error.toMemoryId} (${error.edgeKind})`
7947
8330
  });
7948
8331
  }
8332
+ if (error instanceof exports_graph.EdgeNotFound) {
8333
+ return new EdgeNotFoundError({
8334
+ ...error.id !== undefined ? { id: error.id } : {},
8335
+ message: `Edge not found: ${error.id ?? "unknown id"}`
8336
+ });
8337
+ }
7949
8338
  if (error instanceof JobFailed || error instanceof JobTimeout || error instanceof JobCancelled) {
7950
8339
  return message.includes("LLM not configured") ? new LLMNotConfiguredError({ message }) : new JobFailedError({ message });
7951
8340
  }
@@ -7962,8 +8351,9 @@ var configLayerFromInput = (input) => Layer25.effect(Service4, resolveConfigInpu
7962
8351
  import { Cause as Cause5, Effect as Effect49, Exit as Exit6 } from "effect";
7963
8352
 
7964
8353
  // src/indexer.ts
7965
- import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
7966
- import { basename, dirname as dirname4, extname, join as join3, resolve, sep } from "node:path";
8354
+ import { execFileSync } from "node:child_process";
8355
+ import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
8356
+ import { basename, dirname, extname, join as join4, resolve, sep } from "node:path";
7967
8357
  var INDEXABLE_EXTENSIONS = [
7968
8358
  ".ts",
7969
8359
  ".mts",
@@ -8000,52 +8390,68 @@ var LANGUAGE_BY_EXTENSION = {
8000
8390
  ".cjs": "javascript",
8001
8391
  ".jsx": "jsx"
8002
8392
  };
8003
- var languageForPath = (path3) => LANGUAGE_BY_EXTENSION[extname(path3)];
8393
+ var languageForPath = (path2) => LANGUAGE_BY_EXTENSION[extname(path2)];
8004
8394
  var walkFiles = (paths2, extensions) => {
8005
8395
  const extensionSet = new Set(extensions.map((e) => e.startsWith(".") ? e : `.${e}`));
8006
8396
  const files = [];
8007
8397
  const errors3 = [];
8008
8398
  let unsupported = 0;
8009
- const root = commonRoot([...paths2, process.cwd()]);
8010
- const visit = (path3) => {
8399
+ const resolved = paths2.map((p) => resolve(p));
8400
+ const root = commonRoot([...resolved, process.cwd()]);
8401
+ const visit = (path2) => {
8011
8402
  let stat;
8012
8403
  try {
8013
- stat = statSync2(path3);
8404
+ stat = statSync(path2);
8014
8405
  } catch (cause) {
8015
- errors3.push({ filePath: path3, message: `stat failed: ${messageOf2(cause)}` });
8406
+ errors3.push({ filePath: path2, message: `stat failed: ${messageOf2(cause)}` });
8016
8407
  return;
8017
8408
  }
8018
8409
  if (stat.isDirectory()) {
8019
- const name = basename(path3);
8410
+ const name = basename(path2);
8020
8411
  if (EXCLUDED_DIRS.has(name))
8021
8412
  return;
8022
8413
  let entries;
8023
8414
  try {
8024
- entries = readdirSync(path3);
8415
+ entries = readdirSync(path2);
8025
8416
  } catch (cause) {
8026
- errors3.push({ filePath: path3, message: `readdir failed: ${messageOf2(cause)}` });
8417
+ errors3.push({ filePath: path2, message: `readdir failed: ${messageOf2(cause)}` });
8027
8418
  return;
8028
8419
  }
8029
8420
  for (const entry of entries)
8030
- visit(join3(path3, entry));
8421
+ visit(join4(path2, entry));
8031
8422
  return;
8032
8423
  }
8033
8424
  if (!stat.isFile())
8034
8425
  return;
8035
- if (!extensionSet.has(extname(path3))) {
8426
+ if (!extensionSet.has(extname(path2))) {
8036
8427
  unsupported += 1;
8037
8428
  return;
8038
8429
  }
8039
- files.push(path3);
8430
+ files.push(path2);
8040
8431
  };
8041
- for (const path3 of paths2)
8042
- visit(resolve(path3));
8043
- return { files, unsupported, errors: errors3, root };
8432
+ for (const path2 of resolved)
8433
+ visit(path2);
8434
+ return { files, unsupported, errors: errors3, root, roots: resolved };
8044
8435
  };
8045
- var readCapped = (path3, maxBytes) => {
8046
- const text4 = readFileSync2(path3, "utf8");
8436
+ var readCapped = (path2, maxBytes) => {
8437
+ const text4 = readFileSync2(path2, "utf8");
8047
8438
  return text4.length > maxBytes ? text4.slice(0, maxBytes) : text4;
8048
8439
  };
8440
+ var currentCommit = (firstFile) => {
8441
+ if (firstFile === undefined)
8442
+ return null;
8443
+ try {
8444
+ const out = execFileSync("git", ["rev-parse", "HEAD"], {
8445
+ cwd: dirname(firstFile),
8446
+ encoding: "utf8",
8447
+ stdio: ["ignore", "pipe", "ignore"]
8448
+ });
8449
+ const sha = out.trim();
8450
+ return /^[0-9a-f]{7,40}$/.test(sha) ? sha : null;
8451
+ } catch {
8452
+ return null;
8453
+ }
8454
+ };
8049
8455
  var resolveImportTarget = (fromDir, specifier, extensions) => {
8050
8456
  if (!specifier.startsWith("."))
8051
8457
  return;
@@ -8053,11 +8459,11 @@ var resolveImportTarget = (fromDir, specifier, extensions) => {
8053
8459
  const candidates2 = [
8054
8460
  base,
8055
8461
  ...extensions.map((e) => base + e),
8056
- ...extensions.map((e) => join3(base, `index${e}`))
8462
+ ...extensions.map((e) => join4(base, `index${e}`))
8057
8463
  ];
8058
8464
  for (const candidate of candidates2) {
8059
8465
  try {
8060
- if (existsSync3(candidate) && statSync2(candidate).isFile())
8466
+ if (existsSync4(candidate) && statSync(candidate).isFile())
8061
8467
  return candidate;
8062
8468
  } catch {
8063
8469
  return;
@@ -8068,7 +8474,7 @@ var resolveImportTarget = (fromDir, specifier, extensions) => {
8068
8474
  var resolveImports = (filePath, sources, extensions) => {
8069
8475
  const targets = [];
8070
8476
  for (const source of sources) {
8071
- const target = resolveImportTarget(dirname4(filePath), source, extensions);
8477
+ const target = resolveImportTarget(dirname(filePath), source, extensions);
8072
8478
  if (target !== undefined)
8073
8479
  targets.push(target);
8074
8480
  }
@@ -8098,16 +8504,11 @@ var keyForPath = (root, filePath) => {
8098
8504
  var messageOf2 = (cause) => cause instanceof Error ? cause.message : String(cause);
8099
8505
 
8100
8506
  // src/dimple.ts
8101
- var makeFacade = (runtime, core, parser, extractor, dispose) => {
8102
- let disposed = false;
8103
- const run = (effect) => {
8104
- if (disposed) {
8105
- return Promise.reject(new StoreFailure({ message: "SDK disposed" }));
8106
- }
8107
- return runtime.runPromise(effect.pipe(Effect49.mapError(remapError))).catch((cause) => {
8108
- return Promise.reject(remapError(cause));
8109
- });
8110
- };
8507
+ var makeFacade = (state, reload, dispose) => {
8508
+ const run = state.run;
8509
+ const core = () => state.core();
8510
+ const parser = () => state.parser();
8511
+ const extractor = () => state.extractor();
8111
8512
  const validateQueryOptions = (options) => {
8112
8513
  const limit = options?.limit;
8113
8514
  if (limit !== undefined && (!Number.isInteger(limit) || limit < 0)) {
@@ -8119,6 +8520,23 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8119
8520
  if (k !== undefined && (!Number.isInteger(k) || k < 0)) {
8120
8521
  return new InputError({ message: `query: k must be a non-negative integer (got ${k})` });
8121
8522
  }
8523
+ const budget = options?.budget;
8524
+ if (budget !== undefined) {
8525
+ if (budget === null || typeof budget !== "object" || Array.isArray(budget)) {
8526
+ return new InputError({ message: "query: budget must be an object" });
8527
+ }
8528
+ for (const [name, v] of [
8529
+ ["maxUnits", budget.maxUnits],
8530
+ ["maxChars", budget.maxChars],
8531
+ ["maxMs", budget.maxMs]
8532
+ ]) {
8533
+ if (v !== undefined && (!Number.isInteger(v) || v <= 0)) {
8534
+ return new InputError({
8535
+ message: `query: budget.${name} must be a positive integer (got ${String(v)})`
8536
+ });
8537
+ }
8538
+ }
8539
+ }
8122
8540
  const profile = options?.profile;
8123
8541
  if (profile !== undefined) {
8124
8542
  if (typeof profile === "string") {
@@ -8159,13 +8577,57 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8159
8577
  if (topicKeys !== undefined && (!Array.isArray(topicKeys) || topicKeys.some((t) => typeof t !== "string"))) {
8160
8578
  return new InputError({ message: "query: topics.topicKeys must be a string array" });
8161
8579
  }
8580
+ const contentType = options?.contentType;
8581
+ if (contentType !== undefined) {
8582
+ const kinds = typeof contentType === "string" ? [contentType] : contentType;
8583
+ if (kinds.length === 0 || kinds.some((k2) => typeof k2 !== "string" || k2.length === 0)) {
8584
+ return new InputError({
8585
+ message: "query: contentType must be a non-empty string or a non-empty string array"
8586
+ });
8587
+ }
8588
+ }
8162
8589
  return;
8163
8590
  };
8164
8591
  const runQuery = (text4, options) => run(Effect49.gen(function* () {
8592
+ if (typeof text4 !== "string") {
8593
+ return yield* new InputError({ message: "query: text must be a string" });
8594
+ }
8165
8595
  const invalid = validateQueryOptions(options);
8166
8596
  if (invalid !== undefined)
8167
8597
  return yield* invalid;
8168
- return yield* core.hybrid.hybrid(text4, options).pipe(Effect49.map((rows) => [...rows]));
8598
+ const { budget, ...rest } = options ?? {};
8599
+ const started = Date.now();
8600
+ const capLimit = budget?.maxUnits !== undefined && (rest.limit === undefined || rest.limit > budget.maxUnits) ? budget.maxUnits : rest.limit;
8601
+ let rows = yield* core().hybrid.hybrid(text4, {
8602
+ ...rest,
8603
+ ...capLimit !== undefined ? { limit: capLimit } : {}
8604
+ }).pipe(Effect49.map((r) => [...r]));
8605
+ let truncated = false;
8606
+ if (budget?.maxChars !== undefined && budget.maxChars > 0) {
8607
+ let used = 0;
8608
+ rows = rows.map((row) => {
8609
+ const content = row.memory.content;
8610
+ const room = budget.maxChars - used;
8611
+ if (room <= 0) {
8612
+ truncated = true;
8613
+ return { ...row, memory: { ...row.memory, content: "" } };
8614
+ }
8615
+ if (content.length > room) {
8616
+ truncated = true;
8617
+ used += room;
8618
+ return { ...row, memory: { ...row.memory, content: content.slice(0, room) } };
8619
+ }
8620
+ used += content.length;
8621
+ return row;
8622
+ });
8623
+ }
8624
+ if (budget?.maxMs !== undefined && Date.now() - started > budget.maxMs) {
8625
+ truncated = true;
8626
+ }
8627
+ const out = rows;
8628
+ if (truncated)
8629
+ out.truncated = true;
8630
+ return out;
8169
8631
  }));
8170
8632
  const validateWriteInput = (input) => {
8171
8633
  if (input === null || typeof input !== "object" || Array.isArray(input)) {
@@ -8184,9 +8646,9 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8184
8646
  return new InputError({ message: "write: namespace must be a string" });
8185
8647
  }
8186
8648
  const ttl = input.ttlSecs;
8187
- if (ttl !== undefined && (!Number.isInteger(ttl) || ttl < 0)) {
8649
+ if (typeof ttl === "number" && (!Number.isInteger(ttl) || ttl < 0)) {
8188
8650
  return new InputError({
8189
- message: `write: ttlSecs must be a non-negative integer (got ${String(ttl)})`
8651
+ message: `write: ttlSecs must be a non-negative integer or null (got ${String(ttl)})`
8190
8652
  });
8191
8653
  }
8192
8654
  if (input.metadata !== undefined && typeof input.metadata !== "string") {
@@ -8230,15 +8692,47 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8230
8692
  return;
8231
8693
  };
8232
8694
  const writeUnit = (input, embed) => Effect49.gen(function* () {
8233
- const mem = yield* core.store.write(input);
8695
+ const mem = yield* core().store.write(input);
8234
8696
  if (embed) {
8235
- yield* core.store.ensureTopicTree(core.defaultModel.id);
8236
- const vector = yield* core.defaultEmbedder.embedSingle(input.content);
8237
- yield* core.store.updateEmbedding(mem.id, [...vector], core.defaultModel.id);
8238
- return yield* core.store.get(mem.id);
8697
+ yield* core().store.ensureTopicTree(core().defaultModel.id);
8698
+ const vector = yield* core().defaultEmbedder.embedSingle(input.content);
8699
+ yield* core().store.updateEmbedding(mem.id, [...vector], core().defaultModel.id);
8700
+ return yield* core().store.get(mem.id);
8239
8701
  }
8240
8702
  return mem;
8241
8703
  });
8704
+ const upsertUnit = (input, embed) => Effect49.gen(function* () {
8705
+ const existing = yield* core().store.getByKey(input.logicalKey, input.namespace ?? "default").pipe(Effect49.catchTag("Store.NotFound", () => Effect49.succeed(undefined)));
8706
+ if (existing !== undefined && existing.content === input.content) {
8707
+ const stamp = codeVersionOf(input.metadata);
8708
+ if (stamp !== undefined && codeVersionOf(existing.metadata) === undefined) {
8709
+ yield* core().store.setMemoryMetadata(existing.id, { codeVersion: stamp });
8710
+ }
8711
+ if (embed && (existing.embedding === null || existing.embedding === undefined)) {
8712
+ yield* core().store.ensureTopicTree(core().defaultModel.id);
8713
+ const vector = yield* core().defaultEmbedder.embedSingle(input.content);
8714
+ yield* core().store.updateEmbedding(existing.id, [...vector], core().defaultModel.id);
8715
+ return { kind: "updated", id: existing.id };
8716
+ }
8717
+ return { kind: "skipped", id: existing.id };
8718
+ }
8719
+ if (existing !== undefined) {
8720
+ const { memory: memory2 } = yield* core().store.writeSuperseding(input);
8721
+ if (embed) {
8722
+ yield* core().store.ensureTopicTree(core().defaultModel.id);
8723
+ const vector = yield* core().defaultEmbedder.embedSingle(input.content);
8724
+ yield* core().store.updateEmbedding(memory2.id, [...vector], core().defaultModel.id);
8725
+ }
8726
+ return { kind: "updated", id: memory2.id };
8727
+ }
8728
+ const memory = yield* core().store.write(input);
8729
+ if (embed) {
8730
+ yield* core().store.ensureTopicTree(core().defaultModel.id);
8731
+ const vector = yield* core().defaultEmbedder.embedSingle(input.content);
8732
+ yield* core().store.updateEmbedding(memory.id, [...vector], core().defaultModel.id);
8733
+ }
8734
+ return { kind: "indexed", id: memory.id };
8735
+ });
8242
8736
  const validateIndexOptions = (options) => {
8243
8737
  if (options === null || typeof options !== "object" || Array.isArray(options)) {
8244
8738
  return new InputError({ message: "index: options must be an object" });
@@ -8273,16 +8767,32 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8273
8767
  return;
8274
8768
  };
8275
8769
  const messageOf3 = (cause) => cause instanceof Error ? cause.message : String(cause);
8770
+ const resolveMemory = (idOrKey) => core().store.get(idOrKey).pipe(Effect49.catchTag("Store.NotFound", () => core().store.getByKey(idOrKey, "default").pipe(Effect49.catchTag("Store.NotFound", () => Effect49.fail(new NotFoundError({ id: idOrKey, message: `Memory not found: ${idOrKey}` }))))));
8771
+ const codeVersionOf = (blob) => {
8772
+ if (blob === undefined || blob === null)
8773
+ return;
8774
+ try {
8775
+ const parsed = JSON.parse(blob);
8776
+ if (parsed !== null && typeof parsed === "object") {
8777
+ const v = parsed["codeVersion"];
8778
+ return typeof v === "string" ? v : undefined;
8779
+ }
8780
+ } catch {}
8781
+ return;
8782
+ };
8276
8783
  return {
8784
+ get defaultModelId() {
8785
+ return core().defaultModel.id;
8786
+ },
8277
8787
  write: (input, options) => run(Effect49.gen(function* () {
8278
8788
  const invalid = validateWriteInput(input);
8279
8789
  if (invalid !== undefined)
8280
8790
  return yield* invalid;
8281
8791
  return yield* writeUnit(input, options?.embed !== false);
8282
8792
  })),
8283
- get: (id) => run(core.store.get(id)),
8793
+ get: (id) => run(resolveMemory(id)),
8284
8794
  query: runQuery,
8285
- forget: (id) => run(core.store.forget(id)),
8795
+ forget: (id) => run(resolveMemory(id).pipe(Effect49.flatMap((memory) => core().store.forget(memory.id)))),
8286
8796
  index: (options) => run(Effect49.gen(function* () {
8287
8797
  const invalid = validateIndexOptions(options);
8288
8798
  if (invalid !== undefined)
@@ -8300,13 +8810,19 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8300
8810
  })
8301
8811
  });
8302
8812
  const failed = [...walk.errors];
8813
+ const commit = currentCommit(walk.files[0]);
8303
8814
  let indexed = 0;
8815
+ let updated = 0;
8816
+ let reconciled = 0;
8304
8817
  let skipped = 0;
8305
8818
  const unitIds = new Map;
8819
+ const freshIds = [];
8306
8820
  const pendingEdges = [];
8821
+ const pendingCallEdges = [];
8822
+ const stableChunkId = (chunkId) => chunkId.replace(/:\d+$/, "");
8307
8823
  const parentClassOf = (chunkId) => {
8308
- const parts = chunkId.split(":");
8309
- const parentAndMethod = parts[parts.length - 2];
8824
+ const parts = stableChunkId(chunkId).split(":");
8825
+ const parentAndMethod = parts[parts.length - 1];
8310
8826
  if (parentAndMethod === undefined || !parentAndMethod.includes("."))
8311
8827
  return;
8312
8828
  return parentAndMethod.split(".")[0];
@@ -8316,49 +8832,59 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8316
8832
  const language = languageForPath(filePath);
8317
8833
  if (language === undefined)
8318
8834
  continue;
8835
+ const chunkKeyOf = (chunkId) => `${keyPrefix}${relative}#${stableChunkId(chunkId)}`;
8319
8836
  const exit = yield* Effect49.exit(Effect49.gen(function* () {
8320
8837
  const source = yield* Effect49.try({
8321
8838
  try: () => readCapped(filePath, maxFileBytes),
8322
8839
  catch: (cause) => new InputError({ message: `read failed: ${messageOf3(cause)}` })
8323
8840
  });
8324
- const handle = yield* parser.parse({ lang: language, source });
8325
- const extracted = yield* extractor.extract({ filePath: relative, tree: handle });
8841
+ const handle = yield* parser().parse({ lang: language, source });
8842
+ const extracted = yield* extractor().extract({ filePath: relative, tree: handle });
8326
8843
  let units = 0;
8844
+ let upd = 0;
8327
8845
  let dups = 0;
8328
8846
  const fileKey = `${keyPrefix}${relative}`;
8329
- const fileOutcome = yield* writeUnit({
8847
+ const fileOutcome = yield* upsertUnit({
8330
8848
  logicalKey: fileKey,
8331
8849
  content: source,
8332
8850
  ...namespace !== undefined ? { namespace } : {},
8851
+ ttlSecs: null,
8333
8852
  metadata: JSON.stringify({
8334
8853
  filePath,
8335
8854
  language,
8336
8855
  kind: "file",
8337
- imports: extracted.imports ?? []
8856
+ imports: extracted.imports ?? [],
8857
+ ...commit !== null ? { codeVersion: commit } : {}
8338
8858
  }),
8339
8859
  contentType: `text/x-${language}`
8340
- }, embed).pipe(Effect49.catchTag("Store.DuplicateActiveKey", () => Effect49.succeed("duplicate")));
8341
- if (fileOutcome === "duplicate")
8860
+ }, embed);
8861
+ if (fileOutcome.kind === "skipped") {
8342
8862
  dups += 1;
8343
- else {
8344
- units += 1;
8345
8863
  unitIds.set(fileKey, fileOutcome.id);
8864
+ } else {
8865
+ if (fileOutcome.kind === "updated")
8866
+ upd += 1;
8867
+ else
8868
+ units += 1;
8869
+ unitIds.set(fileKey, fileOutcome.id);
8870
+ freshIds.push(fileOutcome.id);
8346
8871
  }
8347
8872
  const classKeys = new Map;
8348
8873
  for (const chunk of extracted.chunks) {
8349
8874
  if (chunk.kind === "class" || chunk.kind.endsWith("class")) {
8350
- classKeys.set(chunk.name, `${keyPrefix}${relative}#${chunk.id}`);
8875
+ classKeys.set(chunk.name, chunkKeyOf(chunk.id));
8351
8876
  }
8352
8877
  }
8353
8878
  for (const chunk of extracted.chunks) {
8354
- const chunkKey = `${keyPrefix}${relative}#${chunk.id}`;
8879
+ const chunkKey = chunkKeyOf(chunk.id);
8355
8880
  const content = [chunk.docstring, chunk.sourceText].filter((c) => c !== undefined && c.length > 0).join(`
8356
8881
 
8357
8882
  `).slice(0, maxChunkBytes);
8358
- const chunkOutcome = yield* writeUnit({
8883
+ const chunkOutcome = yield* upsertUnit({
8359
8884
  logicalKey: chunkKey,
8360
8885
  content,
8361
8886
  ...namespace !== undefined ? { namespace } : {},
8887
+ ttlSecs: null,
8362
8888
  metadata: JSON.stringify({
8363
8889
  filePath,
8364
8890
  language,
@@ -8366,17 +8892,23 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8366
8892
  name: chunk.name,
8367
8893
  startLine: chunk.startLine,
8368
8894
  endLine: chunk.endLine,
8369
- ...chunk.docstring !== undefined ? { docstring: chunk.docstring } : {}
8895
+ ...chunk.docstring !== undefined ? { docstring: chunk.docstring } : {},
8896
+ ...commit !== null ? { codeVersion: commit } : {}
8370
8897
  }),
8371
8898
  contentType: `text/x-${language}`
8372
- }, embed).pipe(Effect49.catchTag("Store.DuplicateActiveKey", () => Effect49.succeed("duplicate")));
8373
- if (chunkOutcome === "duplicate") {
8899
+ }, embed);
8900
+ if (chunkOutcome.kind === "skipped") {
8374
8901
  dups += 1;
8902
+ unitIds.set(chunkKey, chunkOutcome.id);
8375
8903
  } else {
8376
- units += 1;
8904
+ if (chunkOutcome.kind === "updated")
8905
+ upd += 1;
8906
+ else
8907
+ units += 1;
8377
8908
  unitIds.set(chunkKey, chunkOutcome.id);
8909
+ freshIds.push(chunkOutcome.id);
8378
8910
  }
8379
- if (chunkOutcome !== "duplicate" && chunk.kind === "method") {
8911
+ if (chunkOutcome.kind !== "skipped" && chunk.kind === "method") {
8380
8912
  const parent = parentClassOf(chunk.id);
8381
8913
  const classKey = parent === undefined ? undefined : classKeys.get(parent);
8382
8914
  if (classKey !== undefined) {
@@ -8390,37 +8922,68 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8390
8922
  to: fileKey
8391
8923
  });
8392
8924
  }
8393
- return { units, dups };
8925
+ const importRels = resolveImports(filePath, (extracted.imports ?? []).map((imp) => imp.source), extensions).map((target) => keyForPath(walk.root, target));
8926
+ for (const chunk of extracted.chunks) {
8927
+ const callerKey = chunkKeyOf(chunk.id);
8928
+ if (!unitIds.has(callerKey))
8929
+ continue;
8930
+ for (const call of chunk.calls ?? []) {
8931
+ const candidates2 = [
8932
+ `${keyPrefix}${relative}#${relative}:${call.name}`,
8933
+ ...importRels.map((rel) => `${keyPrefix}${rel}#${rel}:${call.name}`)
8934
+ ];
8935
+ const first = candidates2.find((key) => unitIds.has(key));
8936
+ if (first !== undefined) {
8937
+ pendingCallEdges.push({ from: callerKey, to: first });
8938
+ }
8939
+ }
8940
+ }
8941
+ return { units, upd, dups };
8394
8942
  }));
8395
8943
  if (Exit6.isFailure(exit)) {
8396
8944
  failed.push({ filePath, message: Cause5.pretty(exit.cause) });
8397
8945
  } else {
8398
8946
  indexed += exit.value.units;
8947
+ updated += exit.value.upd;
8399
8948
  skipped += exit.value.dups;
8400
8949
  }
8401
8950
  }
8402
- const edgeEffect = (edge) => {
8403
- const from = unitIds.get(edge.from);
8404
- const to = unitIds.get(edge.to);
8951
+ const vanished = yield* core().store.reconcile(namespace ?? "default", keyPrefix, walk.root, walk.roots, [...unitIds.keys()]);
8952
+ reconciled = vanished.length;
8953
+ yield* core().store.reconcileRenameCandidates(vanished, freshIds);
8954
+ const resolveKey = (key) => {
8955
+ const fromMap = unitIds.get(key);
8956
+ if (fromMap !== undefined)
8957
+ return Effect49.succeed(fromMap);
8958
+ return core().store.getByKey(key, namespace ?? "default").pipe(Effect49.map((m) => m.id), Effect49.catchTag("Store.NotFound", () => Effect49.succeed(undefined)));
8959
+ };
8960
+ const edgeEffect = (edge) => Effect49.gen(function* () {
8961
+ const [from, to] = yield* Effect49.all([resolveKey(edge.from), resolveKey(edge.to)]);
8405
8962
  if (from === undefined || to === undefined || from === to)
8406
- return Effect49.void;
8407
- return core.store.addEdge({
8963
+ return;
8964
+ yield* Effect49.exit(core().store.addEdge({
8408
8965
  fromMemoryId: from,
8409
8966
  toMemoryId: to,
8410
- edgeKind: "memory_supports",
8967
+ edgeKind: edge.edgeKind,
8411
8968
  source: "rule",
8412
8969
  confidence: 1,
8413
8970
  confirmed: true
8414
- });
8415
- };
8971
+ }));
8972
+ });
8416
8973
  for (const edge of pendingEdges) {
8417
- yield* Effect49.exit(edgeEffect(edge));
8974
+ yield* Effect49.exit(edgeEffect({ ...edge, edgeKind: "memory_supports" }));
8975
+ }
8976
+ for (const edge of pendingCallEdges) {
8977
+ yield* Effect49.exit(edgeEffect({ ...edge, edgeKind: "code_calls" }));
8418
8978
  }
8419
8979
  return {
8420
8980
  files: walk.files.length,
8421
8981
  indexed,
8982
+ updated,
8983
+ reconciled,
8422
8984
  skipped,
8423
8985
  unsupported: walk.unsupported,
8986
+ commit,
8424
8987
  failed
8425
8988
  };
8426
8989
  })),
@@ -8430,57 +8993,232 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8430
8993
  message: "updateEmbedding: embedding must be a non-empty array of finite numbers"
8431
8994
  });
8432
8995
  }
8433
- const key = modelKey ?? core.defaultModel.id;
8434
- yield* core.store.ensureTopicTree(key);
8435
- yield* core.store.updateEmbedding(id, [...embedding], key);
8996
+ const key = modelKey ?? core().defaultModel.id;
8997
+ const memory = yield* resolveMemory(id);
8998
+ yield* core().store.ensureTopicTree(key);
8999
+ yield* core().store.updateEmbedding(memory.id, [...embedding], key);
8436
9000
  })),
8437
- embed: (text4) => run(core.defaultEmbedder.embedSingle(text4).pipe(Effect49.map((vector) => [...vector]))),
8438
- dream: (modelKey) => run(core.runtime.enqueue(MemoryDream, { modelKey, runId: crypto.randomUUID() }).pipe(Effect49.flatMap((jobId) => core.runtime.wait(jobId)), Effect49.asVoid)),
8439
- repair: (modelKey) => run(core.runtime.enqueue(GraphRepair, { modelKey, runId: crypto.randomUUID() }).pipe(Effect49.flatMap((jobId) => core.runtime.wait(jobId)), Effect49.asVoid)),
8440
- explain: (input) => run(core.store.explain(input)),
8441
- health: (modelKey) => run(core.store.topicHealth(modelKey)),
9001
+ embed: (text4) => run(core().defaultEmbedder.embedSingle(text4).pipe(Effect49.map((vector) => [...vector]))),
9002
+ dream: (modelKey) => run(core().runtime.enqueue(MemoryDream, { modelKey, runId: crypto.randomUUID() }).pipe(Effect49.flatMap((jobId) => core().runtime.wait(jobId)), Effect49.asVoid)),
9003
+ dbSchema: () => run(core().store.listSchema()),
9004
+ repair: (modelKey) => run(core().runtime.enqueue(GraphRepair, { modelKey, runId: crypto.randomUUID() }).pipe(Effect49.flatMap((jobId) => core().runtime.wait(jobId)), Effect49.asVoid)),
9005
+ explain: (input, options) => run(core().store.explain(input, {
9006
+ ...options?.edgeKinds !== undefined ? { edgeKinds: options.edgeKinds } : {},
9007
+ ...options?.maxDepth !== undefined ? { maxDepth: options.maxDepth } : {},
9008
+ ...options?.maxNeighbors !== undefined ? { maxNeighbors: options.maxNeighbors } : {},
9009
+ ...options?.maxTotal !== undefined ? { maxTotal: options.maxTotal } : {},
9010
+ ...options?.includePending !== undefined ? { includePending: options.includePending } : {}
9011
+ })),
9012
+ health: (modelKey) => run(core().store.topicHealth(modelKey)),
8442
9013
  graph: {
8443
9014
  addEdge: (input) => run(Effect49.gen(function* () {
8444
9015
  const invalid = validateAddEdgeInput(input);
8445
9016
  if (invalid !== undefined)
8446
9017
  return yield* invalid;
8447
- return yield* core.store.addEdge(input);
9018
+ return yield* core().store.addEdge(input);
8448
9019
  })),
8449
- candidates: () => run(core.store.graphCandidates()),
8450
- coverage: (modelKey) => run(core.store.graphCoverage(modelKey))
9020
+ candidates: () => run(core().store.graphCandidates()),
9021
+ coverage: (modelKey) => run(core().store.graphCoverage(modelKey)),
9022
+ confirm: (id) => run(core().store.confirmEdge(id)),
9023
+ reject: (id) => run(core().store.rejectEdge(id)),
9024
+ expire: (id) => run(core().store.expireEdge(id))
8451
9025
  },
8452
- dispose: () => dispose().finally(() => {
8453
- disposed = true;
8454
- })
9026
+ reload: (input) => reload(input),
9027
+ dispose: () => dispose()
8455
9028
  };
8456
9029
  };
8457
9030
 
8458
9031
  // src/make.ts
8459
- var makeDimple = async (options) => {
9032
+ var stableKey = (value2) => {
9033
+ if (Array.isArray(value2))
9034
+ return `[${value2.map(stableKey).join(",")}]`;
9035
+ if (value2 !== null && typeof value2 === "object") {
9036
+ const entries = Object.entries(value2).filter(([, v]) => v !== undefined).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
9037
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableKey(v)}`).join(",")}}`;
9038
+ }
9039
+ return JSON.stringify(value2);
9040
+ };
9041
+ var diffConfig = (oldC, newC) => {
9042
+ if (oldC === undefined)
9043
+ return { storeUrl: true, jobsUrl: true, rest: true };
9044
+ const oldJobsUrl = oldC.jobs?.dbUrl;
9045
+ const newJobsUrl = newC.jobs?.dbUrl;
9046
+ const restOld = { ...oldC, storeUrl: "", jobs: { ...oldC.jobs, dbUrl: "" } };
9047
+ const restNew = { ...newC, storeUrl: "", jobs: { ...newC.jobs, dbUrl: "" } };
9048
+ return {
9049
+ storeUrl: oldC.storeUrl !== newC.storeUrl,
9050
+ jobsUrl: oldJobsUrl !== newJobsUrl,
9051
+ rest: stableKey(restOld) !== stableKey(restNew)
9052
+ };
9053
+ };
9054
+ var RELOAD_DRAIN_MS = 30000;
9055
+ var bootStack = async (options) => {
8460
9056
  const scope = await Effect50.runPromise(Scope5.make());
8461
9057
  let runtime;
8462
9058
  try {
8463
9059
  const layer21 = ("core" in options ? layer13(options.core) : layerConfig7(options.migrationFolder !== undefined ? { migrationFolder: options.migrationFolder } : {}).pipe(Layer26.provide(configLayerFromInput(options.config)))).pipe(Layer26.provide(Layer26.succeed(Scope5.Scope, scope)));
8464
9060
  const appLayer = Layer26.mergeAll(layer21, layer20, layer14);
8465
- const rt = ManagedRuntime2.make(appLayer);
9061
+ const rt = ManagedRuntime.make(appLayer);
8466
9062
  runtime = rt;
8467
9063
  const [core, parser, extractor] = await rt.runPromise(Effect50.all([Service, Service19, Service12]));
8468
- return makeFacade(rt, core, parser, extractor, () => Effect50.runPromise(Scope5.close(scope, Exit7.void).pipe(Effect50.andThen(rt.disposeEffect))));
9064
+ return {
9065
+ runPromise: (effect) => rt.runPromise(effect),
9066
+ disposeEffect: rt.disposeEffect,
9067
+ core,
9068
+ parser,
9069
+ extractor,
9070
+ dispose: () => Effect50.runPromise(Scope5.close(scope, Exit7.void).pipe(Effect50.andThen(rt.disposeEffect)))
9071
+ };
8469
9072
  } catch (cause) {
8470
9073
  await Effect50.runPromise(Scope5.close(scope, Exit7.void).pipe(Effect50.andThen(runtime?.disposeEffect ?? Effect50.void))).catch(() => {
8471
9074
  return;
8472
9075
  });
8473
- return Promise.reject(remapError(cause));
9076
+ return Promise.reject(cause);
8474
9077
  }
8475
9078
  };
9079
+ var makeDimple = async (options) => {
9080
+ const configSource = "config" in options ? options.config : undefined;
9081
+ const migrationFolder = "config" in options ? options.migrationFolder : undefined;
9082
+ let currentConfig;
9083
+ if (configSource !== undefined) {
9084
+ currentConfig = await Effect50.runPromise(resolveConfigInput(configSource)).catch((cause) => Promise.reject(remapError(cause)));
9085
+ }
9086
+ let current = await bootStack(configSource !== undefined && currentConfig !== undefined ? {
9087
+ config: currentConfig,
9088
+ ...migrationFolder !== undefined ? { migrationFolder } : {}
9089
+ } : options).then((stack) => stack, (cause) => Promise.reject(remapError(cause)));
9090
+ let disposed = false;
9091
+ let gate = Promise.resolve();
9092
+ let releaseGate;
9093
+ let inflight = 0;
9094
+ let drainWaiters = [];
9095
+ let reloadChain = Promise.resolve();
9096
+ const run = (effect) => {
9097
+ if (disposed) {
9098
+ return Promise.reject(new StoreFailure({ message: "SDK disposed" }));
9099
+ }
9100
+ return gate.then(() => {
9101
+ if (disposed) {
9102
+ return Promise.reject(new StoreFailure({ message: "SDK disposed" }));
9103
+ }
9104
+ const stack = current;
9105
+ inflight += 1;
9106
+ return stack.runPromise(effect.pipe(Effect50.mapError(remapError))).catch((cause) => {
9107
+ return Promise.reject(remapError(cause));
9108
+ }).finally(() => {
9109
+ inflight -= 1;
9110
+ if (inflight === 0 && drainWaiters.length > 0) {
9111
+ const waiters = drainWaiters;
9112
+ drainWaiters = [];
9113
+ for (const wake of waiters)
9114
+ wake();
9115
+ }
9116
+ });
9117
+ });
9118
+ };
9119
+ const pause = () => {
9120
+ gate = new Promise((resolve2) => {
9121
+ releaseGate = resolve2;
9122
+ });
9123
+ };
9124
+ const resume = () => {
9125
+ const release = releaseGate;
9126
+ releaseGate = undefined;
9127
+ gate = Promise.resolve();
9128
+ release?.();
9129
+ };
9130
+ const drain = () => {
9131
+ if (inflight === 0)
9132
+ return Promise.resolve();
9133
+ return new Promise((resolve2) => {
9134
+ drainWaiters.push(resolve2);
9135
+ });
9136
+ };
9137
+ const drainWithTimeout = () => new Promise((resolve2, reject) => {
9138
+ const timer = setTimeout(() => {
9139
+ reject(new ReloadError({
9140
+ message: `reload: drain timeout after ${RELOAD_DRAIN_MS}ms — in-flight calls still running`
9141
+ }));
9142
+ }, RELOAD_DRAIN_MS);
9143
+ drain().then(() => {
9144
+ clearTimeout(timer);
9145
+ resolve2();
9146
+ }, (cause) => {
9147
+ clearTimeout(timer);
9148
+ reject(cause);
9149
+ });
9150
+ });
9151
+ const reload = (input) => {
9152
+ const task = reloadChain.then(async () => {
9153
+ if (disposed) {
9154
+ return Promise.reject(new ReloadError({ message: "SDK disposed — reload unavailable" }));
9155
+ }
9156
+ if (configSource === undefined) {
9157
+ return Promise.reject(new ReloadError({
9158
+ message: "reload: no config source — this dimple was built from `core` options"
9159
+ }));
9160
+ }
9161
+ const nextInput = input ?? configSource;
9162
+ const resolved = await Effect50.runPromise(resolveConfigInput(nextInput));
9163
+ if (currentConfig !== undefined && stableKey(resolved) === stableKey(currentConfig)) {
9164
+ return {
9165
+ config: resolved,
9166
+ changed: { storeUrl: false, jobsUrl: false, rest: false },
9167
+ reloaded: false
9168
+ };
9169
+ }
9170
+ const changes = diffConfig(currentConfig, resolved);
9171
+ pause();
9172
+ try {
9173
+ const next = await bootStack({
9174
+ config: resolved,
9175
+ ...migrationFolder !== undefined ? { migrationFolder } : {}
9176
+ });
9177
+ await drainWithTimeout();
9178
+ const oldStoreUrl = currentConfig?.storeUrl;
9179
+ const oldJobsUrl = currentConfig?.jobs?.dbUrl;
9180
+ const old = current;
9181
+ current = next;
9182
+ currentConfig = resolved;
9183
+ await old.dispose();
9184
+ if (changes.storeUrl) {
9185
+ await Effect50.runPromise(Effect50.logWarning(`reload: store database switched "${oldStoreUrl}" → "${resolved.storeUrl}" — ` + "the old db is preserved but no longer served (nothing was migrated)").pipe(Effect50.annotateLogs({ scope: "@dimple/sdk", event: "reload.store.switch" })));
9186
+ }
9187
+ if (changes.jobsUrl) {
9188
+ await Effect50.runPromise(Effect50.logWarning(`reload: jobs database switched "${oldJobsUrl}" → "${resolved.jobs?.dbUrl}" — ` + "pending jobs remain durable in the old db").pipe(Effect50.annotateLogs({ scope: "@dimple/sdk", event: "reload.jobs.switch" })));
9189
+ }
9190
+ return { config: resolved, changed: changes, reloaded: true };
9191
+ } finally {
9192
+ resume();
9193
+ }
9194
+ });
9195
+ reloadChain = task.catch(() => {
9196
+ return;
9197
+ });
9198
+ return task.then((config) => config, (cause) => Promise.reject(remapError(cause)));
9199
+ };
9200
+ const dispose = () => {
9201
+ disposed = true;
9202
+ return reloadChain.then(() => current.dispose());
9203
+ };
9204
+ return makeFacade({
9205
+ run,
9206
+ core: () => current.core,
9207
+ parser: () => current.parser,
9208
+ extractor: () => current.extractor
9209
+ }, reload, dispose);
9210
+ };
8476
9211
  export {
8477
9212
  remapError,
8478
9213
  makeDimple,
8479
9214
  StoreFailure,
9215
+ ReloadError,
8480
9216
  NotFoundError,
8481
9217
  LLMNotConfiguredError,
8482
9218
  JobFailedError,
8483
9219
  InputError,
9220
+ EmbeddingFailure,
9221
+ EdgeNotFoundError,
8484
9222
  DuplicateKeyError,
8485
9223
  DuplicateEdgeError,
8486
9224
  ConfigError