@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 {
|
|
5
|
-
|
|
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
|
-
"@
|
|
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
|
-
|
|
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
|
+
"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
|
+
});
|