@stackmemoryai/stackmemory 0.5.49 → 0.5.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +17 -3
  2. package/dist/cli/claude-sm.js +246 -5
  3. package/dist/cli/claude-sm.js.map +3 -3
  4. package/dist/cli/commands/handoff.js +27 -12
  5. package/dist/cli/commands/handoff.js.map +2 -2
  6. package/dist/cli/commands/sweep.js +190 -421
  7. package/dist/cli/commands/sweep.js.map +3 -3
  8. package/dist/cli/index.js +10 -2
  9. package/dist/cli/index.js.map +2 -2
  10. package/dist/core/config/feature-flags.js +7 -1
  11. package/dist/core/config/feature-flags.js.map +2 -2
  12. package/dist/core/context/enhanced-rehydration.js +355 -9
  13. package/dist/core/context/enhanced-rehydration.js.map +3 -3
  14. package/dist/core/context/shared-context-layer.js +229 -0
  15. package/dist/core/context/shared-context-layer.js.map +2 -2
  16. package/dist/features/sweep/index.js +20 -0
  17. package/dist/features/sweep/index.js.map +7 -0
  18. package/dist/features/sweep/prediction-client.js +155 -0
  19. package/dist/features/sweep/prediction-client.js.map +7 -0
  20. package/dist/features/sweep/prompt-builder.js +85 -0
  21. package/dist/features/sweep/prompt-builder.js.map +7 -0
  22. package/dist/features/sweep/pty-wrapper.js +171 -0
  23. package/dist/features/sweep/pty-wrapper.js.map +7 -0
  24. package/dist/features/sweep/state-watcher.js +87 -0
  25. package/dist/features/sweep/state-watcher.js.map +7 -0
  26. package/dist/features/sweep/status-bar.js +88 -0
  27. package/dist/features/sweep/status-bar.js.map +7 -0
  28. package/dist/features/sweep/sweep-server-manager.js +226 -0
  29. package/dist/features/sweep/sweep-server-manager.js.map +7 -0
  30. package/dist/features/sweep/tab-interceptor.js +38 -0
  31. package/dist/features/sweep/tab-interceptor.js.map +7 -0
  32. package/dist/features/sweep/types.js +18 -0
  33. package/dist/features/sweep/types.js.map +7 -0
  34. package/dist/integrations/claude-code/lifecycle-hooks.js +3 -3
  35. package/dist/integrations/claude-code/lifecycle-hooks.js.map +1 -1
  36. package/package.json +1 -1
  37. package/scripts/auto-handoff.sh +1 -1
  38. package/scripts/claude-sm-autostart.js +174 -132
  39. package/scripts/setup-claude-integration.js +14 -10
  40. package/scripts/stackmemory-auto-handoff.sh +3 -3
  41. package/scripts/test-session-handoff.sh +2 -2
  42. package/scripts/test-setup-e2e.sh +154 -0
