@stackmemoryai/stackmemory 0.5.57 → 0.5.58

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.
package/bin/codex-smd ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Codex-SM-Danger CLI Launcher (ESM)
4
+ * Delegates to built CLI in dist without requiring tsx.
5
+ */
6
+ import('../dist/cli/codex-sm-danger.js');
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ import { fileURLToPath as __fileURLToPath } from 'url';
3
+ import { dirname as __pathDirname } from 'path';
4
+ const __filename = __fileURLToPath(import.meta.url);
5
+ const __dirname = __pathDirname(__filename);
6
+ import { spawn } from "child_process";
7
+ import * as path from "path";
8
+ const codexSmPath = path.join(__dirname, "codex-sm.js");
9
+ const args = ["--dangerously-skip-permissions", ...process.argv.slice(2)];
10
+ const child = spawn("node", [codexSmPath, ...args], {
11
+ stdio: "inherit",
12
+ env: process.env
13
+ });
14
+ child.on("exit", (code) => {
15
+ process.exit(code || 0);
16
+ });
17
+ child.on("error", (err) => {
18
+ console.error("Failed to launch codex-sm:", err.message);
19
+ process.exit(1);
20
+ });
21
+ //# sourceMappingURL=codex-sm-danger.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/cli/codex-sm-danger.ts"],
4
+ "sourcesContent": ["#!/usr/bin/env node\n\n/**\n * codex-sm-danger: Codex-SM wrapper with --dangerously-skip-permissions\n * Shorthand for: codex-sm --dangerously-skip-permissions [args]\n */\n\nimport { spawn } from 'child_process';\nimport * as path from 'path';\n\n// __filename and __dirname are provided by esbuild banner for ESM compatibility\n\n// Get the codex-sm script path\nconst codexSmPath = path.join(__dirname, 'codex-sm.js');\n\n// Prepend the danger flag to all args\nconst args = ['--dangerously-skip-permissions', ...process.argv.slice(2)];\n\n// Spawn codex-sm with the danger flag\nconst child = spawn('node', [codexSmPath, ...args], {\n stdio: 'inherit',\n env: process.env,\n});\n\nchild.on('exit', (code) => {\n process.exit(code || 0);\n});\n\nchild.on('error', (err) => {\n console.error('Failed to launch codex-sm:', err.message);\n process.exit(1);\n});\n"],
5
+ "mappings": ";;;;;AAOA,SAAS,aAAa;AACtB,YAAY,UAAU;AAKtB,MAAM,cAAc,KAAK,KAAK,WAAW,aAAa;AAGtD,MAAM,OAAO,CAAC,kCAAkC,GAAG,QAAQ,KAAK,MAAM,CAAC,CAAC;AAGxE,MAAM,QAAQ,MAAM,QAAQ,CAAC,aAAa,GAAG,IAAI,GAAG;AAAA,EAClD,OAAO;AAAA,EACP,KAAK,QAAQ;AACf,CAAC;AAED,MAAM,GAAG,QAAQ,CAAC,SAAS;AACzB,UAAQ,KAAK,QAAQ,CAAC;AACxB,CAAC;AAED,MAAM,GAAG,SAAS,CAAC,QAAQ;AACzB,UAAQ,MAAM,8BAA8B,IAAI,OAAO;AACvD,UAAQ,KAAK,CAAC;AAChB,CAAC;",
6
+ "names": []
7
+ }
@@ -20,6 +20,7 @@ import { FrameManager } from "../../core/context/index.js";
20
20
  import { LinearTaskManager } from "../../features/tasks/linear-task-manager.js";
21
21
  import { logger } from "../../core/monitoring/logger.js";
22
22
  import { EnhancedHandoffGenerator } from "../../core/session/enhanced-handoff.js";
23
+ const countTokens = (text) => Math.ceil(text.length / 3.5);
23
24
  const MAX_HANDOFF_VERSIONS = 10;
24
25
  function saveVersionedHandoff(projectRoot, branch, content) {
25
26
  const handoffsDir = join(projectRoot, ".stackmemory", "handoffs");
@@ -56,7 +57,10 @@ const CommitMessageSchema = z.string().min(1, "Commit message cannot be empty").
56
57
  );
