refactor(core): replace CommonJS requires with named imports (#31)

This commit is contained in:
tommy0103
2026-08-04 05:49:51 +08:00
committed by GitHub
parent 47df0fe76a
commit 1941e64572
6 changed files with 76 additions and 82 deletions
+14 -16
View File
@@ -1,30 +1,28 @@
// node:sqlite lifecycle and migrations for the Core package. // 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 { CLAUDE_DIR, CODEX_DIR, TEXT_LIMIT, trunc, truncJson, extractText, extractContentType, extractMessageIsMeta, filePath, isDir, readLines } from './parsing.ts';
import { configureConnection } from './tx.ts'; import { configureConnection } from './tx.ts';
import { migrateCoreSchemaColumns } from './schema-migrations.ts'; import { migrateCoreSchemaColumns } from './schema-migrations.ts';
import type { NodeSqliteDb, SqliteDb } from './sqlite-types.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 OBELISK_DIR = join(homedir(), '.obelisk');
const LEGACY_DB_PATH = path.join(CLAUDE_DIR, 'obelisk.sqlite'); const LEGACY_DB_PATH = join(CLAUDE_DIR, 'obelisk.sqlite');
const DB_PATH = path.join(OBELISK_DIR, 'obelisk.sqlite'); const DB_PATH = join(OBELISK_DIR, 'obelisk.sqlite');
const SCHEMA = fs.readFileSync(new URL('./schema.sql', import.meta.url), 'utf8'); const SCHEMA = readFileSync(new URL('./schema.sql', import.meta.url), 'utf8');
function migrateLegacyDbIfNeeded() { function migrateLegacyDbIfNeeded() {
if (fs.existsSync(DB_PATH)) return; if (existsSync(DB_PATH)) return;
if (!fs.existsSync(LEGACY_DB_PATH)) return; if (!existsSync(LEGACY_DB_PATH)) return;
fs.mkdirSync(path.dirname(DB_PATH), { recursive: true }); mkdirSync(dirname(DB_PATH), { recursive: true });
fs.copyFileSync(LEGACY_DB_PATH, DB_PATH); copyFileSync(LEGACY_DB_PATH, DB_PATH);
} }
function openDb(): NodeSqliteDb { function openDb(): NodeSqliteDb {
migrateLegacyDbIfNeeded(); migrateLegacyDbIfNeeded();
fs.mkdirSync(path.dirname(DB_PATH), { recursive: true }); mkdirSync(dirname(DB_PATH), { recursive: true });
const db = new DatabaseSync(DB_PATH); const db = new DatabaseSync(DB_PATH);
configureConnection(db, { busyTimeoutMs: 250 }); configureConnection(db, { busyTimeoutMs: 250 });
migrateCoreSchemaColumns(db); 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 };
+3 -2
View File
@@ -1,6 +1,7 @@
// Passive-pull indexing orchestration for the Core package. // Passive-pull indexing orchestration for the Core package.
import { existsSync } from 'node:fs';
import { DB_PATH, openDb, openReadDb, openWriterLeaseDb, rebuildMemoryFts } from './db.ts'; import { DB_PATH, openDb, openReadDb, openWriterLeaseDb, rebuildMemoryFts } from './db.ts';
import { fs, inferProjectPath } from './parsing.ts'; import { inferProjectPath } from './parsing.ts';
import { import {
createProviderIndexPlan, createProviderIndexPlan,
indexProviderPlan, indexProviderPlan,
@@ -67,7 +68,7 @@ function isMissingIndexStateTable(error: unknown): boolean {
} }
function inspectBuildOwnership({ force = false }: { force?: 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(); const db = openReadDb();
try { try {
return shouldSkipBuild(db, { ignoreRecentBuild: force }); return shouldSkipBuild(db, { ignoreRecentBuild: force });
+30 -32
View File
@@ -2,16 +2,14 @@
// providers can be consumed by the app (better-sqlite3 / a Node without // providers can be consumed by the app (better-sqlite3 / a Node without
// node:sqlite). Originally extracted verbatim from db/indexer; it now exposes a // node:sqlite). Originally extracted verbatim from db/indexer; it now exposes a
// typed seam while remaining limited to node:fs/path/os. // typed seam while remaining limited to node:fs/path/os.
import { createRequire } from 'node:module'; import { closeSync, existsSync, openSync, readSync, readdirSync, statSync } from 'node:fs';
const require = createRequire(import.meta.url); import { homedir } from 'node:os';
const fs = require('node:fs'); import { isAbsolute, join, normalize } from 'node:path';
const path = require('node:path');
const os = require('node:os');
const CLAUDE_DIR = path.join(os.homedir(), '.claude'); const CLAUDE_DIR = join(homedir(), '.claude');
const CODEX_DIR = path.join(os.homedir(), '.codex'); const CODEX_DIR = join(homedir(), '.codex');
const PROJECTS_DIR = path.join(CLAUDE_DIR, 'projects'); const PROJECTS_DIR = join(CLAUDE_DIR, 'projects');
const CODEX_SESSIONS_DIR = path.join(CODEX_DIR, 'sessions'); const CODEX_SESSIONS_DIR = join(CODEX_DIR, 'sessions');
const TEXT_LIMIT = 10000; const TEXT_LIMIT = 10000;
type JsonRecord = Record<string, any>; type JsonRecord = Record<string, any>;
@@ -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; 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 { 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 bufSize = 64 * 1024;
const buf = Buffer.alloc(bufSize); const buf = Buffer.alloc(bufSize);
let remainder = ''; let remainder = '';
let bytesRead; let bytesRead;
try { 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 chunk = remainder + buf.toString('utf8', 0, bytesRead);
const lines = chunk.split('\n'); const lines = chunk.split('\n');
remainder = lines.pop() ?? ''; remainder = lines.pop() ?? '';
@@ -121,7 +119,7 @@ function readLines(filePath: string, callback: (line: string) => boolean | void)
} }
if (remainder) callback(remainder); if (remainder) callback(remainder);
} finally { } finally {
fs.closeSync(fd); closeSync(fd);
} }
} }
@@ -132,8 +130,8 @@ function legacyProjectPathFromSlug(project: string | null | undefined): string |
} }
function normalizeObservedCwd(cwd: unknown): string | null { function normalizeObservedCwd(cwd: unknown): string | null {
if (typeof cwd !== 'string' || !cwd.trim() || !path.isAbsolute(cwd)) return null; if (typeof cwd !== 'string' || !cwd.trim() || !isAbsolute(cwd)) return null;
return path.normalize(cwd); return normalize(cwd);
} }
function projectSlugFromPath(projectPath: string | null): string | null { 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[] { function discoverJsonlFiles(projectsDir = PROJECTS_DIR): ClaudeJsonlFile[] {
const files: ClaudeJsonlFile[] = []; const files: ClaudeJsonlFile[] = [];
if (!fs.existsSync(projectsDir)) return files; if (!existsSync(projectsDir)) return files;
let projects; 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) { for (const proj of projects) {
const projPath = path.join(projectsDir, proj); const projPath = join(projectsDir, proj);
if (!isDir(projPath)) continue; if (!isDir(projPath)) continue;
let entries; let entries;
try { entries = fs.readdirSync(projPath); } catch { continue; } try { entries = readdirSync(projPath); } catch { continue; }
for (const f of entries) { for (const f of entries) {
if (f.endsWith('.jsonl')) 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) { for (const sd of entries) {
const saDir = path.join(projPath, sd, 'subagents'); const saDir = join(projPath, sd, 'subagents');
if (!isDir(saDir)) continue; if (!isDir(saDir)) continue;
let saEntries; let saEntries;
try { saEntries = fs.readdirSync(saDir); } catch { continue; } try { saEntries = readdirSync(saDir); } catch { continue; }
for (const sf of saEntries) { for (const sf of saEntries) {
if (sf.endsWith('.jsonl')) 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; if (!isDir(wfRoot)) continue;
let wfDirs; let wfDirs;
try { wfDirs = fs.readdirSync(wfRoot); } catch { continue; } try { wfDirs = readdirSync(wfRoot); } catch { continue; }
for (const wfDir of wfDirs) { for (const wfDir of wfDirs) {
const wfPath = path.join(wfRoot, wfDir); const wfPath = join(wfRoot, wfDir);
if (!isDir(wfPath)) continue; if (!isDir(wfPath)) continue;
let wfEntries; let wfEntries;
try { wfEntries = fs.readdirSync(wfPath); } catch { continue; } try { wfEntries = readdirSync(wfPath); } catch { continue; }
for (const wf of wfEntries) { for (const wf of wfEntries) {
if (wf.endsWith('.jsonl')) 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[] { function discoverCodexJsonlFiles(sessionsDir = CODEX_SESSIONS_DIR): CodexJsonlFile[] {
const files: CodexJsonlFile[] = []; const files: CodexJsonlFile[] = [];
if (!fs.existsSync(sessionsDir)) return files; if (!existsSync(sessionsDir)) return files;
const walk = (dir: string): void => { const walk = (dir: string): void => {
let entries; let entries;
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; } try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return; }
for (const entry of entries) { for (const entry of entries) {
const fp = path.join(dir, entry.name); const fp = join(dir, entry.name);
if (entry.isDirectory()) { if (entry.isDirectory()) {
walk(fp); walk(fp);
} else if (entry.isFile() && entry.name.endsWith('.jsonl')) { } else if (entry.isFile() && entry.name.endsWith('.jsonl')) {
@@ -368,7 +366,7 @@ function codexToolOutput(payload: JsonRecord): string | null {
} }
export { 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, trunc, truncJson, extractText, extractContentType, extractMessageIsMeta, isSkillInstructions, filePath, isDir, readLines,
legacyProjectPathFromSlug, normalizeObservedCwd, projectSlugFromPath, inferProjectPath, legacyProjectPathFromSlug, normalizeObservedCwd, projectSlugFromPath, inferProjectPath,
discoverJsonlFiles, discoverCodexJsonlFiles, discoverJsonlFiles, discoverCodexJsonlFiles,
+15 -17
View File
@@ -6,11 +6,9 @@
// persist layer consumes them. Session aggregates here reflect only THIS chunk // persist layer consumes them. Session aggregates here reflect only THIS chunk
// (started_at/ended_at/message_count); persist merges them with any existing row. // (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 { homedir } from 'node:os';
import { dirname, isAbsolute, join, normalize, relative } from 'node:path'; import { dirname, isAbsolute, join, normalize, relative } from 'node:path';
const require = createRequire(import.meta.url);
const fs = require('node:fs');
import { import {
extractText, extractContentType, extractMessageIsMeta, isSkillInstructions, extractText, extractContentType, extractMessageIsMeta, isSkillInstructions,
@@ -65,7 +63,7 @@ function discoverAt(rootDir: string, ctx: DiscoverContext): IndexUnit[] {
const projectsDir = join(rootDir, 'projects'); const projectsDir = join(rootDir, 'projects');
const historyPath = normalize(join(rootDir, 'history.jsonl')); const historyPath = normalize(join(rootDir, 'history.jsonl'));
const historyTitles = new Map<string, string>(); const historyTitles = new Map<string, string>();
if (fs.existsSync(historyPath)) { if (existsSync(historyPath)) {
readLines(historyPath, (line: string) => { readLines(historyPath, (line: string) => {
try { try {
const item = JSON.parse(line); const item = JSON.parse(line);
@@ -104,7 +102,7 @@ function discoverAt(rootDir: string, ctx: DiscoverContext): IndexUnit[] {
return historyChanged return historyChanged
|| forcedPaths.has(normalizedPath) || forcedPaths.has(normalizedPath)
|| cursor === null || cursor === null
|| Number(cursor.split(':')[0]) < fs.statSync(file.path).mtimeMs; || Number(cursor.split(':')[0]) < statSync(file.path).mtimeMs;
}).map((f: any) => ({ }).map((f: any) => ({
key: f.path, key: f.path,
sessionId: f.sessionId, sessionId: f.sessionId,
@@ -118,20 +116,20 @@ function discoverAt(rootDir: string, ctx: DiscoverContext): IndexUnit[] {
})); }));
const workflowUnits: IndexUnit[] = []; const workflowUnits: IndexUnit[] = [];
if (!fs.existsSync(projectsDir)) return transcriptUnits; if (!existsSync(projectsDir)) return transcriptUnits;
let projects: string[]; let projects: string[];
try { projects = fs.readdirSync(projectsDir); } catch { return transcriptUnits; } try { projects = readdirSync(projectsDir); } catch { return transcriptUnits; }
for (const project of projects) { for (const project of projects) {
const projectPath = join(projectsDir, project); const projectPath = join(projectsDir, project);
if (!isDir(projectPath)) continue; if (!isDir(projectPath)) continue;
let sessionIds: string[]; let sessionIds: string[];
try { sessionIds = fs.readdirSync(projectPath); } catch { continue; } try { sessionIds = readdirSync(projectPath); } catch { continue; }
for (const sessionId of sessionIds) { for (const sessionId of sessionIds) {
const workflowDir = join(projectPath, sessionId, 'workflows'); const workflowDir = join(projectPath, sessionId, 'workflows');
if (!isDir(workflowDir)) continue; if (!isDir(workflowDir)) continue;
const mainTranscriptPath = join(projectPath, `${sessionId}.jsonl`); const mainTranscriptPath = join(projectPath, `${sessionId}.jsonl`);
let files: string[]; let files: string[];
try { files = fs.readdirSync(workflowDir); } catch { continue; } try { files = readdirSync(workflowDir); } catch { continue; }
for (const file of files) { for (const file of files) {
if (!file.endsWith('.json')) continue; if (!file.endsWith('.json')) continue;
const workflowPath = join(workflowDir, file); const workflowPath = join(workflowDir, file);
@@ -142,7 +140,7 @@ function discoverAt(rootDir: string, ctx: DiscoverContext): IndexUnit[] {
&& !changedWorkflowPaths.has(normalizedPath) && !changedWorkflowPaths.has(normalizedPath)
&& !relationshipChanged && !relationshipChanged
) continue; ) continue;
const mtime = fs.statSync(workflowPath).mtimeMs; const mtime = statSync(workflowPath).mtimeMs;
const cursor = ctx.lastCursor(workflowPath); const cursor = ctx.lastCursor(workflowPath);
if (!relationshipChanged && cursor !== null && Number(cursor.split(':')[0]) >= mtime) continue; if (!relationshipChanged && cursor !== null && Number(cursor.split(':')[0]) >= mtime) continue;
workflowUnits.push({ workflowUnits.push({
@@ -172,7 +170,7 @@ function workflowParentToolUseId(
runId: string, runId: string,
workflowName: string | null, workflowName: string | null,
): string | null { ): string | null {
if (!fs.existsSync(transcriptPath)) return null; if (!existsSync(transcriptPath)) return null;
const workflowToolIds = new Set<string>(); const workflowToolIds = new Set<string>();
let parentToolUseId: string | null = null; let parentToolUseId: string | null = null;
readLines(transcriptPath, (line: string) => { readLines(transcriptPath, (line: string) => {
@@ -201,10 +199,10 @@ function workflowParentToolUseId(
} }
function* parseWorkflow(unit: IndexUnit): Generator<TranscriptRecord, Cursor> { function* parseWorkflow(unit: IndexUnit): Generator<TranscriptRecord, Cursor> {
const mtime = fs.statSync(unit.key).mtimeMs; const mtime = statSync(unit.key).mtimeMs;
const outCursor = `${mtime}:1`; const outCursor = `${mtime}:1`;
let workflow: any; 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; if (!workflow?.runId) return outCursor;
const meta = unit.meta as ClaudeWorkflowUnitMeta; const meta = unit.meta as ClaudeWorkflowUnitMeta;
const progress = Array.isArray(workflow.workflowProgress) ? workflow.workflowProgress : []; const progress = Array.isArray(workflow.workflowProgress) ? workflow.workflowProgress : [];
@@ -251,7 +249,7 @@ export function* parse(unit: IndexUnit, cursor: Cursor): Generator<TranscriptRec
return yield* parseWorkflow(unit); return yield* parseWorkflow(unit);
} }
const skip = cursorToSkip(cursor); const skip = cursorToSkip(cursor);
const mtime = fs.statSync(unit.key).mtimeMs; const mtime = statSync(unit.key).mtimeMs;
const isSubagent = unit.isSubagent === true; const isSubagent = unit.isSubagent === true;
const records: TranscriptRecord[] = []; const records: TranscriptRecord[] = [];
const sm = { const sm = {
@@ -339,9 +337,9 @@ export function* parse(unit: IndexUnit, cursor: Cursor): Generator<TranscriptRec
if (isSubagent && unit.agentId) { if (isSubagent && unit.agentId) {
const metaPath = unit.key.replace(/\.jsonl$/, '.meta.json'); const metaPath = unit.key.replace(/\.jsonl$/, '.meta.json');
if (fs.existsSync(metaPath)) { if (existsSync(metaPath)) {
try { try {
const meta = JSON.parse(fs.readFileSync(metaPath, 'utf8')); const meta = JSON.parse(readFileSync(metaPath, 'utf8'));
const workflowRunId = (unit.meta as { workflowRunId?: string } | undefined)?.workflowRunId; const workflowRunId = (unit.meta as { workflowRunId?: string } | undefined)?.workflowRunId;
if (workflowRunId) { if (workflowRunId) {
records.push({ records.push({
@@ -394,7 +392,7 @@ function rawClaude(input: RawLookup): RawRecord | null {
? join(dirname(mainPath), String(input.session?.id ?? ''), 'subagents', 'workflows', runId, `${input.agentId}.jsonl`) ? join(dirname(mainPath), String(input.session?.id ?? ''), 'subagents', 'workflows', runId, `${input.agentId}.jsonl`)
: join(dirname(mainPath), String(input.session?.id ?? ''), 'subagents', `${input.agentId}.jsonl`); : join(dirname(mainPath), String(input.session?.id ?? ''), 'subagents', `${input.agentId}.jsonl`);
} }
if (!fs.existsSync(sourcePath)) return null; if (!existsSync(sourcePath)) return null;
let found: string | null = null; let found: string | null = null;
readLines(sourcePath, (line: string) => { readLines(sourcePath, (line: string) => {
if (!line.includes(input.messageUuid)) return; if (!line.includes(input.messageUuid)) return;
+7 -9
View File
@@ -8,11 +8,9 @@
// record uses countMode 'total' (persist replaces the count, never accumulates). // record uses countMode 'total' (persist replaces the count, never accumulates).
// The per-line logic mirrors the original indexCodexJsonl. // 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 { homedir } from 'node:os';
import { isAbsolute, join, normalize, relative } from 'node:path'; import { isAbsolute, join, normalize, relative } from 'node:path';
const require = createRequire(import.meta.url);
const fs = require('node:fs');
import { import {
trunc, truncJson, readLines, trunc, truncJson, readLines,
@@ -51,7 +49,7 @@ function discoverAt(rootDir: string, ctx: DiscoverContext): IndexUnit[] {
const sessionsDir = join(rootDir, 'sessions'); const sessionsDir = join(rootDir, 'sessions');
const sessionIndexPath = normalize(join(rootDir, 'session_index.jsonl')); const sessionIndexPath = normalize(join(rootDir, 'session_index.jsonl'));
const sessionIndex = new Map<string, { title: string; updatedAt: string | null }>(); const sessionIndex = new Map<string, { title: string; updatedAt: string | null }>();
if (fs.existsSync(sessionIndexPath)) { if (existsSync(sessionIndexPath)) {
readLines(sessionIndexPath, (line: string) => { readLines(sessionIndexPath, (line: string) => {
try { try {
const item = JSON.parse(line); 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 []; if (ctx.changedPaths !== undefined && !sessionIndexChanged && !changedFiles.has(normalize(file.path))) return [];
const cursor = ctx.lastCursor(file.path); const cursor = ctx.lastCursor(file.path);
const guardian = readCodexGuardianThreadInfo(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 []; return [];
} }
let meta: any = null; let meta: any = null;
@@ -116,7 +114,7 @@ export function discover(ctx: DiscoverContext): IndexUnit[] {
} }
export function* parse(unit: IndexUnit, _cursor: Cursor): Generator<TranscriptRecord, Cursor> { export function* parse(unit: IndexUnit, _cursor: Cursor): Generator<TranscriptRecord, Cursor> {
const mtime = fs.statSync(unit.key).mtimeMs; const mtime = statSync(unit.key).mtimeMs;
const records: { lineNum: number; obj: any }[] = []; const records: { lineNum: number; obj: any }[] = [];
let lineNum = 0; let lineNum = 0;
readLines(unit.key, (line: string) => { readLines(unit.key, (line: string) => {
@@ -316,8 +314,8 @@ function findCodexFile(rootDir: string, rawThreadId: string): string | null {
const stack = [join(rootDir, 'sessions')]; const stack = [join(rootDir, 'sessions')];
while (stack.length > 0) { while (stack.length > 0) {
const current = stack.pop()!; const current = stack.pop()!;
if (!fs.existsSync(current)) continue; if (!existsSync(current)) continue;
for (const entry of fs.readdirSync(current, { withFileTypes: true })) { for (const entry of readdirSync(current, { withFileTypes: true })) {
const path = join(current, entry.name); const path = join(current, entry.name);
if (entry.isDirectory()) stack.push(path); if (entry.isDirectory()) stack.push(path);
else if (entry.isFile() && entry.name.endsWith(`${rawThreadId}.jsonl`)) return 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' const path = input.agentId === null && typeof input.session?.jsonl_path === 'string'
? input.session.jsonl_path ? input.session.jsonl_path
: findCodexFile(rootDir, match[1]!); : findCodexFile(rootDir, match[1]!);
if (path === null || !fs.existsSync(path)) return null; if (path === null || !existsSync(path)) return null;
let lineNumber = 0; let lineNumber = 0;
let found: string | null = null; let found: string | null = null;
readLines(path, (line: string) => { readLines(path, (line: string) => {
+7 -6
View File
@@ -1,5 +1,6 @@
// Query and attune sandbox helpers for the Core package. // 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 { createBuiltinProviderRegistry } from './providers/builtins.ts';
import type { ProviderRegistry } from './providers/registry.ts'; import type { ProviderRegistry } from './providers/registry.ts';
import type { SqliteDb, SqliteRow } from './sqlite-types.ts'; import type { SqliteDb, SqliteRow } from './sqlite-types.ts';
@@ -325,7 +326,7 @@ function createQueryApi(
GROUP BY project, project_path GROUP BY project, project_path
`).all(); `).all();
const byProjectPath = paths 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]; .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'); if (byProjectPath) return projectDescriptor(byProjectPath, 'cwd_project_path', 'exact');
@@ -529,12 +530,12 @@ function createAttuneApi(db: SqliteDb) {
if (sessionId) { if (sessionId) {
base = db.prepare('SELECT project_path FROM sessions WHERE id=?').get(sessionId)?.project_path || null; base = db.prepare('SELECT project_path FROM sessions WHERE id=?').get(sessionId)?.project_path || null;
} }
const resolved = path.isAbsolute(memoryPath) const resolved = isAbsolute(memoryPath)
? path.normalize(memoryPath) ? normalize(memoryPath)
: path.resolve(base || process.cwd(), memoryPath); : resolve(base || process.cwd(), memoryPath);
let stat; let stat;
try { try {
stat = fs.statSync(resolved); stat = statSync(resolved);
} catch { } catch {
throw new Error(`remember() memory file does not exist: ${resolved}`); throw new Error(`remember() memory file does not exist: ${resolved}`);
} }