From 1941e6457256e28762ad09d13d3eed9088f2f3f1 Mon Sep 17 00:00:00 2001 From: tommy0103 <43411539+tommy0103@users.noreply.github.com> Date: Tue, 4 Aug 2026 05:49:51 +0800 Subject: [PATCH] refactor(core): replace CommonJS requires with named imports (#31) --- packages/core/src/db.ts | 30 ++++++------- packages/core/src/indexer.ts | 5 ++- packages/core/src/parsing.ts | 62 +++++++++++++-------------- packages/core/src/providers/claude.ts | 32 +++++++------- packages/core/src/providers/codex.ts | 16 +++---- packages/core/src/query.ts | 13 +++--- 6 files changed, 76 insertions(+), 82 deletions(-) diff --git a/packages/core/src/db.ts b/packages/core/src/db.ts index 425e8dc..acadaef 100644 --- a/packages/core/src/db.ts +++ b/packages/core/src/db.ts @@ -1,30 +1,28 @@ // node:sqlite lifecycle and migrations for the Core package. -import { createRequire } from 'node:module'; +import { copyFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs'; +import { homedir } from 'node:os'; +import { dirname, join } from 'node:path'; +import { DatabaseSync } from 'node:sqlite'; import { CLAUDE_DIR, CODEX_DIR, TEXT_LIMIT, trunc, truncJson, extractText, extractContentType, extractMessageIsMeta, filePath, isDir, readLines } from './parsing.ts'; import { configureConnection } from './tx.ts'; import { migrateCoreSchemaColumns } from './schema-migrations.ts'; import type { NodeSqliteDb, SqliteDb } from './sqlite-types.ts'; -const require = createRequire(import.meta.url); -const fs = require('node:fs'); -const path = require('node:path'); -const os = require('node:os'); -const { DatabaseSync } = require('node:sqlite'); -const OBELISK_DIR = path.join(os.homedir(), '.obelisk'); -const LEGACY_DB_PATH = path.join(CLAUDE_DIR, 'obelisk.sqlite'); -const DB_PATH = path.join(OBELISK_DIR, 'obelisk.sqlite'); -const SCHEMA = fs.readFileSync(new URL('./schema.sql', import.meta.url), 'utf8'); +const OBELISK_DIR = join(homedir(), '.obelisk'); +const LEGACY_DB_PATH = join(CLAUDE_DIR, 'obelisk.sqlite'); +const DB_PATH = join(OBELISK_DIR, 'obelisk.sqlite'); +const SCHEMA = readFileSync(new URL('./schema.sql', import.meta.url), 'utf8'); function migrateLegacyDbIfNeeded() { - if (fs.existsSync(DB_PATH)) return; - if (!fs.existsSync(LEGACY_DB_PATH)) return; - fs.mkdirSync(path.dirname(DB_PATH), { recursive: true }); - fs.copyFileSync(LEGACY_DB_PATH, DB_PATH); + if (existsSync(DB_PATH)) return; + if (!existsSync(LEGACY_DB_PATH)) return; + mkdirSync(dirname(DB_PATH), { recursive: true }); + copyFileSync(LEGACY_DB_PATH, DB_PATH); } function openDb(): NodeSqliteDb { migrateLegacyDbIfNeeded(); - fs.mkdirSync(path.dirname(DB_PATH), { recursive: true }); + mkdirSync(dirname(DB_PATH), { recursive: true }); const db = new DatabaseSync(DB_PATH); configureConnection(db, { busyTimeoutMs: 250 }); migrateCoreSchemaColumns(db); @@ -50,4 +48,4 @@ function rebuildMemoryFts(db: SqliteDb): void { } -export { CLAUDE_DIR, CODEX_DIR, OBELISK_DIR, DB_PATH, TEXT_LIMIT, openDb, openReadDb, openWriterLeaseDb, rebuildMemoryFts, trunc, truncJson, extractText, extractContentType, extractMessageIsMeta, filePath, isDir, readLines, fs, path, os }; +export { CLAUDE_DIR, CODEX_DIR, OBELISK_DIR, DB_PATH, TEXT_LIMIT, openDb, openReadDb, openWriterLeaseDb, rebuildMemoryFts, trunc, truncJson, extractText, extractContentType, extractMessageIsMeta, filePath, isDir, readLines }; diff --git a/packages/core/src/indexer.ts b/packages/core/src/indexer.ts index 83663e0..3d7b03c 100644 --- a/packages/core/src/indexer.ts +++ b/packages/core/src/indexer.ts @@ -1,6 +1,7 @@ // Passive-pull indexing orchestration for the Core package. +import { existsSync } from 'node:fs'; import { DB_PATH, openDb, openReadDb, openWriterLeaseDb, rebuildMemoryFts } from './db.ts'; -import { fs, inferProjectPath } from './parsing.ts'; +import { inferProjectPath } from './parsing.ts'; import { createProviderIndexPlan, indexProviderPlan, @@ -67,7 +68,7 @@ function isMissingIndexStateTable(error: unknown): boolean { } function inspectBuildOwnership({ force = false }: { force?: boolean } = {}) { - if (!fs.existsSync(DB_PATH)) return { skip: false }; + if (!existsSync(DB_PATH)) return { skip: false }; const db = openReadDb(); try { return shouldSkipBuild(db, { ignoreRecentBuild: force }); diff --git a/packages/core/src/parsing.ts b/packages/core/src/parsing.ts index c93b67e..adc818e 100644 --- a/packages/core/src/parsing.ts +++ b/packages/core/src/parsing.ts @@ -2,16 +2,14 @@ // providers can be consumed by the app (better-sqlite3 / a Node without // node:sqlite). Originally extracted verbatim from db/indexer; it now exposes a // typed seam while remaining limited to node:fs/path/os. -import { createRequire } from 'node:module'; -const require = createRequire(import.meta.url); -const fs = require('node:fs'); -const path = require('node:path'); -const os = require('node:os'); +import { closeSync, existsSync, openSync, readSync, readdirSync, statSync } from 'node:fs'; +import { homedir } from 'node:os'; +import { isAbsolute, join, normalize } from 'node:path'; -const CLAUDE_DIR = path.join(os.homedir(), '.claude'); -const CODEX_DIR = path.join(os.homedir(), '.codex'); -const PROJECTS_DIR = path.join(CLAUDE_DIR, 'projects'); -const CODEX_SESSIONS_DIR = path.join(CODEX_DIR, 'sessions'); +const CLAUDE_DIR = join(homedir(), '.claude'); +const CODEX_DIR = join(homedir(), '.codex'); +const PROJECTS_DIR = join(CLAUDE_DIR, 'projects'); +const CODEX_SESSIONS_DIR = join(CODEX_DIR, 'sessions'); const TEXT_LIMIT = 10000; type JsonRecord = Record; @@ -102,16 +100,16 @@ function filePath(name: string, input: JsonRecord | null | undefined): string | return ['Read', 'Edit', 'Write', 'NotebookEdit'].includes(name) ? (input.file_path || null) : null; } -function isDir(p: string): boolean { try { return fs.statSync(p).isDirectory(); } catch { return false; } } +function isDir(p: string): boolean { try { return statSync(p).isDirectory(); } catch { return false; } } function readLines(filePath: string, callback: (line: string) => boolean | void): void { - const fd = fs.openSync(filePath, 'r'); + const fd = openSync(filePath, 'r'); const bufSize = 64 * 1024; const buf = Buffer.alloc(bufSize); let remainder = ''; let bytesRead; try { - while ((bytesRead = fs.readSync(fd, buf, 0, bufSize)) > 0) { + while ((bytesRead = readSync(fd, buf, 0, bufSize, null)) > 0) { const chunk = remainder + buf.toString('utf8', 0, bytesRead); const lines = chunk.split('\n'); remainder = lines.pop() ?? ''; @@ -121,7 +119,7 @@ function readLines(filePath: string, callback: (line: string) => boolean | void) } if (remainder) callback(remainder); } finally { - fs.closeSync(fd); + closeSync(fd); } } @@ -132,8 +130,8 @@ function legacyProjectPathFromSlug(project: string | null | undefined): string | } function normalizeObservedCwd(cwd: unknown): string | null { - if (typeof cwd !== 'string' || !cwd.trim() || !path.isAbsolute(cwd)) return null; - return path.normalize(cwd); + if (typeof cwd !== 'string' || !cwd.trim() || !isAbsolute(cwd)) return null; + return normalize(cwd); } function projectSlugFromPath(projectPath: string | null): string | null { @@ -157,39 +155,39 @@ function inferProjectPath(project: string | null | undefined, observedCwds: unkn function discoverJsonlFiles(projectsDir = PROJECTS_DIR): ClaudeJsonlFile[] { const files: ClaudeJsonlFile[] = []; - if (!fs.existsSync(projectsDir)) return files; + if (!existsSync(projectsDir)) return files; let projects; - try { projects = fs.readdirSync(projectsDir); } catch (e) { process.stderr.write(`Warning: cannot read projects dir: ${e instanceof Error ? e.message : String(e)}\n`); return files; } + try { projects = readdirSync(projectsDir); } catch (e) { process.stderr.write(`Warning: cannot read projects dir: ${e instanceof Error ? e.message : String(e)}\n`); return files; } for (const proj of projects) { - const projPath = path.join(projectsDir, proj); + const projPath = join(projectsDir, proj); if (!isDir(projPath)) continue; let entries; - try { entries = fs.readdirSync(projPath); } catch { continue; } + try { entries = readdirSync(projPath); } catch { continue; } for (const f of entries) { if (f.endsWith('.jsonl')) - files.push({ path: path.join(projPath, f), sessionId: f.slice(0, -6), project: proj, isSubagent: false }); + files.push({ path: join(projPath, f), sessionId: f.slice(0, -6), project: proj, isSubagent: false }); } for (const sd of entries) { - const saDir = path.join(projPath, sd, 'subagents'); + const saDir = join(projPath, sd, 'subagents'); if (!isDir(saDir)) continue; let saEntries; - try { saEntries = fs.readdirSync(saDir); } catch { continue; } + try { saEntries = readdirSync(saDir); } catch { continue; } for (const sf of saEntries) { if (sf.endsWith('.jsonl')) - files.push({ path: path.join(saDir, sf), sessionId: sd, project: proj, isSubagent: true, agentId: sf.slice(0, -6) }); + files.push({ path: join(saDir, sf), sessionId: sd, project: proj, isSubagent: true, agentId: sf.slice(0, -6) }); } - const wfRoot = path.join(saDir, 'workflows'); + const wfRoot = join(saDir, 'workflows'); if (!isDir(wfRoot)) continue; let wfDirs; - try { wfDirs = fs.readdirSync(wfRoot); } catch { continue; } + try { wfDirs = readdirSync(wfRoot); } catch { continue; } for (const wfDir of wfDirs) { - const wfPath = path.join(wfRoot, wfDir); + const wfPath = join(wfRoot, wfDir); if (!isDir(wfPath)) continue; let wfEntries; - try { wfEntries = fs.readdirSync(wfPath); } catch { continue; } + try { wfEntries = readdirSync(wfPath); } catch { continue; } for (const wf of wfEntries) { if (wf.endsWith('.jsonl')) - files.push({ path: path.join(wfPath, wf), sessionId: sd, project: proj, isSubagent: true, agentId: wf.slice(0, -6), workflowRunId: wfDir }); + files.push({ path: join(wfPath, wf), sessionId: sd, project: proj, isSubagent: true, agentId: wf.slice(0, -6), workflowRunId: wfDir }); } } } @@ -199,12 +197,12 @@ function discoverJsonlFiles(projectsDir = PROJECTS_DIR): ClaudeJsonlFile[] { function discoverCodexJsonlFiles(sessionsDir = CODEX_SESSIONS_DIR): CodexJsonlFile[] { const files: CodexJsonlFile[] = []; - if (!fs.existsSync(sessionsDir)) return files; + if (!existsSync(sessionsDir)) return files; const walk = (dir: string): void => { let entries; - try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; } + try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return; } for (const entry of entries) { - const fp = path.join(dir, entry.name); + const fp = join(dir, entry.name); if (entry.isDirectory()) { walk(fp); } else if (entry.isFile() && entry.name.endsWith('.jsonl')) { @@ -368,7 +366,7 @@ function codexToolOutput(payload: JsonRecord): string | null { } export { - fs, path, os, CLAUDE_DIR, CODEX_DIR, PROJECTS_DIR, CODEX_SESSIONS_DIR, TEXT_LIMIT, + CLAUDE_DIR, CODEX_DIR, PROJECTS_DIR, CODEX_SESSIONS_DIR, TEXT_LIMIT, trunc, truncJson, extractText, extractContentType, extractMessageIsMeta, isSkillInstructions, filePath, isDir, readLines, legacyProjectPathFromSlug, normalizeObservedCwd, projectSlugFromPath, inferProjectPath, discoverJsonlFiles, discoverCodexJsonlFiles, diff --git a/packages/core/src/providers/claude.ts b/packages/core/src/providers/claude.ts index a2742bf..d3cccbb 100644 --- a/packages/core/src/providers/claude.ts +++ b/packages/core/src/providers/claude.ts @@ -6,11 +6,9 @@ // persist layer consumes them. Session aggregates here reflect only THIS chunk // (started_at/ended_at/message_count); persist merges them with any existing row. -import { createRequire } from 'node:module'; +import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'; import { homedir } from 'node:os'; import { dirname, isAbsolute, join, normalize, relative } from 'node:path'; -const require = createRequire(import.meta.url); -const fs = require('node:fs'); import { extractText, extractContentType, extractMessageIsMeta, isSkillInstructions, @@ -65,7 +63,7 @@ function discoverAt(rootDir: string, ctx: DiscoverContext): IndexUnit[] { const projectsDir = join(rootDir, 'projects'); const historyPath = normalize(join(rootDir, 'history.jsonl')); const historyTitles = new Map(); - if (fs.existsSync(historyPath)) { + if (existsSync(historyPath)) { readLines(historyPath, (line: string) => { try { const item = JSON.parse(line); @@ -104,7 +102,7 @@ function discoverAt(rootDir: string, ctx: DiscoverContext): IndexUnit[] { return historyChanged || forcedPaths.has(normalizedPath) || cursor === null - || Number(cursor.split(':')[0]) < fs.statSync(file.path).mtimeMs; + || Number(cursor.split(':')[0]) < statSync(file.path).mtimeMs; }).map((f: any) => ({ key: f.path, sessionId: f.sessionId, @@ -118,20 +116,20 @@ function discoverAt(rootDir: string, ctx: DiscoverContext): IndexUnit[] { })); const workflowUnits: IndexUnit[] = []; - if (!fs.existsSync(projectsDir)) return transcriptUnits; + if (!existsSync(projectsDir)) return transcriptUnits; let projects: string[]; - try { projects = fs.readdirSync(projectsDir); } catch { return transcriptUnits; } + try { projects = readdirSync(projectsDir); } catch { return transcriptUnits; } for (const project of projects) { const projectPath = join(projectsDir, project); if (!isDir(projectPath)) continue; let sessionIds: string[]; - try { sessionIds = fs.readdirSync(projectPath); } catch { continue; } + try { sessionIds = readdirSync(projectPath); } catch { continue; } for (const sessionId of sessionIds) { const workflowDir = join(projectPath, sessionId, 'workflows'); if (!isDir(workflowDir)) continue; const mainTranscriptPath = join(projectPath, `${sessionId}.jsonl`); let files: string[]; - try { files = fs.readdirSync(workflowDir); } catch { continue; } + try { files = readdirSync(workflowDir); } catch { continue; } for (const file of files) { if (!file.endsWith('.json')) continue; const workflowPath = join(workflowDir, file); @@ -142,7 +140,7 @@ function discoverAt(rootDir: string, ctx: DiscoverContext): IndexUnit[] { && !changedWorkflowPaths.has(normalizedPath) && !relationshipChanged ) continue; - const mtime = fs.statSync(workflowPath).mtimeMs; + const mtime = statSync(workflowPath).mtimeMs; const cursor = ctx.lastCursor(workflowPath); if (!relationshipChanged && cursor !== null && Number(cursor.split(':')[0]) >= mtime) continue; workflowUnits.push({ @@ -172,7 +170,7 @@ function workflowParentToolUseId( runId: string, workflowName: string | null, ): string | null { - if (!fs.existsSync(transcriptPath)) return null; + if (!existsSync(transcriptPath)) return null; const workflowToolIds = new Set(); let parentToolUseId: string | null = null; readLines(transcriptPath, (line: string) => { @@ -201,10 +199,10 @@ function workflowParentToolUseId( } function* parseWorkflow(unit: IndexUnit): Generator { - const mtime = fs.statSync(unit.key).mtimeMs; + const mtime = statSync(unit.key).mtimeMs; const outCursor = `${mtime}:1`; let workflow: any; - try { workflow = JSON.parse(fs.readFileSync(unit.key, 'utf8')); } catch { return outCursor; } + try { workflow = JSON.parse(readFileSync(unit.key, 'utf8')); } catch { return outCursor; } if (!workflow?.runId) return outCursor; const meta = unit.meta as ClaudeWorkflowUnitMeta; const progress = Array.isArray(workflow.workflowProgress) ? workflow.workflowProgress : []; @@ -251,7 +249,7 @@ export function* parse(unit: IndexUnit, cursor: Cursor): Generator { if (!line.includes(input.messageUuid)) return; diff --git a/packages/core/src/providers/codex.ts b/packages/core/src/providers/codex.ts index 1e9be23..78956c5 100644 --- a/packages/core/src/providers/codex.ts +++ b/packages/core/src/providers/codex.ts @@ -8,11 +8,9 @@ // record uses countMode 'total' (persist replaces the count, never accumulates). // The per-line logic mirrors the original indexCodexJsonl. -import { createRequire } from 'node:module'; +import { existsSync, readdirSync, statSync } from 'node:fs'; import { homedir } from 'node:os'; import { isAbsolute, join, normalize, relative } from 'node:path'; -const require = createRequire(import.meta.url); -const fs = require('node:fs'); import { trunc, truncJson, readLines, @@ -51,7 +49,7 @@ function discoverAt(rootDir: string, ctx: DiscoverContext): IndexUnit[] { const sessionsDir = join(rootDir, 'sessions'); const sessionIndexPath = normalize(join(rootDir, 'session_index.jsonl')); const sessionIndex = new Map(); - if (fs.existsSync(sessionIndexPath)) { + if (existsSync(sessionIndexPath)) { readLines(sessionIndexPath, (line: string) => { try { const item = JSON.parse(line); @@ -82,7 +80,7 @@ function discoverAt(rootDir: string, ctx: DiscoverContext): IndexUnit[] { if (ctx.changedPaths !== undefined && !sessionIndexChanged && !changedFiles.has(normalize(file.path))) return []; const cursor = ctx.lastCursor(file.path); const guardian = readCodexGuardianThreadInfo(file.path); - if (!sessionIndexChanged && cursor !== null && Number(cursor.split(':')[0]) >= fs.statSync(file.path).mtimeMs && guardian === null) { + if (!sessionIndexChanged && cursor !== null && Number(cursor.split(':')[0]) >= statSync(file.path).mtimeMs && guardian === null) { return []; } let meta: any = null; @@ -116,7 +114,7 @@ export function discover(ctx: DiscoverContext): IndexUnit[] { } export function* parse(unit: IndexUnit, _cursor: Cursor): Generator { - const mtime = fs.statSync(unit.key).mtimeMs; + const mtime = statSync(unit.key).mtimeMs; const records: { lineNum: number; obj: any }[] = []; let lineNum = 0; readLines(unit.key, (line: string) => { @@ -316,8 +314,8 @@ function findCodexFile(rootDir: string, rawThreadId: string): string | null { const stack = [join(rootDir, 'sessions')]; while (stack.length > 0) { const current = stack.pop()!; - if (!fs.existsSync(current)) continue; - for (const entry of fs.readdirSync(current, { withFileTypes: true })) { + if (!existsSync(current)) continue; + for (const entry of readdirSync(current, { withFileTypes: true })) { const path = join(current, entry.name); if (entry.isDirectory()) stack.push(path); else if (entry.isFile() && entry.name.endsWith(`${rawThreadId}.jsonl`)) return path; @@ -332,7 +330,7 @@ function rawCodex(rootDir: string, input: RawLookup): RawRecord | null { const path = input.agentId === null && typeof input.session?.jsonl_path === 'string' ? input.session.jsonl_path : findCodexFile(rootDir, match[1]!); - if (path === null || !fs.existsSync(path)) return null; + if (path === null || !existsSync(path)) return null; let lineNumber = 0; let found: string | null = null; readLines(path, (line: string) => { diff --git a/packages/core/src/query.ts b/packages/core/src/query.ts index 109286e..57af3ca 100644 --- a/packages/core/src/query.ts +++ b/packages/core/src/query.ts @@ -1,5 +1,6 @@ // Query and attune sandbox helpers for the Core package. -import { fs, path } from './db.ts'; +import { statSync } from 'node:fs'; +import { isAbsolute, normalize, resolve, sep } from 'node:path'; import { createBuiltinProviderRegistry } from './providers/builtins.ts'; import type { ProviderRegistry } from './providers/registry.ts'; import type { SqliteDb, SqliteRow } from './sqlite-types.ts'; @@ -325,7 +326,7 @@ function createQueryApi( GROUP BY project, project_path `).all(); const byProjectPath = paths - .filter((r: DbRow) => cwd === r.project_path || cwd.startsWith(r.project_path + path.sep)) + .filter((r: DbRow) => cwd === r.project_path || cwd.startsWith(r.project_path + sep)) .sort((a: DbRow, b: DbRow) => b.project_path.length - a.project_path.length || String(b.last_seen || '').localeCompare(String(a.last_seen || '')))[0]; if (byProjectPath) return projectDescriptor(byProjectPath, 'cwd_project_path', 'exact'); @@ -529,12 +530,12 @@ function createAttuneApi(db: SqliteDb) { if (sessionId) { base = db.prepare('SELECT project_path FROM sessions WHERE id=?').get(sessionId)?.project_path || null; } - const resolved = path.isAbsolute(memoryPath) - ? path.normalize(memoryPath) - : path.resolve(base || process.cwd(), memoryPath); + const resolved = isAbsolute(memoryPath) + ? normalize(memoryPath) + : resolve(base || process.cwd(), memoryPath); let stat; try { - stat = fs.statSync(resolved); + stat = statSync(resolved); } catch { throw new Error(`remember() memory file does not exist: ${resolved}`); }