@stackmemoryai/stackmemory 0.5.49 → 0.5.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -3
- package/dist/cli/claude-sm.js +246 -5
- package/dist/cli/claude-sm.js.map +3 -3
- package/dist/cli/commands/handoff.js +27 -12
- package/dist/cli/commands/handoff.js.map +2 -2
- package/dist/cli/commands/sweep.js +190 -421
- package/dist/cli/commands/sweep.js.map +3 -3
- package/dist/cli/index.js +10 -2
- package/dist/cli/index.js.map +2 -2
- package/dist/core/config/feature-flags.js +7 -1
- package/dist/core/config/feature-flags.js.map +2 -2
- package/dist/core/context/enhanced-rehydration.js +355 -9
- package/dist/core/context/enhanced-rehydration.js.map +3 -3
- package/dist/core/context/shared-context-layer.js +229 -0
- package/dist/core/context/shared-context-layer.js.map +2 -2
- package/dist/features/sweep/index.js +20 -0
- package/dist/features/sweep/index.js.map +7 -0
- package/dist/features/sweep/prediction-client.js +155 -0
- package/dist/features/sweep/prediction-client.js.map +7 -0
- package/dist/features/sweep/prompt-builder.js +85 -0
- package/dist/features/sweep/prompt-builder.js.map +7 -0
- package/dist/features/sweep/pty-wrapper.js +171 -0
- package/dist/features/sweep/pty-wrapper.js.map +7 -0
- package/dist/features/sweep/state-watcher.js +87 -0
- package/dist/features/sweep/state-watcher.js.map +7 -0
- package/dist/features/sweep/status-bar.js +88 -0
- package/dist/features/sweep/status-bar.js.map +7 -0
- package/dist/features/sweep/sweep-server-manager.js +226 -0
- package/dist/features/sweep/sweep-server-manager.js.map +7 -0
- package/dist/features/sweep/tab-interceptor.js +38 -0
- package/dist/features/sweep/tab-interceptor.js.map +7 -0
- package/dist/features/sweep/types.js +18 -0
- package/dist/features/sweep/types.js.map +7 -0
- package/dist/integrations/claude-code/lifecycle-hooks.js +3 -3
- package/dist/integrations/claude-code/lifecycle-hooks.js.map +1 -1
- package/package.json +1 -1
- package/scripts/auto-handoff.sh +1 -1
- package/scripts/claude-sm-autostart.js +174 -132
- package/scripts/setup-claude-integration.js +14 -10
- package/scripts/stackmemory-auto-handoff.sh +3 -3
- package/scripts/test-session-handoff.sh +2 -2
- package/scripts/test-setup-e2e.sh +154 -0
|
@@ -5,476 +5,245 @@ const __dirname = __pathDirname(__filename);
|
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
import chalk from "chalk";
|
|
7
7
|
import ora from "ora";
|
|
8
|
-
import {
|
|
9
|
-
existsSync,
|
|
10
|
-
readFileSync,
|
|
11
|
-
writeFileSync,
|
|
12
|
-
mkdirSync,
|
|
13
|
-
copyFileSync,
|
|
14
|
-
chmodSync
|
|
15
|
-
} from "fs";
|
|
8
|
+
import { existsSync } from "fs";
|
|
16
9
|
import { join } from "path";
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"python",
|
|
25
|
-
"sweep_predict.py"
|
|
26
|
-
),
|
|
27
|
-
join(
|
|
28
|
-
process.cwd(),
|
|
29
|
-
"node_modules",
|
|
30
|
-
"@stackmemoryai",
|
|
31
|
-
"sweep-addon",
|
|
32
|
-
"python",
|
|
33
|
-
"sweep_predict.py"
|
|
34
|
-
),
|
|
35
|
-
join(process.env.HOME || "", ".stackmemory", "sweep", "sweep_predict.py")
|
|
36
|
-
];
|
|
37
|
-
for (const loc of locations) {
|
|
38
|
-
if (existsSync(loc)) {
|
|
39
|
-
return loc;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
function findHookSource() {
|
|
45
|
-
const locations = [
|
|
46
|
-
join(process.cwd(), "templates", "claude-hooks", "post-edit-sweep.js"),
|
|
47
|
-
join(
|
|
48
|
-
process.cwd(),
|
|
49
|
-
"node_modules",
|
|
50
|
-
"@stackmemoryai",
|
|
51
|
-
"stackmemory",
|
|
52
|
-
"templates",
|
|
53
|
-
"claude-hooks",
|
|
54
|
-
"post-edit-sweep.js"
|
|
55
|
-
),
|
|
56
|
-
join(
|
|
57
|
-
dirname(dirname(dirname(__dirname))),
|
|
58
|
-
"templates",
|
|
59
|
-
"claude-hooks",
|
|
60
|
-
"post-edit-sweep.js"
|
|
61
|
-
)
|
|
62
|
-
];
|
|
63
|
-
for (const loc of locations) {
|
|
64
|
-
if (existsSync(loc)) {
|
|
65
|
-
return loc;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
async function findPython() {
|
|
71
|
-
const candidates = ["python3", "python"];
|
|
72
|
-
for (const cmd of candidates) {
|
|
73
|
-
try {
|
|
74
|
-
execSync(`${cmd} --version`, { stdio: "pipe" });
|
|
75
|
-
return cmd;
|
|
76
|
-
} catch {
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
async function checkSweepStatus() {
|
|
83
|
-
const pythonPath = await findPython();
|
|
84
|
-
if (!pythonPath) {
|
|
85
|
-
return {
|
|
86
|
-
installed: false,
|
|
87
|
-
model_downloaded: false,
|
|
88
|
-
error: "Python not found. Install Python 3.10+"
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
const scriptPath = findPythonScript();
|
|
92
|
-
if (!scriptPath) {
|
|
93
|
-
return {
|
|
94
|
-
installed: false,
|
|
95
|
-
model_downloaded: false,
|
|
96
|
-
python_path: pythonPath,
|
|
97
|
-
error: "Sweep addon not installed. Run: stackmemory sweep setup"
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
const homeDir = process.env.HOME || "";
|
|
101
|
-
const modelPath = join(
|
|
102
|
-
homeDir,
|
|
103
|
-
".stackmemory",
|
|
104
|
-
"models",
|
|
105
|
-
"sweep",
|
|
106
|
-
"sweep-next-edit-1.5b.q8_0.v2.gguf"
|
|
107
|
-
);
|
|
108
|
-
const modelDownloaded = existsSync(modelPath);
|
|
109
|
-
return {
|
|
110
|
-
installed: true,
|
|
111
|
-
model_downloaded: modelDownloaded,
|
|
112
|
-
python_path: pythonPath,
|
|
113
|
-
model_path: modelDownloaded ? modelPath : void 0
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
async function runPrediction(filePath, pythonPath, scriptPath) {
|
|
117
|
-
if (!existsSync(filePath)) {
|
|
118
|
-
return {
|
|
119
|
-
success: false,
|
|
120
|
-
error: "file_not_found",
|
|
121
|
-
message: `File not found: ${filePath}`
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
const currentContent = readFileSync(filePath, "utf-8");
|
|
125
|
-
const input = {
|
|
126
|
-
file_path: filePath,
|
|
127
|
-
current_content: currentContent
|
|
128
|
-
};
|
|
129
|
-
return new Promise((resolve) => {
|
|
130
|
-
const proc = spawn(pythonPath, [scriptPath], {
|
|
131
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
132
|
-
});
|
|
133
|
-
let stdout = "";
|
|
134
|
-
let stderr = "";
|
|
135
|
-
proc.stdout.on("data", (data) => stdout += data);
|
|
136
|
-
proc.stderr.on("data", (data) => stderr += data);
|
|
137
|
-
proc.on("close", (code) => {
|
|
138
|
-
try {
|
|
139
|
-
if (stdout.trim()) {
|
|
140
|
-
const result = JSON.parse(stdout.trim());
|
|
141
|
-
resolve(result);
|
|
142
|
-
} else if (code !== 0) {
|
|
143
|
-
resolve({
|
|
144
|
-
success: false,
|
|
145
|
-
error: "process_error",
|
|
146
|
-
message: stderr || `Process exited with code ${code}`
|
|
147
|
-
});
|
|
148
|
-
} else {
|
|
149
|
-
resolve({
|
|
150
|
-
success: false,
|
|
151
|
-
error: "no_output",
|
|
152
|
-
message: "No output from prediction script"
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
} catch {
|
|
156
|
-
resolve({
|
|
157
|
-
success: false,
|
|
158
|
-
error: "parse_error",
|
|
159
|
-
message: `Failed to parse output: ${stdout}`
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
proc.on("error", (error) => {
|
|
164
|
-
resolve({
|
|
165
|
-
success: false,
|
|
166
|
-
error: "spawn_error",
|
|
167
|
-
message: error.message
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
proc.stdin.write(JSON.stringify(input));
|
|
171
|
-
proc.stdin.end();
|
|
172
|
-
});
|
|
173
|
-
}
|
|
10
|
+
import {
|
|
11
|
+
createServerManager,
|
|
12
|
+
createPredictionClient,
|
|
13
|
+
launchWrapper,
|
|
14
|
+
DEFAULT_SERVER_CONFIG
|
|
15
|
+
} from "../../features/sweep/index.js";
|
|
16
|
+
const HOME = process.env["HOME"] || "/tmp";
|
|
174
17
|
function createSweepCommand() {
|
|
175
|
-
const cmd = new Command("sweep").description(
|
|
176
|
-
"Next-edit predictions using Sweep 1.5B model (optional addon)"
|
|
177
|
-
).addHelpText(
|
|
18
|
+
const cmd = new Command("sweep").description("Manage Sweep Next-Edit prediction server").addHelpText(
|
|
178
19
|
"after",
|
|
179
20
|
`
|
|
180
21
|
Examples:
|
|
181
|
-
stackmemory sweep
|
|
182
|
-
stackmemory sweep
|
|
183
|
-
stackmemory sweep status
|
|
184
|
-
stackmemory sweep predict
|
|
185
|
-
|
|
186
|
-
Requirements:
|
|
187
|
-
- Python 3.10+
|
|
188
|
-
- pip packages: huggingface_hub, llama-cpp-python
|
|
22
|
+
stackmemory sweep start Start the Sweep server
|
|
23
|
+
stackmemory sweep stop Stop the Sweep server
|
|
24
|
+
stackmemory sweep status Check server status
|
|
25
|
+
stackmemory sweep predict Run a prediction manually
|
|
26
|
+
stackmemory sweep hook Install/check Claude Code hook
|
|
189
27
|
|
|
190
|
-
The Sweep 1.5B model
|
|
191
|
-
|
|
192
|
-
- Recent changes (diffs)
|
|
193
|
-
- Context from other files
|
|
194
|
-
|
|
195
|
-
Model is downloaded from HuggingFace on first prediction (~1.5GB).
|
|
28
|
+
The Sweep server uses the Sweep 1.5B model for next-edit predictions.
|
|
29
|
+
Predictions are triggered after file edits via Claude Code hooks.
|
|
196
30
|
`
|
|
197
31
|
);
|
|
198
|
-
cmd.command("
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
32
|
+
cmd.command("start").description("Start the Sweep prediction server").option(
|
|
33
|
+
"--port <number>",
|
|
34
|
+
"Server port",
|
|
35
|
+
String(DEFAULT_SERVER_CONFIG.port)
|
|
36
|
+
).option("--model <path>", "Path to GGUF model file").option(
|
|
37
|
+
"--context <size>",
|
|
38
|
+
"Context size",
|
|
39
|
+
String(DEFAULT_SERVER_CONFIG.contextSize)
|
|
40
|
+
).option("--gpu-layers <n>", "Number of GPU layers (0 for CPU only)", "0").action(async (options) => {
|
|
41
|
+
const spinner = ora("Starting Sweep server...").start();
|
|
42
|
+
try {
|
|
43
|
+
const config = {
|
|
44
|
+
port: parseInt(options.port, 10),
|
|
45
|
+
contextSize: parseInt(options.context, 10),
|
|
46
|
+
gpuLayers: parseInt(options.gpuLayers, 10)
|
|
47
|
+
};
|
|
48
|
+
if (options.model) {
|
|
49
|
+
config.modelPath = options.model;
|
|
50
|
+
}
|
|
51
|
+
const manager = createServerManager(config);
|
|
52
|
+
const status = await manager.startServer();
|
|
53
|
+
spinner.succeed(chalk.green("Sweep server started"));
|
|
54
|
+
console.log(chalk.gray(` PID: ${status.pid}`));
|
|
55
|
+
console.log(chalk.gray(` Port: ${status.port}`));
|
|
56
|
+
console.log(chalk.gray(` Model: ${status.modelPath}`));
|
|
57
|
+
} catch (error) {
|
|
58
|
+
spinner.fail(chalk.red("Failed to start Sweep server"));
|
|
59
|
+
console.error(chalk.red(error.message));
|
|
204
60
|
process.exit(1);
|
|
205
61
|
}
|
|
206
|
-
|
|
62
|
+
});
|
|
63
|
+
cmd.command("stop").description("Stop the Sweep prediction server").action(async () => {
|
|
64
|
+
const spinner = ora("Stopping Sweep server...").start();
|
|
207
65
|
try {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
);
|
|
214
|
-
spinner.succeed(chalk.green("Python dependencies installed"));
|
|
215
|
-
} catch {
|
|
216
|
-
spinner.fail(chalk.red("Failed to install dependencies"));
|
|
217
|
-
console.log(
|
|
218
|
-
chalk.gray(
|
|
219
|
-
`Run: ${pythonPath} -m pip install huggingface_hub llama-cpp-python`
|
|
220
|
-
)
|
|
221
|
-
);
|
|
66
|
+
const manager = createServerManager();
|
|
67
|
+
await manager.stopServer();
|
|
68
|
+
spinner.succeed(chalk.green("Sweep server stopped"));
|
|
69
|
+
} catch (error) {
|
|
70
|
+
spinner.fail(chalk.red("Failed to stop Sweep server"));
|
|
71
|
+
console.error(chalk.red(error.message));
|
|
222
72
|
process.exit(1);
|
|
223
73
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
);
|
|
244
|
-
downloadSpinner.succeed(chalk.green("Model downloaded"));
|
|
245
|
-
} catch {
|
|
246
|
-
downloadSpinner.fail(chalk.red("Model download failed"));
|
|
247
|
-
console.log(chalk.gray("Model will be downloaded on first use"));
|
|
74
|
+
});
|
|
75
|
+
cmd.command("status").description("Check Sweep server status").action(async () => {
|
|
76
|
+
try {
|
|
77
|
+
const manager = createServerManager();
|
|
78
|
+
const status = await manager.getStatus();
|
|
79
|
+
if (status.running) {
|
|
80
|
+
console.log(chalk.green("Sweep server is running"));
|
|
81
|
+
console.log(chalk.gray(` PID: ${status.pid}`));
|
|
82
|
+
console.log(chalk.gray(` Port: ${status.port}`));
|
|
83
|
+
console.log(chalk.gray(` Host: ${status.host}`));
|
|
84
|
+
if (status.startedAt) {
|
|
85
|
+
const uptime = Math.floor((Date.now() - status.startedAt) / 1e3);
|
|
86
|
+
console.log(chalk.gray(` Uptime: ${formatUptime(uptime)}`));
|
|
87
|
+
}
|
|
88
|
+
if (status.modelPath) {
|
|
89
|
+
console.log(chalk.gray(` Model: ${status.modelPath}`));
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
console.log(chalk.yellow("Sweep server is not running"));
|
|
93
|
+
console.log(chalk.gray(" Start with: stackmemory sweep start"));
|
|
248
94
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
95
|
+
const defaultModelPath = join(
|
|
96
|
+
HOME,
|
|
97
|
+
".stackmemory",
|
|
98
|
+
"models",
|
|
99
|
+
"sweep",
|
|
100
|
+
"sweep-next-edit-1.5b.q8_0.v2.gguf"
|
|
252
101
|
);
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
);
|
|
267
|
-
console.log(
|
|
268
|
-
`Addon installed: ${status.installed ? chalk.green("Yes") : chalk.yellow("No")}`
|
|
269
|
-
);
|
|
270
|
-
console.log(
|
|
271
|
-
`Model downloaded: ${status.model_downloaded ? chalk.green("Yes") : chalk.yellow("No (will download on first use)")}`
|
|
272
|
-
);
|
|
273
|
-
if (status.model_path) {
|
|
274
|
-
console.log(chalk.gray(`Model path: ${status.model_path}`));
|
|
275
|
-
}
|
|
276
|
-
if (!status.installed) {
|
|
277
|
-
console.log(chalk.bold("\nTo install:"));
|
|
278
|
-
console.log(" stackmemory sweep setup");
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
cmd.command("predict <file>").description("Predict next edit for a file").option("-o, --output <path>", "Write prediction to file instead of stdout").option("--json", "Output raw JSON result").action(async (file, options) => {
|
|
282
|
-
const status = await checkSweepStatus();
|
|
283
|
-
if (!status.installed) {
|
|
284
|
-
console.error(chalk.red("Sweep addon not installed"));
|
|
285
|
-
console.log(chalk.gray("Run: stackmemory sweep setup"));
|
|
286
|
-
process.exit(1);
|
|
287
|
-
}
|
|
288
|
-
const scriptPath = findPythonScript();
|
|
289
|
-
if (!scriptPath || !status.python_path) {
|
|
290
|
-
console.error(chalk.red("Could not find Sweep prediction script"));
|
|
291
|
-
process.exit(1);
|
|
292
|
-
}
|
|
293
|
-
const spinner = ora("Running prediction...").start();
|
|
294
|
-
if (!status.model_downloaded) {
|
|
295
|
-
spinner.text = "Downloading model (first time only, ~1.5GB)...";
|
|
296
|
-
}
|
|
297
|
-
const result = await runPrediction(file, status.python_path, scriptPath);
|
|
298
|
-
if (!result.success) {
|
|
299
|
-
spinner.fail(
|
|
300
|
-
chalk.red(`Prediction failed: ${result.message || result.error}`)
|
|
102
|
+
if (!existsSync(defaultModelPath)) {
|
|
103
|
+
console.log("");
|
|
104
|
+
console.log(chalk.yellow("Model not found at default location"));
|
|
105
|
+
console.log(
|
|
106
|
+
chalk.gray(
|
|
107
|
+
" Download with:\n huggingface-cli download sweepai/sweep-next-edit-1.5B \\\n sweep-next-edit-1.5b.q8_0.v2.gguf \\\n --local-dir ~/.stackmemory/models/sweep"
|
|
108
|
+
)
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error(
|
|
113
|
+
chalk.red("Error checking status:"),
|
|
114
|
+
error.message
|
|
301
115
|
);
|
|
302
116
|
process.exit(1);
|
|
303
117
|
}
|
|
304
|
-
spinner.succeed(chalk.green("Prediction complete"));
|
|
305
|
-
if (options.json) {
|
|
306
|
-
console.log(JSON.stringify(result, null, 2));
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
console.log(chalk.bold("\nPredicted content:"));
|
|
310
|
-
console.log(chalk.gray("\u2500".repeat(50)));
|
|
311
|
-
console.log(result.predicted_content);
|
|
312
|
-
console.log(chalk.gray("\u2500".repeat(50)));
|
|
313
|
-
if (result.latency_ms) {
|
|
314
|
-
console.log(chalk.gray(`Latency: ${result.latency_ms}ms`));
|
|
315
|
-
}
|
|
316
|
-
if (result.tokens_generated) {
|
|
317
|
-
console.log(chalk.gray(`Tokens: ${result.tokens_generated}`));
|
|
318
|
-
}
|
|
319
|
-
if (options.output) {
|
|
320
|
-
const { writeFileSync: writeFileSync2 } = await import("fs");
|
|
321
|
-
writeFileSync2(options.output, result.predicted_content || "");
|
|
322
|
-
console.log(chalk.green(`
|
|
323
|
-
Written to: ${options.output}`));
|
|
324
|
-
}
|
|
325
118
|
});
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
const
|
|
332
|
-
const hooksJsonPath = join(homeDir, ".claude", "hooks.json");
|
|
119
|
+
cmd.command("predict").description("Run a prediction manually (for testing)").argument("<file>", "File to predict edits for").option(
|
|
120
|
+
"--port <number>",
|
|
121
|
+
"Server port",
|
|
122
|
+
String(DEFAULT_SERVER_CONFIG.port)
|
|
123
|
+
).action(async (file, options) => {
|
|
124
|
+
const spinner = ora("Running prediction...").start();
|
|
333
125
|
try {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
);
|
|
126
|
+
const { readFileSync } = await import("fs");
|
|
127
|
+
const content = readFileSync(file, "utf-8");
|
|
128
|
+
const client = createPredictionClient({
|
|
129
|
+
port: parseInt(options.port, 10)
|
|
130
|
+
});
|
|
131
|
+
const healthy = await client.checkHealth();
|
|
132
|
+
if (!healthy) {
|
|
133
|
+
spinner.fail(chalk.red("Server not running"));
|
|
134
|
+
console.log(chalk.gray("Start with: stackmemory sweep start"));
|
|
342
135
|
process.exit(1);
|
|
343
136
|
}
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
console.log(chalk.gray(`Existing: ${hooks["post-tool-use"]}`));
|
|
360
|
-
console.log(chalk.gray(`Hook installed at: ${hookDest}`));
|
|
361
|
-
console.log(
|
|
362
|
-
chalk.gray("You may need to manually configure the hook chain")
|
|
363
|
-
);
|
|
364
|
-
return;
|
|
137
|
+
const result = await client.predict({
|
|
138
|
+
file_path: file,
|
|
139
|
+
current_content: content,
|
|
140
|
+
recent_diffs: []
|
|
141
|
+
});
|
|
142
|
+
spinner.stop();
|
|
143
|
+
if (result.success && result.predicted_content) {
|
|
144
|
+
console.log(chalk.green("Prediction complete"));
|
|
145
|
+
console.log(chalk.gray(`Latency: ${result.latency_ms}ms`));
|
|
146
|
+
console.log(chalk.gray(`Tokens: ${result.tokens_generated}`));
|
|
147
|
+
console.log("");
|
|
148
|
+
console.log(chalk.cyan("Predicted content:"));
|
|
149
|
+
console.log(result.predicted_content.slice(0, 500));
|
|
150
|
+
if (result.predicted_content.length > 500) {
|
|
151
|
+
console.log(chalk.gray("... (truncated)"));
|
|
365
152
|
}
|
|
153
|
+
} else if (result.success) {
|
|
154
|
+
console.log(chalk.yellow("No changes predicted"));
|
|
366
155
|
} else {
|
|
367
|
-
|
|
368
|
-
writeFileSync(hooksJsonPath, JSON.stringify(hooks, null, 2));
|
|
156
|
+
console.log(chalk.red("Prediction failed:"), result.message);
|
|
369
157
|
}
|
|
370
|
-
spinner.succeed(chalk.green("Sweep hook installed"));
|
|
371
|
-
console.log(chalk.gray(`Hook: ${hookDest}`));
|
|
372
|
-
console.log(chalk.gray(`Config: ${hooksJsonPath}`));
|
|
373
|
-
console.log("");
|
|
374
|
-
console.log(chalk.bold("Usage:"));
|
|
375
|
-
console.log(" Hook runs automatically after Edit/Write operations");
|
|
376
|
-
console.log(" Predictions appear after 2+ edits in session");
|
|
377
|
-
console.log(" Disable: export SWEEP_ENABLED=false");
|
|
378
158
|
} catch (error) {
|
|
379
|
-
spinner.fail(chalk.red("
|
|
380
|
-
console.
|
|
159
|
+
spinner.fail(chalk.red("Prediction failed"));
|
|
160
|
+
console.error(chalk.red(error.message));
|
|
381
161
|
process.exit(1);
|
|
382
162
|
}
|
|
383
163
|
});
|
|
384
|
-
|
|
385
|
-
const
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
console.log(
|
|
392
|
-
`Hook installed: ${hookInstalled ? chalk.green("Yes") : chalk.yellow("No")}`
|
|
164
|
+
cmd.command("hook").description("Check Claude Code hook status").action(async () => {
|
|
165
|
+
const hookPath = join(HOME, ".claude", "hooks", "post-edit-sweep.js");
|
|
166
|
+
const templatePath = join(
|
|
167
|
+
process.cwd(),
|
|
168
|
+
"templates",
|
|
169
|
+
"claude-hooks",
|
|
170
|
+
"post-edit-sweep.js"
|
|
393
171
|
);
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
console.log(
|
|
398
|
-
`Hook configured: ${configured ? chalk.green("Yes") : chalk.yellow("No")}`
|
|
399
|
-
);
|
|
172
|
+
console.log(chalk.cyan("Sweep Hook Status"));
|
|
173
|
+
console.log("");
|
|
174
|
+
if (existsSync(hookPath)) {
|
|
175
|
+
console.log(chalk.green("Hook installed:"), hookPath);
|
|
400
176
|
} else {
|
|
401
|
-
console.log(
|
|
177
|
+
console.log(chalk.yellow("Hook not installed"));
|
|
178
|
+
console.log(chalk.gray(` Copy from: ${templatePath}`));
|
|
179
|
+
console.log(chalk.gray(` To: ${hookPath}`));
|
|
402
180
|
}
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
181
|
+
const settingsPath = join(HOME, ".claude", "settings.json");
|
|
182
|
+
if (existsSync(settingsPath)) {
|
|
183
|
+
try {
|
|
184
|
+
const { readFileSync } = await import("fs");
|
|
185
|
+
const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
186
|
+
const hasHook = JSON.stringify(settings).includes("post-edit-sweep");
|
|
187
|
+
if (hasHook) {
|
|
188
|
+
console.log(chalk.green("Hook registered in settings.json"));
|
|
189
|
+
} else {
|
|
190
|
+
console.log(chalk.yellow("Hook not registered in settings.json"));
|
|
191
|
+
console.log(
|
|
192
|
+
chalk.gray(
|
|
193
|
+
' Add to hooks.PostToolUse:\n { "matcher": "Edit", "hooks": [{ "type": "command", "command": "node ~/.claude/hooks/post-edit-sweep.js" }] }'
|
|
194
|
+
)
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
} catch {
|
|
198
|
+
console.log(chalk.yellow("Could not read settings.json"));
|
|
199
|
+
}
|
|
200
|
+
} else {
|
|
201
|
+
console.log(chalk.yellow("settings.json not found"));
|
|
202
|
+
}
|
|
203
|
+
const statePath = join(HOME, ".stackmemory", "sweep-state.json");
|
|
407
204
|
if (existsSync(statePath)) {
|
|
408
205
|
try {
|
|
206
|
+
const { readFileSync } = await import("fs");
|
|
409
207
|
const state = JSON.parse(readFileSync(statePath, "utf-8"));
|
|
410
208
|
console.log(
|
|
411
209
|
chalk.gray(
|
|
412
|
-
`
|
|
413
|
-
Recent diffs tracked: ${state.recentDiffs?.length || 0}`
|
|
210
|
+
` Recent diffs tracked: ${state.recentDiffs?.length || 0}`
|
|
414
211
|
)
|
|
415
212
|
);
|
|
416
213
|
if (state.lastPrediction) {
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
214
|
+
const ago = Math.floor(
|
|
215
|
+
(Date.now() - state.lastPrediction.timestamp) / 1e3
|
|
216
|
+
);
|
|
217
|
+
console.log(
|
|
218
|
+
chalk.gray(` Last prediction: ${formatUptime(ago)} ago`)
|
|
219
|
+
);
|
|
420
220
|
}
|
|
421
221
|
} catch {
|
|
422
222
|
}
|
|
423
223
|
}
|
|
424
|
-
if (!hookInstalled) {
|
|
425
|
-
console.log(chalk.bold("\nTo install: stackmemory sweep hook install"));
|
|
426
|
-
}
|
|
427
|
-
});
|
|
428
|
-
hookCmd.command("disable").description("Disable the Sweep hook").action(() => {
|
|
429
|
-
console.log(chalk.bold("\nTo disable Sweep predictions:\n"));
|
|
430
|
-
console.log(" Temporarily: export SWEEP_ENABLED=false");
|
|
431
|
-
console.log(" Permanently: Add to ~/.zshrc or ~/.bashrc");
|
|
432
|
-
console.log("");
|
|
433
|
-
console.log("Or remove the hook:");
|
|
434
|
-
console.log(" rm ~/.claude/hooks/post-edit-sweep.js");
|
|
435
|
-
});
|
|
436
|
-
hookCmd.command("clear").description("Clear hook state (recent diffs and predictions)").action(() => {
|
|
437
|
-
const homeDir = process.env.HOME || "";
|
|
438
|
-
const statePath = join(homeDir, ".stackmemory", "sweep-state.json");
|
|
439
|
-
if (existsSync(statePath)) {
|
|
440
|
-
writeFileSync(
|
|
441
|
-
statePath,
|
|
442
|
-
JSON.stringify(
|
|
443
|
-
{
|
|
444
|
-
recentDiffs: [],
|
|
445
|
-
lastPrediction: null,
|
|
446
|
-
pendingPrediction: null,
|
|
447
|
-
fileContents: {}
|
|
448
|
-
},
|
|
449
|
-
null,
|
|
450
|
-
2
|
|
451
|
-
)
|
|
452
|
-
);
|
|
453
|
-
console.log(chalk.green("Sweep state cleared"));
|
|
454
|
-
} else {
|
|
455
|
-
console.log(chalk.gray("No state file found"));
|
|
456
|
-
}
|
|
457
224
|
});
|
|
458
|
-
cmd.action(async () => {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
console.log(chalk.bold("\nRun: stackmemory sweep setup"));
|
|
469
|
-
} else {
|
|
470
|
-
console.log(chalk.bold("\nUsage: stackmemory sweep predict <file>"));
|
|
225
|
+
cmd.command("wrap").description("Launch Claude Code with Sweep prediction status bar").option("--claude-bin <path>", "Path to claude binary").allowUnknownOption(true).action(async (options, command) => {
|
|
226
|
+
try {
|
|
227
|
+
const claudeArgs = command.args || [];
|
|
228
|
+
await launchWrapper({
|
|
229
|
+
claudeBin: options.claudeBin,
|
|
230
|
+
claudeArgs
|
|
231
|
+
});
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.error(chalk.red(error.message));
|
|
234
|
+
process.exit(1);
|
|
471
235
|
}
|
|
472
236
|
});
|
|
473
237
|
return cmd;
|
|
474
238
|
}
|
|
475
|
-
|
|
239
|
+
function formatUptime(seconds) {
|
|
240
|
+
if (seconds < 60) return `${seconds}s`;
|
|
241
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
|
|
242
|
+
const hours = Math.floor(seconds / 3600);
|
|
243
|
+
const mins = Math.floor(seconds % 3600 / 60);
|
|
244
|
+
return `${hours}h ${mins}m`;
|
|
245
|
+
}
|
|
476
246
|
export {
|
|
477
|
-
createSweepCommand
|
|
478
|
-
sweep_default as default
|
|
247
|
+
createSweepCommand
|
|
479
248
|
};
|
|
480
249
|
//# sourceMappingURL=sweep.js.map
|