Refactor: Remove unused logging and path management utilities
- Removed the rollingLog import and its usage in doctor.ts. - Deleted the animatedRainbow function from install.ts. - Removed the log following implementation in logs.ts. - Deleted the PathResolver class and its related methods in paths.ts. - Removed the SettingsManager class and its associated methods in settings.ts. - Deleted the JSONLStorageProvider class and its methods in storage.ts. - Removed the CompressionError class from types.ts. - Cleaned up the Platform utility by removing unnecessary methods related to shell and file permissions.
This commit is contained in:
@@ -3,7 +3,6 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { PathDiscovery } from '../services/path-discovery.js';
|
||||
import { createStores } from '../services/sqlite/index.js';
|
||||
import { rollingLog } from '../shared/rolling-log.js';
|
||||
|
||||
type CheckStatus = 'pass' | 'fail' | 'warn';
|
||||
|
||||
@@ -93,8 +92,4 @@ export async function doctor(options: OptionValues = {}): Promise<void> {
|
||||
console.log('=================');
|
||||
checks.forEach(printCheck);
|
||||
}
|
||||
|
||||
rollingLog('info', 'doctor run completed', {
|
||||
status: checks.map((c) => c.status)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -35,28 +35,6 @@ function createLoadingAnimation(message: string) {
|
||||
};
|
||||
}
|
||||
|
||||
// Create animated rainbow text with adjustable speed
|
||||
function animatedRainbow(text: string, speed: number = 100): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
let offset = 0;
|
||||
const maxFrames = 10;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
// Create a shifted gradient by rotating through different presets
|
||||
const gradients = [fastRainbow, vibrantRainbow, gradient.rainbow, gradient.pastel];
|
||||
const shifted = gradients[offset % gradients.length](text);
|
||||
process.stdout.write('\r' + shifted);
|
||||
offset++;
|
||||
|
||||
if (offset >= maxFrames) {
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
}
|
||||
}, speed);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Fast rainbow gradient preset with tighter color transitions
|
||||
const fastRainbow = gradient(['#ff0000', '#ff4500', '#ffa500', '#ffff00', '#00ff00', '#00ffff', '#0000ff', '#8b00ff']);
|
||||
const vibrantRainbow = gradient(['#ff006e', '#fb5607', '#ffbe0b', '#8338ec', '#3a86ff']);
|
||||
|
||||
@@ -70,15 +70,4 @@ export async function logs(options: OptionValues = {}): Promise<void> {
|
||||
console.log('❌ Could not read logs directory: ~/.claude-mem/logs/');
|
||||
console.log(' Run a compression first to generate logs');
|
||||
}
|
||||
|
||||
// <Block> 2.5 ====================================
|
||||
if (options.follow) {
|
||||
console.log('Following logs... (Press Ctrl+C to stop)');
|
||||
// Basic follow implementation - would need more sophisticated watching in real usage
|
||||
setInterval(() => {
|
||||
// This would need proper file watching implementation
|
||||
}, 1000);
|
||||
}
|
||||
// </Block> =======================================
|
||||
// </Block> =======================================
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
import { sep, basename } from 'path';
|
||||
import { PathDiscovery } from '../services/path-discovery.js';
|
||||
|
||||
/**
|
||||
* PathResolver utility for managing claude-mem file system paths
|
||||
* Now delegates to PathDiscovery service for centralized path management
|
||||
*/
|
||||
export class PathResolver {
|
||||
private pathDiscovery: PathDiscovery;
|
||||
|
||||
// <Block> 1.1 ====================================
|
||||
constructor() {
|
||||
this.pathDiscovery = PathDiscovery.getInstance();
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 1.2 ====================================
|
||||
getConfigDir(): string {
|
||||
return this.pathDiscovery.getDataDirectory();
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 1.3 ====================================
|
||||
getIndexDir(): string {
|
||||
return this.pathDiscovery.getIndexDirectory();
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 1.4 ====================================
|
||||
getIndexPath(): string {
|
||||
return this.pathDiscovery.getIndexPath();
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 1.5 ====================================
|
||||
getArchiveDir(): string {
|
||||
return this.pathDiscovery.getArchivesDirectory();
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 1.6 ====================================
|
||||
getProjectArchiveDir(projectName: string): string {
|
||||
return this.pathDiscovery.getProjectArchiveDirectory(projectName);
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 1.7 ====================================
|
||||
getLogsDir(): string {
|
||||
return this.pathDiscovery.getLogsDirectory();
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 1.8 ====================================
|
||||
static ensureDirectory(dirPath: string): void {
|
||||
PathDiscovery.getInstance().ensureDirectory(dirPath);
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 1.9 ====================================
|
||||
static ensureDirectories(dirPaths: string[]): void {
|
||||
PathDiscovery.getInstance().ensureDirectories(dirPaths);
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 1.10 ===================================
|
||||
static extractProjectName(transcriptPath: string): string {
|
||||
return PathDiscovery.extractProjectName(transcriptPath);
|
||||
}
|
||||
|
||||
// <Block> 1.11 ===================================
|
||||
/**
|
||||
* DRY utility function: Canonical source for getting the current project prefix
|
||||
* Replaces all instances of path.basename(process.cwd()) across the codebase
|
||||
* @returns The current project directory name, sanitized for use as a prefix
|
||||
*/
|
||||
static getCurrentProjectPrefix(): string {
|
||||
return PathDiscovery.getCurrentProjectName();
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 1.12 ===================================
|
||||
/**
|
||||
* DRY utility function: Gets raw project name without sanitization
|
||||
* For use in contexts where original directory name is needed (e.g., display)
|
||||
* @returns The current project directory name as-is
|
||||
*/
|
||||
static getCurrentProjectName(): string {
|
||||
return PathDiscovery.getCurrentProjectName();
|
||||
}
|
||||
// </Block> =======================================
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
import { readFileSync, existsSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { PathResolver } from './paths.js';
|
||||
import type { Settings } from './types.js';
|
||||
|
||||
/**
|
||||
* Settings utilities for managing ~/.claude-mem/settings.json
|
||||
*/
|
||||
export class SettingsManager {
|
||||
private static settingsPath: string;
|
||||
private static cachedSettings: Settings | null = null;
|
||||
|
||||
static {
|
||||
const pathResolver = new PathResolver();
|
||||
this.settingsPath = join(pathResolver.getConfigDir(), 'settings.json');
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely read settings.json with error handling
|
||||
* Returns empty object if file doesn't exist or is malformed
|
||||
*/
|
||||
static readSettings(): Settings {
|
||||
// Return cached settings if available
|
||||
if (this.cachedSettings !== null) {
|
||||
return this.cachedSettings;
|
||||
}
|
||||
|
||||
try {
|
||||
if (existsSync(this.settingsPath)) {
|
||||
const content = readFileSync(this.settingsPath, 'utf-8');
|
||||
const settings = JSON.parse(content) as Settings;
|
||||
this.cachedSettings = settings;
|
||||
return settings;
|
||||
}
|
||||
} catch {
|
||||
// File is malformed or unreadable - return empty settings
|
||||
}
|
||||
|
||||
// File doesn't exist or failed to read
|
||||
const emptySettings: Settings = {};
|
||||
this.cachedSettings = emptySettings;
|
||||
return emptySettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific setting value with optional fallback
|
||||
*/
|
||||
static getSetting<K extends keyof Settings>(
|
||||
key: K,
|
||||
fallback?: Settings[K]
|
||||
): Settings[K] | undefined {
|
||||
const settings = this.readSettings();
|
||||
return settings[key] ?? fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Claude binary path from settings
|
||||
* Falls back to 'claude' if not found or settings don't exist
|
||||
*/
|
||||
static getClaudePath(): string {
|
||||
const claudePath = this.getSetting('claudePath', 'claude');
|
||||
return claudePath as string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cached settings (useful for testing or after settings changes)
|
||||
*/
|
||||
static clearCache(): void {
|
||||
this.cachedSettings = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to get Claude binary path
|
||||
* Can be imported directly for simple use cases
|
||||
*/
|
||||
export function getClaudePath(): string {
|
||||
return SettingsManager.getClaudePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to read all settings
|
||||
* Can be imported directly for simple use cases
|
||||
*/
|
||||
export function readSettings(): Settings {
|
||||
return SettingsManager.readSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to get a specific setting
|
||||
* Can be imported directly for simple use cases
|
||||
*/
|
||||
export function getSetting<K extends keyof Settings>(
|
||||
key: K,
|
||||
fallback?: Settings[K]
|
||||
): Settings[K] | undefined {
|
||||
return SettingsManager.getSetting(key, fallback);
|
||||
}
|
||||
+3
-217
@@ -1,5 +1,3 @@
|
||||
import fs from 'fs';
|
||||
import { PathDiscovery } from '../services/path-discovery.js';
|
||||
import {
|
||||
createStores,
|
||||
SessionStore,
|
||||
@@ -166,237 +164,25 @@ export class SQLiteStorageProvider implements IStorageProvider {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JSONL-based storage provider (legacy fallback)
|
||||
*/
|
||||
export class JSONLStorageProvider implements IStorageProvider {
|
||||
public readonly backend = 'jsonl';
|
||||
|
||||
private pathDiscovery = PathDiscovery.getInstance();
|
||||
|
||||
async isAvailable(): Promise<boolean> {
|
||||
try {
|
||||
// Ensure data directory exists
|
||||
const dataDir = this.pathDiscovery.getDataDirectory();
|
||||
fs.mkdirSync(dataDir, { recursive: true });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private appendToIndex(obj: any): void {
|
||||
const indexPath = this.pathDiscovery.getIndexPath();
|
||||
fs.appendFileSync(indexPath, JSON.stringify(obj) + '\\n', 'utf8');
|
||||
}
|
||||
|
||||
async createSession(session: SessionInput): Promise<void> {
|
||||
const sessionRecord = {
|
||||
type: 'session',
|
||||
session_id: session.session_id,
|
||||
project: session.project,
|
||||
timestamp: session.created_at
|
||||
};
|
||||
this.appendToIndex(sessionRecord);
|
||||
}
|
||||
|
||||
async getSession(): Promise<null> {
|
||||
// Not supported in JSONL mode
|
||||
return null;
|
||||
}
|
||||
|
||||
async hasSession(sessionId: string): Promise<boolean> {
|
||||
const sessionIds = await this.getAllSessionIds();
|
||||
return sessionIds.has(sessionId);
|
||||
}
|
||||
|
||||
async getAllSessionIds(): Promise<Set<string>> {
|
||||
const indexPath = this.pathDiscovery.getIndexPath();
|
||||
if (!fs.existsSync(indexPath)) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(indexPath, 'utf-8');
|
||||
const lines = content.trim().split('\\n').filter(line => line.trim());
|
||||
const sessionIds = new Set<string>();
|
||||
|
||||
for (const line of lines) {
|
||||
try {
|
||||
const obj = JSON.parse(line);
|
||||
if (obj.session_id) {
|
||||
sessionIds.add(obj.session_id);
|
||||
}
|
||||
} catch {
|
||||
// Skip malformed JSON
|
||||
}
|
||||
}
|
||||
|
||||
return sessionIds;
|
||||
}
|
||||
|
||||
async getRecentSessions(): Promise<SessionRow[]> {
|
||||
// Not fully supported in JSONL mode - return empty array
|
||||
return [];
|
||||
}
|
||||
|
||||
async getRecentSessionsForProject(): Promise<SessionRow[]> {
|
||||
// Not fully supported in JSONL mode - return empty array
|
||||
return [];
|
||||
}
|
||||
|
||||
async createMemory(memory: MemoryInput): Promise<void> {
|
||||
const memoryRecord = {
|
||||
type: 'memory',
|
||||
text: memory.text,
|
||||
document_id: memory.document_id,
|
||||
keywords: memory.keywords,
|
||||
session_id: memory.session_id,
|
||||
project: memory.project,
|
||||
timestamp: memory.created_at,
|
||||
archive: memory.archive_basename
|
||||
};
|
||||
this.appendToIndex(memoryRecord);
|
||||
}
|
||||
|
||||
async createMemories(memories: MemoryInput[]): Promise<void> {
|
||||
for (const memory of memories) {
|
||||
await this.createMemory(memory);
|
||||
}
|
||||
}
|
||||
|
||||
async getRecentMemories(): Promise<MemoryRow[]> {
|
||||
// Not fully supported in JSONL mode - return empty array
|
||||
return [];
|
||||
}
|
||||
|
||||
async getRecentMemoriesForProject(): Promise<MemoryRow[]> {
|
||||
// Not fully supported in JSONL mode - return empty array
|
||||
return [];
|
||||
}
|
||||
|
||||
async hasDocumentId(documentId: string): Promise<boolean> {
|
||||
const indexPath = this.pathDiscovery.getIndexPath();
|
||||
if (!fs.existsSync(indexPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(indexPath, 'utf-8');
|
||||
const lines = content.trim().split('\\n').filter(line => line.trim());
|
||||
|
||||
for (const line of lines) {
|
||||
try {
|
||||
const obj = JSON.parse(line);
|
||||
if (obj.type === 'memory' && obj.document_id === documentId) {
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
// Skip malformed JSON
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async createOverview(overview: OverviewInput): Promise<void> {
|
||||
const overviewRecord = {
|
||||
type: 'overview',
|
||||
content: overview.content,
|
||||
session_id: overview.session_id,
|
||||
project: overview.project,
|
||||
timestamp: overview.created_at
|
||||
};
|
||||
this.appendToIndex(overviewRecord);
|
||||
}
|
||||
|
||||
async upsertOverview(overview: OverviewInput): Promise<void> {
|
||||
// Just append in JSONL mode (no real upsert)
|
||||
await this.createOverview(overview);
|
||||
}
|
||||
|
||||
async getRecentOverviews(): Promise<OverviewRow[]> {
|
||||
// Not fully supported in JSONL mode - return empty array
|
||||
return [];
|
||||
}
|
||||
|
||||
async getRecentOverviewsForProject(): Promise<OverviewRow[]> {
|
||||
// Not fully supported in JSONL mode - return empty array
|
||||
return [];
|
||||
}
|
||||
|
||||
async createDiagnostic(diagnostic: DiagnosticInput): Promise<void> {
|
||||
const diagnosticRecord = {
|
||||
type: 'diagnostic',
|
||||
message: diagnostic.message,
|
||||
session_id: diagnostic.session_id,
|
||||
project: diagnostic.project,
|
||||
timestamp: diagnostic.created_at
|
||||
};
|
||||
this.appendToIndex(diagnosticRecord);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Storage provider factory and singleton
|
||||
* Storage provider singleton
|
||||
*/
|
||||
let storageProvider: IStorageProvider | null = null;
|
||||
|
||||
/**
|
||||
* Get the configured storage provider
|
||||
* Get the configured storage provider (always SQLite)
|
||||
*/
|
||||
export async function getStorageProvider(): Promise<IStorageProvider> {
|
||||
if (storageProvider) {
|
||||
return storageProvider;
|
||||
}
|
||||
|
||||
// Try SQLite first
|
||||
const sqliteProvider = new SQLiteStorageProvider();
|
||||
if (await sqliteProvider.isAvailable()) {
|
||||
storageProvider = sqliteProvider;
|
||||
return storageProvider;
|
||||
}
|
||||
|
||||
// Fall back to JSONL
|
||||
const jsonlProvider = new JSONLStorageProvider();
|
||||
if (await jsonlProvider.isAvailable()) {
|
||||
storageProvider = jsonlProvider;
|
||||
return storageProvider;
|
||||
}
|
||||
|
||||
throw new Error('No storage backend available');
|
||||
}
|
||||
|
||||
/**
|
||||
* Force a specific storage provider (useful for testing)
|
||||
*/
|
||||
export function setStorageProvider(provider: IStorageProvider): void {
|
||||
storageProvider = provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if SQLite migration is needed
|
||||
*/
|
||||
export async function needsMigration(): Promise<boolean> {
|
||||
const pathDiscovery = PathDiscovery.getInstance();
|
||||
const indexPath = pathDiscovery.getIndexPath();
|
||||
|
||||
// If JSONL exists but SQLite is not available, migration is needed
|
||||
if (fs.existsSync(indexPath)) {
|
||||
const sqliteProvider = new SQLiteStorageProvider();
|
||||
const sqliteAvailable = await sqliteProvider.isAvailable();
|
||||
|
||||
if (!sqliteAvailable) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if SQLite has data
|
||||
try {
|
||||
const stores = await createStores();
|
||||
const sessionCount = stores.sessions.count();
|
||||
return sessionCount === 0; // Needs migration if SQLite is empty
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
throw new Error('SQLite storage backend unavailable');
|
||||
}
|
||||
@@ -5,24 +5,6 @@
|
||||
* Only includes types that are actively imported and used.
|
||||
*/
|
||||
|
||||
// =============================================================================
|
||||
// ERROR CLASSES
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Custom error class for compression failures
|
||||
*/
|
||||
export class CompressionError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public transcriptPath: string,
|
||||
public stage: 'reading' | 'analyzing' | 'compressing' | 'writing'
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'CompressionError';
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CONFIGURATION TYPES
|
||||
// =============================================================================
|
||||
|
||||
+1
-36
@@ -1,6 +1,5 @@
|
||||
import { platform, homedir } from 'os';
|
||||
import { execSync } from 'child_process';
|
||||
import { chmodSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
const isWindows = platform() === 'win32';
|
||||
@@ -10,20 +9,6 @@ const isWindows = platform() === 'win32';
|
||||
* Handles differences between Windows and Unix-like systems
|
||||
*/
|
||||
export const Platform = {
|
||||
/**
|
||||
* Returns the appropriate shell for the current platform
|
||||
*/
|
||||
getShell: (): string => {
|
||||
return isWindows ? 'powershell' : '/bin/sh';
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the file extension for hook scripts
|
||||
*/
|
||||
getHookExtension: (): string => {
|
||||
return '.js'; // Both platforms can execute Node.js scripts
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds the path to an executable command
|
||||
* @param name - Name of the executable to find
|
||||
@@ -37,16 +22,6 @@ export const Platform = {
|
||||
}).trim();
|
||||
},
|
||||
|
||||
/**
|
||||
* Makes a file executable (Unix only - no-op on Windows)
|
||||
* @param path - Path to the file to make executable
|
||||
*/
|
||||
makeExecutable: (path: string): void => {
|
||||
if (!isWindows) {
|
||||
chmodSync(path, 0o755);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Installs uv package manager using platform-specific method
|
||||
*/
|
||||
@@ -98,15 +73,5 @@ export const Platform = {
|
||||
|
||||
// Bash/Zsh alias syntax
|
||||
return `alias ${aliasName}='${command}'`;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns whether the current platform is Windows
|
||||
*/
|
||||
isWindows: (): boolean => isWindows,
|
||||
|
||||
/**
|
||||
* Returns whether the current platform is Unix-like (macOS/Linux)
|
||||
*/
|
||||
isUnix: (): boolean => !isWindows
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user