57
58
  function createCaptureCommand() {
58
59
  const cmd = new Command("capture");
59
- cmd.description("Commit current work and generate a handoff prompt").option("-m, --message <message>", "Custom commit message").option("--no-commit", "Skip git commit").option("--copy", "Copy the handoff prompt to clipboard").option("--basic", "Use basic handoff format instead of enhanced").action(async (options) => {
60
+ cmd.description("Commit current work and generate a handoff prompt").option("-m, --message <message>", "Custom commit message").option("--no-commit", "Skip git commit").option("--copy", "Copy the handoff prompt to clipboard").option("--basic", "Use basic handoff format instead of enhanced").option("--verbose", "Use verbose handoff format (full markdown)").option("--ultra", "Use ultra-compact pipe-delimited format").option(
61
+ "--format <format>",
62
+ "Output format: auto, ultra, compact, verbose (default: auto)"
63
+ ).action(async (options) => {
60
64
  try {
61
65
  const projectRoot = process.cwd();
62
66
  const dbPath = join(projectRoot, ".stackmemory", "context.db");
@@ -236,8 +240,34 @@ Generated by stackmemory capture at ${timestamp}
236
240
  } else {
237
241
  const enhancedGenerator = new EnhancedHandoffGenerator(projectRoot);
238
242
  const enhancedHandoff = await enhancedGenerator.generate();
239
- handoffPrompt = enhancedGenerator.toMarkdown(enhancedHandoff);
240
- console.log(`Estimated tokens: ~${enhancedHandoff.estimatedTokens}`);
243
+ let format = "auto";
244
+ if (options.verbose) {
245
+ format = "verbose";
246
+ } else if (options.ultra) {
247
+ format = "ultra";
248
+ } else if (options.format) {
249
+ format = options.format;
250
+ }
251
+ if (format === "auto") {
252
+ const selectedFormat = enhancedGenerator.selectFormat(enhancedHandoff);
253
+ handoffPrompt = enhancedGenerator.toAutoFormat(enhancedHandoff);
254
+ const actualTokens = countTokens(handoffPrompt);
255
+ console.log(
256
+ `Estimated tokens: ~${actualTokens} (${selectedFormat}, auto-selected)`
257
+ );
258
+ } else if (format === "ultra") {
259
+ handoffPrompt = enhancedGenerator.toUltraCompact(enhancedHandoff);
260
+ const actualTokens = countTokens(handoffPrompt);
261
+ console.log(`Estimated tokens: ~${actualTokens} (ultra-compact)`);
262
+ } else if (format === "verbose") {
263
+ handoffPrompt = enhancedGenerator.toMarkdown(enhancedHandoff);
264
+ const actualTokens = countTokens(handoffPrompt);
265
+ console.log(`Estimated tokens: ~${actualTokens} (verbose)`);
266
+ } else {
267
+ handoffPrompt = enhancedGenerator.toCompact(enhancedHandoff);
268
+ const actualTokens = countTokens(handoffPrompt);
269
+ console.log(`Estimated tokens: ~${actualTokens} (compact)`);
270
+ }
241
271
  }
242
272
  const stackmemoryDir = join(projectRoot, ".stackmemory");
243
273
  if (!existsSync(stackmemoryDir)) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/cli/commands/handoff.ts"],
4
- "sourcesContent": ["/**\n * Handoff command - Commits work and generates a prompt for the next session\n */\n\nimport { Command } from 'commander';\nimport { execSync, execFileSync, spawn, ChildProcess } from 'child_process';\nimport {\n existsSync,\n readFileSync,\n writeFileSync,\n mkdirSync,\n readdirSync,\n unlinkSync,\n} from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport Database from 'better-sqlite3';\nimport { z } from 'zod';\nimport { FrameManager } from '../../core/context/index.js';\nimport { LinearTaskManager } from '../../features/tasks/linear-task-manager.js';\nimport { logger } from '../../core/monitoring/logger.js';\nimport { EnhancedHandoffGenerator } from '../../core/session/enhanced-handoff.js';\n\n// Handoff versioning - keep last N handoffs\nconst MAX_HANDOFF_VERSIONS = 10;\n\nfunction saveVersionedHandoff(\n projectRoot: string,\n branch: string,\n content: string\n): string {\n const handoffsDir = join(projectRoot, '.stackmemory', 'handoffs');\n if (!existsSync(handoffsDir)) {\n mkdirSync(handoffsDir, { recursive: true });\n }\n\n // Generate versioned filename: YYYY-MM-DD-HH-mm-branch.md\n const now = new Date();\n const timestamp = now.toISOString().slice(0, 16).replace(/[T:]/g, '-');\n const safeBranch = branch.replace(/[^a-zA-Z0-9-]/g, '-').slice(0, 30);\n const filename = `${timestamp}-${safeBranch}.md`;\n const versionedPath = join(handoffsDir, filename);\n\n // Save versioned handoff\n writeFileSync(versionedPath, content);\n\n // Clean up old handoffs (keep last N)\n try {\n const files = readdirSync(handoffsDir)\n .filter((f) => f.endsWith('.md'))\n .sort()\n .reverse();\n\n for (const oldFile of files.slice(MAX_HANDOFF_VERSIONS)) {\n unlinkSync(join(handoffsDir, oldFile));\n }\n } catch {\n // Cleanup failed, not critical\n }\n\n return versionedPath;\n}\n\n// Input validation schemas\nconst CommitMessageSchema = z\n .string()\n .min(1, 'Commit message cannot be empty')\n .max(200, 'Commit message too long')\n .regex(\n /^[a-zA-Z0-9\\s\\-_.,:()\\/\\[\\]]+$/,\n 'Commit message contains invalid characters'\n )\n .refine(\n (msg) => !msg.includes('\\n'),\n 'Commit message cannot contain newlines'\n )\n .refine(\n (msg) => !msg.includes('\"'),\n 'Commit message cannot contain double quotes'\n )\n .refine(\n (msg) => !msg.includes('`'),\n 'Commit message cannot contain backticks'\n );\n\nexport function createCaptureCommand(): Command {\n const cmd = new Command('capture');\n\n cmd\n .description('Commit current work and generate a handoff prompt')\n .option('-m, --message <message>', 'Custom commit message')\n .option('--no-commit', 'Skip git commit')\n .option('--copy', 'Copy the handoff prompt to clipboard')\n .option('--basic', 'Use basic handoff format instead of enhanced')\n .action(async (options) => {\n try {\n const projectRoot = process.cwd();\n const dbPath = join(projectRoot, '.stackmemory', 'context.db');\n\n // 1. Check git status\n let gitStatus = '';\n let hasChanges = false;\n\n try {\n gitStatus = execSync('git status --short', {\n encoding: 'utf-8',\n cwd: projectRoot,\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n hasChanges = gitStatus.trim().length > 0;\n } catch {\n // Not a git repository - silently skip git operations\n }\n\n // 2. Commit if there are changes and not skipped\n if (hasChanges && options.commit !== false) {\n try {\n // Get current branch\n const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {\n encoding: 'utf-8',\n cwd: projectRoot,\n }).trim();\n\n // Stage all changes\n execSync('git add -A', { cwd: projectRoot });\n\n // Generate or use custom commit message\n let commitMessage =\n options.message ||\n `chore: handoff checkpoint on ${currentBranch}`;\n\n // Validate commit message\n try {\n commitMessage = CommitMessageSchema.parse(commitMessage);\n } catch (validationError) {\n console.error(\n '\u274C Invalid commit message:',\n (validationError as Error).message\n );\n return;\n }\n\n // Commit using execFileSync for safety\n execFileSync('git', ['commit', '-m', commitMessage], {\n cwd: projectRoot,\n stdio: 'inherit',\n });\n\n console.log(`\u2705 Committed changes: \"${commitMessage}\"`);\n console.log(` Branch: ${currentBranch}`);\n } catch (err: unknown) {\n console.error(\n '\u274C Failed to commit changes:',\n (err as Error).message\n );\n }\n } else if (!hasChanges) {\n console.log('\u2139\uFE0F No changes to commit');\n }\n\n // 3. Gather context for handoff prompt\n let contextSummary = '';\n let tasksSummary = '';\n let recentWork = '';\n\n if (existsSync(dbPath)) {\n const db = new Database(dbPath);\n\n // Get recent context\n const frameManager = new FrameManager(db, 'cli-project');\n const activeFrames = frameManager.getActiveFramePath();\n\n if (activeFrames.length > 0) {\n contextSummary = 'Active context frames:\\n';\n activeFrames.forEach((frame) => {\n contextSummary += ` - ${frame.name} [${frame.type}]\\n`;\n });\n }\n\n // Get task status\n const taskStore = new LinearTaskManager(projectRoot, db);\n const activeTasks = taskStore.getActiveTasks();\n\n const inProgress = activeTasks.filter(\n (t: any) => t.status === 'in_progress'\n );\n const todo = activeTasks.filter((t: any) => t.status === 'pending');\n const recentlyCompleted = activeTasks\n .filter((t: any) => t.status === 'completed' && t.completed_at)\n .sort(\n (a: any, b: any) => (b.completed_at || 0) - (a.completed_at || 0)\n )\n .slice(0, 3);\n\n if (inProgress.length > 0 || todo.length > 0) {\n tasksSummary = '\\nTasks:\\n';\n\n if (inProgress.length > 0) {\n tasksSummary += 'In Progress:\\n';\n inProgress.forEach((t: any) => {\n const externalId = t.external_refs?.linear?.id;\n tasksSummary += ` - ${t.title}${externalId ? ` [${externalId}]` : ''}\\n`;\n });\n }\n\n if (todo.length > 0) {\n tasksSummary += 'TODO:\\n';\n todo.slice(0, 5).forEach((t: any) => {\n const externalId = t.external_refs?.linear?.id;\n tasksSummary += ` - ${t.title}${externalId ? ` [${externalId}]` : ''}\\n`;\n });\n if (todo.length > 5) {\n tasksSummary += ` ... and ${todo.length - 5} more\\n`;\n }\n }\n }\n\n if (recentlyCompleted.length > 0) {\n recentWork = '\\nRecently Completed:\\n';\n recentlyCompleted.forEach((t: any) => {\n recentWork += ` \u2713 ${t.title}\\n`;\n });\n }\n\n // Get recent events\n const recentEvents = db\n .prepare(\n `\n SELECT event_type as type, payload as data, datetime(ts, 'unixepoch') as time\n FROM events\n ORDER BY ts DESC\n LIMIT 5\n `\n )\n .all() as any[];\n\n if (recentEvents.length > 0) {\n recentWork += '\\nRecent Activity:\\n';\n recentEvents.forEach((event) => {\n const data = JSON.parse(event.data);\n recentWork += ` - ${event.type}: ${data.message || data.name || 'activity'}\\n`;\n });\n }\n\n db.close();\n }\n\n // 4. Get current git info\n let gitInfo = '';\n try {\n const branch = execSync('git rev-parse --abbrev-ref HEAD', {\n encoding: 'utf-8',\n cwd: projectRoot,\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n\n const lastCommit = execSync('git log -1 --oneline', {\n encoding: 'utf-8',\n cwd: projectRoot,\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n\n gitInfo = `\\nGit Status:\\n Branch: ${branch}\\n Last commit: ${lastCommit}\\n`;\n } catch {\n // Not a git repository - silently ignore\n }\n\n // 5. Check for any blockers or notes\n let notes = '';\n const notesPath = join(projectRoot, '.stackmemory', 'handoff.md');\n if (existsSync(notesPath)) {\n const handoffNotes = readFileSync(notesPath, 'utf-8');\n if (handoffNotes.trim()) {\n notes = `\\nNotes from previous handoff:\\n${handoffNotes}\\n`;\n }\n }\n\n // 6. Generate the handoff prompt\n let handoffPrompt: string;\n\n if (options.basic) {\n // Use basic handoff format\n const timestamp = new Date().toISOString();\n handoffPrompt = `# Session Handoff - ${timestamp}\n\n## Project: ${projectRoot.split('/').pop()}\n\n${gitInfo}\n${contextSummary}\n${tasksSummary}\n${recentWork}\n${notes}\n\n## Continue from here:\n\n1. Run \\`stackmemory status\\` to check the current state\n2. Review any in-progress tasks above\n3. Check for any uncommitted changes with \\`git status\\`\n4. Resume work on the active context\n\n## Quick Commands:\n- \\`stackmemory context load --recent\\` - Load recent context\n- \\`stackmemory task list --state in_progress\\` - Show in-progress tasks\n- \\`stackmemory linear sync\\` - Sync with Linear if configured\n- \\`stackmemory log recent\\` - View recent activity\n\n---\nGenerated by stackmemory capture at ${timestamp}\n`;\n } else {\n // Use high-efficacy enhanced handoff generator (default)\n const enhancedGenerator = new EnhancedHandoffGenerator(projectRoot);\n const enhancedHandoff = await enhancedGenerator.generate();\n handoffPrompt = enhancedGenerator.toMarkdown(enhancedHandoff);\n console.log(`Estimated tokens: ~${enhancedHandoff.estimatedTokens}`);\n }\n\n // 7. Save handoff prompt (both latest and versioned)\n const stackmemoryDir = join(projectRoot, '.stackmemory');\n if (!existsSync(stackmemoryDir)) {\n mkdirSync(stackmemoryDir, { recursive: true });\n }\n const handoffPath = join(stackmemoryDir, 'last-handoff.md');\n writeFileSync(handoffPath, handoffPrompt);\n\n // Save versioned copy\n let branch = 'unknown';\n try {\n branch = execSync('git rev-parse --abbrev-ref HEAD', {\n encoding: 'utf-8',\n cwd: projectRoot,\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n } catch {\n // Not a git repo\n }\n const versionedPath = saveVersionedHandoff(\n projectRoot,\n branch,\n handoffPrompt\n );\n console.log(\n `Versioned: ${versionedPath.split('/').slice(-2).join('/')}`\n );\n\n // 8. Display the prompt\n console.log('\\n' + '='.repeat(60));\n console.log(handoffPrompt);\n console.log('='.repeat(60));\n\n // 9. Copy to clipboard if requested\n if (options.copy) {\n try {\n // Use execFileSync with predefined commands for safety\n if (process.platform === 'darwin') {\n execFileSync('pbcopy', [], {\n input: handoffPrompt,\n cwd: projectRoot,\n });\n } else if (process.platform === 'win32') {\n execFileSync('clip', [], {\n input: handoffPrompt,\n cwd: projectRoot,\n });\n } else {\n execFileSync('xclip', ['-selection', 'clipboard'], {\n input: handoffPrompt,\n cwd: projectRoot,\n });\n }\n\n console.log('\\n\u2705 Handoff prompt copied to clipboard!');\n } catch {\n console.log('\\n\u26A0\uFE0F Could not copy to clipboard');\n }\n }\n\n console.log(`\\n\uD83D\uDCBE Handoff saved to: ${handoffPath}`);\n console.log('\uD83D\uDCCB Use this prompt when starting your next session');\n } catch (error: unknown) {\n logger.error('Capture command failed', error as Error);\n console.error('\u274C Capture failed:', (error as Error).message);\n process.exit(1);\n }\n });\n\n return cmd;\n}\n\nexport function createRestoreCommand(): Command {\n const cmd = new Command('restore');\n\n cmd\n .description('Restore context from last handoff')\n .option('--no-copy', 'Do not copy prompt to clipboard')\n .action(async (options) => {\n try {\n const projectRoot = process.cwd();\n const handoffPath = join(\n projectRoot,\n '.stackmemory',\n 'last-handoff.md'\n );\n const metaPath = join(\n process.env['HOME'] || '~',\n '.stackmemory',\n 'handoffs',\n 'last-handoff-meta.json'\n );\n\n if (!existsSync(handoffPath)) {\n console.log('\u274C No handoff found in this project');\n console.log('\uD83D\uDCA1 Run \"stackmemory capture\" to create one');\n return;\n }\n\n // Read handoff prompt\n const handoffPrompt = readFileSync(handoffPath, 'utf-8');\n\n // Display the prompt\n console.log('\\n' + '='.repeat(60));\n console.log('\uD83D\uDCCB RESTORED HANDOFF');\n console.log('='.repeat(60));\n console.log(handoffPrompt);\n console.log('='.repeat(60));\n\n // Check for metadata\n if (existsSync(metaPath)) {\n const metadata = JSON.parse(readFileSync(metaPath, 'utf-8'));\n console.log('\\n\uD83D\uDCCA Session Metadata:');\n console.log(` Timestamp: ${metadata.timestamp}`);\n console.log(` Reason: ${metadata.reason}`);\n console.log(` Duration: ${metadata.session_duration}s`);\n console.log(` Command: ${metadata.command}`);\n }\n\n // Check current git status\n try {\n const gitStatus = execSync('git status --short', {\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n if (gitStatus) {\n console.log('\\n Current uncommitted changes:');\n console.log(gitStatus);\n }\n } catch {\n // Not a git repo - silently ignore\n }\n\n // Copy to clipboard unless disabled\n if (options.copy !== false) {\n try {\n // Use execFileSync with predefined commands for safety\n if (process.platform === 'darwin') {\n execFileSync('pbcopy', [], {\n input: handoffPrompt,\n cwd: projectRoot,\n });\n } else if (process.platform === 'win32') {\n execFileSync('clip', [], {\n input: handoffPrompt,\n cwd: projectRoot,\n });\n } else {\n execFileSync('xclip', ['-selection', 'clipboard'], {\n input: handoffPrompt,\n cwd: projectRoot,\n });\n }\n\n console.log('\\n\u2705 Handoff prompt copied to clipboard!');\n } catch {\n console.log('\\n\u26A0\uFE0F Could not copy to clipboard');\n }\n }\n\n console.log('\\n\uD83D\uDE80 Ready to continue where you left off!');\n } catch (error: unknown) {\n logger.error('Restore failed', error as Error);\n console.error('\u274C Restore failed:', (error as Error).message);\n process.exit(1);\n }\n });\n\n return cmd;\n}\n\ninterface AutoCaptureMetadata {\n timestamp: string;\n reason: string;\n exit_code: number;\n command: string;\n pid: number;\n cwd: string;\n user: string;\n session_duration: number;\n}\n\nasync function captureHandoff(\n reason: string,\n exitCode: number,\n wrappedCommand: string,\n sessionStart: number,\n quiet: boolean\n): Promise<void> {\n const projectRoot = process.cwd();\n const handoffDir = join(homedir(), '.stackmemory', 'handoffs');\n const logFile = join(handoffDir, 'auto-handoff.log');\n\n // Ensure handoff directory exists\n if (!existsSync(handoffDir)) {\n mkdirSync(handoffDir, { recursive: true });\n }\n\n const logMessage = (msg: string): void => {\n const timestamp = new Date().toISOString().slice(0, 19).replace('T', ' ');\n const logLine = `[${timestamp}] ${msg}\\n`;\n try {\n writeFileSync(logFile, logLine, { flag: 'a' });\n } catch {\n // Logging failed, continue anyway\n }\n };\n\n if (!quiet) {\n console.log('\\nCapturing handoff context...');\n }\n logMessage(`Capturing handoff: reason=${reason}, exit_code=${exitCode}`);\n\n try {\n // Run stackmemory capture --no-commit\n execFileSync(\n process.execPath,\n [process.argv[1], 'capture', '--no-commit'],\n {\n cwd: projectRoot,\n stdio: quiet ? 'pipe' : 'inherit',\n }\n );\n\n // Save metadata\n const metadata: AutoCaptureMetadata = {\n timestamp: new Date().toISOString(),\n reason,\n exit_code: exitCode,\n command: wrappedCommand,\n pid: process.pid,\n cwd: projectRoot,\n user: process.env['USER'] || 'unknown',\n session_duration: Math.floor((Date.now() - sessionStart) / 1000),\n };\n\n const metadataPath = join(handoffDir, 'last-handoff-meta.json');\n writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));\n\n if (!quiet) {\n console.log('Handoff captured successfully');\n logMessage(`Handoff captured: ${metadataPath}`);\n\n // Show session summary\n console.log('\\nSession Summary:');\n console.log(` Duration: ${metadata.session_duration} seconds`);\n console.log(` Exit reason: ${reason}`);\n\n // Check for uncommitted changes\n try {\n const gitStatus = execSync('git status --short', {\n encoding: 'utf-8',\n cwd: projectRoot,\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n if (gitStatus) {\n console.log('\\nYou have uncommitted changes');\n console.log(' Run \"git status\" to review');\n }\n } catch {\n // Not a git repo - silently ignore\n }\n\n console.log('\\nRun \"stackmemory restore\" in your next session');\n }\n } catch (err) {\n if (!quiet) {\n console.error('Failed to capture handoff:', (err as Error).message);\n }\n logMessage(`ERROR: ${(err as Error).message}`);\n }\n}\n\nexport function createAutoCaptureCommand(): Command {\n const cmd = new Command('auto-capture');\n\n cmd\n .description('Wrap a command with automatic handoff capture on termination')\n .option('-a, --auto', 'Auto-capture on normal exit (no prompt)')\n .option('-q, --quiet', 'Suppress output')\n .option('-t, --tag <tag>', 'Tag this session')\n .argument('[command...]', 'Command to wrap with auto-handoff')\n .action(async (commandArgs: string[], options) => {\n const autoCapture = options.auto || false;\n const quiet = options.quiet || false;\n const tag = options.tag || '';\n\n // If no command provided, show usage\n if (!commandArgs || commandArgs.length === 0) {\n console.log('StackMemory Auto-Handoff');\n console.log('-'.repeat(50));\n console.log('');\n console.log(\n 'Wraps a command with automatic handoff capture on termination.'\n );\n console.log('');\n console.log('Usage:');\n console.log(' stackmemory auto-capture [options] <command> [args...]');\n console.log('');\n console.log('Examples:');\n console.log(' stackmemory auto-capture claude');\n console.log(' stackmemory auto-capture -a npm run dev');\n console.log(' stackmemory auto-capture -t \"feature-work\" vim');\n console.log('');\n console.log('Options:');\n console.log(\n ' -a, --auto Auto-capture on normal exit (no prompt)'\n );\n console.log(' -q, --quiet Suppress output');\n console.log(' -t, --tag <tag> Tag this session');\n return;\n }\n\n const wrappedCommand = commandArgs.join(' ');\n const sessionStart = Date.now();\n let capturedAlready = false;\n\n if (!quiet) {\n console.log('StackMemory Auto-Handoff Wrapper');\n console.log(`Wrapping: ${wrappedCommand}`);\n if (tag) {\n console.log(`Tag: ${tag}`);\n }\n console.log('Handoff will be captured on termination');\n console.log('');\n }\n\n // Spawn the wrapped command\n const [cmd, ...args] = commandArgs;\n let childProcess: ChildProcess;\n\n try {\n childProcess = spawn(cmd, args, {\n stdio: 'inherit',\n shell: false,\n cwd: process.cwd(),\n env: process.env,\n });\n } catch (err) {\n console.error(`Failed to start command: ${(err as Error).message}`);\n process.exit(1);\n return;\n }\n\n // Handle signals - forward to child and capture on termination\n const handleSignal = async (\n signal: NodeJS.Signals,\n exitCode: number\n ): Promise<void> => {\n if (capturedAlready) return;\n capturedAlready = true;\n\n if (!quiet) {\n console.log(`\\nReceived ${signal}`);\n }\n\n // Kill the child process if still running\n if (childProcess.pid && !childProcess.killed) {\n childProcess.kill(signal);\n }\n\n await captureHandoff(\n signal,\n exitCode,\n wrappedCommand,\n sessionStart,\n quiet\n );\n process.exit(exitCode);\n };\n\n process.on('SIGINT', () => handleSignal('SIGINT', 130));\n process.on('SIGTERM', () => handleSignal('SIGTERM', 143));\n process.on('SIGHUP', () => handleSignal('SIGHUP', 129));\n\n // Handle child process exit\n childProcess.on('exit', async (code, signal) => {\n if (capturedAlready) return;\n capturedAlready = true;\n\n const exitCode = code ?? (signal ? 128 : 0);\n\n if (signal) {\n // Child was killed by a signal\n await captureHandoff(\n signal,\n exitCode,\n wrappedCommand,\n sessionStart,\n quiet\n );\n } else if (exitCode !== 0) {\n // Unexpected exit\n if (!quiet) {\n console.log(`\\nCommand exited with code: ${exitCode}`);\n }\n await captureHandoff(\n 'unexpected_exit',\n exitCode,\n wrappedCommand,\n sessionStart,\n quiet\n );\n } else if (autoCapture) {\n // Normal exit with auto-capture enabled\n await captureHandoff(\n 'normal_exit',\n 0,\n wrappedCommand,\n sessionStart,\n quiet\n );\n } else {\n // Normal exit - prompt for capture (simplified for CLI, auto-capture)\n // In non-interactive contexts, default to capturing\n if (process.stdin.isTTY) {\n // Interactive - we could prompt but keeping it simple\n console.log(\n '\\nSession ending. Use -a flag for auto-capture on normal exit.'\n );\n }\n }\n\n process.exit(exitCode);\n });\n\n // Handle spawn errors\n childProcess.on('error', async (err) => {\n if (capturedAlready) return;\n capturedAlready = true;\n\n console.error(`Command error: ${err.message}`);\n await captureHandoff(\n 'spawn_error',\n 1,\n wrappedCommand,\n sessionStart,\n quiet\n );\n process.exit(1);\n });\n });\n\n return cmd;\n}\n\n/** @deprecated Use createCaptureCommand, createRestoreCommand, createAutoCaptureCommand */\nexport function createHandoffCommand(): Command {\n const cmd = new Command('handoff');\n cmd.description('(deprecated) Use \"capture\" or \"restore\" instead');\n return cmd;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Handoff command - Commits work and generates a prompt for the next session\n */\n\nimport { Command } from 'commander';\nimport { execSync, execFileSync, spawn, ChildProcess } from 'child_process';\nimport {\n existsSync,\n readFileSync,\n writeFileSync,\n mkdirSync,\n readdirSync,\n unlinkSync,\n} from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport Database from 'better-sqlite3';\nimport { z } from 'zod';\nimport { FrameManager } from '../../core/context/index.js';\nimport { LinearTaskManager } from '../../features/tasks/linear-task-manager.js';\nimport { logger } from '../../core/monitoring/logger.js';\nimport { EnhancedHandoffGenerator } from '../../core/session/enhanced-handoff.js';\n\n// Simple token estimation (avg 3.5 chars per token for English)\nconst countTokens = (text: string): number => Math.ceil(text.length / 3.5);\n\n// Handoff versioning - keep last N handoffs\nconst MAX_HANDOFF_VERSIONS = 10;\n\nfunction saveVersionedHandoff(\n projectRoot: string,\n branch: string,\n content: string\n): string {\n const handoffsDir = join(projectRoot, '.stackmemory', 'handoffs');\n if (!existsSync(handoffsDir)) {\n mkdirSync(handoffsDir, { recursive: true });\n }\n\n // Generate versioned filename: YYYY-MM-DD-HH-mm-branch.md\n const now = new Date();\n const timestamp = now.toISOString().slice(0, 16).replace(/[T:]/g, '-');\n const safeBranch = branch.replace(/[^a-zA-Z0-9-]/g, '-').slice(0, 30);\n const filename = `${timestamp}-${safeBranch}.md`;\n const versionedPath = join(handoffsDir, filename);\n\n // Save versioned handoff\n writeFileSync(versionedPath, content);\n\n // Clean up old handoffs (keep last N)\n try {\n const files = readdirSync(handoffsDir)\n .filter((f) => f.endsWith('.md'))\n .sort()\n .reverse();\n\n for (const oldFile of files.slice(MAX_HANDOFF_VERSIONS)) {\n unlinkSync(join(handoffsDir, oldFile));\n }\n } catch {\n // Cleanup failed, not critical\n }\n\n return versionedPath;\n}\n\n// Input validation schemas\nconst CommitMessageSchema = z\n .string()\n .min(1, 'Commit message cannot be empty')\n .max(200, 'Commit message too long')\n .regex(\n /^[a-zA-Z0-9\\s\\-_.,:()\\/\\[\\]]+$/,\n 'Commit message contains invalid characters'\n )\n .refine(\n (msg) => !msg.includes('\\n'),\n 'Commit message cannot contain newlines'\n )\n .refine(\n (msg) => !msg.includes('\"'),\n 'Commit message cannot contain double quotes'\n )\n .refine(\n (msg) => !msg.includes('`'),\n 'Commit message cannot contain backticks'\n );\n\nexport function createCaptureCommand(): Command {\n const cmd = new Command('capture');\n\n cmd\n .description('Commit current work and generate a handoff prompt')\n .option('-m, --message <message>', 'Custom commit message')\n .option('--no-commit', 'Skip git commit')\n .option('--copy', 'Copy the handoff prompt to clipboard')\n .option('--basic', 'Use basic handoff format instead of enhanced')\n .option('--verbose', 'Use verbose handoff format (full markdown)')\n .option('--ultra', 'Use ultra-compact pipe-delimited format')\n .option(\n '--format <format>',\n 'Output format: auto, ultra, compact, verbose (default: auto)'\n )\n .action(async (options) => {\n try {\n const projectRoot = process.cwd();\n const dbPath = join(projectRoot, '.stackmemory', 'context.db');\n\n // 1. Check git status\n let gitStatus = '';\n let hasChanges = false;\n\n try {\n gitStatus = execSync('git status --short', {\n encoding: 'utf-8',\n cwd: projectRoot,\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n hasChanges = gitStatus.trim().length > 0;\n } catch {\n // Not a git repository - silently skip git operations\n }\n\n // 2. Commit if there are changes and not skipped\n if (hasChanges && options.commit !== false) {\n try {\n // Get current branch\n const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {\n encoding: 'utf-8',\n cwd: projectRoot,\n }).trim();\n\n // Stage all changes\n execSync('git add -A', { cwd: projectRoot });\n\n // Generate or use custom commit message\n let commitMessage =\n options.message ||\n `chore: handoff checkpoint on ${currentBranch}`;\n\n // Validate commit message\n try {\n commitMessage = CommitMessageSchema.parse(commitMessage);\n } catch (validationError) {\n console.error(\n '\u274C Invalid commit message:',\n (validationError as Error).message\n );\n return;\n }\n\n // Commit using execFileSync for safety\n execFileSync('git', ['commit', '-m', commitMessage], {\n cwd: projectRoot,\n stdio: 'inherit',\n });\n\n console.log(`\u2705 Committed changes: \"${commitMessage}\"`);\n console.log(` Branch: ${currentBranch}`);\n } catch (err: unknown) {\n console.error(\n '\u274C Failed to commit changes:',\n (err as Error).message\n );\n }\n } else if (!hasChanges) {\n console.log('\u2139\uFE0F No changes to commit');\n }\n\n // 3. Gather context for handoff prompt\n let contextSummary = '';\n let tasksSummary = '';\n let recentWork = '';\n\n if (existsSync(dbPath)) {\n const db = new Database(dbPath);\n\n // Get recent context\n const frameManager = new FrameManager(db, 'cli-project');\n const activeFrames = frameManager.getActiveFramePath();\n\n if (activeFrames.length > 0) {\n contextSummary = 'Active context frames:\\n';\n activeFrames.forEach((frame) => {\n contextSummary += ` - ${frame.name} [${frame.type}]\\n`;\n });\n }\n\n // Get task status\n const taskStore = new LinearTaskManager(projectRoot, db);\n const activeTasks = taskStore.getActiveTasks();\n\n const inProgress = activeTasks.filter(\n (t: any) => t.status === 'in_progress'\n );\n const todo = activeTasks.filter((t: any) => t.status === 'pending');\n const recentlyCompleted = activeTasks\n .filter((t: any) => t.status === 'completed' && t.completed_at)\n .sort(\n (a: any, b: any) => (b.completed_at || 0) - (a.completed_at || 0)\n )\n .slice(0, 3);\n\n if (inProgress.length > 0 || todo.length > 0) {\n tasksSummary = '\\nTasks:\\n';\n\n if (inProgress.length > 0) {\n tasksSummary += 'In Progress:\\n';\n inProgress.forEach((t: any) => {\n const externalId = t.external_refs?.linear?.id;\n tasksSummary += ` - ${t.title}${externalId ? ` [${externalId}]` : ''}\\n`;\n });\n }\n\n if (todo.length > 0) {\n tasksSummary += 'TODO:\\n';\n todo.slice(0, 5).forEach((t: any) => {\n const externalId = t.external_refs?.linear?.id;\n tasksSummary += ` - ${t.title}${externalId ? ` [${externalId}]` : ''}\\n`;\n });\n if (todo.length > 5) {\n tasksSummary += ` ... and ${todo.length - 5} more\\n`;\n }\n }\n }\n\n if (recentlyCompleted.length > 0) {\n recentWork = '\\nRecently Completed:\\n';\n recentlyCompleted.forEach((t: any) => {\n recentWork += ` \u2713 ${t.title}\\n`;\n });\n }\n\n // Get recent events\n const recentEvents = db\n .prepare(\n `\n SELECT event_type as type, payload as data, datetime(ts, 'unixepoch') as time\n FROM events\n ORDER BY ts DESC\n LIMIT 5\n `\n )\n .all() as any[];\n\n if (recentEvents.length > 0) {\n recentWork += '\\nRecent Activity:\\n';\n recentEvents.forEach((event) => {\n const data = JSON.parse(event.data);\n recentWork += ` - ${event.type}: ${data.message || data.name || 'activity'}\\n`;\n });\n }\n\n db.close();\n }\n\n // 4. Get current git info\n let gitInfo = '';\n try {\n const branch = execSync('git rev-parse --abbrev-ref HEAD', {\n encoding: 'utf-8',\n cwd: projectRoot,\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n\n const lastCommit = execSync('git log -1 --oneline', {\n encoding: 'utf-8',\n cwd: projectRoot,\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n\n gitInfo = `\\nGit Status:\\n Branch: ${branch}\\n Last commit: ${lastCommit}\\n`;\n } catch {\n // Not a git repository - silently ignore\n }\n\n // 5. Check for any blockers or notes\n let notes = '';\n const notesPath = join(projectRoot, '.stackmemory', 'handoff.md');\n if (existsSync(notesPath)) {\n const handoffNotes = readFileSync(notesPath, 'utf-8');\n if (handoffNotes.trim()) {\n notes = `\\nNotes from previous handoff:\\n${handoffNotes}\\n`;\n }\n }\n\n // 6. Generate the handoff prompt\n let handoffPrompt: string;\n\n if (options.basic) {\n // Use basic handoff format\n const timestamp = new Date().toISOString();\n handoffPrompt = `# Session Handoff - ${timestamp}\n\n## Project: ${projectRoot.split('/').pop()}\n\n${gitInfo}\n${contextSummary}\n${tasksSummary}\n${recentWork}\n${notes}\n\n## Continue from here:\n\n1. Run \\`stackmemory status\\` to check the current state\n2. Review any in-progress tasks above\n3. Check for any uncommitted changes with \\`git status\\`\n4. Resume work on the active context\n\n## Quick Commands:\n- \\`stackmemory context load --recent\\` - Load recent context\n- \\`stackmemory task list --state in_progress\\` - Show in-progress tasks\n- \\`stackmemory linear sync\\` - Sync with Linear if configured\n- \\`stackmemory log recent\\` - View recent activity\n\n---\nGenerated by stackmemory capture at ${timestamp}\n`;\n } else {\n // Use high-efficacy enhanced handoff generator (default)\n const enhancedGenerator = new EnhancedHandoffGenerator(projectRoot);\n const enhancedHandoff = await enhancedGenerator.generate();\n\n // Determine format: explicit flags > --format option > auto-select\n let format: 'ultra' | 'compact' | 'verbose' | 'auto' = 'auto';\n if (options.verbose) {\n format = 'verbose';\n } else if (options.ultra) {\n format = 'ultra';\n } else if (options.format) {\n format = options.format as typeof format;\n }\n\n // Generate handoff in selected format\n if (format === 'auto') {\n const selectedFormat =\n enhancedGenerator.selectFormat(enhancedHandoff);\n handoffPrompt = enhancedGenerator.toAutoFormat(enhancedHandoff);\n const actualTokens = countTokens(handoffPrompt);\n console.log(\n `Estimated tokens: ~${actualTokens} (${selectedFormat}, auto-selected)`\n );\n } else if (format === 'ultra') {\n handoffPrompt = enhancedGenerator.toUltraCompact(enhancedHandoff);\n const actualTokens = countTokens(handoffPrompt);\n console.log(`Estimated tokens: ~${actualTokens} (ultra-compact)`);\n } else if (format === 'verbose') {\n handoffPrompt = enhancedGenerator.toMarkdown(enhancedHandoff);\n const actualTokens = countTokens(handoffPrompt);\n console.log(`Estimated tokens: ~${actualTokens} (verbose)`);\n } else {\n handoffPrompt = enhancedGenerator.toCompact(enhancedHandoff);\n const actualTokens = countTokens(handoffPrompt);\n console.log(`Estimated tokens: ~${actualTokens} (compact)`);\n }\n }\n\n // 7. Save handoff prompt (both latest and versioned)\n const stackmemoryDir = join(projectRoot, '.stackmemory');\n if (!existsSync(stackmemoryDir)) {\n mkdirSync(stackmemoryDir, { recursive: true });\n }\n const handoffPath = join(stackmemoryDir, 'last-handoff.md');\n writeFileSync(handoffPath, handoffPrompt);\n\n // Save versioned copy\n let branch = 'unknown';\n try {\n branch = execSync('git rev-parse --abbrev-ref HEAD', {\n encoding: 'utf-8',\n cwd: projectRoot,\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n } catch {\n // Not a git repo\n }\n const versionedPath = saveVersionedHandoff(\n projectRoot,\n branch,\n handoffPrompt\n );\n console.log(\n `Versioned: ${versionedPath.split('/').slice(-2).join('/')}`\n );\n\n // 8. Display the prompt\n console.log('\\n' + '='.repeat(60));\n console.log(handoffPrompt);\n console.log('='.repeat(60));\n\n // 9. Copy to clipboard if requested\n if (options.copy) {\n try {\n // Use execFileSync with predefined commands for safety\n if (process.platform === 'darwin') {\n execFileSync('pbcopy', [], {\n input: handoffPrompt,\n cwd: projectRoot,\n });\n } else if (process.platform === 'win32') {\n execFileSync('clip', [], {\n input: handoffPrompt,\n cwd: projectRoot,\n });\n } else {\n execFileSync('xclip', ['-selection', 'clipboard'], {\n input: handoffPrompt,\n cwd: projectRoot,\n });\n }\n\n console.log('\\n\u2705 Handoff prompt copied to clipboard!');\n } catch {\n console.log('\\n\u26A0\uFE0F Could not copy to clipboard');\n }\n }\n\n console.log(`\\n\uD83D\uDCBE Handoff saved to: ${handoffPath}`);\n console.log('\uD83D\uDCCB Use this prompt when starting your next session');\n } catch (error: unknown) {\n logger.error('Capture command failed', error as Error);\n console.error('\u274C Capture failed:', (error as Error).message);\n process.exit(1);\n }\n });\n\n return cmd;\n}\n\nexport function createRestoreCommand(): Command {\n const cmd = new Command('restore');\n\n cmd\n .description('Restore context from last handoff')\n .option('--no-copy', 'Do not copy prompt to clipboard')\n .action(async (options) => {\n try {\n const projectRoot = process.cwd();\n const handoffPath = join(\n projectRoot,\n '.stackmemory',\n 'last-handoff.md'\n );\n const metaPath = join(\n process.env['HOME'] || '~',\n '.stackmemory',\n 'handoffs',\n 'last-handoff-meta.json'\n );\n\n if (!existsSync(handoffPath)) {\n console.log('\u274C No handoff found in this project');\n console.log('\uD83D\uDCA1 Run \"stackmemory capture\" to create one');\n return;\n }\n\n // Read handoff prompt\n const handoffPrompt = readFileSync(handoffPath, 'utf-8');\n\n // Display the prompt\n console.log('\\n' + '='.repeat(60));\n console.log('\uD83D\uDCCB RESTORED HANDOFF');\n console.log('='.repeat(60));\n console.log(handoffPrompt);\n console.log('='.repeat(60));\n\n // Check for metadata\n if (existsSync(metaPath)) {\n const metadata = JSON.parse(readFileSync(metaPath, 'utf-8'));\n console.log('\\n\uD83D\uDCCA Session Metadata:');\n console.log(` Timestamp: ${metadata.timestamp}`);\n console.log(` Reason: ${metadata.reason}`);\n console.log(` Duration: ${metadata.session_duration}s`);\n console.log(` Command: ${metadata.command}`);\n }\n\n // Check current git status\n try {\n const gitStatus = execSync('git status --short', {\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n if (gitStatus) {\n console.log('\\n Current uncommitted changes:');\n console.log(gitStatus);\n }\n } catch {\n // Not a git repo - silently ignore\n }\n\n // Copy to clipboard unless disabled\n if (options.copy !== false) {\n try {\n // Use execFileSync with predefined commands for safety\n if (process.platform === 'darwin') {\n execFileSync('pbcopy', [], {\n input: handoffPrompt,\n cwd: projectRoot,\n });\n } else if (process.platform === 'win32') {\n execFileSync('clip', [], {\n input: handoffPrompt,\n cwd: projectRoot,\n });\n } else {\n execFileSync('xclip', ['-selection', 'clipboard'], {\n input: handoffPrompt,\n cwd: projectRoot,\n });\n }\n\n console.log('\\n\u2705 Handoff prompt copied to clipboard!');\n } catch {\n console.log('\\n\u26A0\uFE0F Could not copy to clipboard');\n }\n }\n\n console.log('\\n\uD83D\uDE80 Ready to continue where you left off!');\n } catch (error: unknown) {\n logger.error('Restore failed', error as Error);\n console.error('\u274C Restore failed:', (error as Error).message);\n process.exit(1);\n }\n });\n\n return cmd;\n}\n\ninterface AutoCaptureMetadata {\n timestamp: string;\n reason: string;\n exit_code: number;\n command: string;\n pid: number;\n cwd: string;\n user: string;\n session_duration: number;\n}\n\nasync function captureHandoff(\n reason: string,\n exitCode: number,\n wrappedCommand: string,\n sessionStart: number,\n quiet: boolean\n): Promise<void> {\n const projectRoot = process.cwd();\n const handoffDir = join(homedir(), '.stackmemory', 'handoffs');\n const logFile = join(handoffDir, 'auto-handoff.log');\n\n // Ensure handoff directory exists\n if (!existsSync(handoffDir)) {\n mkdirSync(handoffDir, { recursive: true });\n }\n\n const logMessage = (msg: string): void => {\n const timestamp = new Date().toISOString().slice(0, 19).replace('T', ' ');\n const logLine = `[${timestamp}] ${msg}\\n`;\n try {\n writeFileSync(logFile, logLine, { flag: 'a' });\n } catch {\n // Logging failed, continue anyway\n }\n };\n\n if (!quiet) {\n console.log('\\nCapturing handoff context...');\n }\n logMessage(`Capturing handoff: reason=${reason}, exit_code=${exitCode}`);\n\n try {\n // Run stackmemory capture --no-commit\n execFileSync(\n process.execPath,\n [process.argv[1], 'capture', '--no-commit'],\n {\n cwd: projectRoot,\n stdio: quiet ? 'pipe' : 'inherit',\n }\n );\n\n // Save metadata\n const metadata: AutoCaptureMetadata = {\n timestamp: new Date().toISOString(),\n reason,\n exit_code: exitCode,\n command: wrappedCommand,\n pid: process.pid,\n cwd: projectRoot,\n user: process.env['USER'] || 'unknown',\n session_duration: Math.floor((Date.now() - sessionStart) / 1000),\n };\n\n const metadataPath = join(handoffDir, 'last-handoff-meta.json');\n writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));\n\n if (!quiet) {\n console.log('Handoff captured successfully');\n logMessage(`Handoff captured: ${metadataPath}`);\n\n // Show session summary\n console.log('\\nSession Summary:');\n console.log(` Duration: ${metadata.session_duration} seconds`);\n console.log(` Exit reason: ${reason}`);\n\n // Check for uncommitted changes\n try {\n const gitStatus = execSync('git status --short', {\n encoding: 'utf-8',\n cwd: projectRoot,\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n if (gitStatus) {\n console.log('\\nYou have uncommitted changes');\n console.log(' Run \"git status\" to review');\n }\n } catch {\n // Not a git repo - silently ignore\n }\n\n console.log('\\nRun \"stackmemory restore\" in your next session');\n }\n } catch (err) {\n if (!quiet) {\n console.error('Failed to capture handoff:', (err as Error).message);\n }\n logMessage(`ERROR: ${(err as Error).message}`);\n }\n}\n\nexport function createAutoCaptureCommand(): Command {\n const cmd = new Command('auto-capture');\n\n cmd\n .description('Wrap a command with automatic handoff capture on termination')\n .option('-a, --auto', 'Auto-capture on normal exit (no prompt)')\n .option('-q, --quiet', 'Suppress output')\n .option('-t, --tag <tag>', 'Tag this session')\n .argument('[command...]', 'Command to wrap with auto-handoff')\n .action(async (commandArgs: string[], options) => {\n const autoCapture = options.auto || false;\n const quiet = options.quiet || false;\n const tag = options.tag || '';\n\n // If no command provided, show usage\n if (!commandArgs || commandArgs.length === 0) {\n console.log('StackMemory Auto-Handoff');\n console.log('-'.repeat(50));\n console.log('');\n console.log(\n 'Wraps a command with automatic handoff capture on termination.'\n );\n console.log('');\n console.log('Usage:');\n console.log(' stackmemory auto-capture [options] <command> [args...]');\n console.log('');\n console.log('Examples:');\n console.log(' stackmemory auto-capture claude');\n console.log(' stackmemory auto-capture -a npm run dev');\n console.log(' stackmemory auto-capture -t \"feature-work\" vim');\n console.log('');\n console.log('Options:');\n console.log(\n ' -a, --auto Auto-capture on normal exit (no prompt)'\n );\n console.log(' -q, --quiet Suppress output');\n console.log(' -t, --tag <tag> Tag this session');\n return;\n }\n\n const wrappedCommand = commandArgs.join(' ');\n const sessionStart = Date.now();\n let capturedAlready = false;\n\n if (!quiet) {\n console.log('StackMemory Auto-Handoff Wrapper');\n console.log(`Wrapping: ${wrappedCommand}`);\n if (tag) {\n console.log(`Tag: ${tag}`);\n }\n console.log('Handoff will be captured on termination');\n console.log('');\n }\n\n // Spawn the wrapped command\n const [cmd, ...args] = commandArgs;\n let childProcess: ChildProcess;\n\n try {\n childProcess = spawn(cmd, args, {\n stdio: 'inherit',\n shell: false,\n cwd: process.cwd(),\n env: process.env,\n });\n } catch (err) {\n console.error(`Failed to start command: ${(err as Error).message}`);\n process.exit(1);\n return;\n }\n\n // Handle signals - forward to child and capture on termination\n const handleSignal = async (\n signal: NodeJS.Signals,\n exitCode: number\n ): Promise<void> => {\n if (capturedAlready) return;\n capturedAlready = true;\n\n if (!quiet) {\n console.log(`\\nReceived ${signal}`);\n }\n\n // Kill the child process if still running\n if (childProcess.pid && !childProcess.killed) {\n childProcess.kill(signal);\n }\n\n await captureHandoff(\n signal,\n exitCode,\n wrappedCommand,\n sessionStart,\n quiet\n );\n process.exit(exitCode);\n };\n\n process.on('SIGINT', () => handleSignal('SIGINT', 130));\n process.on('SIGTERM', () => handleSignal('SIGTERM', 143));\n process.on('SIGHUP', () => handleSignal('SIGHUP', 129));\n\n // Handle child process exit\n childProcess.on('exit', async (code, signal) => {\n if (capturedAlready) return;\n capturedAlready = true;\n\n const exitCode = code ?? (signal ? 128 : 0);\n\n if (signal) {\n // Child was killed by a signal\n await captureHandoff(\n signal,\n exitCode,\n wrappedCommand,\n sessionStart,\n quiet\n );\n } else if (exitCode !== 0) {\n // Unexpected exit\n if (!quiet) {\n console.log(`\\nCommand exited with code: ${exitCode}`);\n }\n await captureHandoff(\n 'unexpected_exit',\n exitCode,\n wrappedCommand,\n sessionStart,\n quiet\n );\n } else if (autoCapture) {\n // Normal exit with auto-capture enabled\n await captureHandoff(\n 'normal_exit',\n 0,\n wrappedCommand,\n sessionStart,\n quiet\n );\n } else {\n // Normal exit - prompt for capture (simplified for CLI, auto-capture)\n // In non-interactive contexts, default to capturing\n if (process.stdin.isTTY) {\n // Interactive - we could prompt but keeping it simple\n console.log(\n '\\nSession ending. Use -a flag for auto-capture on normal exit.'\n );\n }\n }\n\n process.exit(exitCode);\n });\n\n // Handle spawn errors\n childProcess.on('error', async (err) => {\n if (capturedAlready) return;\n capturedAlready = true;\n\n console.error(`Command error: ${err.message}`);\n await captureHandoff(\n 'spawn_error',\n 1,\n wrappedCommand,\n sessionStart,\n quiet\n );\n process.exit(1);\n });\n });\n\n return cmd;\n}\n\n/** @deprecated Use createCaptureCommand, createRestoreCommand, createAutoCaptureCommand */\nexport function createHandoffCommand(): Command {\n const cmd = new Command('handoff');\n cmd.description('(deprecated) Use \"capture\" or \"restore\" instead');\n return cmd;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["branch", "cmd"]
7
7
  }
@@ -11,7 +11,7 @@ import {
11
11
  writeFileSync,
12
12
  mkdirSync
13
13
  } from "fs";
14
- import { join, basename } from "path";
14
+ import { basename, join } from "path";
15
15
  import { homedir, tmpdir } from "os";
16
16
  import { globSync } from "glob";
17
17
  let countTokens;
@@ -558,7 +558,7 @@ class EnhancedHandoffGenerator {
558
558
  }
559
559
  }
560
560
  /**
561
- * Convert handoff to markdown
561
+ * Convert handoff to markdown (verbose format)
562
562
  */
563
563
  toMarkdown(handoff) {
564
564
  const lines = [];
@@ -651,6 +651,140 @@ class EnhancedHandoffGenerator {
651
651
  lines.push(`*Generated at ${handoff.timestamp}*`);
652
652
  return lines.join("\n");
653
653
  }
654
+ /**
655
+ * Convert handoff to compact format (~50% smaller)
656
+ * Optimized for minimal context window usage
657
+ */
658
+ toCompact(handoff) {
659
+ const lines = [];
660
+ lines.push(`# Handoff: ${handoff.project}@${handoff.branch}`);
661
+ const status = handoff.activeWork.status === "in_progress" ? "WIP" : handoff.activeWork.status;
662
+ lines.push(`## Work: ${handoff.activeWork.description} [${status}]`);
663
+ if (handoff.activeWork.keyFiles.length > 0) {
664
+ const files = handoff.activeWork.keyFiles.slice(0, 5).map((f) => basename(f)).join(", ");
665
+ const progress = handoff.activeWork.progress ? ` (${handoff.activeWork.progress.replace(" in current session", "")})` : "";
666
+ lines.push(`Files: ${files}${progress}`);
667
+ }
668
+ if (handoff.decisions.length > 0) {
669
+ lines.push("");
670
+ lines.push("## Decisions");
671
+ for (const d of handoff.decisions.slice(0, 7)) {
672
+ const what = d.what.length > 40 ? d.what.slice(0, 37) + "..." : d.what;
673
+ const why = d.why ? ` \u2192 ${d.why.slice(0, 50)}` : "";
674
+ lines.push(`- ${what}${why}`);
675
+ }
676
+ }
677
+ if (handoff.blockers.length > 0) {
678
+ lines.push("");
679
+ lines.push("## Blockers");
680
+ for (const b of handoff.blockers) {
681
+ const status2 = b.status === "open" ? "!" : "\u2713";
682
+ const tried = b.attempted.length > 0 ? ` \u2192 ${b.attempted[0]}` : "";
683
+ lines.push(`${status2} ${b.issue}${tried}`);
684
+ }
685
+ }
686
+ if (handoff.reviewFeedback && handoff.reviewFeedback.length > 0) {
687
+ lines.push("");
688
+ lines.push("## Feedback");
689
+ for (const r of handoff.reviewFeedback.slice(0, 2)) {
690
+ lines.push(`[${r.source}]`);
691
+ for (const p of r.keyPoints.slice(0, 3)) {
692
+ lines.push(`- ${p.slice(0, 60)}`);
693
+ }
694
+ for (const a of r.actionItems.slice(0, 2)) {
695
+ lines.push(`\u2192 ${a.slice(0, 60)}`);
696
+ }
697
+ }
698
+ }
699
+ if (handoff.nextActions.length > 0) {
700
+ lines.push("");
701
+ lines.push("## Next");
702
+ for (const a of handoff.nextActions.slice(0, 3)) {
703
+ lines.push(`- ${a.slice(0, 60)}`);
704
+ }
705
+ }
706
+ lines.push("");
707
+ lines.push(`---`);
708
+ lines.push(
709
+ `~${handoff.estimatedTokens} tokens | ${handoff.timestamp.split("T")[0]}`
710
+ );
711
+ return lines.join("\n");
712
+ }
713
+ /**
714
+ * Convert handoff to ultra-compact pipe-delimited format (~90% smaller)
715
+ * Optimized for minimal token usage while preserving critical context
716
+ * Target: ~100-150 tokens
717
+ */
718
+ toUltraCompact(handoff) {
719
+ const lines = [];
720
+ const status = handoff.activeWork.status === "in_progress" ? "WIP" : handoff.activeWork.status;
721
+ const commitCount = handoff.activeWork.progress?.match(/(\d+)/)?.[1] || "0";
722
+ lines.push(
723
+ `[H]${handoff.project}@${handoff.branch}|${status}|${commitCount}c`
724
+ );
725
+ if (handoff.activeWork.keyFiles.length > 0) {
726
+ const files = handoff.activeWork.keyFiles.slice(0, 5).map((f) => basename(f).replace(/\.(ts|js|tsx|jsx)$/, "")).join(",");
727
+ lines.push(`[F]${files}`);
728
+ }
729
+ if (handoff.decisions.length > 0) {
730
+ const decisions = handoff.decisions.slice(0, 5).map((d) => {
731
+ const what = d.what.slice(0, 25).replace(/\|/g, "/");
732
+ const why = d.why ? `\u2192${d.why.slice(0, 20)}` : "";
733
+ return `${what}${why}`;
734
+ }).join("|");
735
+ lines.push(`[D]${decisions}`);
736
+ }
737
+ if (handoff.blockers.length > 0) {
738
+ const blockers = handoff.blockers.slice(0, 3).map((b) => {
739
+ const marker = b.status === "open" ? "!" : "\u2713";
740
+ const issue = b.issue.slice(0, 20).replace(/\|/g, "/");
741
+ const tried = b.attempted.length > 0 ? `\u2192${b.attempted[0].slice(0, 15)}` : "";
742
+ return `${marker}${issue}${tried}`;
743
+ }).join("|");
744
+ lines.push(`[B]${blockers}`);
745
+ }
746
+ if (handoff.nextActions.length > 0) {
747
+ const actions = handoff.nextActions.slice(0, 3).map((a) => a.slice(0, 25).replace(/\|/g, "/")).join("|");
748
+ lines.push(`[N]${actions}`);
749
+ }
750
+ const ultraCompactContent = lines.join("\n");
751
+ const tokens = countTokens(ultraCompactContent);
752
+ lines.push(`~${tokens}t|${handoff.timestamp.split("T")[0]}`);
753
+ return lines.join("\n");
754
+ }
755
+ /**
756
+ * Auto-select format based on context budget and content complexity
757
+ * Returns: 'ultra' | 'compact' | 'verbose'
758
+ */
759
+ selectFormat(handoff, contextBudget) {
760
+ if (contextBudget !== void 0) {
761
+ if (contextBudget < 500) return "ultra";
762
+ if (contextBudget < 2e3) return "compact";
763
+ return "verbose";
764
+ }
765
+ const complexity = handoff.decisions.length + handoff.blockers.length + (handoff.reviewFeedback?.length || 0) * 2 + handoff.nextActions.length;
766
+ if (complexity <= 3 && handoff.activeWork.keyFiles.length <= 3) {
767
+ return "ultra";
768
+ }
769
+ if (complexity > 8 || handoff.reviewFeedback && handoff.reviewFeedback.length > 1) {
770
+ return "verbose";
771
+ }
772
+ return "compact";
773
+ }
774
+ /**
775
+ * Generate handoff in auto-selected format
776
+ */
777
+ toAutoFormat(handoff, contextBudget) {
778
+ const format = this.selectFormat(handoff, contextBudget);
779
+ switch (format) {
780
+ case "ultra":
781
+ return this.toUltraCompact(handoff);
782
+ case "verbose":
783
+ return this.toMarkdown(handoff);
784
+ default:
785
+ return this.toCompact(handoff);
786
+ }
787
+ }
654
788
  }
655
789
  export {
656
790
  EnhancedHandoffGenerator