@@ -0,0 +1,38 @@
1
+ import { fileURLToPath as __fileURLToPath } from 'url';
2
+ import { dirname as __pathDirname } from 'path';
3
+ const __filename = __fileURLToPath(import.meta.url);
4
+ const __dirname = __pathDirname(__filename);
5
+ const TAB = 9;
6
+ const ESC = 27;
7
+ class TabInterceptor {
8
+ predictionActive = false;
9
+ callbacks;
10
+ constructor(callbacks) {
11
+ this.callbacks = callbacks;
12
+ }
13
+ setPredictionActive(active) {
14
+ this.predictionActive = active;
15
+ }
16
+ isPredictionActive() {
17
+ return this.predictionActive;
18
+ }
19
+ process(data) {
20
+ if (!this.predictionActive) {
21
+ this.callbacks.onPassthrough(data);
22
+ return;
23
+ }
24
+ if (data.length === 1 && data[0] === TAB) {
25
+ this.callbacks.onAccept();
26
+ return;
27
+ }
28
+ if (data.length === 1 && data[0] === ESC) {
29
+ this.callbacks.onDismiss();
30
+ return;
31
+ }
32
+ this.callbacks.onPassthrough(data);
33
+ }
34
+ }
35
+ export {
36
+ TabInterceptor
37
+ };
38
+ //# sourceMappingURL=tab-interceptor.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/features/sweep/tab-interceptor.ts"],
4
+ "sourcesContent": ["/**\n * Tab Interceptor\n *\n * Intercepts Tab and Esc keystrokes when a Sweep prediction is active.\n * All other input passes through to the PTY child unchanged.\n */\n\nconst TAB = 0x09;\nconst ESC = 0x1b;\n\nexport interface TabInterceptorCallbacks {\n onAccept: () => void;\n onDismiss: () => void;\n onPassthrough: (data: Buffer) => void;\n}\n\nexport class TabInterceptor {\n private predictionActive = false;\n private callbacks: TabInterceptorCallbacks;\n\n constructor(callbacks: TabInterceptorCallbacks) {\n this.callbacks = callbacks;\n }\n\n setPredictionActive(active: boolean): void {\n this.predictionActive = active;\n }\n\n isPredictionActive(): boolean {\n return this.predictionActive;\n }\n\n process(data: Buffer): void {\n if (!this.predictionActive) {\n this.callbacks.onPassthrough(data);\n return;\n }\n\n // Tab key: accept prediction\n if (data.length === 1 && data[0] === TAB) {\n this.callbacks.onAccept();\n return;\n }\n\n // Bare Escape: dismiss prediction\n // Distinguish from escape sequences (arrow keys, etc.) by length.\n // Bare Esc is a single byte; escape sequences are multi-byte.\n if (data.length === 1 && data[0] === ESC) {\n this.callbacks.onDismiss();\n return;\n }\n\n // Everything else passes through (including escape sequences)\n this.callbacks.onPassthrough(data);\n }\n}\n"],
5
+ "mappings": ";;;;AAOA,MAAM,MAAM;AACZ,MAAM,MAAM;AAQL,MAAM,eAAe;AAAA,EAClB,mBAAmB;AAAA,EACnB;AAAA,EAER,YAAY,WAAoC;AAC9C,SAAK,YAAY;AAAA,EACnB;AAAA,EAEA,oBAAoB,QAAuB;AACzC,SAAK,mBAAmB;AAAA,EAC1B;AAAA,EAEA,qBAA8B;AAC5B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,QAAQ,MAAoB;AAC1B,QAAI,CAAC,KAAK,kBAAkB;AAC1B,WAAK,UAAU,cAAc,IAAI;AACjC;AAAA,IACF;AAGA,QAAI,KAAK,WAAW,KAAK,KAAK,CAAC,MAAM,KAAK;AACxC,WAAK,UAAU,SAAS;AACxB;AAAA,IACF;AAKA,QAAI,KAAK,WAAW,KAAK,KAAK,CAAC,MAAM,KAAK;AACxC,WAAK,UAAU,UAAU;AACzB;AAAA,IACF;AAGA,SAAK,UAAU,cAAc,IAAI;AAAA,EACnC;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,18 @@
1
+ import { fileURLToPath as __fileURLToPath } from 'url';
2
+ import { dirname as __pathDirname } from 'path';
3
+ const __filename = __fileURLToPath(import.meta.url);
4
+ const __dirname = __pathDirname(__filename);
5
+ const DEFAULT_SERVER_CONFIG = {
6
+ port: 8766,
7
+ host: "127.0.0.1",
8
+ modelPath: "",
9
+ contextSize: 8192,
10
+ threads: void 0,
11
+ gpuLayers: 0
12
+ };
13
+ const SWEEP_STOP_TOKENS = ["<|file_sep|>", "</s>"];
14
+ export {
15
+ DEFAULT_SERVER_CONFIG,
16
+ SWEEP_STOP_TOKENS
17
+ };
18
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/features/sweep/types.ts"],
4
+ "sourcesContent": ["/**\n * Sweep Next-Edit Types\n *\n * Types for the Sweep 1.5B model server integration.\n */\n\nexport interface SweepServerConfig {\n port: number;\n host: string;\n modelPath: string;\n contextSize: number;\n threads?: number;\n gpuLayers?: number;\n}\n\nexport interface SweepServerStatus {\n running: boolean;\n pid?: number;\n port?: number;\n host?: string;\n startedAt?: number;\n modelPath?: string;\n}\n\nexport interface SweepPredictInput {\n file_path: string;\n current_content: string;\n original_content?: string;\n context_files?: Record<string, string>;\n recent_diffs?: DiffEntry[];\n max_tokens?: number;\n temperature?: number;\n top_k?: number;\n}\n\nexport interface DiffEntry {\n file_path: string;\n original: string;\n updated: string;\n timestamp?: number;\n}\n\nexport interface SweepPredictResult {\n success: boolean;\n predicted_content?: string;\n file_path?: string;\n latency_ms?: number;\n tokens_generated?: number;\n error?: string;\n message?: string;\n}\n\nexport interface SweepPromptInput {\n filePath: string;\n originalContent: string;\n currentContent: string;\n recentDiffs: DiffEntry[];\n contextFiles?: Record<string, string>;\n}\n\nexport interface CompletionRequest {\n model: string;\n prompt: string;\n max_tokens: number;\n temperature: number;\n top_k?: number;\n stop?: string[];\n stream?: boolean;\n}\n\nexport interface CompletionResponse {\n id: string;\n object: string;\n created: number;\n model: string;\n choices: Array<{\n text: string;\n index: number;\n logprobs: null;\n finish_reason: string;\n }>;\n usage: {\n prompt_tokens: number;\n completion_tokens: number;\n total_tokens: number;\n };\n}\n\nexport const DEFAULT_SERVER_CONFIG: SweepServerConfig = {\n port: 8766,\n host: '127.0.0.1',\n modelPath: '',\n contextSize: 8192,\n threads: undefined,\n gpuLayers: 0,\n};\n\nexport const SWEEP_STOP_TOKENS = ['<|file_sep|>', '</s>'];\n"],
5
+ "mappings": ";;;;AAwFO,MAAM,wBAA2C;AAAA,EACtD,MAAM;AAAA,EACN,MAAM;AAAA,EACN,WAAW;AAAA,EACX,aAAa;AAAA,EACb,SAAS;AAAA,EACT,WAAW;AACb;AAEO,MAAM,oBAAoB,CAAC,gBAAgB,MAAM;",
6
+ "names": []
7
+ }
@@ -118,7 +118,7 @@ stackmemory monitor --activity 2>/dev/null || true
118
118
  echo "\u{1F4E6} Saving session state..."
