@hanhnd/agent-kit 1.0.38 → 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
@@ -15504,10 +15504,6 @@ var MemoryStore = class {
15504
15504
  }
15505
15505
  return Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
15506
15506
  }
15507
- normalizeVectorDistance(distance) {
15508
- if (distance <= 0) return 1;
15509
- return Math.max(0, Math.min(1, 1 - distance));
15510
- }
15511
15507
  hashesBySource(source) {
15512
15508
  const rows = this.db.prepare(`SELECT id FROM memory_chunks WHERE source = ?`).all(source);
15513
15509
  return new Set(rows.map((r) => r.id));
@@ -15544,61 +15540,12 @@ var MemoryStore = class {
15544
15540
  });
15545
15541
  deleteAll();
15546
15542
  }
15547
- searchDense(embedding, limit) {
15548
- try {
15549
- const queryVector = this.toVectorBinding(embedding);
15550
- const rows = this.db.prepare(
15551
- `SELECT mc.id, vector_distance_cos(mc.embedding, vector32(?)) AS distance
15552
- FROM vector_top_k('idx_memory_chunks_embedding', vector32(?), ?) AS vector_matches
15553
- JOIN memory_chunks mc ON mc.rowid = vector_matches.id
15554
- ORDER BY distance
15555
- LIMIT ?`
15556
- ).all(queryVector, queryVector, limit, limit);
15557
- return rows.map((r) => ({
15558
- id: r.id,
15559
- score: this.normalizeVectorDistance(r.distance)
15560
- }));
15561
- } catch (err) {
15562
- console.warn("[memory-store] Dense search failed:", err);
15563
- return [];
15564
- }
15565
- }
15566
- searchBm25(query, limit) {
15567
- if (!query.trim()) return [];
15543
+ buildFtsQuery(query) {
15568
15544
  const tokens = query.trim().split(/\s+/).map((t) => t.replace(/[^a-zA-Z0-9_]/g, "").toLowerCase()).filter((t) => t.length > 1);
15569
- const ftsQuery = (0, import_stopword.removeStopwords)(tokens, import_stopword.eng).map((t) => `${t.replace(/s$/, "")}*`).join(" OR ");
15570
- if (!ftsQuery) return [];
15571
- try {
15572
- const rows = this.db.prepare(
15573
- `SELECT mc.id, bm25(memory_fts, 0.2, 2.0, 5.0) AS rank
15574
- FROM memory_fts
15575
- JOIN memory_chunks mc ON mc.rowid = memory_fts.rowid
15576
- WHERE memory_fts MATCH ?
15577
- ORDER BY rank
15578
- LIMIT ?`
15579
- ).all(ftsQuery, limit);
15580
- if (rows.length === 0) return [];
15581
- const ranks = rows.map((r) => r.rank);
15582
- const minRank = Math.min(...ranks);
15583
- const maxRank = Math.max(...ranks);
15584
- const range = maxRank - minRank;
15585
- return rows.map((r) => ({
15586
- id: r.id,
15587
- score: range > 0 ? (maxRank - r.rank) / range : 1
15588
- }));
15589
- } catch (err) {
15590
- console.warn("[memory-store] BM25 search failed:", err);
15591
- return [];
15592
- }
15545
+ return (0, import_stopword.removeStopwords)(tokens, import_stopword.eng).map((t) => `${t.replace(/s$/, "")}*`).join(" OR ");
15593
15546
  }
15594
- getChunksByIds(ids) {
15595
- if (ids.length === 0) return [];
15596
- const placeholders = ids.map(() => "?").join(", ");
15597
- const rows = this.db.prepare(
15598
- `SELECT id, source, source_type, heading, heading_level, content, line_start, line_end, file_mtime_at
15599
- FROM memory_chunks WHERE id IN (${placeholders})`
15600
- ).all(...ids);
15601
- return rows.map((r) => ({
15547
+ mapMemoryChunkRow(r) {
15548
+ return {
15602
15549
  id: r.id,
15603
15550
  source: r.source,
15604
15551
  sourceType: r.source_type,
@@ -15608,7 +15555,201 @@ var MemoryStore = class {
15608
15555
  lineStart: r.line_start,
15609
15556
  lineEnd: r.line_end,
15610
15557
  fileMtimeAt: r.file_mtime_at
15611
- }));
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
+ }
15612
15753
  }
15613
15754
  get vecAvailable() {
15614
15755
  return true;
@@ -15778,14 +15919,14 @@ var MemoryIndexer = class {
15778
15919
  const existingChunkIds = this.store.hashesBySource(source);
15779
15920
  let text;
15780
15921
  try {
15781
- text = fs6.readFileSync(absolutePath, "utf8");
15922
+ text = await fs6.promises.readFile(absolutePath, "utf8");
15782
15923
  } catch (err) {
15783
15924
  console.warn(`[memory-indexer] Cannot read file ${absolutePath}:`, err);
15784
15925
  return { indexed: 0, deleted: 0, skipped: existingChunkIds.size };
15785
15926
  }
15786
15927
  let fileMtimeAt;
15787
15928
  try {
15788
- fileMtimeAt = fs6.statSync(absolutePath).mtimeMs;
15929
+ fileMtimeAt = (await fs6.promises.stat(absolutePath)).mtimeMs;
15789
15930
  } catch (err) {
15790
15931
  console.warn(`[memory-indexer] Cannot stat file ${absolutePath}:`, err);
15791
15932
  fileMtimeAt = Date.now();
@@ -15818,22 +15959,30 @@ var MemoryIndexer = class {
15818
15959
  }
15819
15960
  async indexDirectory(rootDir, opts = {}) {
15820
15961
  const totals = { indexed: 0, deleted: 0, skipped: 0 };
15821
- 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;
15822
15970
  const relativeBase = opts.relativeBase ?? this.config.wikiDir;
15823
15971
  const excludeFiles = new Set(opts.excludeFiles ?? []);
15824
15972
  const files = [];
15825
- const walk = (dirPath) => {
15973
+ const walk = async (dirPath) => {
15826
15974
  let entries;
15827
15975
  try {
15828
- entries = fs6.readdirSync(dirPath, { withFileTypes: true });
15976
+ entries = await fs6.promises.readdir(dirPath, { withFileTypes: true });
15829
15977
  } catch (err) {
15830
15978
  console.warn(`[memory-indexer] Cannot scan directory ${dirPath}:`, err);
15831
15979
  return;
15832
15980
  }
15981
+ entries.sort((a, b) => a.name.localeCompare(b.name));
15833
15982
  for (const entry of entries) {
15834
15983
  const entryPath = path8.join(dirPath, entry.name);
15835
15984
  if (entry.isDirectory()) {
15836
- walk(entryPath);
15985
+ await walk(entryPath);
15837
15986
  continue;
15838
15987
  }
15839
15988
  if (entry.isFile() && /\.md$/i.test(entry.name) && !excludeFiles.has(entry.name)) {
@@ -15841,7 +15990,7 @@ var MemoryIndexer = class {
15841
15990
  }
15842
15991
  }
15843
15992
  };
15844
- walk(rootDir);
15993
+ await walk(rootDir);
15845
15994
  const currentSources = new Set(files.map((file) => path8.relative(relativeBase, file)));
15846
15995
  for (const stale of this.store.indexedSources()) {
15847
15996
  if (!currentSources.has(stale)) {
@@ -15859,77 +16008,34 @@ var MemoryIndexer = class {
15859
16008
  }
15860
16009
  async search(query, topK) {
15861
16010
  const fetchLimit = topK * FETCH_MULTIPLIER;
15862
- let denseResults = [];
16011
+ let embedding;
15863
16012
  if (this.store.vecAvailable) {
15864
16013
  try {
15865
- const queryEmbedding = await this.embedder.embed([query]);
15866
- denseResults = this.store.searchDense(queryEmbedding[0], fetchLimit);
16014
+ const embeddings = await this.embedder.embed([query]);
16015
+ embedding = embeddings[0];
15867
16016
  } catch (err) {
15868
16017
  console.warn("[memory-indexer] Dense search embedding failed:", err);
15869
16018
  }
15870
16019
  }
15871
- const bm25Results = this.store.searchBm25(query, fetchLimit);
15872
- const denseIds = new Set(denseResults.map((r) => r.id));
15873
- const bm25Ids = new Set(bm25Results.map((r) => r.id));
15874
- const unionIds = /* @__PURE__ */ new Set([...denseIds, ...bm25Ids]);
15875
- const denseRetrievalScore = new Map(denseResults.map((r) => [r.id, r.score]));
15876
- const unionArray = [...unionIds];
15877
- const chunks = this.store.getChunksByIds(unionArray);
15878
- const chunkMap = new Map(chunks.map((c) => [c.id, c]));
15879
- const unionIndexOf = new Map(unionArray.map((id, idx) => [id, idx]));
15880
- const recencyOrdered = [...unionArray].sort((a, b) => {
15881
- const mtimeA = chunkMap.get(a)?.fileMtimeAt ?? 0;
15882
- const mtimeB = chunkMap.get(b)?.fileMtimeAt ?? 0;
15883
- if (mtimeB !== mtimeA) return mtimeB - mtimeA;
15884
- return (unionIndexOf.get(a) ?? 0) - (unionIndexOf.get(b) ?? 0);
15885
- });
15886
- const scoreMap = /* @__PURE__ */ new Map();
15887
- denseResults.forEach((r, rank) => {
15888
- scoreMap.set(r.id, { dense: 1 / (RRF_K + rank + 1), bm25: 0, recency: 0 });
15889
- });
15890
- bm25Results.forEach((r, rank) => {
15891
- const entry = scoreMap.get(r.id) ?? { dense: 0, bm25: 0, recency: 0 };
15892
- entry.bm25 = 1 / (RRF_K + rank + 1);
15893
- scoreMap.set(r.id, entry);
15894
- });
15895
- recencyOrdered.forEach((id, rank) => {
15896
- const entry = scoreMap.get(id);
15897
- if (!entry) return;
15898
- entry.recency = RECENCY_WEIGHT * (1 / (RRF_K + rank + 1));
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
15899
16028
  });
15900
- const denseActive = denseResults.length > 0;
15901
- const bm25Active = bm25Results.length > 0;
15902
- const recencyActive = unionIds.size > 0;
15903
- const maxScore = ((denseActive ? 1 : 0) + (bm25Active ? 1 : 0) + (recencyActive ? RECENCY_WEIGHT : 0)) / (RRF_K + 1);
15904
- const ranked = [...scoreMap.entries()].map(([id, scores]) => ({
15905
- id,
15906
- totalScore: scores.dense + scores.bm25 + scores.recency,
15907
- hasDense: scores.dense > 0,
15908
- hasBm25: scores.bm25 > 0,
15909
- isDenseOnly: denseIds.has(id) && !bm25Ids.has(id)
15910
- })).filter((candidate) => {
15911
- if (!candidate.isDenseOnly) return true;
15912
- const retrieval = denseRetrievalScore.get(candidate.id);
15913
- if (retrieval === void 0) return true;
15914
- return retrieval >= DENSE_SCORE_FLOOR;
15915
- }).sort((a, b) => b.totalScore - a.totalScore);
16029
+ if (rows.length === 0) return [];
15916
16030
  const results = [];
15917
- const seenSources = /* @__PURE__ */ new Set();
15918
- for (const r of ranked) {
15919
- if (results.length >= topK) break;
15920
- const chunk = chunkMap.get(r.id);
15921
- if (!chunk) continue;
15922
- if (seenSources.has(chunk.source)) continue;
15923
- seenSources.add(chunk.source);
15924
- const normalizedScore = maxScore > 0 ? r.totalScore / maxScore : 0;
15925
- const retriever = r.hasDense && r.hasBm25 ? "both" : r.hasDense ? "dense" : "bm25";
16031
+ for (const row of rows) {
15926
16032
  let contentSource = "file";
15927
16033
  try {
15928
- 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");
15929
16035
  } catch {
15930
16036
  contentSource = "fallback";
15931
16037
  }
15932
- results.push({ chunk, score: normalizedScore, retriever, contentSource });
16038
+ results.push({ chunk: row.chunk, score: row.score, retriever: row.retriever, contentSource });
15933
16039
  }
15934
16040
  return results;
15935
16041
  }
@@ -15938,13 +16044,13 @@ var MemoryIndexer = class {
15938
16044
  const rawDir = path8.join(this.config.wikiDir, "raw");
15939
16045
  const savePath = path8.join(rawDir, `conv_save_${datePart}.md`);
15940
16046
  const lockPath = `${savePath}.lock`;
15941
- fs6.mkdirSync(rawDir, { recursive: true });
16047
+ await fs6.promises.mkdir(rawDir, { recursive: true });
15942
16048
  const acquired = await acquireLock(lockPath, LOCK_TIMEOUT_MS, LOCK_RETRY_MS);
15943
16049
  if (!acquired) {
15944
16050
  console.warn("[memory-indexer] Could not acquire write lock, writing anyway (fail-open)");
15945
16051
  }
15946
16052
  try {
15947
- fs6.appendFileSync(savePath, `
16053
+ await fs6.promises.appendFile(savePath, `
15948
16054
  ${content}
15949
16055
  `, "utf8");
15950
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
  }