diff --git a/.claude/worktrees/agent-a09c3d7c b/.claude/worktrees/agent-a09c3d7c new file mode 160000 index 0000000..a1e75e6 --- /dev/null +++ b/.claude/worktrees/agent-a09c3d7c @@ -0,0 +1 @@ +Subproject commit a1e75e6b76f9ada4369d969c397d6c5aed6255f6 diff --git a/.claude/worktrees/agent-a1c56452 b/.claude/worktrees/agent-a1c56452 new file mode 160000 index 0000000..5f35206 --- /dev/null +++ b/.claude/worktrees/agent-a1c56452 @@ -0,0 +1 @@ +Subproject commit 5f3520658d949372276bf27be0aa8b2ff0d9193c diff --git a/.claude/worktrees/agent-a327b0c2 b/.claude/worktrees/agent-a327b0c2 new file mode 160000 index 0000000..b61747f --- /dev/null +++ b/.claude/worktrees/agent-a327b0c2 @@ -0,0 +1 @@ +Subproject commit b61747fb164909e9d8d79955685eb4d130f9dbe9 diff --git a/.claude/worktrees/agent-a462be71/.claude/worktrees/agent-a5e65331 b/.claude/worktrees/agent-a462be71/.claude/worktrees/agent-a5e65331 new file mode 160000 index 0000000..061b963 --- /dev/null +++ b/.claude/worktrees/agent-a462be71/.claude/worktrees/agent-a5e65331 @@ -0,0 +1 @@ +Subproject commit 061b96336a0a1971e82737d42b3278a38eb576c6 diff --git a/.claude/worktrees/agent-a51ac752 b/.claude/worktrees/agent-a51ac752 new file mode 160000 index 0000000..13c9a44 --- /dev/null +++ b/.claude/worktrees/agent-a51ac752 @@ -0,0 +1 @@ +Subproject commit 13c9a44967435ac4e4dd08680597602c127b2985 diff --git a/.claude/worktrees/agent-a662428a b/.claude/worktrees/agent-a662428a new file mode 160000 index 0000000..edf3802 --- /dev/null +++ b/.claude/worktrees/agent-a662428a @@ -0,0 +1 @@ +Subproject commit edf380273e98942deeec978d9e773e73ec96a02c diff --git a/.claude/worktrees/agent-a802499a b/.claude/worktrees/agent-a802499a new file mode 160000 index 0000000..3f27bbf --- /dev/null +++ b/.claude/worktrees/agent-a802499a @@ -0,0 +1 @@ +Subproject commit 3f27bbf38eaad7bb9d9a0f7191abc62e7b8ce685 diff --git a/.claude/worktrees/agent-a886725f b/.claude/worktrees/agent-a886725f new file mode 160000 index 0000000..bf6e1be --- /dev/null +++ b/.claude/worktrees/agent-a886725f @@ -0,0 +1 @@ +Subproject commit bf6e1bebc6d6d30bb553cfdb98801488efae56bf diff --git a/.claude/worktrees/agent-a8d7741c b/.claude/worktrees/agent-a8d7741c new file mode 160000 index 0000000..7f4c339 --- /dev/null +++ b/.claude/worktrees/agent-a8d7741c @@ -0,0 +1 @@ +Subproject commit 7f4c33968fcc94268e1281b0b23335abc5c2ea85 diff --git a/.claude/worktrees/agent-a8f0cb8d b/.claude/worktrees/agent-a8f0cb8d new file mode 160000 index 0000000..49f9da1 --- /dev/null +++ b/.claude/worktrees/agent-a8f0cb8d @@ -0,0 +1 @@ +Subproject commit 49f9da196c3650575eba0f1ab93aac8efe327137 diff --git a/.claude/worktrees/agent-ab455ec1 b/.claude/worktrees/agent-ab455ec1 new file mode 160000 index 0000000..5f35206 --- /dev/null +++ b/.claude/worktrees/agent-ab455ec1 @@ -0,0 +1 @@ +Subproject commit 5f3520658d949372276bf27be0aa8b2ff0d9193c diff --git a/.claude/worktrees/agent-abffe11f b/.claude/worktrees/agent-abffe11f new file mode 160000 index 0000000..bf6e1be --- /dev/null +++ b/.claude/worktrees/agent-abffe11f @@ -0,0 +1 @@ +Subproject commit bf6e1bebc6d6d30bb553cfdb98801488efae56bf diff --git a/.claude/worktrees/agent-aca555c0 b/.claude/worktrees/agent-aca555c0 new file mode 160000 index 0000000..f8ae7c2 --- /dev/null +++ b/.claude/worktrees/agent-aca555c0 @@ -0,0 +1 @@ +Subproject commit f8ae7c2badfc4b27660afcc3dec28a23c0b723cf diff --git a/.claude/worktrees/agent-af29ee1c b/.claude/worktrees/agent-af29ee1c new file mode 160000 index 0000000..015b533 --- /dev/null +++ b/.claude/worktrees/agent-af29ee1c @@ -0,0 +1 @@ +Subproject commit 015b533d3e65b8e3e7ca866c3265b0627dca0ca6 diff --git a/.claude/worktrees/agent-af8e138d b/.claude/worktrees/agent-af8e138d new file mode 160000 index 0000000..bf6e1be --- /dev/null +++ b/.claude/worktrees/agent-af8e138d @@ -0,0 +1 @@ +Subproject commit bf6e1bebc6d6d30bb553cfdb98801488efae56bf diff --git a/.claude/worktrees/feat-integration-tests b/.claude/worktrees/feat-integration-tests new file mode 160000 index 0000000..3c5364e --- /dev/null +++ b/.claude/worktrees/feat-integration-tests @@ -0,0 +1 @@ +Subproject commit 3c5364e49e2a7071eb68167c72efa8b0e0b058d4 diff --git a/.claude/worktrees/feat-provider-tests b/.claude/worktrees/feat-provider-tests new file mode 160000 index 0000000..35faeb1 --- /dev/null +++ b/.claude/worktrees/feat-provider-tests @@ -0,0 +1 @@ +Subproject commit 35faeb1247ab25ce9914e1ef2041ceeeb69f0d19 diff --git a/.claude/worktrees/feat-spider b/.claude/worktrees/feat-spider new file mode 160000 index 0000000..71b1cab --- /dev/null +++ b/.claude/worktrees/feat-spider @@ -0,0 +1 @@ +Subproject commit 71b1cab873f737d746a0bfd9d5d2bc6d57d86cc1 diff --git a/.claude/worktrees/fix-error-handling b/.claude/worktrees/fix-error-handling new file mode 160000 index 0000000..f1c1b19 --- /dev/null +++ b/.claude/worktrees/fix-error-handling @@ -0,0 +1 @@ +Subproject commit f1c1b19de378ca5382785485cbd522c4ccb14500 diff --git a/.claude/worktrees/fix-performance b/.claude/worktrees/fix-performance new file mode 160000 index 0000000..9a33416 --- /dev/null +++ b/.claude/worktrees/fix-performance @@ -0,0 +1 @@ +Subproject commit 9a33416de914953a21ef7725666c016fc309a9e5 diff --git a/.claude/worktrees/fix-quick-wins b/.claude/worktrees/fix-quick-wins new file mode 160000 index 0000000..9ac1262 --- /dev/null +++ b/.claude/worktrees/fix-quick-wins @@ -0,0 +1 @@ +Subproject commit 9ac1262e9914f1e365152919a40735f515bfccd9 diff --git a/.claude/worktrees/fix-security b/.claude/worktrees/fix-security new file mode 160000 index 0000000..cc2df04 --- /dev/null +++ b/.claude/worktrees/fix-security @@ -0,0 +1 @@ +Subproject commit cc2df047a397c2ce9fff2a4b15431a035c01b0aa diff --git a/.claude/worktrees/fix-type-safety b/.claude/worktrees/fix-type-safety new file mode 160000 index 0000000..26f2c4b --- /dev/null +++ b/.claude/worktrees/fix-type-safety @@ -0,0 +1 @@ +Subproject commit 26f2c4b36b77cb9b6541434f6cdfe44d84512fa9 diff --git a/.claude/worktrees/issue-330-cli-logging-pack-perf b/.claude/worktrees/issue-330-cli-logging-pack-perf new file mode 160000 index 0000000..d5dae05 --- /dev/null +++ b/.claude/worktrees/issue-330-cli-logging-pack-perf @@ -0,0 +1 @@ +Subproject commit d5dae051782aa368b9c9ddfefd6771598e6ba349 diff --git a/.claude/worktrees/passthrough-mode b/.claude/worktrees/passthrough-mode new file mode 160000 index 0000000..b3f6965 --- /dev/null +++ b/.claude/worktrees/passthrough-mode @@ -0,0 +1 @@ +Subproject commit b3f6965499448eaf098d87ed1bd74077106aedfd diff --git a/.claude/worktrees/refactor-cli-decompose b/.claude/worktrees/refactor-cli-decompose new file mode 160000 index 0000000..bb0b1e3 --- /dev/null +++ b/.claude/worktrees/refactor-cli-decompose @@ -0,0 +1 @@ +Subproject commit bb0b1e3b899de34dc642b88fc3d1f7645a671047 diff --git a/.claude/worktrees/refactor-mcp-decompose b/.claude/worktrees/refactor-mcp-decompose new file mode 160000 index 0000000..9892601 --- /dev/null +++ b/.claude/worktrees/refactor-mcp-decompose @@ -0,0 +1 @@ +Subproject commit 98926016be2d607a9ce1c545f6d50e494fa42236 diff --git a/src/cli/index.ts b/src/cli/index.ts index 58031c0..ef71e9f 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -69,15 +69,16 @@ import { syncOneNote, disconnectOneNote, } from "../connectors/onenote.js"; -import { loadConnectorConfig, saveConnectorConfig } from "../connectors/index.js"; -import { syncNotion, disconnectNotion } from "../connectors/notion.js"; -import type { NotionConfig } from "../connectors/notion.js"; -import { syncSlack, disconnectSlack, type SlackConfig } from "../connectors/slack.js"; import { + loadConnectorConfig, + saveConnectorConfig, saveNamedConnectorConfig, loadNamedConnectorConfig, hasNamedConnectorConfig, } from "../connectors/index.js"; +import { syncNotion, disconnectNotion } from "../connectors/notion.js"; +import type { NotionConfig } from "../connectors/notion.js"; +import { syncSlack, disconnectSlack, type SlackConfig } from "../connectors/slack.js"; import { createSavedSearch, listSavedSearches, diff --git a/src/connectors/obsidian.ts b/src/connectors/obsidian.ts index 9f10ac9..57dc563 100644 --- a/src/connectors/obsidian.ts +++ b/src/connectors/obsidian.ts @@ -156,7 +156,7 @@ function transformObsidianBody(body: string, fileMap: Map): stri /(? { const displayText = display ?? link; - const slug = link.toLowerCase().replace(/\s+/g, "-"); + const slug = link.toLowerCase().replaceAll(/\s+/g, "-"); return `[${displayText}](${slug})`; }, ); diff --git a/src/connectors/onenote.ts b/src/connectors/onenote.ts index 6fe9f88..35d3035 100644 --- a/src/connectors/onenote.ts +++ b/src/connectors/onenote.ts @@ -223,10 +223,10 @@ export function convertOneNoteHtml(html: string): string { let md = nhm.translate(processed).trim(); // Post-process: replace tokens with final markdown - md = md.replace(/CHECKDONE7X9Z\s*/g, "- [x] "); - md = md.replace(/CHECKTODO7X9Z\s*/g, "- [ ] "); - md = md.replace(/INKPLACEHOLDER7X9Z/g, "[handwritten content]"); - md = md.replace(/IMGPLACEHOLDER7X9Z/g, "[image]"); + md = md.replaceAll(/CHECKDONE7X9Z\s*/g, "- [x] "); + md = md.replaceAll(/CHECKTODO7X9Z\s*/g, "- [ ] "); + md = md.replaceAll("INKPLACEHOLDER7X9Z", "[handwritten content]"); + md = md.replaceAll("IMGPLACEHOLDER7X9Z", "[image]"); md = md.replace(/FILEATTACH7X9Z([^\s]+?)ENDATTACH7X9Z/g, "[attached: $1]"); return md; diff --git a/src/connectors/slack.ts b/src/connectors/slack.ts index 96a9e44..5257a38 100644 --- a/src/connectors/slack.ts +++ b/src/connectors/slack.ts @@ -289,7 +289,7 @@ function formatTimestamp(ts: string): string { } function truncateTitle(text: string, maxLen: number = 80): string { - const cleaned = text.replace(/\n/g, " ").trim(); + const cleaned = text.replaceAll("\n", " ").trim(); if (cleaned.length <= maxLen) return cleaned; return cleaned.slice(0, maxLen - 3) + "..."; } diff --git a/src/core/dedup.ts b/src/core/dedup.ts index 0166353..a2653de 100644 --- a/src/core/dedup.ts +++ b/src/core/dedup.ts @@ -77,7 +77,7 @@ export async function checkDuplicate( if (row) { log.debug({ existingDocId: row.id }, "Exact duplicate detected via content hash"); - return { isDuplicate: true, matchType: "exact", existingDocId: row.id, similarity: 1.0 }; + return { isDuplicate: true, matchType: "exact", existingDocId: row.id, similarity: 1 }; } } diff --git a/src/core/indexing.ts b/src/core/indexing.ts index 07c08b3..3b0d372 100644 --- a/src/core/indexing.ts +++ b/src/core/indexing.ts @@ -59,7 +59,7 @@ function startChunkAtHeading( line: string, ): { lines: string[]; length: number } { const level = (headingMatch[1] ?? "").length; - while (headingStack.length > 0 && (headingStack[headingStack.length - 1]?.level ?? 0) >= level) { + while (headingStack.length > 0 && (headingStack.at(-1)?.level ?? 0) >= level) { headingStack.pop(); } const breadcrumb = headingStack.map((h) => h.text).join(" > "); @@ -223,7 +223,7 @@ function addDeduplicatedChunks( seenHashes: Set, ): void { for (const chunk of windowChunks) { - const normalized = chunk.replace(/\s+/g, " ").trim(); + const normalized = chunk.replaceAll(/\s+/g, " ").trim(); const hash = createHash("sha256").update(normalized).digest("hex"); if (!seenHashes.has(hash)) { seenHashes.add(hash); diff --git a/src/core/packs.ts b/src/core/packs.ts index ba686c8..47bfc57 100644 --- a/src/core/packs.ts +++ b/src/core/packs.ts @@ -693,9 +693,9 @@ function matchesExcludePattern(relativePath: string, pattern: string): boolean { // Escape regex special chars except * and ** const escaped = pattern .replace(/[.+^${}()|[\]\\]/g, "\\$&") - .replace(/\*\*/g, "\0") - .replace(/\*/g, "[^/]*") - .replace(/\0/g, ".*"); + .replaceAll("**", "\0") + .replaceAll("*", "[^/]*") + .replaceAll("\0", ".*"); return new RegExp(`^${escaped}$`).test(relativePath); } diff --git a/src/core/parsers/csv.ts b/src/core/parsers/csv.ts index c974250..99012d1 100644 --- a/src/core/parsers/csv.ts +++ b/src/core/parsers/csv.ts @@ -19,11 +19,13 @@ export class CsvParser implements DocumentParser { const rows = records.slice(1); const escapeCell = (cell: string): string => - cell.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\n/g, " "); + cell.replaceAll("\\", "\\\\").replaceAll("|", "\\|").replaceAll("\n", " "); const lines: string[] = []; - lines.push("| " + header.map(escapeCell).join(" | ") + " |"); - lines.push("| " + header.map(() => "---").join(" | ") + " |"); + lines.push( + "| " + header.map(escapeCell).join(" | ") + " |", + "| " + header.map(() => "---").join(" | ") + " |", + ); for (const row of rows) { // Normalize row length to match header const normalized = Array.from({ length: colCount }, (_, i) => row[i] ?? ""); diff --git a/src/core/parsers/epub.ts b/src/core/parsers/epub.ts index b19a4c0..950a2a9 100644 --- a/src/core/parsers/epub.ts +++ b/src/core/parsers/epub.ts @@ -38,8 +38,8 @@ export class EpubParser implements DocumentParser { const html: string = await getChapter.call(epub, item.id); // Strip HTML tags to get plain text const text = html - .replace(/<[^>]+>/g, " ") - .replace(/\s+/g, " ") + .replaceAll(/<[^>]+>/g, " ") + .replaceAll(/\s+/g, " ") .trim(); if (text.length > 0) { chapters.push(text); diff --git a/src/core/parsers/html.ts b/src/core/parsers/html.ts index 1e9d4a1..1d46baf 100644 --- a/src/core/parsers/html.ts +++ b/src/core/parsers/html.ts @@ -14,7 +14,7 @@ export class HtmlParser implements DocumentParser { const markdown = nhm.translate(html); // Collapse excessive blank lines left by ignored elements - return Promise.resolve(markdown.replace(/\n{3,}/g, "\n\n").trimEnd()); + return Promise.resolve(markdown.replaceAll(/\n{3,}/g, "\n\n").trimEnd()); } catch (err: unknown) { const message = err instanceof Error ? err.message : "Unknown HTML parsing error"; throw new ValidationError(`Failed to parse HTML: ${message}`); diff --git a/src/core/search.ts b/src/core/search.ts index 254d430..76d8519 100644 --- a/src/core/search.ts +++ b/src/core/search.ts @@ -182,7 +182,7 @@ function computeRrfScores(map: Map): SearchResult[] { for (const item of map.values()) { let rrfScore = 0; for (const rank of item.ranks) { - rrfScore += 1.0 / (RRF_K + rank); + rrfScore += 1 / (RRF_K + rank); } const boostFactors = [...item.result.scoreExplanation.boostFactors]; fused.push({ @@ -718,8 +718,7 @@ function keywordSearch( const baseParams = [...params]; sql += " LIMIT ? OFFSET ?"; - params.push(limit); - params.push(offset); + params.push(limit, offset); const KeywordRowSchema = z.object({ chunk_id: z.string(), @@ -825,7 +824,7 @@ export function getRelatedChunks( ): RelatedChunksResult { const { chunkId } = options; const limit = Math.max(1, Math.min(options.limit ?? 10, 1000)); - const minScore = options.minScore ?? 0.0; + const minScore = options.minScore ?? 0; // Look up the source chunk const SourceChunkSchema = z.object({ @@ -1050,7 +1049,7 @@ function fts5Search( const needsRatingJoin = options.minRating !== undefined; - const ftsQuery = words.map((w) => `"${w.replace(/"/g, '""')}"`).join(" AND "); + const ftsQuery = words.map((w) => `"${w.replaceAll('"', '""')}"`).join(" AND "); const params: unknown[] = [ftsQuery]; let sql = ` @@ -1086,8 +1085,7 @@ function fts5Search( let baseParams = [...params]; sql += " ORDER BY rank LIMIT ? OFFSET ?"; - params.push(limit); - params.push(offset); + params.push(limit, offset); const Fts5RowSchema = z.object({ chunk_id: z.string(), @@ -1140,8 +1138,7 @@ function fts5Search( baseParams = [...orParams]; orSql += " ORDER BY rank LIMIT ? OFFSET ?"; - orParams.push(limit); - orParams.push(offset); + orParams.push(limit, offset); rows = validateRows(Fts5RowSchema, db.prepare(orSql).all(...orParams), "fts5Search.orRows"); } diff --git a/src/core/spider.ts b/src/core/spider.ts index e308525..07de1c1 100644 --- a/src/core/spider.ts +++ b/src/core/spider.ts @@ -277,7 +277,7 @@ function extractTitle(html: string, url: string): string { const parsed = new URL(url); const path = parsed.pathname.replace(/\/$/, ""); const last = path.split("/").pop(); - if (last) return last.replace(/[-_]/g, " ").replace(/\.\w+$/, ""); + if (last) return last.replaceAll(/[-_]/g, " ").replace(/\.\w+$/, ""); return parsed.hostname; } catch { return url; diff --git a/src/core/tags.ts b/src/core/tags.ts index 1dc38e1..2f85b69 100644 --- a/src/core/tags.ts +++ b/src/core/tags.ts @@ -462,7 +462,7 @@ export function suggestTags( for (const [term, count] of tf) { if (existingTags.has(term)) continue; const normalizedTf = count / maxTf; - const knownBoost = knownTags.has(term) ? 2.0 : 1.0; + const knownBoost = knownTags.has(term) ? 2 : 1; scored.push({ term, score: normalizedTf * knownBoost }); } diff --git a/src/core/topics.ts b/src/core/topics.ts index 85c0820..92e18fa 100644 --- a/src/core/topics.ts +++ b/src/core/topics.ts @@ -32,7 +32,7 @@ export function createTopic(db: Database.Database, input: CreateTopicInput): Top input.name .toLowerCase() .replace(/[^a-z0-9]+/g, "-") - .replace(/^-|-$/g, "") || randomUUID(); + .replaceAll(/^-|-$/g, "") || randomUUID(); // Verify parent exists if provided if (input.parentId) { diff --git a/src/core/webhooks.ts b/src/core/webhooks.ts index 07e9209..c4ae670 100644 --- a/src/core/webhooks.ts +++ b/src/core/webhooks.ts @@ -163,7 +163,7 @@ function validateUrl(url: string): void { /** Resolve hostname and block private/internal IPs (SSRF protection). */ export async function validateWebhookUrlSsrf(url: string): Promise { const parsed = new URL(url); - const hostname = parsed.hostname.replace(/^\[|\]$/g, ""); + const hostname = parsed.hostname.replaceAll(/^\[|\]$/g, ""); const results = await Promise.allSettled([dns.resolve4(hostname), dns.resolve6(hostname)]); const addresses: string[] = []; diff --git a/src/web/dashboard.ts b/src/web/dashboard.ts index 9b980ea..2ae3cc4 100644 --- a/src/web/dashboard.ts +++ b/src/web/dashboard.ts @@ -278,7 +278,7 @@ export function getDashboardHtml(): string { function escAttr(s) { if (!s) return ''; - return s.replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''').replace(//g, '>'); + return s.replaceAll('&', '&').replaceAll('"', '"').replaceAll("'", ''').replaceAll('<', '<').replaceAll('>', '>'); } // Event delegation for cards and delete buttons diff --git a/tests/integration/retrieval-quality.test.ts b/tests/integration/retrieval-quality.test.ts index e894288..0fd33c5 100644 --- a/tests/integration/retrieval-quality.test.ts +++ b/tests/integration/retrieval-quality.test.ts @@ -58,7 +58,7 @@ class TfIdfEmbeddingProvider implements EmbeddingProvider { private tokenize(text: string): string[] { return text .toLowerCase() - .replace(/[^a-z0-9]+/g, " ") + .replaceAll(/[^a-z0-9]+/g, " ") .split(/\s+/) .filter((w) => w.length > 1); } diff --git a/tests/unit/batch-search.test.ts b/tests/unit/batch-search.test.ts index 023a5e0..b887d80 100644 --- a/tests/unit/batch-search.test.ts +++ b/tests/unit/batch-search.test.ts @@ -2,8 +2,7 @@ import { describe, it, expect, beforeEach, afterEach } from "vitest"; import type Database from "better-sqlite3"; import { createTestDbWithVec } from "../fixtures/test-db.js"; import { MockEmbeddingProvider } from "../fixtures/mock-provider.js"; -import { seedTestDocument } from "../fixtures/helpers.js"; -import { insertChunk } from "../fixtures/helpers.js"; +import { seedTestDocument, insertChunk } from "../fixtures/helpers.js"; import { searchBatch, BATCH_SEARCH_MAX_REQUESTS, diff --git a/tests/unit/dedup.test.ts b/tests/unit/dedup.test.ts index 31d1ab8..56e7b30 100644 --- a/tests/unit/dedup.test.ts +++ b/tests/unit/dedup.test.ts @@ -43,7 +43,7 @@ describe("dedup", () => { expect(result.isDuplicate).toBe(true); expect(result.matchType).toBe("exact"); - expect(result.similarity).toBe(1.0); + expect(result.similarity).toBe(1); expect(result.existingDocId).toBeDefined(); }); diff --git a/tests/unit/indexing.test.ts b/tests/unit/indexing.test.ts index f00a652..883d3a7 100644 --- a/tests/unit/indexing.test.ts +++ b/tests/unit/indexing.test.ts @@ -189,7 +189,7 @@ More content here.`; // Build content where the same logical text appears with different whitespace const line = "Hello world this is a test line."; const variant1 = line + "\n"; - const variant2 = line.replace(/ /g, " ") + "\n"; // double spaces + const variant2 = line.replaceAll(" ", " ") + "\n"; // double spaces // Interleave so overlap might pick up both const content = (variant1.repeat(50) + variant2.repeat(50)).repeat(2); const chunks = chunkContentStreaming(content, { windowSize: 512 }); @@ -197,7 +197,7 @@ More content here.`; // After whitespace normalization, duplicates should be removed const seen = new Set(); for (const chunk of chunks) { - const normalized = chunk.replace(/\s+/g, " ").trim(); + const normalized = chunk.replaceAll(/\s+/g, " ").trim(); expect(seen.has(normalized)).toBe(false); seen.add(normalized); } diff --git a/tests/unit/obsidian.test.ts b/tests/unit/obsidian.test.ts index 42af44a..4787d18 100644 --- a/tests/unit/obsidian.test.ts +++ b/tests/unit/obsidian.test.ts @@ -1,5 +1,9 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { parseObsidianMarkdown } from "../../src/connectors/obsidian.js"; +import { + parseObsidianMarkdown, + syncObsidianVault, + disconnectVault, +} from "../../src/connectors/obsidian.js"; import { createTestDb, createTestDbWithVec } from "../fixtures/test-db.js"; import { MockEmbeddingProvider } from "../fixtures/mock-provider.js"; import type Database from "better-sqlite3"; @@ -29,7 +33,6 @@ vi.mock("node:fs/promises", async (importOriginal) => { }); import { readdirSync, readFileSync, statSync, existsSync, writeFileSync } from "node:fs"; -import { syncObsidianVault, disconnectVault } from "../../src/connectors/obsidian.js"; import { initLogger } from "../../src/logger.js"; const mockedReaddirSync = vi.mocked(readdirSync);