119
119
 
120
120
  # Generate handoff
121
- stackmemory handoff --generate > /dev/null 2>&1 && echo "\u2705 Handoff saved"
121
+ stackmemory capture --no-commit > /dev/null 2>&1 && echo "\u2705 Handoff saved"
122
122
 
123
123
  # Save ledger if context is significant
124
124
  CONTEXT_STATUS=$(stackmemory clear --check 2>/dev/null | grep -o '[0-9]\\+%' | head -1 | tr -d '%')
@@ -143,7 +143,7 @@ stackmemory clear --save > /dev/null 2>&1
143
143
  echo "\u2705 Continuity ledger saved"
144
144
 
145
145
  # Generate quick handoff
146
- stackmemory handoff --generate > /dev/null 2>&1
146
+ stackmemory capture --no-commit > /dev/null 2>&1
147
147
  echo "\u2705 Handoff document saved"
148
148
 
149
149
  echo "\u2705 Ready for /clear - context will be restored automatically"
@@ -158,7 +158,7 @@ echo "\u{1F4A1} After /clear, run: stackmemory clear --restore"
158
158
  # Generates handoff after 5 minutes of inactivity
159
159
 
160
160
  echo "\u23F8\uFE0F Session idle - generating handoff..."
161
- stackmemory handoff --generate > /dev/null 2>&1
161
+ stackmemory capture --no-commit > /dev/null 2>&1
162
162
  echo "\u2705 Handoff saved. Ready to resume anytime."
