@stackmemoryai/stackmemory 0.5.3 → 0.5.4

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.
@@ -1,9 +1,19 @@
1
1
  import { Command } from "commander";
2
2
  import chalk from "chalk";
3
3
  import ora from "ora";
4
- import { existsSync, readFileSync } from "fs";
5
- import { join } from "path";
4
+ import {
5
+ existsSync,
6
+ readFileSync,
7
+ writeFileSync,
8
+ mkdirSync,
9
+ copyFileSync,
10
+ chmodSync
11
+ } from "fs";
12
+ import { join, dirname } from "path";
13
+ import { fileURLToPath } from "url";
6
14
  import { spawn, execSync } from "child_process";
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = dirname(__filename);
7
17
  function findPythonScript() {
8
18
  const locations = [
9
19
  join(
@@ -16,7 +26,7 @@ function findPythonScript() {
16
26
  join(
17
27
  process.cwd(),
18
28
  "node_modules",
19
- "@stackmemory",
29
+ "@stackmemoryai",
20
30
  "sweep-addon",
21
31
  "python",
22
32
  "sweep_predict.py"
@@ -30,6 +40,32 @@ function findPythonScript() {
30
40
  }
31
41
  return null;
32
42
  }
43
+ function findHookSource() {
44
+ const locations = [
45
+ join(process.cwd(), "templates", "claude-hooks", "post-edit-sweep.js"),
46
+ join(
47
+ process.cwd(),
48
+ "node_modules",
49
+ "@stackmemoryai",
50
+ "stackmemory",
51
+ "templates",
52
+ "claude-hooks",
53
+ "post-edit-sweep.js"
54
+ ),
55
+ join(
56
+ dirname(dirname(dirname(__dirname))),
57
+ "templates",
58
+ "claude-hooks",
59
+ "post-edit-sweep.js"
60
+ )
61
+ ];
62
+ for (const loc of locations) {
63
+ if (existsSync(loc)) {
64
+ return loc;
65
+ }
66
+ }
67
+ return null;
68
+ }
33
69
  async function findPython() {
34
70
  const candidates = ["python3", "python"];
35
71
  for (const cmd of candidates) {
@@ -280,12 +316,144 @@ hf_hub_download(
280
316
  console.log(chalk.gray(`Tokens: ${result.tokens_generated}`));
281
317
  }
282
318
  if (options.output) {
283
- const { writeFileSync } = await import("fs");
284
- writeFileSync(options.output, result.predicted_content || "");
319
+ const { writeFileSync: writeFileSync2 } = await import("fs");
320
+ writeFileSync2(options.output, result.predicted_content || "");
285
321
  console.log(chalk.green(`
286
322
  Written to: ${options.output}`));
287
323
  }
288
324
  });
325
+ const hookCmd = cmd.command("hook").description("Manage Claude Code integration hook");
326
+ hookCmd.command("install").description("Install Sweep prediction hook for Claude Code").action(async () => {
327
+ const spinner = ora("Installing Sweep hook...").start();
328
+ const homeDir = process.env.HOME || "";
329
+ const hookDir = join(homeDir, ".claude", "hooks");
330
+ const sweepDir = join(homeDir, ".stackmemory", "sweep");
331
+ const hooksJsonPath = join(homeDir, ".claude", "hooks.json");
332
+ try {
333
+ mkdirSync(hookDir, { recursive: true });
334
+ mkdirSync(sweepDir, { recursive: true });
335
+ const hookSource = findHookSource();
336
+ if (!hookSource) {
337
+ spinner.fail(chalk.red("Hook template not found"));
338
+ console.log(
339
+ chalk.gray("Ensure stackmemory is installed from the repository")
340
+ );
341
+ process.exit(1);
342
+ }
343
+ const hookDest = join(hookDir, "post-edit-sweep.js");
344
+ copyFileSync(hookSource, hookDest);
345
+ chmodSync(hookDest, "755");
346
+ const pythonScriptSource = findPythonScript();
347
+ if (pythonScriptSource) {
348
+ const pythonDest = join(sweepDir, "sweep_predict.py");
349
+ copyFileSync(pythonScriptSource, pythonDest);
350
+ }
351
+ if (existsSync(hooksJsonPath)) {
352
+ const hooks = JSON.parse(readFileSync(hooksJsonPath, "utf-8"));
353
+ if (!hooks["post-tool-use"]) {
354
+ hooks["post-tool-use"] = hookDest;
355
+ writeFileSync(hooksJsonPath, JSON.stringify(hooks, null, 2));
356
+ } else if (!hooks["post-tool-use"].includes("sweep")) {
357
+ spinner.warn(chalk.yellow("post-tool-use hook already configured"));
358
+ console.log(chalk.gray(`Existing: ${hooks["post-tool-use"]}`));
359
+ console.log(chalk.gray(`Hook installed at: ${hookDest}`));
360
+ console.log(
361
+ chalk.gray("You may need to manually configure the hook chain")
362
+ );
363
+ return;
364
+ }
365
+ } else {
366
+ const hooks = { "post-tool-use": hookDest };
367
+ writeFileSync(hooksJsonPath, JSON.stringify(hooks, null, 2));
368
+ }
369
+ spinner.succeed(chalk.green("Sweep hook installed"));
370
+ console.log(chalk.gray(`Hook: ${hookDest}`));
371
+ console.log(chalk.gray(`Config: ${hooksJsonPath}`));
372
+ console.log("");
373
+ console.log(chalk.bold("Usage:"));
374
+ console.log(" Hook runs automatically after Edit/Write operations");
375
+ console.log(" Predictions appear after 2+ edits in session");
376
+ console.log(" Disable: export SWEEP_ENABLED=false");
377
+ } catch (error) {
378
+ spinner.fail(chalk.red("Installation failed"));
379
+ console.log(chalk.gray(error.message));
380
+ process.exit(1);
381
+ }
382
+ });
383
+ hookCmd.command("status").description("Check hook installation status").action(async () => {
384
+ const homeDir = process.env.HOME || "";
385
+ const hookPath = join(homeDir, ".claude", "hooks", "post-edit-sweep.js");
386
+ const hooksJsonPath = join(homeDir, ".claude", "hooks.json");
387
+ const statePath = join(homeDir, ".stackmemory", "sweep-state.json");
388
+ console.log(chalk.bold("\nSweep Hook Status\n"));
389
+ const hookInstalled = existsSync(hookPath);
390
+ console.log(
391
+ `Hook installed: ${hookInstalled ? chalk.green("Yes") : chalk.yellow("No")}`
392
+ );
393
+ if (existsSync(hooksJsonPath)) {
394
+ const hooks = JSON.parse(readFileSync(hooksJsonPath, "utf-8"));
395
+ const configured = hooks["post-tool-use"] && hooks["post-tool-use"].includes("sweep");
396
+ console.log(
397
+ `Hook configured: ${configured ? chalk.green("Yes") : chalk.yellow("No")}`
398
+ );
399
+ } else {
400
+ console.log(`Hook configured: ${chalk.yellow("No hooks.json")}`);
401
+ }
402
+ const enabled = process.env.SWEEP_ENABLED !== "false";
403
+ console.log(
404
+ `Enabled: ${enabled ? chalk.green("Yes") : chalk.yellow("Disabled (SWEEP_ENABLED=false)")}`
405
+ );
406
+ if (existsSync(statePath)) {
407
+ try {
408
+ const state = JSON.parse(readFileSync(statePath, "utf-8"));
409
+ console.log(
410
+ chalk.gray(
411
+ `
412
+ Recent diffs tracked: ${state.recentDiffs?.length || 0}`
413
+ )
414
+ );
415
+ if (state.lastPrediction) {
416
+ const age = Date.now() - state.lastPrediction.timestamp;
417
+ const ageStr = age < 6e4 ? `${Math.round(age / 1e3)}s ago` : `${Math.round(age / 6e4)}m ago`;
418
+ console.log(chalk.gray(`Last prediction: ${ageStr}`));
419
+ }
420
+ } catch {
421
+ }
422
+ }
423
+ if (!hookInstalled) {
424
+ console.log(chalk.bold("\nTo install: stackmemory sweep hook install"));
425
+ }
426
+ });
427
+ hookCmd.command("disable").description("Disable the Sweep hook").action(() => {
428
+ console.log(chalk.bold("\nTo disable Sweep predictions:\n"));
429
+ console.log(" Temporarily: export SWEEP_ENABLED=false");
430
+ console.log(" Permanently: Add to ~/.zshrc or ~/.bashrc");
431
+ console.log("");
432
+ console.log("Or remove the hook:");
433
+ console.log(" rm ~/.claude/hooks/post-edit-sweep.js");
434
+ });
435
+ hookCmd.command("clear").description("Clear hook state (recent diffs and predictions)").action(() => {
436
+ const homeDir = process.env.HOME || "";
437
+ const statePath = join(homeDir, ".stackmemory", "sweep-state.json");
438
+ if (existsSync(statePath)) {
439
+ writeFileSync(
440
+ statePath,
441
+ JSON.stringify(
442
+ {
443
+ recentDiffs: [],
444
+ lastPrediction: null,
445
+ pendingPrediction: null,
446
+ fileContents: {}
447
+ },
448
+ null,
449
+ 2
450
+ )
451
+ );
452
+ console.log(chalk.green("Sweep state cleared"));
453
+ } else {
454
+ console.log(chalk.gray("No state file found"));
455
+ }
456
+ });
289
457
  cmd.action(async () => {
290
458
  const status = await checkSweepStatus();
291
459
  console.log(chalk.bold("\nSweep 1.5B Addon Status\n"));
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/cli/commands/sweep.ts"],
4
- "sourcesContent": ["/**\n * Sweep command for StackMemory\n * Provides next-edit predictions using the Sweep 1.5B model\n *\n * Usage:\n * stackmemory sweep setup Install dependencies and optionally download model\n * stackmemory sweep status Check if Sweep addon is properly configured\n * stackmemory sweep predict <file> Run prediction on a file\n */\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { spawn, execSync } from 'child_process';\n\ninterface SweepStatus {\n installed: boolean;\n model_downloaded: boolean;\n python_path?: string;\n model_path?: string;\n error?: string;\n}\n\ninterface 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\nfunction findPythonScript(): string | null {\n const locations = [\n join(\n process.cwd(),\n 'packages',\n 'sweep-addon',\n 'python',\n 'sweep_predict.py'\n ),\n join(\n process.cwd(),\n 'node_modules',\n '@stackmemory',\n 'sweep-addon',\n 'python',\n 'sweep_predict.py'\n ),\n join(process.env.HOME || '', '.stackmemory', 'sweep', 'sweep_predict.py'),\n ];\n\n for (const loc of locations) {\n if (existsSync(loc)) {\n return loc;\n }\n }\n return null;\n}\n\nasync function findPython(): Promise<string | null> {\n const candidates = ['python3', 'python'];\n\n for (const cmd of candidates) {\n try {\n execSync(`${cmd} --version`, { stdio: 'pipe' });\n return cmd;\n } catch {\n continue;\n }\n }\n return null;\n}\n\nasync function checkSweepStatus(): Promise<SweepStatus> {\n const pythonPath = await findPython();\n if (!pythonPath) {\n return {\n installed: false,\n model_downloaded: false,\n error: 'Python not found. Install Python 3.10+',\n };\n }\n\n const scriptPath = findPythonScript();\n if (!scriptPath) {\n return {\n installed: false,\n model_downloaded: false,\n python_path: pythonPath,\n error: 'Sweep addon not installed. Run: stackmemory sweep setup',\n };\n }\n\n const homeDir = process.env.HOME || '';\n const modelPath = join(\n homeDir,\n '.stackmemory',\n 'models',\n 'sweep',\n 'sweep-next-edit-1.5b.q8_0.v2.gguf'\n );\n const modelDownloaded = existsSync(modelPath);\n\n return {\n installed: true,\n model_downloaded: modelDownloaded,\n python_path: pythonPath,\n model_path: modelDownloaded ? modelPath : undefined,\n };\n}\n\nasync function runPrediction(\n filePath: string,\n pythonPath: string,\n scriptPath: string\n): Promise<SweepPredictResult> {\n if (!existsSync(filePath)) {\n return {\n success: false,\n error: 'file_not_found',\n message: `File not found: ${filePath}`,\n };\n }\n\n const currentContent = readFileSync(filePath, 'utf-8');\n\n const input = {\n file_path: filePath,\n current_content: currentContent,\n };\n\n return new Promise((resolve) => {\n const proc = spawn(pythonPath, [scriptPath], {\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n\n let stdout = '';\n let stderr = '';\n\n proc.stdout.on('data', (data) => (stdout += data));\n proc.stderr.on('data', (data) => (stderr += data));\n\n proc.on('close', (code) => {\n try {\n if (stdout.trim()) {\n const result = JSON.parse(stdout.trim());\n resolve(result);\n } else if (code !== 0) {\n resolve({\n success: false,\n error: 'process_error',\n message: stderr || `Process exited with code ${code}`,\n });\n } else {\n resolve({\n success: false,\n error: 'no_output',\n message: 'No output from prediction script',\n });\n }\n } catch {\n resolve({\n success: false,\n error: 'parse_error',\n message: `Failed to parse output: ${stdout}`,\n });\n }\n });\n\n proc.on('error', (error) => {\n resolve({\n success: false,\n error: 'spawn_error',\n message: error.message,\n });\n });\n\n proc.stdin.write(JSON.stringify(input));\n proc.stdin.end();\n });\n}\n\nexport function createSweepCommand(): Command {\n const cmd = new Command('sweep')\n .description(\n 'Next-edit predictions using Sweep 1.5B model (optional addon)'\n )\n .addHelpText(\n 'after',\n `\nExamples:\n stackmemory sweep setup Install Python dependencies\n stackmemory sweep setup --download Also download the model (1.5GB)\n stackmemory sweep status Check addon status\n stackmemory sweep predict src/app.ts Predict next edit for a file\n\nRequirements:\n - Python 3.10+\n - pip packages: huggingface_hub, llama-cpp-python\n\nThe Sweep 1.5B model predicts what code changes you'll make next based on:\n - Current file content\n - Recent changes (diffs)\n - Context from other files\n\nModel is downloaded from HuggingFace on first prediction (~1.5GB).\n`\n );\n\n cmd\n .command('setup')\n .description('Install Python dependencies for Sweep addon')\n .option('--download', 'Also download the model now')\n .action(async (options) => {\n const spinner = ora('Checking Python...').start();\n\n const pythonPath = await findPython();\n if (!pythonPath) {\n spinner.fail(chalk.red('Python not found'));\n console.log(chalk.gray('Please install Python 3.10+'));\n process.exit(1);\n }\n\n spinner.text = 'Installing Python dependencies...';\n\n try {\n execSync(\n `${pythonPath} -m pip install --quiet huggingface_hub llama-cpp-python`,\n {\n stdio: 'pipe',\n }\n );\n spinner.succeed(chalk.green('Python dependencies installed'));\n } catch {\n spinner.fail(chalk.red('Failed to install dependencies'));\n console.log(\n chalk.gray(\n `Run: ${pythonPath} -m pip install huggingface_hub llama-cpp-python`\n )\n );\n process.exit(1);\n }\n\n if (options.download) {\n const downloadSpinner = ora('Downloading Sweep 1.5B model...').start();\n downloadSpinner.text = 'Downloading model from HuggingFace (~1.5GB)...';\n\n try {\n execSync(\n `${pythonPath} -c \"\nfrom huggingface_hub import hf_hub_download\nimport os\nmodel_dir = os.path.expanduser('~/.stackmemory/models/sweep')\nos.makedirs(model_dir, exist_ok=True)\nhf_hub_download(\n repo_id='sweepai/sweep-next-edit-1.5B',\n filename='sweep-next-edit-1.5b.q8_0.v2.gguf',\n repo_type='model',\n local_dir=model_dir,\n local_dir_use_symlinks=False\n)\n\"`,\n { stdio: 'pipe', timeout: 600000 }\n );\n downloadSpinner.succeed(chalk.green('Model downloaded'));\n } catch {\n downloadSpinner.fail(chalk.red('Model download failed'));\n console.log(chalk.gray('Model will be downloaded on first use'));\n }\n } else {\n console.log(\n chalk.gray('\\nModel will be downloaded on first prediction (~1.5GB)')\n );\n console.log(chalk.gray('Or run: stackmemory sweep setup --download'));\n }\n\n console.log(chalk.bold('\\nSetup complete!'));\n });\n\n cmd\n .command('status')\n .description('Check Sweep addon status')\n .action(async () => {\n console.log(chalk.bold('\\nSweep 1.5B Addon Status\\n'));\n\n const status = await checkSweepStatus();\n\n if (status.error) {\n console.log(chalk.red(`Error: ${status.error}`));\n console.log('');\n }\n\n console.log(\n `Python: ${status.python_path ? chalk.green(status.python_path) : chalk.red('Not found')}`\n );\n console.log(\n `Addon installed: ${status.installed ? chalk.green('Yes') : chalk.yellow('No')}`\n );\n console.log(\n `Model downloaded: ${status.model_downloaded ? chalk.green('Yes') : chalk.yellow('No (will download on first use)')}`\n );\n\n if (status.model_path) {\n console.log(chalk.gray(`Model path: ${status.model_path}`));\n }\n\n if (!status.installed) {\n console.log(chalk.bold('\\nTo install:'));\n console.log(' stackmemory sweep setup');\n }\n });\n\n cmd\n .command('predict <file>')\n .description('Predict next edit for a file')\n .option('-o, --output <path>', 'Write prediction to file instead of stdout')\n .option('--json', 'Output raw JSON result')\n .action(async (file, options) => {\n const status = await checkSweepStatus();\n\n if (!status.installed) {\n console.error(chalk.red('Sweep addon not installed'));\n console.log(chalk.gray('Run: stackmemory sweep setup'));\n process.exit(1);\n }\n\n const scriptPath = findPythonScript();\n if (!scriptPath || !status.python_path) {\n console.error(chalk.red('Could not find Sweep prediction script'));\n process.exit(1);\n }\n\n const spinner = ora('Running prediction...').start();\n\n if (!status.model_downloaded) {\n spinner.text = 'Downloading model (first time only, ~1.5GB)...';\n }\n\n const result = await runPrediction(file, status.python_path, scriptPath);\n\n if (!result.success) {\n spinner.fail(\n chalk.red(`Prediction failed: ${result.message || result.error}`)\n );\n process.exit(1);\n }\n\n spinner.succeed(chalk.green('Prediction complete'));\n\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n return;\n }\n\n console.log(chalk.bold('\\nPredicted content:'));\n console.log(chalk.gray('\u2500'.repeat(50)));\n console.log(result.predicted_content);\n console.log(chalk.gray('\u2500'.repeat(50)));\n\n if (result.latency_ms) {\n console.log(chalk.gray(`Latency: ${result.latency_ms}ms`));\n }\n if (result.tokens_generated) {\n console.log(chalk.gray(`Tokens: ${result.tokens_generated}`));\n }\n\n if (options.output) {\n const { writeFileSync } = await import('fs');\n writeFileSync(options.output, result.predicted_content || '');\n console.log(chalk.green(`\\nWritten to: ${options.output}`));\n }\n });\n\n cmd.action(async () => {\n const status = await checkSweepStatus();\n console.log(chalk.bold('\\nSweep 1.5B Addon Status\\n'));\n\n console.log(\n `Installed: ${status.installed ? chalk.green('Yes') : chalk.yellow('No')}`\n );\n console.log(\n `Model ready: ${status.model_downloaded ? chalk.green('Yes') : chalk.yellow('No')}`\n );\n\n if (!status.installed) {\n console.log(chalk.bold('\\nRun: stackmemory sweep setup'));\n } else {\n console.log(chalk.bold('\\nUsage: stackmemory sweep predict <file>'));\n }\n });\n\n return cmd;\n}\n\nexport default createSweepCommand();\n"],
5
- "mappings": "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",
6
- "names": []
4
+ "sourcesContent": ["/**\n * Sweep command for StackMemory\n * Provides next-edit predictions using the Sweep 1.5B model\n *\n * Usage:\n * stackmemory sweep setup Install dependencies and optionally download model\n * stackmemory sweep status Check if Sweep addon is properly configured\n * stackmemory sweep predict <file> Run prediction on a file\n */\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport {\n existsSync,\n readFileSync,\n writeFileSync,\n mkdirSync,\n copyFileSync,\n chmodSync,\n} from 'fs';\nimport { join, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport { spawn, execSync } from 'child_process';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\ninterface SweepStatus {\n installed: boolean;\n model_downloaded: boolean;\n python_path?: string;\n model_path?: string;\n error?: string;\n}\n\ninterface 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\nfunction findPythonScript(): string | null {\n const locations = [\n join(\n process.cwd(),\n 'packages',\n 'sweep-addon',\n 'python',\n 'sweep_predict.py'\n ),\n join(\n process.cwd(),\n 'node_modules',\n '@stackmemoryai',\n 'sweep-addon',\n 'python',\n 'sweep_predict.py'\n ),\n join(process.env.HOME || '', '.stackmemory', 'sweep', 'sweep_predict.py'),\n ];\n\n for (const loc of locations) {\n if (existsSync(loc)) {\n return loc;\n }\n }\n return null;\n}\n\nfunction findHookSource(): string | null {\n const locations = [\n join(process.cwd(), 'templates', 'claude-hooks', 'post-edit-sweep.js'),\n join(\n process.cwd(),\n 'node_modules',\n '@stackmemoryai',\n 'stackmemory',\n 'templates',\n 'claude-hooks',\n 'post-edit-sweep.js'\n ),\n join(\n dirname(dirname(dirname(__dirname))),\n 'templates',\n 'claude-hooks',\n 'post-edit-sweep.js'\n ),\n ];\n\n for (const loc of locations) {\n if (existsSync(loc)) {\n return loc;\n }\n }\n return null;\n}\n\nasync function findPython(): Promise<string | null> {\n const candidates = ['python3', 'python'];\n\n for (const cmd of candidates) {\n try {\n execSync(`${cmd} --version`, { stdio: 'pipe' });\n return cmd;\n } catch {\n continue;\n }\n }\n return null;\n}\n\nasync function checkSweepStatus(): Promise<SweepStatus> {\n const pythonPath = await findPython();\n if (!pythonPath) {\n return {\n installed: false,\n model_downloaded: false,\n error: 'Python not found. Install Python 3.10+',\n };\n }\n\n const scriptPath = findPythonScript();\n if (!scriptPath) {\n return {\n installed: false,\n model_downloaded: false,\n python_path: pythonPath,\n error: 'Sweep addon not installed. Run: stackmemory sweep setup',\n };\n }\n\n const homeDir = process.env.HOME || '';\n const modelPath = join(\n homeDir,\n '.stackmemory',\n 'models',\n 'sweep',\n 'sweep-next-edit-1.5b.q8_0.v2.gguf'\n );\n const modelDownloaded = existsSync(modelPath);\n\n return {\n installed: true,\n model_downloaded: modelDownloaded,\n python_path: pythonPath,\n model_path: modelDownloaded ? modelPath : undefined,\n };\n}\n\nasync function runPrediction(\n filePath: string,\n pythonPath: string,\n scriptPath: string\n): Promise<SweepPredictResult> {\n if (!existsSync(filePath)) {\n return {\n success: false,\n error: 'file_not_found',\n message: `File not found: ${filePath}`,\n };\n }\n\n const currentContent = readFileSync(filePath, 'utf-8');\n\n const input = {\n file_path: filePath,\n current_content: currentContent,\n };\n\n return new Promise((resolve) => {\n const proc = spawn(pythonPath, [scriptPath], {\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n\n let stdout = '';\n let stderr = '';\n\n proc.stdout.on('data', (data) => (stdout += data));\n proc.stderr.on('data', (data) => (stderr += data));\n\n proc.on('close', (code) => {\n try {\n if (stdout.trim()) {\n const result = JSON.parse(stdout.trim());\n resolve(result);\n } else if (code !== 0) {\n resolve({\n success: false,\n error: 'process_error',\n message: stderr || `Process exited with code ${code}`,\n });\n } else {\n resolve({\n success: false,\n error: 'no_output',\n message: 'No output from prediction script',\n });\n }\n } catch {\n resolve({\n success: false,\n error: 'parse_error',\n message: `Failed to parse output: ${stdout}`,\n });\n }\n });\n\n proc.on('error', (error) => {\n resolve({\n success: false,\n error: 'spawn_error',\n message: error.message,\n });\n });\n\n proc.stdin.write(JSON.stringify(input));\n proc.stdin.end();\n });\n}\n\nexport function createSweepCommand(): Command {\n const cmd = new Command('sweep')\n .description(\n 'Next-edit predictions using Sweep 1.5B model (optional addon)'\n )\n .addHelpText(\n 'after',\n `\nExamples:\n stackmemory sweep setup Install Python dependencies\n stackmemory sweep setup --download Also download the model (1.5GB)\n stackmemory sweep status Check addon status\n stackmemory sweep predict src/app.ts Predict next edit for a file\n\nRequirements:\n - Python 3.10+\n - pip packages: huggingface_hub, llama-cpp-python\n\nThe Sweep 1.5B model predicts what code changes you'll make next based on:\n - Current file content\n - Recent changes (diffs)\n - Context from other files\n\nModel is downloaded from HuggingFace on first prediction (~1.5GB).\n`\n );\n\n cmd\n .command('setup')\n .description('Install Python dependencies for Sweep addon')\n .option('--download', 'Also download the model now')\n .action(async (options) => {\n const spinner = ora('Checking Python...').start();\n\n const pythonPath = await findPython();\n if (!pythonPath) {\n spinner.fail(chalk.red('Python not found'));\n console.log(chalk.gray('Please install Python 3.10+'));\n process.exit(1);\n }\n\n spinner.text = 'Installing Python dependencies...';\n\n try {\n execSync(\n `${pythonPath} -m pip install --quiet huggingface_hub llama-cpp-python`,\n {\n stdio: 'pipe',\n }\n );\n spinner.succeed(chalk.green('Python dependencies installed'));\n } catch {\n spinner.fail(chalk.red('Failed to install dependencies'));\n console.log(\n chalk.gray(\n `Run: ${pythonPath} -m pip install huggingface_hub llama-cpp-python`\n )\n );\n process.exit(1);\n }\n\n if (options.download) {\n const downloadSpinner = ora('Downloading Sweep 1.5B model...').start();\n downloadSpinner.text = 'Downloading model from HuggingFace (~1.5GB)...';\n\n try {\n execSync(\n `${pythonPath} -c \"\nfrom huggingface_hub import hf_hub_download\nimport os\nmodel_dir = os.path.expanduser('~/.stackmemory/models/sweep')\nos.makedirs(model_dir, exist_ok=True)\nhf_hub_download(\n repo_id='sweepai/sweep-next-edit-1.5B',\n filename='sweep-next-edit-1.5b.q8_0.v2.gguf',\n repo_type='model',\n local_dir=model_dir,\n local_dir_use_symlinks=False\n)\n\"`,\n { stdio: 'pipe', timeout: 600000 }\n );\n downloadSpinner.succeed(chalk.green('Model downloaded'));\n } catch {\n downloadSpinner.fail(chalk.red('Model download failed'));\n console.log(chalk.gray('Model will be downloaded on first use'));\n }\n } else {\n console.log(\n chalk.gray('\\nModel will be downloaded on first prediction (~1.5GB)')\n );\n console.log(chalk.gray('Or run: stackmemory sweep setup --download'));\n }\n\n console.log(chalk.bold('\\nSetup complete!'));\n });\n\n cmd\n .command('status')\n .description('Check Sweep addon status')\n .action(async () => {\n console.log(chalk.bold('\\nSweep 1.5B Addon Status\\n'));\n\n const status = await checkSweepStatus();\n\n if (status.error) {\n console.log(chalk.red(`Error: ${status.error}`));\n console.log('');\n }\n\n console.log(\n `Python: ${status.python_path ? chalk.green(status.python_path) : chalk.red('Not found')}`\n );\n console.log(\n `Addon installed: ${status.installed ? chalk.green('Yes') : chalk.yellow('No')}`\n );\n console.log(\n `Model downloaded: ${status.model_downloaded ? chalk.green('Yes') : chalk.yellow('No (will download on first use)')}`\n );\n\n if (status.model_path) {\n console.log(chalk.gray(`Model path: ${status.model_path}`));\n }\n\n if (!status.installed) {\n console.log(chalk.bold('\\nTo install:'));\n console.log(' stackmemory sweep setup');\n }\n });\n\n cmd\n .command('predict <file>')\n .description('Predict next edit for a file')\n .option('-o, --output <path>', 'Write prediction to file instead of stdout')\n .option('--json', 'Output raw JSON result')\n .action(async (file, options) => {\n const status = await checkSweepStatus();\n\n if (!status.installed) {\n console.error(chalk.red('Sweep addon not installed'));\n console.log(chalk.gray('Run: stackmemory sweep setup'));\n process.exit(1);\n }\n\n const scriptPath = findPythonScript();\n if (!scriptPath || !status.python_path) {\n console.error(chalk.red('Could not find Sweep prediction script'));\n process.exit(1);\n }\n\n const spinner = ora('Running prediction...').start();\n\n if (!status.model_downloaded) {\n spinner.text = 'Downloading model (first time only, ~1.5GB)...';\n }\n\n const result = await runPrediction(file, status.python_path, scriptPath);\n\n if (!result.success) {\n spinner.fail(\n chalk.red(`Prediction failed: ${result.message || result.error}`)\n );\n process.exit(1);\n }\n\n spinner.succeed(chalk.green('Prediction complete'));\n\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n return;\n }\n\n console.log(chalk.bold('\\nPredicted content:'));\n console.log(chalk.gray('\u2500'.repeat(50)));\n console.log(result.predicted_content);\n console.log(chalk.gray('\u2500'.repeat(50)));\n\n if (result.latency_ms) {\n console.log(chalk.gray(`Latency: ${result.latency_ms}ms`));\n }\n if (result.tokens_generated) {\n console.log(chalk.gray(`Tokens: ${result.tokens_generated}`));\n }\n\n if (options.output) {\n const { writeFileSync } = await import('fs');\n writeFileSync(options.output, result.predicted_content || '');\n console.log(chalk.green(`\\nWritten to: ${options.output}`));\n }\n });\n\n const hookCmd = cmd\n .command('hook')\n .description('Manage Claude Code integration hook');\n\n hookCmd\n .command('install')\n .description('Install Sweep prediction hook for Claude Code')\n .action(async () => {\n const spinner = ora('Installing Sweep hook...').start();\n\n const homeDir = process.env.HOME || '';\n const hookDir = join(homeDir, '.claude', 'hooks');\n const sweepDir = join(homeDir, '.stackmemory', 'sweep');\n const hooksJsonPath = join(homeDir, '.claude', 'hooks.json');\n\n try {\n mkdirSync(hookDir, { recursive: true });\n mkdirSync(sweepDir, { recursive: true });\n\n const hookSource = findHookSource();\n if (!hookSource) {\n spinner.fail(chalk.red('Hook template not found'));\n console.log(\n chalk.gray('Ensure stackmemory is installed from the repository')\n );\n process.exit(1);\n }\n\n const hookDest = join(hookDir, 'post-edit-sweep.js');\n copyFileSync(hookSource, hookDest);\n chmodSync(hookDest, '755');\n\n const pythonScriptSource = findPythonScript();\n if (pythonScriptSource) {\n const pythonDest = join(sweepDir, 'sweep_predict.py');\n copyFileSync(pythonScriptSource, pythonDest);\n }\n\n if (existsSync(hooksJsonPath)) {\n const hooks = JSON.parse(readFileSync(hooksJsonPath, 'utf-8'));\n if (!hooks['post-tool-use']) {\n hooks['post-tool-use'] = hookDest;\n writeFileSync(hooksJsonPath, JSON.stringify(hooks, null, 2));\n } else if (!hooks['post-tool-use'].includes('sweep')) {\n spinner.warn(chalk.yellow('post-tool-use hook already configured'));\n console.log(chalk.gray(`Existing: ${hooks['post-tool-use']}`));\n console.log(chalk.gray(`Hook installed at: ${hookDest}`));\n console.log(\n chalk.gray('You may need to manually configure the hook chain')\n );\n return;\n }\n } else {\n const hooks = { 'post-tool-use': hookDest };\n writeFileSync(hooksJsonPath, JSON.stringify(hooks, null, 2));\n }\n\n spinner.succeed(chalk.green('Sweep hook installed'));\n console.log(chalk.gray(`Hook: ${hookDest}`));\n console.log(chalk.gray(`Config: ${hooksJsonPath}`));\n console.log('');\n console.log(chalk.bold('Usage:'));\n console.log(' Hook runs automatically after Edit/Write operations');\n console.log(' Predictions appear after 2+ edits in session');\n console.log(' Disable: export SWEEP_ENABLED=false');\n } catch (error) {\n spinner.fail(chalk.red('Installation failed'));\n console.log(chalk.gray((error as Error).message));\n process.exit(1);\n }\n });\n\n hookCmd\n .command('status')\n .description('Check hook installation status')\n .action(async () => {\n const homeDir = process.env.HOME || '';\n const hookPath = join(homeDir, '.claude', 'hooks', 'post-edit-sweep.js');\n const hooksJsonPath = join(homeDir, '.claude', 'hooks.json');\n const statePath = join(homeDir, '.stackmemory', 'sweep-state.json');\n\n console.log(chalk.bold('\\nSweep Hook Status\\n'));\n\n const hookInstalled = existsSync(hookPath);\n console.log(\n `Hook installed: ${hookInstalled ? chalk.green('Yes') : chalk.yellow('No')}`\n );\n\n if (existsSync(hooksJsonPath)) {\n const hooks = JSON.parse(readFileSync(hooksJsonPath, 'utf-8'));\n const configured =\n hooks['post-tool-use'] && hooks['post-tool-use'].includes('sweep');\n console.log(\n `Hook configured: ${configured ? chalk.green('Yes') : chalk.yellow('No')}`\n );\n } else {\n console.log(`Hook configured: ${chalk.yellow('No hooks.json')}`);\n }\n\n const enabled = process.env.SWEEP_ENABLED !== 'false';\n console.log(\n `Enabled: ${enabled ? chalk.green('Yes') : chalk.yellow('Disabled (SWEEP_ENABLED=false)')}`\n );\n\n if (existsSync(statePath)) {\n try {\n const state = JSON.parse(readFileSync(statePath, 'utf-8'));\n console.log(\n chalk.gray(\n `\\nRecent diffs tracked: ${state.recentDiffs?.length || 0}`\n )\n );\n if (state.lastPrediction) {\n const age = Date.now() - state.lastPrediction.timestamp;\n const ageStr =\n age < 60000\n ? `${Math.round(age / 1000)}s ago`\n : `${Math.round(age / 60000)}m ago`;\n console.log(chalk.gray(`Last prediction: ${ageStr}`));\n }\n } catch {\n // Ignore parse errors\n }\n }\n\n if (!hookInstalled) {\n console.log(chalk.bold('\\nTo install: stackmemory sweep hook install'));\n }\n });\n\n hookCmd\n .command('disable')\n .description('Disable the Sweep hook')\n .action(() => {\n console.log(chalk.bold('\\nTo disable Sweep predictions:\\n'));\n console.log(' Temporarily: export SWEEP_ENABLED=false');\n console.log(' Permanently: Add to ~/.zshrc or ~/.bashrc');\n console.log('');\n console.log('Or remove the hook:');\n console.log(' rm ~/.claude/hooks/post-edit-sweep.js');\n });\n\n hookCmd\n .command('clear')\n .description('Clear hook state (recent diffs and predictions)')\n .action(() => {\n const homeDir = process.env.HOME || '';\n const statePath = join(homeDir, '.stackmemory', 'sweep-state.json');\n\n if (existsSync(statePath)) {\n writeFileSync(\n statePath,\n JSON.stringify(\n {\n recentDiffs: [],\n lastPrediction: null,\n pendingPrediction: null,\n fileContents: {},\n },\n null,\n 2\n )\n );\n console.log(chalk.green('Sweep state cleared'));\n } else {\n console.log(chalk.gray('No state file found'));\n }\n });\n\n cmd.action(async () => {\n const status = await checkSweepStatus();\n console.log(chalk.bold('\\nSweep 1.5B Addon Status\\n'));\n\n console.log(\n `Installed: ${status.installed ? chalk.green('Yes') : chalk.yellow('No')}`\n );\n console.log(\n `Model ready: ${status.model_downloaded ? chalk.green('Yes') : chalk.yellow('No')}`\n );\n\n if (!status.installed) {\n console.log(chalk.bold('\\nRun: stackmemory sweep setup'));\n } else {\n console.log(chalk.bold('\\nUsage: stackmemory sweep predict <file>'));\n }\n });\n\n return cmd;\n}\n\nexport default createSweepCommand();\n"],
5
+ "mappings": "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",
6
+ "names": ["writeFileSync"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackmemoryai/stackmemory",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
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",
@@ -0,0 +1,89 @@
1
+ #!/bin/bash
2
+ # Install Sweep prediction hook for Claude Code
3
+
4
+ set -e
5
+
6
+ HOOK_DIR="$HOME/.claude/hooks"
7
+ SWEEP_DIR="$HOME/.stackmemory/sweep"
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ REPO_DIR="$(dirname "$SCRIPT_DIR")"
10
+
11
+ echo "Installing Sweep prediction hook for Claude Code..."
12
+
13
+ # Create directories
14
+ mkdir -p "$HOOK_DIR"
15
+ mkdir -p "$SWEEP_DIR"
16
+
17
+ # Copy hook script
18
+ cp "$REPO_DIR/templates/claude-hooks/post-edit-sweep.js" "$HOOK_DIR/"
19
+ chmod +x "$HOOK_DIR/post-edit-sweep.js"
20
+
21
+ # Copy Python prediction script
22
+ cp "$REPO_DIR/packages/sweep-addon/python/sweep_predict.py" "$SWEEP_DIR/"
23
+
24
+ # Update hooks.json if it exists, otherwise create it
25
+ HOOKS_JSON="$HOME/.claude/hooks.json"
26
+ if [ -f "$HOOKS_JSON" ]; then
27
+ # Check if post-tool-use already configured
28
+ if grep -q "post-tool-use" "$HOOKS_JSON"; then
29
+ echo "Note: post-tool-use hook already configured in $HOOKS_JSON"
30
+ echo "You may need to manually add the sweep hook."
31
+ else
32
+ echo "Adding sweep hook to $HOOKS_JSON..."
33
+ # Use node to safely update JSON
34
+ node -e "
35
+ const fs = require('fs');
36
+ const hooks = JSON.parse(fs.readFileSync('$HOOKS_JSON', 'utf-8'));
37
+ hooks['post-tool-use'] = '$HOOK_DIR/post-edit-sweep.js';
38
+ fs.writeFileSync('$HOOKS_JSON', JSON.stringify(hooks, null, 2));
39
+ console.log('Updated hooks.json');
40
+ "
41
+ fi
42
+ else
43
+ echo "Creating $HOOKS_JSON..."
44
+ cat > "$HOOKS_JSON" << 'EOF'
45
+ {
46
+ "post-tool-use": "~/.claude/hooks/post-edit-sweep.js"
47
+ }
48
+ EOF
49
+ fi
50
+
51
+ # Check Python dependencies
52
+ echo ""
53
+ echo "Checking Python dependencies..."
54
+ if python3 -c "import llama_cpp" 2>/dev/null; then
55
+ echo " llama-cpp-python: installed"
56
+ else
57
+ echo " llama-cpp-python: NOT INSTALLED"
58
+ echo " Run: pip install llama-cpp-python"
59
+ fi
60
+
61
+ if python3 -c "import huggingface_hub" 2>/dev/null; then
62
+ echo " huggingface_hub: installed"
63
+ else
64
+ echo " huggingface_hub: NOT INSTALLED"
65
+ echo " Run: pip install huggingface_hub"
66
+ fi
67
+
68
+ # Check model
69
+ MODEL_PATH="$HOME/.stackmemory/models/sweep/sweep-next-edit-1.5b.q8_0.v2.gguf"
70
+ if [ -f "$MODEL_PATH" ]; then
71
+ echo " Model: downloaded"
72
+ else
73
+ echo " Model: NOT DOWNLOADED"
74
+ echo " Run: stackmemory sweep setup --download"
75
+ fi
76
+
77
+ echo ""
78
+ echo "Installation complete!"
79
+ echo ""
80
+ echo "Hook installed at: $HOOK_DIR/post-edit-sweep.js"
81
+ echo "Python script at: $SWEEP_DIR/sweep_predict.py"
82
+ echo ""
83
+ echo "Usage:"
84
+ echo " - Hook runs automatically after Edit/Write operations"
85
+ echo " - Predictions appear after 2+ edits in session"
86
+ echo " - Check status: node $HOOK_DIR/post-edit-sweep.js --status"
87
+ echo " - Clear state: node $HOOK_DIR/post-edit-sweep.js --clear"
88
+ echo " - Disable: export SWEEP_ENABLED=false"
89
+ echo ""
@@ -0,0 +1,437 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Post-Edit Sweep Hook for Claude Code
5
+ *
6
+ * Runs Sweep 1.5B predictions after file edits to suggest next changes.
7
+ * Tracks recent diffs and provides context-aware predictions.
8
+ */
9
+
10
+ import fs from 'fs';
11
+ import path from 'path';
12
+ import { spawn } from 'child_process';
13
+ import { fileURLToPath } from 'url';
14
+
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = path.dirname(__filename);
17
+
18
+ const CONFIG = {
19
+ enabled: process.env.SWEEP_ENABLED !== 'false',
20
+ maxRecentDiffs: 5,
21
+ predictionTimeout: 30000,
22
+ minEditSize: 10,
23
+ debounceMs: 2000,
24
+ minDiffsForPrediction: 2,
25
+ cooldownMs: 10000,
26
+ codeExtensions: [
27
+ '.ts',
28
+ '.tsx',
29
+ '.js',
30
+ '.jsx',
31
+ '.py',
32
+ '.go',
33
+ '.rs',
34
+ '.java',
35
+ '.c',
36
+ '.cpp',
37
+ '.h',
38
+ '.hpp',
39
+ '.cs',
40
+ '.rb',
41
+ '.php',
42
+ '.swift',
43
+ '.kt',
44
+ '.scala',
45
+ '.vue',
46
+ '.svelte',
47
+ '.astro',
48
+ ],
49
+ stateFile: path.join(
50
+ process.env.HOME || '/tmp',
51
+ '.stackmemory',
52
+ 'sweep-state.json'
53
+ ),
54
+ logFile: path.join(
55
+ process.env.HOME || '/tmp',
56
+ '.stackmemory',
57
+ 'sweep-predictions.log'
58
+ ),
59
+ pythonScript: path.join(
60
+ process.env.HOME || '/tmp',
61
+ '.stackmemory',
62
+ 'sweep',
63
+ 'sweep_predict.py'
64
+ ),
65
+ };
66
+
67
+ // Fallback locations for sweep_predict.py
68
+ const SCRIPT_LOCATIONS = [
69
+ CONFIG.pythonScript,
70
+ path.join(
71
+ process.cwd(),
72
+ 'packages',
73
+ 'sweep-addon',
74
+ 'python',
75
+ 'sweep_predict.py'
76
+ ),
77
+ path.join(
78
+ process.cwd(),
79
+ 'node_modules',
80
+ '@stackmemoryai',
81
+ 'sweep-addon',
82
+ 'python',
83
+ 'sweep_predict.py'
84
+ ),
85
+ ];
86
+
87
+ function findPythonScript() {
88
+ for (const loc of SCRIPT_LOCATIONS) {
89
+ if (fs.existsSync(loc)) {
90
+ return loc;
91
+ }
92
+ }
93
+ return null;
94
+ }
95
+
96
+ function loadState() {
97
+ try {
98
+ if (fs.existsSync(CONFIG.stateFile)) {
99
+ return JSON.parse(fs.readFileSync(CONFIG.stateFile, 'utf-8'));
100
+ }
101
+ } catch {
102
+ // Ignore errors
103
+ }
104
+ return {
105
+ recentDiffs: [],
106
+ lastPrediction: null,
107
+ pendingPrediction: null,
108
+ fileContents: {},
109
+ };
110
+ }
111
+
112
+ function saveState(state) {
113
+ try {
114
+ const dir = path.dirname(CONFIG.stateFile);
115
+ if (!fs.existsSync(dir)) {
116
+ fs.mkdirSync(dir, { recursive: true });
117
+ }
118
+ fs.writeFileSync(CONFIG.stateFile, JSON.stringify(state, null, 2));
119
+ } catch {
120
+ // Ignore errors
121
+ }
122
+ }
123
+
124
+ function log(message, data = {}) {
125
+ try {
126
+ const dir = path.dirname(CONFIG.logFile);
127
+ if (!fs.existsSync(dir)) {
128
+ fs.mkdirSync(dir, { recursive: true });
129
+ }
130
+ const entry = {
131
+ timestamp: new Date().toISOString(),
132
+ message,
133
+ ...data,
134
+ };
135
+ fs.appendFileSync(CONFIG.logFile, JSON.stringify(entry) + '\n');
136
+ } catch {
137
+ // Ignore
138
+ }
139
+ }
140
+
141
+ async function runPrediction(filePath, currentContent, recentDiffs) {
142
+ const scriptPath = findPythonScript();
143
+ if (!scriptPath) {
144
+ log('Sweep script not found');
145
+ return null;
146
+ }
147
+
148
+ const input = {
149
+ file_path: filePath,
150
+ current_content: currentContent,
151
+ recent_diffs: recentDiffs,
152
+ };
153
+
154
+ return new Promise((resolve) => {
155
+ const proc = spawn('python3', [scriptPath], {
156
+ stdio: ['pipe', 'pipe', 'pipe'],
157
+ timeout: CONFIG.predictionTimeout,
158
+ });
159
+
160
+ let stdout = '';
161
+ let stderr = '';
162
+
163
+ proc.stdout.on('data', (data) => (stdout += data));
164
+ proc.stderr.on('data', (data) => (stderr += data));
165
+
166
+ const timeout = setTimeout(() => {
167
+ proc.kill();
168
+ resolve(null);
169
+ }, CONFIG.predictionTimeout);
170
+
171
+ proc.on('close', (code) => {
172
+ clearTimeout(timeout);
173
+ try {
174
+ if (stdout.trim()) {
175
+ const result = JSON.parse(stdout.trim());
176
+ resolve(result);
177
+ } else {
178
+ resolve(null);
179
+ }
180
+ } catch {
181
+ resolve(null);
182
+ }
183
+ });
184
+
185
+ proc.on('error', () => {
186
+ clearTimeout(timeout);
187
+ resolve(null);
188
+ });
189
+
190
+ proc.stdin.write(JSON.stringify(input));
191
+ proc.stdin.end();
192
+ });
193
+ }
194
+
195
+ async function readInput() {
196
+ let input = '';
197
+ for await (const chunk of process.stdin) {
198
+ input += chunk;
199
+ }
200
+ return JSON.parse(input);
201
+ }
202
+
203
+ function isCodeFile(filePath) {
204
+ const ext = path.extname(filePath).toLowerCase();
205
+ return CONFIG.codeExtensions.includes(ext);
206
+ }
207
+
208
+ function shouldRunPrediction(state, filePath) {
209
+ if (state.recentDiffs.length < CONFIG.minDiffsForPrediction) {
210
+ return false;
211
+ }
212
+
213
+ if (state.lastPrediction) {
214
+ const timeSince = Date.now() - state.lastPrediction.timestamp;
215
+ if (timeSince < CONFIG.cooldownMs) {
216
+ return false;
217
+ }
218
+ }
219
+
220
+ if (state.pendingPrediction) {
221
+ const timeSince = Date.now() - state.pendingPrediction;
222
+ if (timeSince < CONFIG.debounceMs) {
223
+ return false;
224
+ }
225
+ }
226
+
227
+ return true;
228
+ }
229
+
230
+ async function handleEdit(toolInput, toolResult) {
231
+ if (!CONFIG.enabled) return;
232
+
233
+ const { file_path, old_string, new_string } = toolInput;
234
+ if (!file_path || !old_string || !new_string) return;
235
+
236
+ if (!isCodeFile(file_path)) {
237
+ log('Skipping non-code file', { file_path });
238
+ return;
239
+ }
240
+
241
+ if (
242
+ new_string.length < CONFIG.minEditSize &&
243
+ old_string.length < CONFIG.minEditSize
244
+ ) {
245
+ return;
246
+ }
247
+
248
+ const state = loadState();
249
+
250
+ const diff = {
251
+ file_path,
252
+ original: old_string,
253
+ updated: new_string,
254
+ timestamp: Date.now(),
255
+ };
256
+
257
+ state.recentDiffs.unshift(diff);
258
+ state.recentDiffs = state.recentDiffs.slice(0, CONFIG.maxRecentDiffs);
259
+
260
+ try {
261
+ if (fs.existsSync(file_path)) {
262
+ state.fileContents[file_path] = fs.readFileSync(file_path, 'utf-8');
263
+ }
264
+ } catch {
265
+ // Ignore
266
+ }
267
+
268
+ saveState(state);
269
+ log('Edit recorded', { file_path, diffSize: new_string.length });
270
+
271
+ if (shouldRunPrediction(state, file_path)) {
272
+ state.pendingPrediction = Date.now();
273
+ saveState(state);
274
+
275
+ setTimeout(() => {
276
+ runPredictionAsync(file_path, loadState());
277
+ }, CONFIG.debounceMs);
278
+ }
279
+ }
280
+
281
+ async function runPredictionAsync(filePath, state) {
282
+ try {
283
+ const currentContent = state.fileContents[filePath] || '';
284
+ if (!currentContent) {
285
+ state.pendingPrediction = null;
286
+ saveState(state);
287
+ return;
288
+ }
289
+
290
+ const result = await runPrediction(
291
+ filePath,
292
+ currentContent,
293
+ state.recentDiffs
294
+ );
295
+
296
+ state.pendingPrediction = null;
297
+
298
+ if (result && result.success && result.predicted_content) {
299
+ state.lastPrediction = {
300
+ file_path: filePath,
301
+ prediction: result.predicted_content,
302
+ latency_ms: result.latency_ms,
303
+ timestamp: Date.now(),
304
+ };
305
+ saveState(state);
306
+
307
+ log('Prediction complete', {
308
+ file_path: filePath,
309
+ latency_ms: result.latency_ms,
310
+ tokens: result.tokens_generated,
311
+ });
312
+
313
+ const hint = formatPredictionHint(result);
314
+ if (hint) {
315
+ console.error(hint);
316
+ }
317
+ } else {
318
+ saveState(state);
319
+ }
320
+ } catch (error) {
321
+ state.pendingPrediction = null;
322
+ saveState(state);
323
+ log('Prediction error', { error: error.message });
324
+ }
325
+ }
326
+
327
+ function formatPredictionHint(result) {
328
+ if (!result.predicted_content || result.predicted_content.trim().length < 5) {
329
+ return null;
330
+ }
331
+
332
+ const preview = result.predicted_content
333
+ .trim()
334
+ .split('\n')
335
+ .slice(0, 3)
336
+ .join('\n');
337
+ const truncated = result.predicted_content.length > 200;
338
+
339
+ return `
340
+ [Sweep Prediction] Next edit suggestion (${result.latency_ms}ms):
341
+ ${preview}${truncated ? '\n...' : ''}
342
+ `;
343
+ }
344
+
345
+ async function handleWrite(toolInput, toolResult) {
346
+ if (!CONFIG.enabled) return;
347
+
348
+ const { file_path, content } = toolInput;
349
+ if (!file_path || !content) return;
350
+
351
+ if (!isCodeFile(file_path)) {
352
+ return;
353
+ }
354
+
355
+ const state = loadState();
356
+ state.fileContents[file_path] = content;
357
+ saveState(state);
358
+
359
+ log('Write recorded', { file_path, size: content.length });
360
+ }
361
+
362
+ async function main() {
363
+ try {
364
+ const input = await readInput();
365
+ const { tool_name, tool_input, tool_result, event_type } = input;
366
+
367
+ // Only handle post-tool-use events
368
+ if (event_type !== 'post_tool_use') {
369
+ process.exit(0);
370
+ }
371
+
372
+ // Handle different tools
373
+ switch (tool_name) {
374
+ case 'Edit':
375
+ await handleEdit(tool_input, tool_result);
376
+ break;
377
+ case 'Write':
378
+ await handleWrite(tool_input, tool_result);
379
+ break;
380
+ }
381
+
382
+ // Success
383
+ console.log(JSON.stringify({ status: 'ok' }));
384
+ } catch (error) {
385
+ log('Hook error', { error: error.message });
386
+ console.log(JSON.stringify({ status: 'error', message: error.message }));
387
+ }
388
+ }
389
+
390
+ // Handle info request
391
+ if (process.argv.includes('--info')) {
392
+ console.log(
393
+ JSON.stringify({
394
+ hook: 'post-edit-sweep',
395
+ version: '1.0.0',
396
+ description: 'Runs Sweep 1.5B predictions after file edits',
397
+ config: {
398
+ enabled: CONFIG.enabled,
399
+ maxRecentDiffs: CONFIG.maxRecentDiffs,
400
+ predictionTimeout: CONFIG.predictionTimeout,
401
+ },
402
+ })
403
+ );
404
+ process.exit(0);
405
+ }
406
+
407
+ // Handle status request
408
+ if (process.argv.includes('--status')) {
409
+ const state = loadState();
410
+ const scriptPath = findPythonScript();
411
+ console.log(
412
+ JSON.stringify(
413
+ {
414
+ enabled: CONFIG.enabled,
415
+ scriptFound: !!scriptPath,
416
+ scriptPath,
417
+ recentDiffs: state.recentDiffs.length,
418
+ lastPrediction: state.lastPrediction,
419
+ },
420
+ null,
421
+ 2
422
+ )
423
+ );
424
+ process.exit(0);
425
+ }
426
+
427
+ // Handle clear request
428
+ if (process.argv.includes('--clear')) {
429
+ saveState({ recentDiffs: [], lastPrediction: null, fileContents: {} });
430
+ console.log('Sweep state cleared');
431
+ process.exit(0);
432
+ }
433
+
434
+ main().catch((error) => {
435
+ console.error(JSON.stringify({ status: 'error', message: error.message }));
436
+ process.exit(1);
437
+ });