@stackmemoryai/stackmemory 1.2.1 → 1.2.2
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/dist/src/cli/commands/skills.js +123 -1
- package/dist/src/hooks/graphiti-hooks.js +149 -0
- package/dist/src/hooks/session-summary.js +30 -0
- package/dist/src/integrations/graphiti/linear-graphiti-bridge.js +115 -0
- package/dist/src/integrations/greptile/client.js +101 -0
- package/dist/src/integrations/greptile/config.js +14 -0
- package/dist/src/integrations/greptile/index.js +11 -0
- package/dist/src/integrations/greptile/types.js +4 -0
- package/dist/src/integrations/linear/webhook.js +16 -0
- package/dist/src/integrations/mcp/handlers/greptile-handlers.js +456 -0
- package/dist/src/integrations/mcp/server.js +27 -0
- package/dist/src/skills/claude-skills.js +46 -1
- package/dist/src/skills/parallel-agent-skill.js +514 -0
- package/package.json +1 -1
- package/scripts/gepa/.before-optimize.md +140 -159
- package/scripts/gepa/config.json +7 -1
- package/scripts/gepa/evals/fixtures/api-endpoint.ts +31 -0
- package/scripts/gepa/evals/fixtures/brittle-integration.ts +38 -0
- package/scripts/gepa/evals/fixtures/fts5-triggers.sql +23 -0
- package/scripts/gepa/evals/fixtures/leaky-service.ts +70 -0
- package/scripts/gepa/evals/fixtures/mcp-dispatch-stub.ts +39 -0
- package/scripts/gepa/evals/fixtures/pr-diff.txt +24 -0
- package/scripts/gepa/evals/fixtures/unsafe-webhook.ts +34 -0
- package/scripts/gepa/evals/fixtures/unwrapped-db-op.ts +42 -0
- package/scripts/gepa/evals/stackmemory-tasks.jsonl +8 -0
- package/scripts/gepa/generations/gen-000/baseline.md +172 -159
- package/scripts/gepa/generations/gen-001/baseline.md +172 -159
- package/scripts/gepa/generations/gen-001/variant-a.md +156 -146
- package/scripts/gepa/generations/gen-001/variant-b.md +199 -170
- package/scripts/gepa/generations/gen-001/variant-c.md +127 -46
- package/scripts/gepa/generations/gen-001/variant-d.md +160 -107
- package/scripts/gepa/hooks/reflect.js +44 -5
- package/scripts/gepa/optimize.js +281 -39
- package/scripts/gepa/results/eval-1-baseline.json +187 -10
- package/scripts/gepa/results/eval-1-variant-a.json +188 -11
- package/scripts/gepa/results/eval-1-variant-b.json +188 -11
- package/scripts/gepa/results/eval-1-variant-c.json +168 -11
- package/scripts/gepa/results/eval-1-variant-d.json +169 -12
- package/scripts/gepa/state.json +18 -18
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
2
|
+
import { dirname as __pathDirname } from 'path';
|
|
3
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
4
|
+
const __dirname = __pathDirname(__filename);
|
|
5
|
+
import { spawn } from "child_process";
|
|
6
|
+
import * as fs from "fs";
|
|
7
|
+
import * as path from "path";
|
|
8
|
+
import { execSync } from "child_process";
|
|
9
|
+
import { logger } from "../core/monitoring/logger.js";
|
|
10
|
+
import { hookEmitter } from "../hooks/events.js";
|
|
11
|
+
const DEFAULT_TIMEOUT = 5 * 60 * 1e3;
|
|
12
|
+
function spawnAgent(prompt, opts) {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
let workDir;
|
|
15
|
+
try {
|
|
16
|
+
workDir = fs.mkdtempSync(path.join("/tmp", `${opts.prefix}-`));
|
|
17
|
+
} catch (err) {
|
|
18
|
+
return reject(
|
|
19
|
+
new Error(`Failed to create temp dir: ${err.message}`)
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
execSync(`git clone --depth=1 "${opts.repoRoot}" "${workDir}"`, {
|
|
24
|
+
stdio: "pipe",
|
|
25
|
+
timeout: 3e4
|
|
26
|
+
});
|
|
27
|
+
} catch (err) {
|
|
28
|
+
cleanupDir(workDir);
|
|
29
|
+
return reject(new Error(`git clone failed: ${err.message}`));
|
|
30
|
+
}
|
|
31
|
+
const child = spawn("claude", ["--print"], {
|
|
32
|
+
cwd: workDir,
|
|
33
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
34
|
+
env: { ...process.env }
|
|
35
|
+
});
|
|
36
|
+
let stdout = "";
|
|
37
|
+
let stderr = "";
|
|
38
|
+
let killed = false;
|
|
39
|
+
const timer = setTimeout(() => {
|
|
40
|
+
killed = true;
|
|
41
|
+
child.kill("SIGTERM");
|
|
42
|
+
}, opts.timeout);
|
|
43
|
+
if (child.stdout) {
|
|
44
|
+
child.stdout.on("data", (d) => stdout += d);
|
|
45
|
+
}
|
|
46
|
+
if (child.stderr) {
|
|
47
|
+
child.stderr.on("data", (d) => stderr += d);
|
|
48
|
+
}
|
|
49
|
+
child.on("close", (code) => {
|
|
50
|
+
clearTimeout(timer);
|
|
51
|
+
resolve({ stdout, stderr, exitCode: code, workDir, timedOut: killed });
|
|
52
|
+
});
|
|
53
|
+
child.on("error", (err) => {
|
|
54
|
+
clearTimeout(timer);
|
|
55
|
+
resolve({
|
|
56
|
+
stdout,
|
|
57
|
+
stderr: err.message,
|
|
58
|
+
exitCode: 1,
|
|
59
|
+
workDir,
|
|
60
|
+
timedOut: false
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
if (child.stdin) {
|
|
64
|
+
child.stdin.write(prompt);
|
|
65
|
+
child.stdin.end();
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function cleanupDir(dir) {
|
|
70
|
+
try {
|
|
71
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
72
|
+
} catch {
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function slugify(text) {
|
|
76
|
+
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
77
|
+
}
|
|
78
|
+
class ParallelAgentSkill {
|
|
79
|
+
constructor(context) {
|
|
80
|
+
this.context = context;
|
|
81
|
+
this.repoRoot = process.cwd();
|
|
82
|
+
}
|
|
83
|
+
repoRoot;
|
|
84
|
+
/**
|
|
85
|
+
* Research — explore codebase, save findings as a frame.
|
|
86
|
+
*/
|
|
87
|
+
async research(question, options) {
|
|
88
|
+
if (!question?.trim()) {
|
|
89
|
+
return { success: false, message: "Question is required" };
|
|
90
|
+
}
|
|
91
|
+
const timeout = options?.timeout ?? DEFAULT_TIMEOUT;
|
|
92
|
+
const prompt = [
|
|
93
|
+
"Explore this codebase and answer the following question.",
|
|
94
|
+
"Do NOT modify any files. Output your findings as structured markdown.",
|
|
95
|
+
"",
|
|
96
|
+
`Question: ${question}`
|
|
97
|
+
].join("\n");
|
|
98
|
+
let result;
|
|
99
|
+
try {
|
|
100
|
+
hookEmitter.emitHook({
|
|
101
|
+
type: "agent_start",
|
|
102
|
+
timestamp: Date.now(),
|
|
103
|
+
data: { agentType: "research", workDir: "", task: question }
|
|
104
|
+
}).catch(() => {
|
|
105
|
+
});
|
|
106
|
+
result = await spawnAgent(prompt, {
|
|
107
|
+
prefix: "sm-research",
|
|
108
|
+
timeout,
|
|
109
|
+
repoRoot: this.repoRoot
|
|
110
|
+
});
|
|
111
|
+
} catch (err) {
|
|
112
|
+
const msg = err.message;
|
|
113
|
+
hookEmitter.emitHook({
|
|
114
|
+
type: "agent_error",
|
|
115
|
+
timestamp: Date.now(),
|
|
116
|
+
data: { agentType: "research", error: msg }
|
|
117
|
+
}).catch(() => {
|
|
118
|
+
});
|
|
119
|
+
return {
|
|
120
|
+
success: false,
|
|
121
|
+
message: `Agent failed to start: ${msg}`
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
if (result.timedOut) {
|
|
126
|
+
hookEmitter.emitHook({
|
|
127
|
+
type: "agent_complete",
|
|
128
|
+
timestamp: Date.now(),
|
|
129
|
+
data: {
|
|
130
|
+
agentType: "research",
|
|
131
|
+
workDir: result.workDir,
|
|
132
|
+
exitCode: result.exitCode,
|
|
133
|
+
timedOut: true
|
|
134
|
+
}
|
|
135
|
+
}).catch(() => {
|
|
136
|
+
});
|
|
137
|
+
return {
|
|
138
|
+
success: false,
|
|
139
|
+
message: `Research agent timed out after ${timeout / 1e3}s`,
|
|
140
|
+
data: { partial: result.stdout.slice(0, 2e3) }
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const findings = result.stdout || "(no output)";
|
|
144
|
+
let frameId;
|
|
145
|
+
try {
|
|
146
|
+
frameId = await this.saveFrame("research", question, findings);
|
|
147
|
+
} catch (err) {
|
|
148
|
+
logger.warn("Failed to save research frame:", err);
|
|
149
|
+
}
|
|
150
|
+
hookEmitter.emitHook({
|
|
151
|
+
type: "agent_complete",
|
|
152
|
+
timestamp: Date.now(),
|
|
153
|
+
data: {
|
|
154
|
+
agentType: "research",
|
|
155
|
+
workDir: result.workDir,
|
|
156
|
+
exitCode: result.exitCode,
|
|
157
|
+
timedOut: false,
|
|
158
|
+
frameId
|
|
159
|
+
}
|
|
160
|
+
}).catch(() => {
|
|
161
|
+
});
|
|
162
|
+
return {
|
|
163
|
+
success: true,
|
|
164
|
+
message: `Research complete${frameId ? ` (frame: ${frameId})` : ""}`,
|
|
165
|
+
data: { findings, frameId, exitCode: result.exitCode }
|
|
166
|
+
};
|
|
167
|
+
} finally {
|
|
168
|
+
cleanupDir(result.workDir);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Maintain — low-stakes fix, produces a .patch file.
|
|
173
|
+
*/
|
|
174
|
+
async maintain(task, options) {
|
|
175
|
+
if (!task?.trim()) {
|
|
176
|
+
return { success: false, message: "Task description is required" };
|
|
177
|
+
}
|
|
178
|
+
const timeout = options?.timeout ?? DEFAULT_TIMEOUT;
|
|
179
|
+
const prompt = [
|
|
180
|
+
task,
|
|
181
|
+
"",
|
|
182
|
+
"After making changes, verify by running: npm run lint && npm run test:run"
|
|
183
|
+
].join("\n");
|
|
184
|
+
let result;
|
|
185
|
+
try {
|
|
186
|
+
hookEmitter.emitHook({
|
|
187
|
+
type: "agent_start",
|
|
188
|
+
timestamp: Date.now(),
|
|
189
|
+
data: { agentType: "maintain", workDir: "", task }
|
|
190
|
+
}).catch(() => {
|
|
191
|
+
});
|
|
192
|
+
result = await spawnAgent(prompt, {
|
|
193
|
+
prefix: "sm-maint",
|
|
194
|
+
timeout,
|
|
195
|
+
repoRoot: this.repoRoot
|
|
196
|
+
});
|
|
197
|
+
} catch (err) {
|
|
198
|
+
const msg = err.message;
|
|
199
|
+
hookEmitter.emitHook({
|
|
200
|
+
type: "agent_error",
|
|
201
|
+
timestamp: Date.now(),
|
|
202
|
+
data: { agentType: "maintain", error: msg }
|
|
203
|
+
}).catch(() => {
|
|
204
|
+
});
|
|
205
|
+
return {
|
|
206
|
+
success: false,
|
|
207
|
+
message: `Agent failed to start: ${msg}`
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
try {
|
|
211
|
+
if (result.timedOut) {
|
|
212
|
+
hookEmitter.emitHook({
|
|
213
|
+
type: "agent_complete",
|
|
214
|
+
timestamp: Date.now(),
|
|
215
|
+
data: {
|
|
216
|
+
agentType: "maintain",
|
|
217
|
+
workDir: result.workDir,
|
|
218
|
+
exitCode: result.exitCode,
|
|
219
|
+
timedOut: true
|
|
220
|
+
}
|
|
221
|
+
}).catch(() => {
|
|
222
|
+
});
|
|
223
|
+
return {
|
|
224
|
+
success: false,
|
|
225
|
+
message: `Maintenance agent timed out after ${timeout / 1e3}s`,
|
|
226
|
+
data: { partial: result.stdout.slice(0, 2e3) }
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
let diff = "";
|
|
230
|
+
try {
|
|
231
|
+
diff = execSync("git diff HEAD", {
|
|
232
|
+
cwd: result.workDir,
|
|
233
|
+
encoding: "utf-8",
|
|
234
|
+
timeout: 1e4
|
|
235
|
+
});
|
|
236
|
+
} catch {
|
|
237
|
+
}
|
|
238
|
+
if (!diff.trim()) {
|
|
239
|
+
hookEmitter.emitHook({
|
|
240
|
+
type: "agent_complete",
|
|
241
|
+
timestamp: Date.now(),
|
|
242
|
+
data: {
|
|
243
|
+
agentType: "maintain",
|
|
244
|
+
workDir: result.workDir,
|
|
245
|
+
exitCode: result.exitCode,
|
|
246
|
+
timedOut: false
|
|
247
|
+
}
|
|
248
|
+
}).catch(() => {
|
|
249
|
+
});
|
|
250
|
+
return {
|
|
251
|
+
success: false,
|
|
252
|
+
message: "Agent made no changes (empty diff)",
|
|
253
|
+
data: { output: result.stdout.slice(0, 2e3) }
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
let validated = false;
|
|
257
|
+
try {
|
|
258
|
+
execSync("npm run lint", {
|
|
259
|
+
cwd: result.workDir,
|
|
260
|
+
stdio: "pipe",
|
|
261
|
+
timeout: 6e4
|
|
262
|
+
});
|
|
263
|
+
validated = true;
|
|
264
|
+
} catch {
|
|
265
|
+
}
|
|
266
|
+
const patchDir = path.join(this.repoRoot, ".stackmemory", "patches");
|
|
267
|
+
fs.mkdirSync(patchDir, { recursive: true });
|
|
268
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
269
|
+
const slug = slugify(task);
|
|
270
|
+
const patchFile = `${timestamp}-${slug}.patch`;
|
|
271
|
+
const patchPath = path.join(patchDir, patchFile);
|
|
272
|
+
fs.writeFileSync(patchPath, diff);
|
|
273
|
+
const diffLines = diff.split("\n");
|
|
274
|
+
const filesChanged = diffLines.filter(
|
|
275
|
+
(l) => l.startsWith("diff --git")
|
|
276
|
+
).length;
|
|
277
|
+
const additions = diffLines.filter((l) => /^\+[^+]/.test(l)).length;
|
|
278
|
+
const deletions = diffLines.filter((l) => /^-[^-]/.test(l)).length;
|
|
279
|
+
hookEmitter.emitHook({
|
|
280
|
+
type: "agent_complete",
|
|
281
|
+
timestamp: Date.now(),
|
|
282
|
+
data: {
|
|
283
|
+
agentType: "maintain",
|
|
284
|
+
workDir: result.workDir,
|
|
285
|
+
exitCode: result.exitCode,
|
|
286
|
+
timedOut: false,
|
|
287
|
+
patchPath,
|
|
288
|
+
validated
|
|
289
|
+
}
|
|
290
|
+
}).catch(() => {
|
|
291
|
+
});
|
|
292
|
+
return {
|
|
293
|
+
success: true,
|
|
294
|
+
message: `Patch saved: ${patchFile}${validated ? "" : " (lint failed)"}`,
|
|
295
|
+
data: {
|
|
296
|
+
patchPath,
|
|
297
|
+
validated,
|
|
298
|
+
filesChanged,
|
|
299
|
+
additions,
|
|
300
|
+
deletions,
|
|
301
|
+
output: result.stdout.slice(0, 2e3)
|
|
302
|
+
},
|
|
303
|
+
action: `Apply with: git apply ${patchPath}`
|
|
304
|
+
};
|
|
305
|
+
} finally {
|
|
306
|
+
cleanupDir(result.workDir);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* SpecRun — implement a spec file on a branch, validate.
|
|
311
|
+
*/
|
|
312
|
+
async specRun(specPath, options) {
|
|
313
|
+
if (!specPath?.trim()) {
|
|
314
|
+
return { success: false, message: "Spec file path is required" };
|
|
315
|
+
}
|
|
316
|
+
const absSpec = path.isAbsolute(specPath) ? specPath : path.join(this.repoRoot, specPath);
|
|
317
|
+
if (!fs.existsSync(absSpec)) {
|
|
318
|
+
return { success: false, message: `Spec file not found: ${specPath}` };
|
|
319
|
+
}
|
|
320
|
+
const specContent = fs.readFileSync(absSpec, "utf-8");
|
|
321
|
+
const timeout = options?.timeout ?? DEFAULT_TIMEOUT;
|
|
322
|
+
const prompt = [
|
|
323
|
+
"Implement the following specification. Follow it precisely.",
|
|
324
|
+
"",
|
|
325
|
+
"--- SPEC START ---",
|
|
326
|
+
specContent,
|
|
327
|
+
"--- SPEC END ---"
|
|
328
|
+
].join("\n");
|
|
329
|
+
let workDir;
|
|
330
|
+
try {
|
|
331
|
+
workDir = fs.mkdtempSync(path.join("/tmp", "sm-spec-"));
|
|
332
|
+
} catch (err) {
|
|
333
|
+
return {
|
|
334
|
+
success: false,
|
|
335
|
+
message: `Failed to create temp dir: ${err.message}`
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
try {
|
|
339
|
+
execSync(`git clone --depth=1 "${this.repoRoot}" "${workDir}"`, {
|
|
340
|
+
stdio: "pipe",
|
|
341
|
+
timeout: 3e4
|
|
342
|
+
});
|
|
343
|
+
} catch (err) {
|
|
344
|
+
const msg = err.message;
|
|
345
|
+
cleanupDir(workDir);
|
|
346
|
+
hookEmitter.emitHook({
|
|
347
|
+
type: "agent_error",
|
|
348
|
+
timestamp: Date.now(),
|
|
349
|
+
data: { agentType: "spec-run", error: msg, workDir }
|
|
350
|
+
}).catch(() => {
|
|
351
|
+
});
|
|
352
|
+
return {
|
|
353
|
+
success: false,
|
|
354
|
+
message: `git clone failed: ${msg}`
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
const branchName = `agent/spec-${Date.now()}`;
|
|
358
|
+
try {
|
|
359
|
+
execSync(`git checkout -b "${branchName}"`, {
|
|
360
|
+
cwd: workDir,
|
|
361
|
+
stdio: "pipe",
|
|
362
|
+
timeout: 5e3
|
|
363
|
+
});
|
|
364
|
+
} catch (err) {
|
|
365
|
+
const msg = err.message;
|
|
366
|
+
cleanupDir(workDir);
|
|
367
|
+
hookEmitter.emitHook({
|
|
368
|
+
type: "agent_error",
|
|
369
|
+
timestamp: Date.now(),
|
|
370
|
+
data: { agentType: "spec-run", error: msg, workDir }
|
|
371
|
+
}).catch(() => {
|
|
372
|
+
});
|
|
373
|
+
return {
|
|
374
|
+
success: false,
|
|
375
|
+
message: `Failed to create branch: ${msg}`
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
hookEmitter.emitHook({
|
|
379
|
+
type: "agent_start",
|
|
380
|
+
timestamp: Date.now(),
|
|
381
|
+
data: { agentType: "spec-run", workDir, task: specPath }
|
|
382
|
+
}).catch(() => {
|
|
383
|
+
});
|
|
384
|
+
const child = spawn("claude", ["--print"], {
|
|
385
|
+
cwd: workDir,
|
|
386
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
387
|
+
env: { ...process.env }
|
|
388
|
+
});
|
|
389
|
+
let stdout = "";
|
|
390
|
+
let stderr = "";
|
|
391
|
+
let killed = false;
|
|
392
|
+
const agentDone = new Promise((resolve) => {
|
|
393
|
+
const timer = setTimeout(() => {
|
|
394
|
+
killed = true;
|
|
395
|
+
child.kill("SIGTERM");
|
|
396
|
+
}, timeout);
|
|
397
|
+
if (child.stdout) {
|
|
398
|
+
child.stdout.on("data", (d) => stdout += d);
|
|
399
|
+
}
|
|
400
|
+
if (child.stderr) {
|
|
401
|
+
child.stderr.on("data", (d) => stderr += d);
|
|
402
|
+
}
|
|
403
|
+
child.on("close", (code) => {
|
|
404
|
+
clearTimeout(timer);
|
|
405
|
+
resolve({ stdout, stderr, exitCode: code, timedOut: killed });
|
|
406
|
+
});
|
|
407
|
+
child.on("error", (err) => {
|
|
408
|
+
clearTimeout(timer);
|
|
409
|
+
resolve({
|
|
410
|
+
stdout,
|
|
411
|
+
stderr: err.message,
|
|
412
|
+
exitCode: 1,
|
|
413
|
+
timedOut: false
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
if (child.stdin) {
|
|
418
|
+
child.stdin.write(prompt);
|
|
419
|
+
child.stdin.end();
|
|
420
|
+
}
|
|
421
|
+
const agentResult = await agentDone;
|
|
422
|
+
if (agentResult.timedOut) {
|
|
423
|
+
return {
|
|
424
|
+
success: false,
|
|
425
|
+
message: `Spec agent timed out after ${timeout / 1e3}s`,
|
|
426
|
+
data: {
|
|
427
|
+
workDir,
|
|
428
|
+
branch: branchName,
|
|
429
|
+
partial: agentResult.stdout.slice(0, 2e3)
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
const validation = { lint: false, test: false, build: false };
|
|
434
|
+
try {
|
|
435
|
+
execSync("npm run lint", {
|
|
436
|
+
cwd: workDir,
|
|
437
|
+
stdio: "pipe",
|
|
438
|
+
timeout: 6e4
|
|
439
|
+
});
|
|
440
|
+
validation.lint = true;
|
|
441
|
+
} catch {
|
|
442
|
+
}
|
|
443
|
+
try {
|
|
444
|
+
execSync("npm run test:run", {
|
|
445
|
+
cwd: workDir,
|
|
446
|
+
stdio: "pipe",
|
|
447
|
+
timeout: 12e4
|
|
448
|
+
});
|
|
449
|
+
validation.test = true;
|
|
450
|
+
} catch {
|
|
451
|
+
}
|
|
452
|
+
try {
|
|
453
|
+
execSync("npm run build", {
|
|
454
|
+
cwd: workDir,
|
|
455
|
+
stdio: "pipe",
|
|
456
|
+
timeout: 6e4
|
|
457
|
+
});
|
|
458
|
+
validation.build = true;
|
|
459
|
+
} catch {
|
|
460
|
+
}
|
|
461
|
+
let diffStat = "";
|
|
462
|
+
try {
|
|
463
|
+
diffStat = execSync("git diff --stat HEAD~1", {
|
|
464
|
+
cwd: workDir,
|
|
465
|
+
encoding: "utf-8",
|
|
466
|
+
timeout: 5e3
|
|
467
|
+
});
|
|
468
|
+
} catch {
|
|
469
|
+
}
|
|
470
|
+
const allPassed = validation.lint && validation.test && validation.build;
|
|
471
|
+
hookEmitter.emitHook({
|
|
472
|
+
type: "agent_complete",
|
|
473
|
+
timestamp: Date.now(),
|
|
474
|
+
data: {
|
|
475
|
+
agentType: "spec-run",
|
|
476
|
+
workDir,
|
|
477
|
+
exitCode: agentResult.exitCode,
|
|
478
|
+
timedOut: false,
|
|
479
|
+
branch: branchName,
|
|
480
|
+
validation
|
|
481
|
+
}
|
|
482
|
+
}).catch(() => {
|
|
483
|
+
});
|
|
484
|
+
return {
|
|
485
|
+
success: allPassed,
|
|
486
|
+
message: allPassed ? `Spec implemented on branch ${branchName}` : `Spec implemented but validation failed on branch ${branchName}`,
|
|
487
|
+
data: {
|
|
488
|
+
workDir,
|
|
489
|
+
branch: branchName,
|
|
490
|
+
validation,
|
|
491
|
+
diffStat: diffStat.trim(),
|
|
492
|
+
output: agentResult.stdout.slice(0, 2e3)
|
|
493
|
+
},
|
|
494
|
+
action: allPassed ? `Review: cd ${workDir} && git log --oneline` : `Inspect: cd ${workDir} && git diff`
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
async saveFrame(type, query, content) {
|
|
498
|
+
const db = this.context.database;
|
|
499
|
+
if (!db) return void 0;
|
|
500
|
+
const frameId = await db.createFrame({
|
|
501
|
+
run_id: `agent-${Date.now()}`,
|
|
502
|
+
project_id: this.context.projectId,
|
|
503
|
+
type,
|
|
504
|
+
name: `Agent ${type}: ${query.slice(0, 60)}`,
|
|
505
|
+
state: "completed",
|
|
506
|
+
inputs: { query, agentType: type },
|
|
507
|
+
digest_text: content
|
|
508
|
+
});
|
|
509
|
+
return frameId;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
export {
|
|
513
|
+
ParallelAgentSkill
|
|
514
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackmemoryai/stackmemory",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "Project-scoped memory for AI coding tools. Durable context across sessions with MCP integration, frames, smart retrieval, Claude Code skills, and automatic hooks.",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20.0.0",
|