Refactor path management: Migrate path discovery logic to shared paths module

- Removed `path-discovery.ts` service and replaced its usage with a new `paths.ts` module.
- Updated all commands and services to utilize the new path constants and helper functions.
- Ensured all necessary directories are created using the new utility functions.
- Improved code readability and maintainability by centralizing path configurations.
This commit is contained in:
Alex Newman
2025-10-16 14:46:47 -04:00
parent 2ba840aaac
commit f2551ac366
18 changed files with 327 additions and 233 deletions
+93 -93
View File
File diff suppressed because one or more lines are too long
+31 -48
View File
@@ -105,7 +105,6 @@ src/
│ ├── prompts.ts # Prompt builders for SDK │ ├── prompts.ts # Prompt builders for SDK
│ └── parser.ts # XML response parser │ └── parser.ts # XML response parser
├── services/ # Core services ├── services/ # Core services
│ ├── path-discovery.ts # Cross-platform path resolution
│ └── sqlite/ # Database layer │ └── sqlite/ # Database layer
│ ├── index.ts # Database exports │ ├── index.ts # Database exports
│ ├── Database.ts # SQLite manager with migrations │ ├── Database.ts # SQLite manager with migrations
@@ -114,6 +113,7 @@ src/
│ └── types.ts # TypeScript interfaces │ └── types.ts # TypeScript interfaces
├── shared/ # Shared configuration ├── shared/ # Shared configuration
│ ├── config.ts # Package metadata │ ├── config.ts # Package metadata
│ ├── paths.ts # Path configuration constants
│ ├── storage.ts # Storage provider interface │ ├── storage.ts # Storage provider interface
│ └── types.ts # Core type definitions │ └── types.ts # Core type definitions
└── utils/ # Utility functions └── utils/ # Utility functions
@@ -714,59 +714,42 @@ export type { ParsedObservation, ParsedSummary } from './parser.js';
## Services ## Services
### `src/services/path-discovery.ts` ### `src/shared/paths.ts`
**Purpose**: Central path resolution service for cross-platform compatibility **Purpose**: Simple path configuration with standard constants
**Key Class**: `PathDiscovery` (Singleton) **Key Exports**:
**Key Responsibilities**: **Data Directory Constants**:
- Discover paths across different installation scenarios (global npm, local, development) - `DATA_DIR`: `~/.claude-mem` (or CLAUDE_MEM_DATA_DIR env var)
- Handle cross-platform differences (Windows, macOS, Linux) - `ARCHIVES_DIR`: `~/.claude-mem/archives`
- Support environment variable overrides - `LOGS_DIR`: `~/.claude-mem/logs`
- Find package resources (hooks, commands) - `TRASH_DIR`: `~/.claude-mem/trash`
- `BACKUPS_DIR`: `~/.claude-mem/backups`
- `CHROMA_DIR`: `~/.claude-mem/chroma`
- `USER_SETTINGS_PATH`: `~/.claude-mem/settings.json`
- `DB_PATH`: `~/.claude-mem/claude-mem.db`
**Key Methods**: **Claude Integration Constants**:
- `CLAUDE_CONFIG_DIR`: `~/.claude` (or CLAUDE_CONFIG_DIR env var)
- `CLAUDE_SETTINGS_PATH`: `~/.claude/settings.json`
- `CLAUDE_COMMANDS_DIR`: `~/.claude/commands`
- `CLAUDE_MD_PATH`: `~/.claude/CLAUDE.md`
**Data Directories**: **Helper Functions**:
- `getDataDirectory()`: `~/.claude-mem` (or CLAUDE_MEM_DATA_DIR) - `getProjectArchiveDir(projectName)`: Get project-specific archive directory
- `getArchivesDirectory()`: `~/.claude-mem/archives` - `getWorkerSocketPath(sessionId)`: Get Unix socket path for worker process
- `getLogsDirectory()`: `~/.claude-mem/logs` - `ensureDir(dirPath)`: Create directory with `{recursive: true}`
- `getIndexDirectory()`: `~/.claude-mem` - `ensureAllDataDirs()`: Create all data directories
- `getIndexPath()`: `~/.claude-mem/claude-mem-index.jsonl` - `ensureAllClaudeDirs()`: Create all Claude integration directories
- `getTrashDirectory()`: `~/.claude-mem/trash` - `getCurrentProjectName()`: Get project name from git root or cwd
- `getBackupsDirectory()`: `~/.claude-mem/backups`
- `getChromaDirectory()`: `~/.claude-mem/chroma`
- `getProjectArchiveDirectory(projectName)`: `~/.claude-mem/archives/{project}`
- `getUserSettingsPath()`: `~/.claude-mem/settings.json`
**Claude Integration Paths**:
- `getClaudeConfigDirectory()`: `~/.claude` (or CLAUDE_CONFIG_DIR)
- `getClaudeSettingsPath()`: `~/.claude/settings.json`
- `getClaudeCommandsDirectory()`: `~/.claude/commands`
- `getClaudeMdPath()`: `~/.claude/CLAUDE.md`
- `getMcpConfigPath()`: `~/.claude.json`
- `getProjectMcpConfigPath()`: `./.mcp.json`
**Package Discovery**:
- `getPackageRoot()`: Find claude-mem package root (2 fallback methods) - `getPackageRoot()`: Find claude-mem package root (2 fallback methods)
- `findPackageCommandsDirectory()`: Find commands directory in package - `getPackageCommandsDir()`: Find commands directory in package
- `createBackupFilename(originalPath)`: Generate timestamped backup filename
**Utility Methods**: **Environment Variable Overrides**:
- `ensureDirectory(dirPath)`: Create directory if missing - `CLAUDE_MEM_DATA_DIR`: Override data directory location
- `ensureDirectories(dirPaths)`: Create multiple directories - `CLAUDE_CONFIG_DIR`: Override Claude config directory location
- `ensureAllDataDirectories()`: Create all claude-mem data dirs
- `ensureAllClaudeDirectories()`: Create all Claude integration dirs
**Static Helpers**:
- `PathDiscovery.extractProjectName(filePath)`: Extract project from path
- `PathDiscovery.getCurrentProjectName()`: Get current project (git root or cwd)
- `PathDiscovery.createBackupFilename(originalPath)`: Generate timestamped backup
- `PathDiscovery.isPathAccessible(path)`: Check path existence and access
**Environment Overrides**:
- `CLAUDE_MEM_DATA_DIR`: Override data directory
- `CLAUDE_CONFIG_DIR`: Override Claude config directory
--- ---
@@ -1500,13 +1483,13 @@ Show:
- `src/sdk/parser.ts:26-132` - XML parser - `src/sdk/parser.ts:26-132` - XML parser
### Services ### Services
- `src/services/path-discovery.ts:16-341` - Path resolution
- `src/services/sqlite/Database.ts:20-177` - Database manager - `src/services/sqlite/Database.ts:20-177` - Database manager
- `src/services/sqlite/HooksDatabase.ts:11-207` - Hooks database - `src/services/sqlite/HooksDatabase.ts:11-207` - Hooks database
- `src/services/sqlite/migrations.ts:7-374` - Schema migrations - `src/services/sqlite/migrations.ts:7-374` - Schema migrations
### Shared ### Shared
- `src/shared/config.ts:1-51` - Package metadata - `src/shared/config.ts:1-51` - Package metadata
- `src/shared/paths.ts:1-146` - Path configuration
- `src/shared/types.ts:15-30` - Core types - `src/shared/types.ts:15-30` - Core types
- `src/shared/storage.ts:26-188` - Storage abstraction - `src/shared/storage.ts:26-188` - Storage abstraction
+9 -12
View File
@@ -1,7 +1,7 @@
import { OptionValues } from 'commander'; import { OptionValues } from 'commander';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { PathDiscovery } from '../services/path-discovery.js'; import * as paths from '../shared/paths.js';
import { HooksDatabase } from '../services/sqlite/index.js'; import { HooksDatabase } from '../services/sqlite/index.js';
type CheckStatus = 'pass' | 'fail' | 'warn'; type CheckStatus = 'pass' | 'fail' | 'warn';
@@ -20,24 +20,22 @@ function printCheck(result: CheckResult): void {
} }
export async function doctor(options: OptionValues = {}): Promise<void> { export async function doctor(options: OptionValues = {}): Promise<void> {
const discovery = PathDiscovery.getInstance();
const checks: CheckResult[] = []; const checks: CheckResult[] = [];
// Data directory // Data directory
try { try {
const dataDir = discovery.getDataDirectory(); if (!fs.existsSync(paths.DATA_DIR)) {
if (!fs.existsSync(dataDir)) { fs.mkdirSync(paths.DATA_DIR, { recursive: true });
fs.mkdirSync(dataDir, { recursive: true }); checks.push({ name: `Data directory created at ${paths.DATA_DIR}`, status: 'warn' });
checks.push({ name: `Data directory created at ${dataDir}`, status: 'warn' });
} else { } else {
const stats = fs.statSync(dataDir); const stats = fs.statSync(paths.DATA_DIR);
let writable = false; let writable = false;
try { try {
fs.accessSync(dataDir, fs.constants.W_OK); fs.accessSync(paths.DATA_DIR, fs.constants.W_OK);
writable = true; writable = true;
} catch {} } catch {}
checks.push({ checks.push({
name: `Data directory ${dataDir}`, name: `Data directory ${paths.DATA_DIR}`,
status: stats.isDirectory() && writable ? 'pass' : 'fail', status: stats.isDirectory() && writable ? 'pass' : 'fail',
details: stats.isDirectory() && writable ? 'accessible' : 'not writable' details: stats.isDirectory() && writable ? 'accessible' : 'not writable'
}); });
@@ -68,12 +66,11 @@ export async function doctor(options: OptionValues = {}): Promise<void> {
// Chroma connectivity // Chroma connectivity
try { try {
const chromaDir = discovery.getChromaDirectory(); const chromaExists = fs.existsSync(paths.CHROMA_DIR);
const chromaExists = fs.existsSync(chromaDir);
checks.push({ checks.push({
name: 'Chroma vector store', name: 'Chroma vector store',
status: chromaExists ? 'pass' : 'warn', status: chromaExists ? 'pass' : 'warn',
details: chromaExists ? `data dir ${path.resolve(chromaDir)}` : 'Not yet initialized' details: chromaExists ? `data dir ${path.resolve(paths.CHROMA_DIR)}` : 'Not yet initialized'
}); });
} catch (error: any) { } catch (error: any) {
checks.push({ checks.push({
+11 -23
View File
@@ -10,8 +10,8 @@ import chalk from 'chalk';
import boxen from 'boxen'; import boxen from 'boxen';
import { PACKAGE_NAME } from '../shared/config.js'; import { PACKAGE_NAME } from '../shared/config.js';
import type { Settings } from '../shared/types.js'; import type { Settings } from '../shared/types.js';
import { PathDiscovery } from '../services/path-discovery.js';
import { Platform } from '../utils/platform.js'; import { Platform } from '../utils/platform.js';
import * as paths from '../shared/paths.js';
// Enhanced animation utilities // Enhanced animation utilities
@@ -60,8 +60,7 @@ function installUv(): void {
function hasExistingInstallation(): boolean { function hasExistingInstallation(): boolean {
const pathDiscovery = PathDiscovery.getInstance(); const settingsPath = paths.CLAUDE_SETTINGS_PATH;
const settingsPath = pathDiscovery.getClaudeSettingsPath();
if (!existsSync(settingsPath)) return false; if (!existsSync(settingsPath)) return false;
@@ -181,16 +180,14 @@ async function runInstallationWizard(existingInstall: boolean): Promise<InstallC
// <Block> Directory structure creation - natural setup flow // <Block> Directory structure creation - natural setup flow
function ensureDirectoryStructure(): void { function ensureDirectoryStructure(): void {
const pathDiscovery = PathDiscovery.getInstance();
// Create all data directories // Create all data directories
pathDiscovery.ensureAllDataDirectories(); paths.ensureAllDataDirs();
// Create all Claude integration directories // Create all Claude integration directories
pathDiscovery.ensureAllClaudeDirectories(); paths.ensureAllClaudeDirs();
// Create package.json in .claude-mem to fix ESM module issues // Create package.json in .claude-mem to fix ESM module issues
const packageJsonPath = join(pathDiscovery.getDataDirectory(), 'package.json'); const packageJsonPath = join(paths.DATA_DIR, 'package.json');
if (!existsSync(packageJsonPath)) { if (!existsSync(packageJsonPath)) {
const packageJson = { const packageJson = {
name: "claude-mem-data", name: "claude-mem-data",
@@ -204,9 +201,7 @@ function ensureDirectoryStructure(): void {
function copyFileRecursively(src: string, dest: string): void { function copyFileRecursively(src: string, dest: string): void {
const stat = statSync(src); const stat = statSync(src);
if (stat.isDirectory()) { if (stat.isDirectory()) {
if (!existsSync(dest)) {
mkdirSync(dest, { recursive: true }); mkdirSync(dest, { recursive: true });
}
const files = readdirSync(src); const files = readdirSync(src);
files.forEach((file: string) => { files.forEach((file: string) => {
copyFileRecursively(join(src, file), join(dest, file)); copyFileRecursively(join(src, file), join(dest, file));
@@ -219,14 +214,11 @@ function copyFileRecursively(src: string, dest: string): void {
function ensureClaudeMdInstructions(): void { function ensureClaudeMdInstructions(): void {
const pathDiscovery = PathDiscovery.getInstance(); const claudeMdPath = paths.CLAUDE_MD_PATH;
const claudeMdPath = pathDiscovery.getClaudeMdPath();
const claudeMdDir = dirname(claudeMdPath); const claudeMdDir = dirname(claudeMdPath);
// Ensure .claude directory exists // Ensure .claude directory exists
if (!existsSync(claudeMdDir)) {
mkdirSync(claudeMdDir, { recursive: true }); mkdirSync(claudeMdDir, { recursive: true });
}
const instructions = ` const instructions = `
<!-- CLAUDE-MEM QUICK REFERENCE --> <!-- CLAUDE-MEM QUICK REFERENCE -->
@@ -308,7 +300,7 @@ function installChromaMcp(forceReinstall: boolean = false): void {
} }
} }
const chromaMcpCommand = `claude mcp add claude-mem -- uvx chroma-mcp --client-type persistent --data-dir ${PathDiscovery.getInstance().getChromaDirectory()}`; const chromaMcpCommand = `claude mcp add claude-mem -- uvx chroma-mcp --client-type persistent --data-dir ${paths.CHROMA_DIR}`;
execSync(chromaMcpCommand, { stdio: 'inherit' }); execSync(chromaMcpCommand, { stdio: 'inherit' });
} }
@@ -355,13 +347,12 @@ function getSettingsPath(config: InstallConfig): string {
} else if (config.scope === 'project') { } else if (config.scope === 'project') {
return join(process.cwd(), '.claude', 'settings.json'); return join(process.cwd(), '.claude', 'settings.json');
} else { } else {
return PathDiscovery.getInstance().getClaudeSettingsPath(); return paths.CLAUDE_SETTINGS_PATH;
} }
} }
function configureUserSettings(config: InstallConfig): void { function configureUserSettings(config: InstallConfig): void {
const pathDiscovery = PathDiscovery.getInstance(); const userSettingsPath = paths.USER_SETTINGS_PATH;
const userSettingsPath = pathDiscovery.getUserSettingsPath();
let userSettings: Settings = existsSync(userSettingsPath) let userSettings: Settings = existsSync(userSettingsPath)
? JSON.parse(readFileSync(userSettingsPath, 'utf8')) ? JSON.parse(readFileSync(userSettingsPath, 'utf8'))
@@ -384,9 +375,7 @@ function configureSmartTrashAlias(): void {
if (!existsSync(configPath)) { if (!existsSync(configPath)) {
// Create the file if it doesn't exist (especially for PowerShell profiles) // Create the file if it doesn't exist (especially for PowerShell profiles)
const dir = dirname(configPath); const dir = dirname(configPath);
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true }); mkdirSync(dir, { recursive: true });
}
writeFileSync(configPath, ''); writeFileSync(configPath, '');
} }
@@ -401,9 +390,8 @@ function configureSmartTrashAlias(): void {
function installClaudeCommands(): void { function installClaudeCommands(): void {
const pathDiscovery = PathDiscovery.getInstance(); const claudeCommandsDir = paths.CLAUDE_COMMANDS_DIR;
const claudeCommandsDir = pathDiscovery.getClaudeCommandsDirectory(); const packageCommandsDir = paths.getPackageCommandsDir();
const packageCommandsDir = pathDiscovery.findPackageCommandsDirectory();
mkdirSync(claudeCommandsDir, { recursive: true }); mkdirSync(claudeCommandsDir, { recursive: true });
+2 -2
View File
@@ -1,7 +1,7 @@
import { OptionValues } from 'commander'; import { OptionValues } from 'commander';
import { readFileSync, readdirSync, statSync } from 'fs'; import { readFileSync, readdirSync, statSync } from 'fs';
import { join } from 'path'; import { join } from 'path';
import { PathDiscovery } from '../services/path-discovery.js'; import * as paths from '../shared/paths.js';
// <Block> 1.1 ==================================== // <Block> 1.1 ====================================
async function showLog(logPath: string, logType: string, tail: number): Promise<void> { async function showLog(logPath: string, logType: string, tail: number): Promise<void> {
@@ -39,7 +39,7 @@ async function showLog(logPath: string, logType: string, tail: number): Promise<
// <Block> 2.1 ==================================== // <Block> 2.1 ====================================
export async function logs(options: OptionValues = {}): Promise<void> { export async function logs(options: OptionValues = {}): Promise<void> {
// <Block> 2.2 ==================================== // <Block> 2.2 ====================================
const logsDir = PathDiscovery.getLogsDirectory(); const logsDir = paths.LOGS_DIR;
const tail = parseInt(options.tail) || 20; const tail = parseInt(options.tail) || 20;
// </Block> ======================================= // </Block> =======================================
+2 -2
View File
@@ -1,10 +1,10 @@
import { readdirSync, renameSync } from 'fs'; import { readdirSync, renameSync } from 'fs';
import { join } from 'path'; import { join } from 'path';
import * as p from '@clack/prompts'; import * as p from '@clack/prompts';
import { PathDiscovery } from '../services/path-discovery.js'; import * as paths from '../shared/paths.js';
export async function restore(): Promise<void> { export async function restore(): Promise<void> {
const trashDir = PathDiscovery.getInstance().getTrashDirectory(); const trashDir = paths.TRASH_DIR;
const files = readdirSync(trashDir); const files = readdirSync(trashDir);
if (files.length === 0) { if (files.length === 0) {
+6 -6
View File
@@ -2,7 +2,7 @@ import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
import { join, resolve, dirname } from 'path'; import { join, resolve, dirname } from 'path';
import { execSync } from 'child_process'; import { execSync } from 'child_process';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { PathDiscovery } from '../services/path-discovery.js'; import * as paths from '../shared/paths.js';
import { HooksDatabase } from '../services/sqlite/index.js'; import { HooksDatabase } from '../services/sqlite/index.js';
import chalk from 'chalk'; import chalk from 'chalk';
@@ -12,7 +12,7 @@ export async function status(): Promise<void> {
console.log('🔍 Claude Memory System Status Check'); console.log('🔍 Claude Memory System Status Check');
console.log('=====================================\n'); console.log('=====================================\n');
const pathDiscovery = PathDiscovery.getInstance(); // paths imported
console.log('⚙️ Settings Configuration:'); console.log('⚙️ Settings Configuration:');
@@ -53,13 +53,13 @@ export async function status(): Promise<void> {
} }
}; };
checkSettings('Global', pathDiscovery.getClaudeSettingsPath()); checkSettings('Global', paths.ClaudeSettingsPath());
checkSettings('Project', join(process.cwd(), '.claude', 'settings.json')); checkSettings('Project', join(process.cwd(), '.claude', 'settings.json'));
console.log(''); console.log('');
console.log('📦 Compressed Transcripts:'); console.log('📦 Compressed Transcripts:');
const claudeProjectsDir = join(pathDiscovery.getClaudeConfigDirectory(), 'projects'); const claudeProjectsDir = join(paths.ClaudeConfigDirectory(), 'projects');
if (existsSync(claudeProjectsDir)) { if (existsSync(claudeProjectsDir)) {
try { try {
@@ -116,13 +116,13 @@ export async function status(): Promise<void> {
console.log('🧠 Chroma Storage Status:'); console.log('🧠 Chroma Storage Status:');
console.log(' ✅ Storage backend: Chroma MCP'); console.log(' ✅ Storage backend: Chroma MCP');
console.log(` 📍 Data location: ${pathDiscovery.getChromaDirectory()}`); console.log(` 📍 Data location: ${paths.ChromaDirectory()}`);
console.log(' 🔍 Features: Vector search, semantic similarity, document storage'); console.log(' 🔍 Features: Vector search, semantic similarity, document storage');
console.log(''); console.log('');
console.log('📊 Summary:'); console.log('📊 Summary:');
const globalPath = pathDiscovery.getClaudeSettingsPath(); const globalPath = paths.ClaudeSettingsPath();
const projectPath = join(process.cwd(), '.claude', 'settings.json'); const projectPath = join(process.cwd(), '.claude', 'settings.json');
let isInstalled = false; let isInstalled = false;
+2 -2
View File
@@ -1,10 +1,10 @@
import { rmSync, readdirSync, existsSync, statSync } from 'fs'; import { rmSync, readdirSync, existsSync, statSync } from 'fs';
import { join } from 'path'; import { join } from 'path';
import * as p from '@clack/prompts'; import * as p from '@clack/prompts';
import { PathDiscovery } from '../services/path-discovery.js'; import * as paths from '../shared/paths.js';
export async function emptyTrash(options: { force?: boolean } = {}): Promise<void> { export async function emptyTrash(options: { force?: boolean } = {}): Promise<void> {
const trashDir = PathDiscovery.getInstance().getTrashDirectory(); const trashDir = paths.TRASH_DIR;
// Check if trash directory exists // Check if trash directory exists
if (!existsSync(trashDir)) { if (!existsSync(trashDir)) {
+2 -2
View File
@@ -1,7 +1,7 @@
import { readdirSync, statSync } from 'fs'; import { readdirSync, statSync } from 'fs';
import { join, basename } from 'path'; import { join, basename } from 'path';
import * as p from '@clack/prompts'; import * as p from '@clack/prompts';
import { PathDiscovery } from '../services/path-discovery.js'; import * as paths from '../shared/paths.js';
interface TrashItem { interface TrashItem {
originalName: string; originalName: string;
@@ -51,7 +51,7 @@ function getDirectorySize(dirPath: string): number {
} }
export async function viewTrash(): Promise<void> { export async function viewTrash(): Promise<void> {
const trashDir = PathDiscovery.getInstance().getTrashDirectory(); const trashDir = paths.TRASH_DIR;
try { try {
const files = readdirSync(trashDir); const files = readdirSync(trashDir);
+2 -2
View File
@@ -1,7 +1,7 @@
import { renameSync, existsSync, mkdirSync, statSync } from 'fs'; import { renameSync, existsSync, mkdirSync, statSync } from 'fs';
import { join, basename } from 'path'; import { join, basename } from 'path';
import { glob } from 'glob'; import { glob } from 'glob';
import { PathDiscovery } from '../services/path-discovery.js'; import * as paths from '../shared/paths.js';
interface TrashOptions { interface TrashOptions {
force?: boolean; force?: boolean;
@@ -9,7 +9,7 @@ interface TrashOptions {
} }
export async function trash(filePaths: string | string[], options: TrashOptions = {}): Promise<void> { export async function trash(filePaths: string | string[], options: TrashOptions = {}): Promise<void> {
const trashDir = PathDiscovery.getInstance().getTrashDirectory(); const trashDir = paths.TRASH_DIR;
if (!existsSync(trashDir)) mkdirSync(trashDir, { recursive: true }); if (!existsSync(trashDir)) mkdirSync(trashDir, { recursive: true });
// Handle single string or array of paths // Handle single string or array of paths
+3 -4
View File
@@ -2,7 +2,7 @@ import { OptionValues } from 'commander';
import { readFileSync, writeFileSync, existsSync } from 'fs'; import { readFileSync, writeFileSync, existsSync } from 'fs';
import { join } from 'path'; import { join } from 'path';
import { homedir } from 'os'; import { homedir } from 'os';
import { PathDiscovery } from '../services/path-discovery.js'; import * as paths from '../shared/paths.js';
async function removeSmartTrashAlias(): Promise<boolean> { async function removeSmartTrashAlias(): Promise<boolean> {
const homeDir = homedir(); const homeDir = homedir();
@@ -71,7 +71,7 @@ export async function uninstall(options: OptionValues = {}): Promise<void> {
if (options.all) { if (options.all) {
locations.push({ locations.push({
name: 'User', name: 'User',
path: PathDiscovery.getInstance().getClaudeSettingsPath() path: paths.CLAUDE_SETTINGS_PATH
}); });
locations.push({ locations.push({
name: 'Project', name: 'Project',
@@ -79,10 +79,9 @@ export async function uninstall(options: OptionValues = {}): Promise<void> {
}); });
} else { } else {
const isProject = options.project; const isProject = options.project;
const pathDiscovery = PathDiscovery.getInstance();
locations.push({ locations.push({
name: isProject ? 'Project' : 'User', name: isProject ? 'Project' : 'User',
path: isProject ? join(process.cwd(), '.claude', 'settings.json') : pathDiscovery.getClaudeSettingsPath() path: isProject ? join(process.cwd(), '.claude', 'settings.json') : paths.CLAUDE_SETTINGS_PATH
}); });
} }
+1 -2
View File
@@ -1,5 +1,4 @@
import { HooksDatabase } from '../services/sqlite/HooksDatabase.js'; import { HooksDatabase } from '../services/sqlite/HooksDatabase.js';
import { PathDiscovery } from '../services/path-discovery.js';
import path from 'path'; import path from 'path';
export interface SessionStartInput { export interface SessionStartInput {
@@ -69,7 +68,7 @@ export function contextHook(input: SessionStartInput): void {
output.push(`**Files Edited:** ${files.join(', ')}`); output.push(`**Files Edited:** ${files.join(', ')}`);
} }
} catch { } catch {
// If not valid JSON, show as text // Backwards compatibility: if not valid JSON, show as text
if (summary.files_edited.trim()) { if (summary.files_edited.trim()) {
output.push(`**Files Edited:** ${summary.files_edited}`); output.push(`**Files Edited:** ${summary.files_edited}`);
} }
+2 -4
View File
@@ -1,7 +1,6 @@
import net from 'net'; import net from 'net';
import { join } from 'path';
import { HooksDatabase } from '../services/sqlite/HooksDatabase.js'; import { HooksDatabase } from '../services/sqlite/HooksDatabase.js';
import { PathDiscovery } from '../services/path-discovery.js'; import { getWorkerSocketPath } from '../shared/paths.js';
export interface PostToolUseInput { export interface PostToolUseInput {
session_id: string; session_id: string;
@@ -45,8 +44,7 @@ export function saveHook(input: PostToolUseInput): void {
} }
// Get socket path // Get socket path
const dataDir = PathDiscovery.getInstance().getDataDirectory(); const socketPath = getWorkerSocketPath(session.id);
const socketPath = join(dataDir, `worker-${session.id}.sock`);
// Send observation via Unix socket // Send observation via Unix socket
const message = { const message = {
+2 -4
View File
@@ -1,7 +1,6 @@
import net from 'net'; import net from 'net';
import { join } from 'path';
import { HooksDatabase } from '../services/sqlite/HooksDatabase.js'; import { HooksDatabase } from '../services/sqlite/HooksDatabase.js';
import { PathDiscovery } from '../services/path-discovery.js'; import { getWorkerSocketPath } from '../shared/paths.js';
export interface StopInput { export interface StopInput {
session_id: string; session_id: string;
@@ -29,8 +28,7 @@ export function summaryHook(input: StopInput): void {
} }
// Get socket path // Get socket path
const dataDir = PathDiscovery.getInstance().getDataDirectory(); const socketPath = getWorkerSocketPath(session.id);
const socketPath = join(dataDir, `worker-${session.id}.sock`);
// Send FINALIZE message via Unix socket // Send FINALIZE message via Unix socket
const message = { const message = {
+2 -6
View File
@@ -6,10 +6,9 @@
import net from 'net'; import net from 'net';
import { unlinkSync, existsSync } from 'fs'; import { unlinkSync, existsSync } from 'fs';
import { join } from 'path';
import { query } from '@anthropic-ai/claude-agent-sdk'; import { query } from '@anthropic-ai/claude-agent-sdk';
import { HooksDatabase } from '../services/sqlite/HooksDatabase.js'; import { HooksDatabase } from '../services/sqlite/HooksDatabase.js';
import { PathDiscovery } from '../services/path-discovery.js'; import { getWorkerSocketPath } from '../shared/paths.js';
import { buildInitPrompt, buildObservationPrompt, buildFinalizePrompt } from './prompts.js'; import { buildInitPrompt, buildObservationPrompt, buildFinalizePrompt } from './prompts.js';
import { parseObservations, parseSummary } from './parser.js'; import { parseObservations, parseSummary } from './parser.js';
import type { SDKSession } from './prompts.js'; import type { SDKSession } from './prompts.js';
@@ -64,10 +63,7 @@ class SDKWorker {
this.sessionDbId = sessionDbId; this.sessionDbId = sessionDbId;
this.db = new HooksDatabase(); this.db = new HooksDatabase();
this.abortController = new AbortController(); this.abortController = new AbortController();
this.socketPath = getWorkerSocketPath(sessionDbId);
// Socket path: ~/.claude-mem/worker-{sessionId}.sock
const dataDir = PathDiscovery.getInstance().getDataDirectory();
this.socketPath = join(dataDir, `worker-${sessionDbId}.sock`);
} }
/** /**
+3 -7
View File
@@ -1,7 +1,5 @@
import { Database as BunDatabase } from 'bun:sqlite'; import { Database as BunDatabase } from 'bun:sqlite';
import path from 'path'; import { DATA_DIR, DB_PATH, ensureDir } from '../../shared/paths.js';
import fs from 'fs';
import { PathDiscovery } from '../path-discovery.js';
// Type alias for better-sqlite3 compatibility // Type alias for better-sqlite3 compatibility
type Database = BunDatabase; type Database = BunDatabase;
@@ -47,11 +45,9 @@ export class DatabaseManager {
} }
// Ensure the data directory exists // Ensure the data directory exists
const dataDir = PathDiscovery.getInstance().getDataDirectory(); ensureDir(DATA_DIR);
fs.mkdirSync(dataDir, { recursive: true });
const dbPath = path.join(dataDir, 'claude-mem.db'); this.db = new BunDatabase(DB_PATH, { create: true, readwrite: true });
this.db = new BunDatabase(dbPath, { create: true, readwrite: true });
// Apply optimized SQLite settings // Apply optimized SQLite settings
this.db.run('PRAGMA journal_mode = WAL'); this.db.run('PRAGMA journal_mode = WAL');
+3 -8
View File
@@ -1,7 +1,5 @@
import { Database } from 'bun:sqlite'; import { Database } from 'bun:sqlite';
import path from 'path'; import { DATA_DIR, DB_PATH, ensureDir } from '../../shared/paths.js';
import fs from 'fs';
import { PathDiscovery } from '../path-discovery.js';
/** /**
* Lightweight database interface for hooks * Lightweight database interface for hooks
@@ -12,11 +10,8 @@ export class HooksDatabase {
private db: Database; private db: Database;
constructor() { constructor() {
const dataDir = PathDiscovery.getInstance().getDataDirectory(); ensureDir(DATA_DIR);
fs.mkdirSync(dataDir, { recursive: true }); this.db = new Database(DB_PATH, { create: true, readwrite: true });
const dbPath = path.join(dataDir, 'claude-mem.db');
this.db = new Database(dbPath, { create: true, readwrite: true });
// Ensure optimized settings // Ensure optimized settings
this.db.run('PRAGMA journal_mode = WAL'); this.db.run('PRAGMA journal_mode = WAL');
+145
View File
@@ -0,0 +1,145 @@
import { join, dirname, basename, sep } from 'path';
import { homedir } from 'os';
import { existsSync, mkdirSync } from 'fs';
import { execSync } from 'child_process';
import { fileURLToPath } from 'url';
/**
* Simple path configuration for claude-mem
* Standard paths based on Claude Code conventions
*/
// Base directories
export const DATA_DIR = process.env.CLAUDE_MEM_DATA_DIR || join(homedir(), '.claude-mem');
export const CLAUDE_CONFIG_DIR = process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude');
// Data subdirectories
export const ARCHIVES_DIR = join(DATA_DIR, 'archives');
export const LOGS_DIR = join(DATA_DIR, 'logs');
export const TRASH_DIR = join(DATA_DIR, 'trash');
export const BACKUPS_DIR = join(DATA_DIR, 'backups');
export const CHROMA_DIR = join(DATA_DIR, 'chroma');
export const USER_SETTINGS_PATH = join(DATA_DIR, 'settings.json');
export const DB_PATH = join(DATA_DIR, 'claude-mem.db');
// Claude integration paths
export const CLAUDE_SETTINGS_PATH = join(CLAUDE_CONFIG_DIR, 'settings.json');
export const CLAUDE_COMMANDS_DIR = join(CLAUDE_CONFIG_DIR, 'commands');
export const CLAUDE_MD_PATH = join(CLAUDE_CONFIG_DIR, 'CLAUDE.md');
/**
* Get project-specific archive directory
*/
export function getProjectArchiveDir(projectName: string): string {
return join(ARCHIVES_DIR, projectName);
}
/**
* Get worker socket path for a session
*/
export function getWorkerSocketPath(sessionId: number): string {
return join(DATA_DIR, `worker-${sessionId}.sock`);
}
/**
* Ensure a directory exists
*/
export function ensureDir(dirPath: string): void {
mkdirSync(dirPath, { recursive: true });
}
/**
* Ensure all data directories exist
*/
export function ensureAllDataDirs(): void {
ensureDir(DATA_DIR);
ensureDir(ARCHIVES_DIR);
ensureDir(LOGS_DIR);
ensureDir(TRASH_DIR);
ensureDir(BACKUPS_DIR);
ensureDir(CHROMA_DIR);
}
/**
* Ensure all Claude integration directories exist
*/
export function ensureAllClaudeDirs(): void {
ensureDir(CLAUDE_CONFIG_DIR);
ensureDir(CLAUDE_COMMANDS_DIR);
}
/**
* Get current project name from git root or cwd
*/
export function getCurrentProjectName(): string {
try {
const gitRoot = execSync('git rev-parse --show-toplevel', {
cwd: process.cwd(),
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'ignore']
}).trim();
return basename(gitRoot);
} catch {
return basename(process.cwd());
}
}
/**
* Find package root directory (for install command)
*/
export function getPackageRoot(): string {
// Method 1: Try require.resolve for package.json
try {
const packageJsonPath = require.resolve('claude-mem/package.json');
return dirname(packageJsonPath);
} catch {
// Continue to next method
}
// Method 2: Walk up from current module location
const currentFile = fileURLToPath(import.meta.url);
let currentDir = dirname(currentFile);
for (let i = 0; i < 10; i++) {
const packageJsonPath = join(currentDir, 'package.json');
if (existsSync(packageJsonPath)) {
const packageJson = require(packageJsonPath);
if (packageJson.name === 'claude-mem') {
return currentDir;
}
}
const parentDir = dirname(currentDir);
if (parentDir === currentDir) break;
currentDir = parentDir;
}
throw new Error('Cannot locate claude-mem package root. Ensure claude-mem is properly installed.');
}
/**
* Find commands directory in the installed package
*/
export function getPackageCommandsDir(): string {
const packageRoot = getPackageRoot();
const commandsDir = join(packageRoot, 'commands');
if (!existsSync(join(commandsDir, 'save.md'))) {
throw new Error('Package commands directory missing required files');
}
return commandsDir;
}
/**
* Create a timestamped backup filename
*/
export function createBackupFilename(originalPath: string): string {
const timestamp = new Date()
.toISOString()
.replace(/[:.]/g, '-')
.replace('T', '_')
.slice(0, 19);
return `${originalPath}.backup.${timestamp}`;
}