refactor(core): replace CommonJS requires with named imports (#31)
This commit is contained in:
+14
-16
@@ -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 };
|
||||||
|
|||||||
@@ -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 });
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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}`);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user