Release v3.5.4

- Updated to match npm package structure
- Includes minified dist/claude-mem.min.js
- Added commands directory
- Updated hooks with latest fixes
- Synced with npm package claude-mem@3.5.4
This commit is contained in:
Alex Newman
2025-09-09 02:10:00 -04:00
parent 4da61a77c7
commit aae7de8e05
15 changed files with 625 additions and 231 deletions
+17 -12
View File
@@ -9,6 +9,7 @@
*/
import { loadCliCommand } from './shared/config-loader.js';
import { getLogsDir } from './shared/path-resolver.js';
import {
createHookResponse,
executeCliCommand,
@@ -16,7 +17,9 @@ import {
debugLog
} from './shared/hook-helpers.js';
const cliCommand = loadCliCommand();
// Set up stdin immediately before any async operations
process.stdin.setEncoding('utf8');
process.stdin.resume(); // Explicitly enter flowing mode to prevent data loss
// Read input from stdin
let input = '';
@@ -26,6 +29,9 @@ process.stdin.on('data', chunk => {
process.stdin.on('end', async () => {
try {
// Load CLI command inside try-catch to handle config errors properly
const cliCommand = loadCliCommand();
const payload = JSON.parse(input);
debugLog('Pre-compact hook started', { payload });
@@ -33,17 +39,18 @@ process.stdin.on('end', async () => {
const validation = validateHookPayload(payload, 'PreCompact');
if (!validation.valid) {
const response = createHookResponse('PreCompact', false, { reason: validation.error });
console.log(JSON.stringify(response));
debugLog('Validation failed', { response });
// Exit silently - validation failure is expected flow control
process.exit(0);
}
// Check for environment-based blocking conditions
if (payload.trigger === 'auto' && process.env.DISABLE_AUTO_COMPRESSION === 'true') {
debugLog('Auto-compression disabled by configuration');
const response = createHookResponse('PreCompact', false, {
reason: 'Auto-compression disabled by configuration'
});
console.log(JSON.stringify(response));
debugLog('Auto-compression disabled', { response });
// Exit silently - disabled compression is expected flow control
process.exit(0);
}
@@ -56,26 +63,24 @@ process.stdin.on('end', async () => {
const result = await executeCliCommand(cliCommand, ['compress', payload.transcript_path]);
if (!result.success) {
debugLog('Compression command failed', { stderr: result.stderr });
const response = createHookResponse('PreCompact', false, {
reason: `Compression failed: ${result.stderr || 'Unknown error'}`
});
console.log(JSON.stringify(response));
process.exit(0);
debugLog('Compression command failed', { stderr: result.stderr, response });
console.log(`claude-mem error: compression failed, see logs at ${getLogsDir()}`);
process.exit(1); // Exit with error code for actual compression failure
}
// Success - create standardized approval response using HookTemplates
// Success - exit silently (suppressOutput is true)
debugLog('Compression completed successfully');
const response = createHookResponse('PreCompact', true);
console.log(JSON.stringify(response));
process.exit(0);
} catch (error) {
debugLog('Pre-compact hook error', { error: error.message });
const response = createHookResponse('PreCompact', false, {
reason: `Hook execution error: ${error.message}`
});
console.log(JSON.stringify(response));
debugLog('Pre-compact hook error', { error: error.message, response });
console.log(`claude-mem error: hook failed, see logs at ${getLogsDir()}`);
process.exit(1);
}
});
+7 -4
View File
@@ -5,16 +5,15 @@
*/
import { loadCliCommand } from './shared/config-loader.js';
import { getSettingsPath, getArchivesDir } from './shared/path-resolver.js';
import { execSync } from 'child_process';
import { join } from 'path';
import { homedir } from 'os';
import { existsSync, readFileSync } from 'fs';
const cliCommand = loadCliCommand();
// Check if save-on-clear is enabled
function isSaveOnClearEnabled() {
const settingsPath = join(homedir(), '.claude-mem', 'settings.json');
const settingsPath = getSettingsPath();
if (existsSync(settingsPath)) {
try {
const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
@@ -26,6 +25,10 @@ function isSaveOnClearEnabled() {
return false;
}
// Set up stdin immediately before any async operations
process.stdin.setEncoding('utf8');
process.stdin.resume(); // Explicitly enter flowing mode to prevent data loss
// Read input
let input = '';
process.stdin.on('data', chunk => {
@@ -41,7 +44,7 @@ process.stdin.on('end', async () => {
try {
// Use the CLI to compress current transcript
execSync(`${cliCommand} compress --output ${homedir()}/.claude-mem/archives`, {
execSync(`${cliCommand} compress --output ${getArchivesDir()}`, {
stdio: 'inherit',
env: { ...process.env, CLAUDE_MEM_SILENT: 'true' }
});
+10 -6
View File
@@ -21,6 +21,10 @@ import {
const cliCommand = loadCliCommand();
// Set up stdin immediately before any async operations
process.stdin.setEncoding('utf8');
process.stdin.resume(); // Explicitly enter flowing mode to prevent data loss
// Read input from stdin
let input = '';
process.stdin.on('data', chunk => {
@@ -47,14 +51,14 @@ process.stdin.on('end', async () => {
// Skip load-context when source is "resume" to avoid duplicate context
if (payload.source === 'resume') {
debugLog('Skipping load-context for resume source');
// Output nothing at all for resume - no message, no context
// Output valid JSON response with suppressOutput for resume
const response = createHookResponse('SessionStart', true);
console.log(JSON.stringify(response));
process.exit(0);
}
// Extract project name from current working directory and sanitize
const rawProjectName = path.basename(process.cwd());
const projectName = rawProjectName.replace(/-/g, '_');
debugLog('Extracted project name', { rawProjectName, projectName });
// Extract project name from current working directory
const projectName = path.basename(process.cwd());
// Load context using standardized CLI execution helper
const contextResult = await executeCliCommand(cliCommand, [
@@ -152,7 +156,7 @@ function extractProjectName(transcriptPath) {
// Look for project pattern: /path/to/PROJECT_NAME/.claude/
// Need to get PROJECT_NAME, not the parent directory
const parts = transcriptPath.split('/');
const parts = transcriptPath.split(path.sep);
const claudeIndex = parts.indexOf('.claude');
if (claudeIndex > 0) {
+2 -5
View File
@@ -47,13 +47,10 @@ export function createHookResponse(hookType, success, options = {}) {
}
};
} else if (success) {
// No context - just suppress output without any message
return {
continue: true,
suppressOutput: true,
hookSpecificOutput: {
hookEventName: 'SessionStart',
additionalContext: 'Starting fresh session - no previous context available'
}
suppressOutput: true
};
} else {
return {
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env node
/**
* Path resolver utility for Claude Memory hooks
* Provides proper path handling using environment variables
*/
import { join } from 'path';
import { homedir } from 'os';
/**
* Gets the base data directory for claude-mem
* @returns {string} Data directory path
*/
export function getDataDir() {
return process.env.CLAUDE_MEM_DATA_DIR || join(homedir(), '.claude-mem');
}
/**
* Gets the settings file path
* @returns {string} Settings file path
*/
export function getSettingsPath() {
return join(getDataDir(), 'settings.json');
}
/**
* Gets the archives directory path
* @returns {string} Archives directory path
*/
export function getArchivesDir() {
return process.env.CLAUDE_MEM_ARCHIVES_DIR || join(getDataDir(), 'archives');
}
/**
* Gets the logs directory path
* @returns {string} Logs directory path
*/
export function getLogsDir() {
return process.env.CLAUDE_MEM_LOGS_DIR || join(getDataDir(), 'logs');
}
/**
* Gets all common paths used by hooks
* @returns {Object} Object containing all common paths
*/
export function getPaths() {
return {
dataDir: getDataDir(),
settingsPath: getSettingsPath(),
archivesDir: getArchivesDir(),
logsDir: getLogsDir()
};
}