163
163
  `
164
164
  );
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/integrations/claude-code/lifecycle-hooks.ts"],
4
- "sourcesContent": ["/**\n * Claude Code Lifecycle Hooks for StackMemory\n * Integrates with Claude Code's session lifecycle\n */\n\nimport { SessionMonitor } from '../../core/monitoring/session-monitor';\nimport { FrameManager } from '../../core/frame/frame-manager';\nimport { DatabaseManager } from '../../core/storage/database-manager';\nimport * as fs from 'fs/promises';\nimport * as path from 'path';\nimport * as os from 'os';\n\nexport interface ClaudeCodeHookConfig {\n projectRoot: string;\n autoTriggers: {\n onContextHigh: boolean; // Auto-save at 70%\n onContextCritical: boolean; // Force-save at 85%\n onSessionIdle: boolean; // Handoff on 5min idle\n onSessionEnd: boolean; // Handoff on close\n onClearCommand: boolean; // Save before /clear\n };\n claudeHooksPath?: string; // Path to .claude/hooks\n}\n\nexport class ClaudeCodeLifecycleHooks {\n private monitor?: SessionMonitor;\n private config: ClaudeCodeHookConfig;\n private isActive: boolean = false;\n private hookScripts: Map<string, string> = new Map();\n\n constructor(config: ClaudeCodeHookConfig) {\n this.config = {\n ...config,\n claudeHooksPath:\n config.claudeHooksPath || path.join(os.homedir(), '.claude', 'hooks'),\n };\n }\n\n /**\n * Initialize hooks and start monitoring\n */\n async initialize(): Promise<void> {\n if (this.isActive) return;\n\n // Initialize database and managers\n const dbPath = path.join(\n this.config.projectRoot,\n '.stackmemory',\n 'db',\n 'stackmemory.db'\n );\n const dbManager = new DatabaseManager(dbPath);\n await dbManager.initialize();\n\n const frameManager = new FrameManager(dbManager);\n\n // Create session monitor\n this.monitor = new SessionMonitor(\n frameManager,\n dbManager,\n this.config.projectRoot,\n {\n contextWarningThreshold: 0.6,\n contextCriticalThreshold: 0.7,\n contextAutoSaveThreshold: 0.85,\n idleTimeoutMinutes: 5,\n autoSaveLedger: this.config.autoTriggers.onContextCritical,\n autoGenerateHandoff: this.config.autoTriggers.onSessionIdle,\n sessionEndHandoff: this.config.autoTriggers.onSessionEnd,\n }\n );\n\n // Register event handlers\n this.registerEventHandlers();\n\n // Install Claude Code hooks\n await this.installClaudeHooks();\n\n // Start monitoring\n await this.monitor.start();\n\n this.isActive = true;\n console.log('\u2705 Claude Code lifecycle hooks initialized');\n }\n\n /**\n * Register event handlers for monitor events\n */\n private registerEventHandlers(): void {\n if (!this.monitor) return;\n\n // Context events\n this.monitor.on('context:warning', (data) => {\n console.log(`\u26A0\uFE0F Context at ${Math.round(data.percentage * 100)}%`);\n });\n\n this.monitor.on('context:high', async (data) => {\n if (this.config.autoTriggers.onContextHigh) {\n console.log('\uD83D\uDFE1 High context - preparing auto-save');\n await this.executeHook('on-context-high', data);\n }\n });\n\n this.monitor.on('context:ledger_saved', (data) => {\n console.log(`\u2705 Ledger saved (${data.compression}x compression)`);\n console.log('\uD83D\uDCA1 You can now safely use /clear');\n });\n\n // Handoff events\n this.monitor.on('handoff:generated', (data) => {\n console.log(`\uD83D\uDCCB Handoff saved (trigger: ${data.trigger})`);\n });\n }\n\n /**\n * Install hooks into Claude Code configuration\n */\n private async installClaudeHooks(): Promise<void> {\n // Create hooks directory if it doesn't exist\n await fs.mkdir(this.config.claudeHooksPath!, { recursive: true });\n\n // Install context monitor hook\n await this.installHook(\n 'on-message-submit',\n `\n#!/bin/bash\n# StackMemory Context Monitor Hook\n# Monitors token usage and triggers auto-save when needed\n\n# Get estimated token count from Claude (if available)\nTOKEN_COUNT=\\${CLAUDE_TOKEN_COUNT:-0}\nMAX_TOKENS=\\${CLAUDE_MAX_TOKENS:-100000}\n\nif [ \"\\$TOKEN_COUNT\" -gt 0 ]; then\n USAGE=\\$((TOKEN_COUNT * 100 / MAX_TOKENS))\n \n if [ \"\\$USAGE\" -gt 85 ]; then\n echo \"\uD83D\uDD34 Critical: Context at \\${USAGE}% - Auto-saving...\"\n stackmemory clear --save > /dev/null 2>&1\n echo \"\u2705 Ledger saved. Consider using /clear\"\n elif [ \"\\$USAGE\" -gt 70 ]; then\n echo \"\u26A0\uFE0F Warning: Context at \\${USAGE}%\"\n echo \"\uD83D\uDCA1 Run: stackmemory clear --save\"\n fi\nfi\n\n# Update activity timestamp\nstackmemory monitor --activity 2>/dev/null || true\n`\n );\n\n // Install session end hook\n await this.installHook(\n 'on-session-end',\n `\n#!/bin/bash\n# StackMemory Session End Hook\n# Generates handoff document when session ends\n\necho \"\uD83D\uDCE6 Saving session state...\"\n\n# Generate handoff\nstackmemory handoff --generate > /dev/null 2>&1 && echo \"\u2705 Handoff saved\"\n\n# Save ledger if context is significant\nCONTEXT_STATUS=$(stackmemory clear --check 2>/dev/null | grep -o '[0-9]\\\\+%' | head -1 | tr -d '%')\nif [ \"\\${CONTEXT_STATUS:-0}\" -gt 30 ]; then\n stackmemory clear --save > /dev/null 2>&1 && echo \"\u2705 Continuity ledger saved\"\nfi\n\necho \"\uD83D\uDC4B Session state preserved for next time\"\n`\n );\n\n // Install clear command interceptor\n await this.installHook(\n 'on-command-clear',\n `\n#!/bin/bash\n# StackMemory Clear Interceptor\n# Saves state before /clear command\n\necho \"\uD83D\uDD04 Preparing for /clear...\"\n\n# Save continuity ledger\nstackmemory clear --save > /dev/null 2>&1\necho \"\u2705 Continuity ledger saved\"\n\n# Generate quick handoff\nstackmemory handoff --generate > /dev/null 2>&1\necho \"\u2705 Handoff document saved\"\n\necho \"\u2705 Ready for /clear - context will be restored automatically\"\necho \"\uD83D\uDCA1 After /clear, run: stackmemory clear --restore\"\n`\n );\n\n // Install idle detector\n await this.installHook(\n 'on-idle-5min',\n `\n#!/bin/bash\n# StackMemory Idle Detector\n# Generates handoff after 5 minutes of inactivity\n\necho \"\u23F8\uFE0F Session idle - generating handoff...\"\nstackmemory handoff --generate > /dev/null 2>&1\necho \"\u2705 Handoff saved. Ready to resume anytime.\"\n`\n );\n }\n\n /**\n * Install a specific hook script\n */\n private async installHook(name: string, script: string): Promise<void> {\n const hookPath = path.join(this.config.claudeHooksPath!, name);\n\n // Store script content\n this.hookScripts.set(name, script);\n\n // Write hook file\n await fs.writeFile(hookPath, script.trim(), { mode: 0o755 });\n\n // Make executable\n await fs.chmod(hookPath, 0o755);\n }\n\n /**\n * Execute a hook with context\n */\n private async executeHook(hookName: string, context: any): Promise<void> {\n const hookPath = path.join(this.config.claudeHooksPath!, hookName);\n\n try {\n // Check if hook exists\n await fs.access(hookPath);\n\n // Execute hook with environment variables\n const { exec } = await import('child_process');\n const { promisify } = await import('util');\n const execAsync = promisify(exec);\n\n const env = {\n ...process.env,\n STACKMEMORY_CONTEXT: JSON.stringify(context),\n STACKMEMORY_PROJECT: this.config.projectRoot,\n };\n\n const { stdout, stderr } = await execAsync(hookPath, { env });\n\n if (stdout) console.log(stdout);\n if (stderr) console.error(stderr);\n } catch (error: unknown) {\n // Hook doesn't exist or failed\n console.debug(`Hook ${hookName} not found or failed:`, error);\n }\n }\n\n /**\n * Stop monitoring and cleanup\n */\n async stop(): Promise<void> {\n if (this.monitor) {\n await this.monitor.stop();\n }\n this.isActive = false;\n console.log('\uD83D\uDED1 Claude Code lifecycle hooks stopped');\n }\n\n /**\n * Get current status\n */\n getStatus(): any {\n return {\n isActive: this.isActive,\n monitorStatus: this.monitor?.getStatus(),\n config: this.config,\n installedHooks: Array.from(this.hookScripts.keys()),\n };\n }\n}\n\n/**\n * Global singleton instance\n */\nlet globalInstance: ClaudeCodeLifecycleHooks | undefined;\n\n/**\n * Initialize global hooks\n */\nexport async function initializeClaudeHooks(\n projectRoot?: string\n): Promise<ClaudeCodeLifecycleHooks> {\n if (!projectRoot) {\n projectRoot = process.cwd();\n }\n\n if (!globalInstance) {\n globalInstance = new ClaudeCodeLifecycleHooks({\n projectRoot,\n autoTriggers: {\n onContextHigh: true,\n onContextCritical: true,\n onSessionIdle: true,\n onSessionEnd: true,\n onClearCommand: true,\n },\n });\n\n await globalInstance.initialize();\n }\n\n return globalInstance;\n}\n\n/**\n * Get global instance\n */\nexport function getClaudeHooks(): ClaudeCodeLifecycleHooks | undefined {\n return globalInstance;\n}\n\n/**\n * Stop global hooks\n */\nexport async function stopClaudeHooks(): Promise<void> {\n if (globalInstance) {\n await globalInstance.stop();\n globalInstance = undefined;\n }\n}\n"],
4
+ "sourcesContent": ["/**\n * Claude Code Lifecycle Hooks for StackMemory\n * Integrates with Claude Code's session lifecycle\n */\n\nimport { SessionMonitor } from '../../core/monitoring/session-monitor';\nimport { FrameManager } from '../../core/frame/frame-manager';\nimport { DatabaseManager } from '../../core/storage/database-manager';\nimport * as fs from 'fs/promises';\nimport * as path from 'path';\nimport * as os from 'os';\n\nexport interface ClaudeCodeHookConfig {\n projectRoot: string;\n autoTriggers: {\n onContextHigh: boolean; // Auto-save at 70%\n onContextCritical: boolean; // Force-save at 85%\n onSessionIdle: boolean; // Handoff on 5min idle\n onSessionEnd: boolean; // Handoff on close\n onClearCommand: boolean; // Save before /clear\n };\n claudeHooksPath?: string; // Path to .claude/hooks\n}\n\nexport class ClaudeCodeLifecycleHooks {\n private monitor?: SessionMonitor;\n private config: ClaudeCodeHookConfig;\n private isActive: boolean = false;\n private hookScripts: Map<string, string> = new Map();\n\n constructor(config: ClaudeCodeHookConfig) {\n this.config = {\n ...config,\n claudeHooksPath:\n config.claudeHooksPath || path.join(os.homedir(), '.claude', 'hooks'),\n };\n }\n\n /**\n * Initialize hooks and start monitoring\n */\n async initialize(): Promise<void> {\n if (this.isActive) return;\n\n // Initialize database and managers\n const dbPath = path.join(\n this.config.projectRoot,\n '.stackmemory',\n 'db',\n 'stackmemory.db'\n );\n const dbManager = new DatabaseManager(dbPath);\n await dbManager.initialize();\n\n const frameManager = new FrameManager(dbManager);\n\n // Create session monitor\n this.monitor = new SessionMonitor(\n frameManager,\n dbManager,\n this.config.projectRoot,\n {\n contextWarningThreshold: 0.6,\n contextCriticalThreshold: 0.7,\n contextAutoSaveThreshold: 0.85,\n idleTimeoutMinutes: 5,\n autoSaveLedger: this.config.autoTriggers.onContextCritical,\n autoGenerateHandoff: this.config.autoTriggers.onSessionIdle,\n sessionEndHandoff: this.config.autoTriggers.onSessionEnd,\n }\n );\n\n // Register event handlers\n this.registerEventHandlers();\n\n // Install Claude Code hooks\n await this.installClaudeHooks();\n\n // Start monitoring\n await this.monitor.start();\n\n this.isActive = true;\n console.log('\u2705 Claude Code lifecycle hooks initialized');\n }\n\n /**\n * Register event handlers for monitor events\n */\n private registerEventHandlers(): void {\n if (!this.monitor) return;\n\n // Context events\n this.monitor.on('context:warning', (data) => {\n console.log(`\u26A0\uFE0F Context at ${Math.round(data.percentage * 100)}%`);\n });\n\n this.monitor.on('context:high', async (data) => {\n if (this.config.autoTriggers.onContextHigh) {\n console.log('\uD83D\uDFE1 High context - preparing auto-save');\n await this.executeHook('on-context-high', data);\n }\n });\n\n this.monitor.on('context:ledger_saved', (data) => {\n console.log(`\u2705 Ledger saved (${data.compression}x compression)`);\n console.log('\uD83D\uDCA1 You can now safely use /clear');\n });\n\n // Handoff events\n this.monitor.on('handoff:generated', (data) => {\n console.log(`\uD83D\uDCCB Handoff saved (trigger: ${data.trigger})`);\n });\n }\n\n /**\n * Install hooks into Claude Code configuration\n */\n private async installClaudeHooks(): Promise<void> {\n // Create hooks directory if it doesn't exist\n await fs.mkdir(this.config.claudeHooksPath!, { recursive: true });\n\n // Install context monitor hook\n await this.installHook(\n 'on-message-submit',\n `\n#!/bin/bash\n# StackMemory Context Monitor Hook\n# Monitors token usage and triggers auto-save when needed\n\n# Get estimated token count from Claude (if available)\nTOKEN_COUNT=\\${CLAUDE_TOKEN_COUNT:-0}\nMAX_TOKENS=\\${CLAUDE_MAX_TOKENS:-100000}\n\nif [ \"\\$TOKEN_COUNT\" -gt 0 ]; then\n USAGE=\\$((TOKEN_COUNT * 100 / MAX_TOKENS))\n \n if [ \"\\$USAGE\" -gt 85 ]; then\n echo \"\uD83D\uDD34 Critical: Context at \\${USAGE}% - Auto-saving...\"\n stackmemory clear --save > /dev/null 2>&1\n echo \"\u2705 Ledger saved. Consider using /clear\"\n elif [ \"\\$USAGE\" -gt 70 ]; then\n echo \"\u26A0\uFE0F Warning: Context at \\${USAGE}%\"\n echo \"\uD83D\uDCA1 Run: stackmemory clear --save\"\n fi\nfi\n\n# Update activity timestamp\nstackmemory monitor --activity 2>/dev/null || true\n`\n );\n\n // Install session end hook\n await this.installHook(\n 'on-session-end',\n `\n#!/bin/bash\n# StackMemory Session End Hook\n# Generates handoff document when session ends\n\necho \"\uD83D\uDCE6 Saving session state...\"\n\n# Generate handoff\nstackmemory capture --no-commit > /dev/null 2>&1 && echo \"\u2705 Handoff saved\"\n\n# Save ledger if context is significant\nCONTEXT_STATUS=$(stackmemory clear --check 2>/dev/null | grep -o '[0-9]\\\\+%' | head -1 | tr -d '%')\nif [ \"\\${CONTEXT_STATUS:-0}\" -gt 30 ]; then\n stackmemory clear --save > /dev/null 2>&1 && echo \"\u2705 Continuity ledger saved\"\nfi\n\necho \"\uD83D\uDC4B Session state preserved for next time\"\n`\n );\n\n // Install clear command interceptor\n await this.installHook(\n 'on-command-clear',\n `\n#!/bin/bash\n# StackMemory Clear Interceptor\n# Saves state before /clear command\n\necho \"\uD83D\uDD04 Preparing for /clear...\"\n\n# Save continuity ledger\nstackmemory clear --save > /dev/null 2>&1\necho \"\u2705 Continuity ledger saved\"\n\n# Generate quick handoff\nstackmemory capture --no-commit > /dev/null 2>&1\necho \"\u2705 Handoff document saved\"\n\necho \"\u2705 Ready for /clear - context will be restored automatically\"\necho \"\uD83D\uDCA1 After /clear, run: stackmemory clear --restore\"\n`\n );\n\n // Install idle detector\n await this.installHook(\n 'on-idle-5min',\n `\n#!/bin/bash\n# StackMemory Idle Detector\n# Generates handoff after 5 minutes of inactivity\n\necho \"\u23F8\uFE0F Session idle - generating handoff...\"\nstackmemory capture --no-commit > /dev/null 2>&1\necho \"\u2705 Handoff saved. Ready to resume anytime.\"\n`\n );\n }\n\n /**\n * Install a specific hook script\n */\n private async installHook(name: string, script: string): Promise<void> {\n const hookPath = path.join(this.config.claudeHooksPath!, name);\n\n // Store script content\n this.hookScripts.set(name, script);\n\n // Write hook file\n await fs.writeFile(hookPath, script.trim(), { mode: 0o755 });\n\n // Make executable\n await fs.chmod(hookPath, 0o755);\n }\n\n /**\n * Execute a hook with context\n */\n private async executeHook(hookName: string, context: any): Promise<void> {\n const hookPath = path.join(this.config.claudeHooksPath!, hookName);\n\n try {\n // Check if hook exists\n await fs.access(hookPath);\n\n // Execute hook with environment variables\n const { exec } = await import('child_process');\n const { promisify } = await import('util');\n const execAsync = promisify(exec);\n\n const env = {\n ...process.env,\n STACKMEMORY_CONTEXT: JSON.stringify(context),\n STACKMEMORY_PROJECT: this.config.projectRoot,\n };\n\n const { stdout, stderr } = await execAsync(hookPath, { env });\n\n if (stdout) console.log(stdout);\n if (stderr) console.error(stderr);\n } catch (error: unknown) {\n // Hook doesn't exist or failed\n console.debug(`Hook ${hookName} not found or failed:`, error);\n }\n }\n\n /**\n * Stop monitoring and cleanup\n */\n async stop(): Promise<void> {\n if (this.monitor) {\n await this.monitor.stop();\n }\n this.isActive = false;\n console.log('\uD83D\uDED1 Claude Code lifecycle hooks stopped');\n }\n\n /**\n * Get current status\n */\n getStatus(): any {\n return {\n isActive: this.isActive,\n monitorStatus: this.monitor?.getStatus(),\n config: this.config,\n installedHooks: Array.from(this.hookScripts.keys()),\n };\n }\n}\n\n/**\n * Global singleton instance\n */\nlet globalInstance: ClaudeCodeLifecycleHooks | undefined;\n\n/**\n * Initialize global hooks\n */\nexport async function initializeClaudeHooks(\n projectRoot?: string\n): Promise<ClaudeCodeLifecycleHooks> {\n if (!projectRoot) {\n projectRoot = process.cwd();\n }\n\n if (!globalInstance) {\n globalInstance = new ClaudeCodeLifecycleHooks({\n projectRoot,\n autoTriggers: {\n onContextHigh: true,\n onContextCritical: true,\n onSessionIdle: true,\n onSessionEnd: true,\n onClearCommand: true,\n },\n });\n\n await globalInstance.initialize();\n }\n\n return globalInstance;\n}\n\n/**\n * Get global instance\n */\nexport function getClaudeHooks(): ClaudeCodeLifecycleHooks | undefined {\n return globalInstance;\n}\n\n/**\n * Stop global hooks\n */\nexport async function stopClaudeHooks(): Promise<void> {\n if (globalInstance) {\n await globalInstance.stop();\n globalInstance = undefined;\n }\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackmemoryai/stackmemory",
3
- "version": "0.5.49",
3
+ "version": "0.5.52",
4
4
  "description": "Lossless memory runtime for AI coding tools - organizes context as a call stack instead of linear chat logs, with team collaboration and infinite retention",
5
5
  "engines": {
6
6
  "node": ">=20.0.0",
@@ -218,7 +218,7 @@ cleanup_handoffs() {
218
218
  handle_termination() {
219
219
  echo -e "\n${YELLOW}⚠️ Session terminating...${NC}"
220
220
  capture_context
221
- echo -e "${GREEN}✨ Handoff complete! Run 'auto-handoff restore' to continue${NC}"
221
+ echo -e "${GREEN}✨ Handoff complete! Run 'stackmemory restore' to continue${NC}"
222
222
  exit 0
223
223
  }
224
224