@hanhnd/agent-kit 1.0.37 → 1.0.39

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/cli.js CHANGED
@@ -15036,6 +15036,9 @@ var DEFAULT_MEMORY_CONFIG = {
15036
15036
  var LOCK_RETRY_MS = 50;
15037
15037
  var LOCK_TIMEOUT_MS = 500;
15038
15038
  var RRF_K = 60;
15039
+ var FETCH_MULTIPLIER = 4;
15040
+ var RECENCY_WEIGHT = 0.5;
15041
+ var DENSE_SCORE_FLOOR = 0.2;
15039
15042
 
15040
15043
  // src/core/config/index.ts
15041
15044
  function resolveMemoryConfig(settings, workspaceRoot) {
@@ -15456,6 +15459,7 @@ var MemoryStore = class {
15456
15459
  return 0;
15457
15460
  }
15458
15461
  upsert(chunks, embeddings) {
15462
+ const deleteChunk = this.db.prepare(`DELETE FROM memory_chunks WHERE id = ?`);
15459
15463
  const insertChunk = this.db.prepare(`
15460
15464
  INSERT OR REPLACE INTO memory_chunks
15461
15465
  (id, source, source_type, heading, heading_level, content, line_start, line_end, indexed_at, file_mtime_at, embedding)
@@ -15466,7 +15470,7 @@ var MemoryStore = class {
15466
15470
  const chunk = chunks[i];
15467
15471
  try {
15468
15472
  const vectorBinding = this.toVectorBinding(embeddings[i]);
15469
- this.db.prepare(`DELETE FROM memory_chunks WHERE id = ?`).run(chunk.id);
15473
+ deleteChunk.run(chunk.id);
15470
15474
  insertChunk.run(
15471
15475
  chunk.id,
15472
15476
  chunk.source,
@@ -15495,15 +15499,10 @@ var MemoryStore = class {
15495
15499
  if (!embedding) throw new Error("missing embedding");
15496
15500
  if (!(embedding instanceof Float32Array)) throw new Error("missing embedding");
15497
15501
  if (embedding.length !== this.config.vectorDimension) throw new Error("embedding dimension mismatch");
15498
- const values = Array.from(embedding, (value) => {
15499
- if (!Number.isFinite(value)) throw new Error("embedding contains non-finite value");
15500
- return value;
15501
- });
15502
- return JSON.stringify(values);
15503
- }
15504
- normalizeVectorDistance(distance) {
15505
- if (distance <= 0) return 1;
15506
- return Math.max(0, Math.min(1, 1 - distance));
15502
+ for (let i = 0; i < embedding.length; i++) {
15503
+ if (!Number.isFinite(embedding[i])) throw new Error("embedding contains non-finite value");
15504
+ }
15505
+ return Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
15507
15506
  }
15508
15507
  hashesBySource(source) {
15509
15508
  const rows = this.db.prepare(`SELECT id FROM memory_chunks WHERE source = ?`).all(source);
@@ -15541,61 +15540,12 @@ var MemoryStore = class {
15541
15540
  });
15542
15541
  deleteAll();
15543
15542
  }
15544
- searchDense(embedding, limit) {
15545
- try {
15546
- const queryVector = this.toVectorBinding(embedding);
15547
- const rows = this.db.prepare(
15548
- `SELECT mc.id, vector_distance_cos(mc.embedding, vector32(?)) AS distance
15549
- FROM vector_top_k('idx_memory_chunks_embedding', vector32(?), ?) AS vector_matches
15550
- JOIN memory_chunks mc ON mc.rowid = vector_matches.id
15551
- ORDER BY distance
15552
- LIMIT ?`
15553
- ).all(queryVector, queryVector, limit, limit);
15554
- return rows.map((r) => ({
15555
- id: r.id,
15556
- score: this.normalizeVectorDistance(r.distance)
15557
- }));
15558
- } catch (err) {
15559
- console.warn("[memory-store] Dense search failed:", err);
15560
- return [];
15561
- }
15562
- }
15563
- searchBm25(query, limit) {
15564
- if (!query.trim()) return [];
15543
+ buildFtsQuery(query) {
15565
15544
  const tokens = query.trim().split(/\s+/).map((t) => t.replace(/[^a-zA-Z0-9_]/g, "").toLowerCase()).filter((t) => t.length > 1);
15566
- const ftsQuery = (0, import_stopword.removeStopwords)(tokens, import_stopword.eng).map((t) => `${t.replace(/s$/, "")}*`).join(" OR ");
15567
- if (!ftsQuery) return [];
15568
- try {
15569
- const rows = this.db.prepare(
15570
- `SELECT mc.id, bm25(memory_fts, 0.2, 2.0, 5.0) AS rank
15571
- FROM memory_fts
15572
- JOIN memory_chunks mc ON mc.rowid = memory_fts.rowid
15573
- WHERE memory_fts MATCH ?
15574
- ORDER BY rank
15575
- LIMIT ?`
15576
- ).all(ftsQuery, limit);
15577
- if (rows.length === 0) return [];
15578
- const ranks = rows.map((r) => r.rank);
15579
- const minRank = Math.min(...ranks);
15580
- const maxRank = Math.max(...ranks);
15581
- const range = maxRank - minRank;
15582
- return rows.map((r) => ({
15583
- id: r.id,
15584
- score: range > 0 ? (maxRank - r.rank) / range : 1
15585
- }));
15586
- } catch (err) {
15587
- console.warn("[memory-store] BM25 search failed:", err);
15588
- return [];
15589
- }
15545
+ return (0, import_stopword.removeStopwords)(tokens, import_stopword.eng).map((t) => `${t.replace(/s$/, "")}*`).join(" OR ");
15590
15546
  }
15591
- getChunksByIds(ids) {
15592
- if (ids.length === 0) return [];
15593
- const placeholders = ids.map(() => "?").join(", ");
15594
- const rows = this.db.prepare(
15595
- `SELECT id, source, source_type, heading, heading_level, content, line_start, line_end, file_mtime_at
15596
- FROM memory_chunks WHERE id IN (${placeholders})`
15597
- ).all(...ids);
15598
- return rows.map((r) => ({
15547
+ mapMemoryChunkRow(r) {
15548
+ return {
15599
15549
  id: r.id,
15600
15550
  source: r.source,
15601
15551
  sourceType: r.source_type,
@@ -15605,7 +15555,201 @@ var MemoryStore = class {
15605
15555
  lineStart: r.line_start,
15606
15556
  lineEnd: r.line_end,
15607
15557
  fileMtimeAt: r.file_mtime_at
15608
- }));
15558
+ };
15559
+ }
15560
+ searchHybrid(options) {
15561
+ const {
15562
+ query,
15563
+ embedding,
15564
+ topK,
15565
+ fetchLimit,
15566
+ denseScoreFloor,
15567
+ recencyWeight,
15568
+ rrfK,
15569
+ sourceType,
15570
+ sinceMtimeAt,
15571
+ includeDebug
15572
+ } = options;
15573
+ const ftsQuery = this.buildFtsQuery(query);
15574
+ const hasDense = embedding !== void 0;
15575
+ const hasBm25 = ftsQuery.length > 0;
15576
+ if (!hasDense && !hasBm25) return [];
15577
+ const hasFilters = sourceType !== void 0 || sinceMtimeAt !== void 0;
15578
+ try {
15579
+ const params = [];
15580
+ let denseCte = "";
15581
+ let denseRankedCte = "";
15582
+ if (hasDense) {
15583
+ const queryBuf = this.toVectorBinding(embedding);
15584
+ if (hasFilters) {
15585
+ const filterClauses = [];
15586
+ if (sourceType) {
15587
+ filterClauses.push("mc.source_type = ?");
15588
+ params.push(sourceType);
15589
+ }
15590
+ if (sinceMtimeAt !== void 0) {
15591
+ filterClauses.push("mc.file_mtime_at >= ?");
15592
+ params.push(sinceMtimeAt);
15593
+ }
15594
+ const whereClause = filterClauses.length > 0 ? `WHERE ${filterClauses.join(" AND ")}` : "";
15595
+ params.push(queryBuf);
15596
+ params.push(fetchLimit);
15597
+ denseCte = `dense_data AS (
15598
+ SELECT mc.id, mc.source, mc.source_type, mc.file_mtime_at,
15599
+ mc.heading, mc.heading_level, mc.content, mc.line_start, mc.line_end,
15600
+ vector_distance_cos(mc.embedding, vector32(?)) AS dense_dist
15601
+ FROM memory_chunks mc
15602
+ ${whereClause}
15603
+ ORDER BY dense_dist
15604
+ LIMIT ?
15605
+ )`;
15606
+ } else {
15607
+ params.push(queryBuf);
15608
+ params.push(queryBuf);
15609
+ params.push(fetchLimit);
15610
+ denseCte = `dense_data AS (
15611
+ SELECT mc.id, mc.source, mc.source_type, mc.file_mtime_at,
15612
+ mc.heading, mc.heading_level, mc.content, mc.line_start, mc.line_end,
15613
+ vector_distance_cos(mc.embedding, vector32(?)) AS dense_dist
15614
+ FROM vector_top_k('idx_memory_chunks_embedding', vector32(?), ?) AS vtk
15615
+ JOIN memory_chunks mc ON mc.rowid = vtk.id
15616
+ )`;
15617
+ }
15618
+ denseRankedCte = `dense_ranked AS (
15619
+ SELECT *, ROW_NUMBER() OVER (ORDER BY dense_dist, id) AS dense_rank
15620
+ FROM dense_data
15621
+ )`;
15622
+ }
15623
+ let bm25Cte = "";
15624
+ let bm25RankedCte = "";
15625
+ if (hasBm25) {
15626
+ const bm25FilterClauses = [];
15627
+ if (sourceType) bm25FilterClauses.push("mc.source_type = ?");
15628
+ if (sinceMtimeAt !== void 0) bm25FilterClauses.push("mc.file_mtime_at >= ?");
15629
+ const bm25WhereExtra = bm25FilterClauses.length > 0 ? ` AND ${bm25FilterClauses.join(" AND ")}` : "";
15630
+ params.push(ftsQuery);
15631
+ if (sourceType) params.push(sourceType);
15632
+ if (sinceMtimeAt !== void 0) params.push(sinceMtimeAt);
15633
+ params.push(fetchLimit);
15634
+ bm25Cte = `bm25_data AS (
15635
+ SELECT mc.id, mc.source, mc.source_type, mc.file_mtime_at,
15636
+ mc.heading, mc.heading_level, mc.content, mc.line_start, mc.line_end,
15637
+ bm25(memory_fts, 0.2, 2.0, 5.0) AS bm25_neg
15638
+ FROM memory_fts
15639
+ JOIN memory_chunks mc ON mc.rowid = memory_fts.rowid
15640
+ WHERE memory_fts MATCH ?${bm25WhereExtra}
15641
+ LIMIT ?
15642
+ )`;
15643
+ bm25RankedCte = `bm25_ranked AS (
15644
+ SELECT *, ROW_NUMBER() OVER (ORDER BY bm25_neg, id) AS bm25_rank
15645
+ FROM bm25_data
15646
+ )`;
15647
+ }
15648
+ let candidatesCte = "";
15649
+ if (hasDense && hasBm25) {
15650
+ candidatesCte = `all_candidates AS (
15651
+ SELECT d.id, d.source, d.source_type, d.file_mtime_at,
15652
+ d.heading, d.heading_level, d.content, d.line_start, d.line_end,
15653
+ CAST(d.dense_rank AS INTEGER) AS dense_rank, d.dense_dist,
15654
+ b.bm25_rank,
15655
+ CASE WHEN b.id IS NOT NULL THEN 'both' ELSE 'dense' END AS retriever
15656
+ FROM dense_ranked d
15657
+ LEFT JOIN bm25_ranked b ON b.id = d.id
15658
+ UNION ALL
15659
+ SELECT b.id, b.source, b.source_type, b.file_mtime_at,
15660
+ b.heading, b.heading_level, b.content, b.line_start, b.line_end,
15661
+ NULL AS dense_rank, NULL AS dense_dist,
15662
+ CAST(b.bm25_rank AS INTEGER) AS bm25_rank,
15663
+ 'bm25' AS retriever
15664
+ FROM bm25_ranked b
15665
+ LEFT JOIN dense_ranked d ON d.id = b.id
15666
+ WHERE d.id IS NULL
15667
+ )`;
15668
+ } else if (hasDense) {
15669
+ candidatesCte = `all_candidates AS (
15670
+ SELECT id, source, source_type, file_mtime_at,
15671
+ heading, heading_level, content, line_start, line_end,
15672
+ CAST(dense_rank AS INTEGER) AS dense_rank, dense_dist,
15673
+ NULL AS bm25_rank, 'dense' AS retriever
15674
+ FROM dense_ranked
15675
+ )`;
15676
+ } else {
15677
+ candidatesCte = `all_candidates AS (
15678
+ SELECT id, source, source_type, file_mtime_at,
15679
+ heading, heading_level, content, line_start, line_end,
15680
+ NULL AS dense_rank, NULL AS dense_dist,
15681
+ CAST(bm25_rank AS INTEGER) AS bm25_rank, 'bm25' AS retriever
15682
+ FROM bm25_ranked
15683
+ )`;
15684
+ }
15685
+ params.push(rrfK, rrfK, recencyWeight, rrfK, denseScoreFloor, topK);
15686
+ const sql = `
15687
+ WITH
15688
+ ${[denseCte, denseRankedCte, bm25Cte, bm25RankedCte, candidatesCte].filter(Boolean).join(",\n")}
15689
+ ,
15690
+ recency_ranked AS (
15691
+ SELECT id, ROW_NUMBER() OVER (ORDER BY file_mtime_at DESC, source, id) AS recency_rank
15692
+ FROM all_candidates
15693
+ ),
15694
+ scored AS (
15695
+ SELECT c.id, c.source, c.source_type, c.file_mtime_at,
15696
+ c.heading, c.heading_level, c.content, c.line_start, c.line_end,
15697
+ c.dense_rank, c.dense_dist, c.bm25_rank, r.recency_rank,
15698
+ c.retriever,
15699
+ COALESCE(1.0 / (? + c.dense_rank), 0.0) +
15700
+ COALESCE(1.0 / (? + c.bm25_rank), 0.0) +
15701
+ ? * (1.0 / (? + r.recency_rank)) AS total_score
15702
+ FROM all_candidates c
15703
+ JOIN recency_ranked r ON r.id = c.id
15704
+ WHERE c.retriever != 'dense' OR (1.0 - c.dense_dist) >= ?
15705
+ ),
15706
+ best_per_source AS (
15707
+ SELECT *,
15708
+ ROW_NUMBER() OVER (PARTITION BY source ORDER BY total_score DESC, source, id) AS source_rank
15709
+ FROM scored
15710
+ )
15711
+ SELECT id, source, source_type, file_mtime_at, heading, heading_level, content, line_start, line_end,
15712
+ dense_rank, dense_dist, bm25_rank, recency_rank, total_score, retriever
15713
+ FROM best_per_source
15714
+ WHERE source_rank = 1
15715
+ ORDER BY total_score DESC, source, id
15716
+ LIMIT ?
15717
+ `;
15718
+ const rows = this.db.prepare(sql).all(...params);
15719
+ if (rows.length === 0) return [];
15720
+ const denseActive = hasDense;
15721
+ const bm25Active = hasBm25;
15722
+ const recencyActive = rows.length > 0;
15723
+ const maxScore = ((denseActive ? 1 : 0) + (bm25Active ? 1 : 0) + (recencyActive ? recencyWeight : 0)) / (rrfK + 1);
15724
+ return rows.map((r) => {
15725
+ const chunk = this.mapMemoryChunkRow(r);
15726
+ const normalizedScore = maxScore > 0 ? r.total_score / maxScore : 0;
15727
+ const row = {
15728
+ chunk,
15729
+ score: normalizedScore,
15730
+ retriever: r.retriever
15731
+ };
15732
+ if (includeDebug) {
15733
+ const denseScore = r.dense_dist !== null ? Math.max(0, Math.min(1, 1 - r.dense_dist)) : void 0;
15734
+ const bm25Score = r.bm25_rank !== null ? 1 / (rrfK + r.bm25_rank) : void 0;
15735
+ const recencyScore = recencyWeight * (1 / (rrfK + r.recency_rank));
15736
+ const debug = {
15737
+ denseRank: r.dense_rank ?? void 0,
15738
+ denseScore,
15739
+ bm25Rank: r.bm25_rank ?? void 0,
15740
+ bm25Score,
15741
+ recencyRank: r.recency_rank,
15742
+ recencyScore,
15743
+ totalScore: r.total_score
15744
+ };
15745
+ row.debug = debug;
15746
+ }
15747
+ return row;
15748
+ });
15749
+ } catch (err) {
15750
+ console.warn("[memory-store] Hybrid search failed:", err);
15751
+ return [];
15752
+ }
15609
15753
  }
15610
15754
  get vecAvailable() {
15611
15755
  return true;
@@ -15775,14 +15919,14 @@ var MemoryIndexer = class {
15775
15919
  const existingChunkIds = this.store.hashesBySource(source);
15776
15920
  let text;
15777
15921
  try {
15778
- text = fs6.readFileSync(absolutePath, "utf8");
15922
+ text = await fs6.promises.readFile(absolutePath, "utf8");
15779
15923
  } catch (err) {
15780
15924
  console.warn(`[memory-indexer] Cannot read file ${absolutePath}:`, err);
15781
15925
  return { indexed: 0, deleted: 0, skipped: existingChunkIds.size };
15782
15926
  }
15783
15927
  let fileMtimeAt;
15784
15928
  try {
15785
- fileMtimeAt = fs6.statSync(absolutePath).mtimeMs;
15929
+ fileMtimeAt = (await fs6.promises.stat(absolutePath)).mtimeMs;
15786
15930
  } catch (err) {
15787
15931
  console.warn(`[memory-indexer] Cannot stat file ${absolutePath}:`, err);
15788
15932
  fileMtimeAt = Date.now();
@@ -15815,22 +15959,30 @@ var MemoryIndexer = class {
15815
15959
  }
15816
15960
  async indexDirectory(rootDir, opts = {}) {
15817
15961
  const totals = { indexed: 0, deleted: 0, skipped: 0 };
15818
- if (!fs6.existsSync(rootDir)) return totals;
15962
+ let rootExists;
15963
+ try {
15964
+ await fs6.promises.stat(rootDir);
15965
+ rootExists = true;
15966
+ } catch {
15967
+ rootExists = false;
15968
+ }
15969
+ if (!rootExists) return totals;
15819
15970
  const relativeBase = opts.relativeBase ?? this.config.wikiDir;
15820
15971
  const excludeFiles = new Set(opts.excludeFiles ?? []);
15821
15972
  const files = [];
15822
- const walk = (dirPath) => {
15973
+ const walk = async (dirPath) => {
15823
15974
  let entries;
15824
15975
  try {
15825
- entries = fs6.readdirSync(dirPath, { withFileTypes: true });
15976
+ entries = await fs6.promises.readdir(dirPath, { withFileTypes: true });
15826
15977
  } catch (err) {
15827
15978
  console.warn(`[memory-indexer] Cannot scan directory ${dirPath}:`, err);
15828
15979
  return;
15829
15980
  }
15981
+ entries.sort((a, b) => a.name.localeCompare(b.name));
15830
15982
  for (const entry of entries) {
15831
15983
  const entryPath = path8.join(dirPath, entry.name);
15832
15984
  if (entry.isDirectory()) {
15833
- walk(entryPath);
15985
+ await walk(entryPath);
15834
15986
  continue;
15835
15987
  }
15836
15988
  if (entry.isFile() && /\.md$/i.test(entry.name) && !excludeFiles.has(entry.name)) {
@@ -15838,7 +15990,7 @@ var MemoryIndexer = class {
15838
15990
  }
15839
15991
  }
15840
15992
  };
15841
- walk(rootDir);
15993
+ await walk(rootDir);
15842
15994
  const currentSources = new Set(files.map((file) => path8.relative(relativeBase, file)));
15843
15995
  for (const stale of this.store.indexedSources()) {
15844
15996
  if (!currentSources.has(stale)) {
@@ -15855,58 +16007,35 @@ var MemoryIndexer = class {
15855
16007
  return totals;
15856
16008
  }
15857
16009
  async search(query, topK) {
15858
- const fetchLimit = topK * 2;
15859
- let denseResults = [];
16010
+ const fetchLimit = topK * FETCH_MULTIPLIER;
16011
+ let embedding;
15860
16012
  if (this.store.vecAvailable) {
15861
16013
  try {
15862
- const queryEmbedding = await this.embedder.embed([query]);
15863
- denseResults = this.store.searchDense(queryEmbedding[0], fetchLimit);
16014
+ const embeddings = await this.embedder.embed([query]);
16015
+ embedding = embeddings[0];
15864
16016
  } catch (err) {
15865
16017
  console.warn("[memory-indexer] Dense search embedding failed:", err);
15866
16018
  }
15867
16019
  }
15868
- const bm25Results = this.store.searchBm25(query, fetchLimit);
15869
- const bm25Ids = new Set(bm25Results.map((result) => result.id));
15870
- if (bm25Ids.size > 0) {
15871
- denseResults = denseResults.filter((result) => bm25Ids.has(result.id));
15872
- }
15873
- const scoreMap = /* @__PURE__ */ new Map();
15874
- denseResults.forEach((r, rank) => {
15875
- scoreMap.set(r.id, { dense: 1 / (RRF_K + rank + 1), bm25: 0 });
15876
- });
15877
- bm25Results.forEach((r, rank) => {
15878
- const entry = scoreMap.get(r.id) ?? { dense: 0, bm25: 0 };
15879
- entry.bm25 = 1 / (RRF_K + rank + 1);
15880
- scoreMap.set(r.id, entry);
16020
+ const rows = this.store.searchHybrid({
16021
+ query,
16022
+ embedding,
16023
+ topK,
16024
+ fetchLimit,
16025
+ denseScoreFloor: DENSE_SCORE_FLOOR,
16026
+ recencyWeight: RECENCY_WEIGHT,
16027
+ rrfK: RRF_K
15881
16028
  });
15882
- const hasDense = this.store.vecAvailable && denseResults.length > 0;
15883
- const numRetrievers = hasDense ? 2 : 1;
15884
- const maxScore = numRetrievers / (RRF_K + 1);
15885
- const ranked = [...scoreMap.entries()].map(([id, scores]) => ({
15886
- id,
15887
- totalScore: scores.dense + scores.bm25,
15888
- hasDense: scores.dense > 0,
15889
- hasBm25: scores.bm25 > 0
15890
- })).sort((a, b) => b.totalScore - a.totalScore);
15891
- const chunks = this.store.getChunksByIds(ranked.map((r) => r.id));
15892
- const chunkMap = new Map(chunks.map((c) => [c.id, c]));
16029
+ if (rows.length === 0) return [];
15893
16030
  const results = [];
15894
- const seenSources = /* @__PURE__ */ new Set();
15895
- for (const r of ranked) {
15896
- if (results.length >= topK) break;
15897
- const chunk = chunkMap.get(r.id);
15898
- if (!chunk) continue;
15899
- if (seenSources.has(chunk.source)) continue;
15900
- seenSources.add(chunk.source);
15901
- const normalizedScore = maxScore > 0 ? r.totalScore / maxScore : 0;
15902
- const retriever = r.hasDense && r.hasBm25 ? "both" : r.hasDense ? "dense" : "bm25";
16031
+ for (const row of rows) {
15903
16032
  let contentSource = "file";
15904
16033
  try {
15905
- chunk.content = fs6.readFileSync(path8.join(this.config.wikiDir, chunk.source), "utf8");
16034
+ row.chunk.content = await fs6.promises.readFile(path8.join(this.config.wikiDir, row.chunk.source), "utf8");
15906
16035
  } catch {
15907
16036
  contentSource = "fallback";
15908
16037
  }
15909
- results.push({ chunk, score: normalizedScore, retriever, contentSource });
16038
+ results.push({ chunk: row.chunk, score: row.score, retriever: row.retriever, contentSource });
15910
16039
  }
15911
16040
  return results;
15912
16041
  }
@@ -15915,13 +16044,13 @@ var MemoryIndexer = class {
15915
16044
  const rawDir = path8.join(this.config.wikiDir, "raw");
15916
16045
  const savePath = path8.join(rawDir, `conv_save_${datePart}.md`);
15917
16046
  const lockPath = `${savePath}.lock`;
15918
- fs6.mkdirSync(rawDir, { recursive: true });
16047
+ await fs6.promises.mkdir(rawDir, { recursive: true });
15919
16048
  const acquired = await acquireLock(lockPath, LOCK_TIMEOUT_MS, LOCK_RETRY_MS);
15920
16049
  if (!acquired) {
15921
16050
  console.warn("[memory-indexer] Could not acquire write lock, writing anyway (fail-open)");
15922
16051
  }
15923
16052
  try {
15924
- fs6.appendFileSync(savePath, `
16053
+ await fs6.promises.appendFile(savePath, `
15925
16054
  ${content}
15926
16055
  `, "utf8");
15927
16056
  } finally {
@@ -51,7 +51,7 @@ function registerMemoryToolHandlers(server, indexer, store, config, workspaceRoo
51
51
  const blocks = [];
52
52
  for (const row of rows) {
53
53
  try {
54
- const content = fs.readFileSync(path.join(config.wikiDir, row.source), 'utf8');
54
+ const content = await fs.promises.readFile(path.join(config.wikiDir, row.source), 'utf8');
55
55
  const displaySource = row.source.replace(/^compiled\//, '');
56
56
  blocks.push(`### ${displaySource}\n${content}`);
57
57
  }