@stackmemoryai/stackmemory 1.2.0 → 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.
Files changed (47) hide show
  1. package/dist/src/cli/claude-sm.js +65 -0
  2. package/dist/src/cli/commands/skills.js +123 -1
  3. package/dist/src/hooks/graphiti-hooks.js +149 -0
  4. package/dist/src/hooks/session-summary.js +30 -0
  5. package/dist/src/integrations/graphiti/linear-graphiti-bridge.js +115 -0
  6. package/dist/src/integrations/greptile/client.js +101 -0
  7. package/dist/src/integrations/greptile/config.js +14 -0
  8. package/dist/src/integrations/greptile/index.js +11 -0
  9. package/dist/src/integrations/greptile/types.js +4 -0
  10. package/dist/src/integrations/linear/webhook.js +16 -0
  11. package/dist/src/integrations/mcp/handlers/greptile-handlers.js +456 -0
  12. package/dist/src/integrations/mcp/server.js +136 -0
  13. package/dist/src/integrations/mcp/tool-definitions.js +53 -1
  14. package/dist/src/skills/claude-skills.js +46 -1
  15. package/dist/src/skills/parallel-agent-skill.js +514 -0
  16. package/dist/src/utils/hook-installer.js +155 -0
  17. package/package.json +2 -2
  18. package/scripts/gepa/.before-optimize.md +140 -0
  19. package/scripts/gepa/config.json +7 -1
  20. package/scripts/gepa/evals/fixtures/api-endpoint.ts +31 -0
  21. package/scripts/gepa/evals/fixtures/brittle-integration.ts +38 -0
  22. package/scripts/gepa/evals/fixtures/fts5-triggers.sql +23 -0
  23. package/scripts/gepa/evals/fixtures/leaky-service.ts +70 -0
  24. package/scripts/gepa/evals/fixtures/mcp-dispatch-stub.ts +39 -0
  25. package/scripts/gepa/evals/fixtures/pr-diff.txt +24 -0
  26. package/scripts/gepa/evals/fixtures/unsafe-webhook.ts +34 -0
  27. package/scripts/gepa/evals/fixtures/unwrapped-db-op.ts +42 -0
  28. package/scripts/gepa/evals/stackmemory-tasks.jsonl +8 -0
  29. package/scripts/gepa/generations/gen-000/baseline.md +48 -0
  30. package/scripts/gepa/generations/gen-001/baseline.md +172 -0
  31. package/scripts/gepa/generations/gen-001/variant-a.md +176 -0
  32. package/scripts/gepa/generations/gen-001/variant-b.md +266 -0
  33. package/scripts/gepa/generations/gen-001/variant-c.md +142 -0
  34. package/scripts/gepa/generations/gen-001/variant-d.md +172 -0
  35. package/scripts/gepa/hooks/reflect.js +44 -5
  36. package/scripts/gepa/optimize.js +281 -39
  37. package/scripts/gepa/results/eval-1-baseline.json +218 -0
  38. package/scripts/gepa/results/eval-1-variant-a.json +218 -0
  39. package/scripts/gepa/results/eval-1-variant-b.json +218 -0
  40. package/scripts/gepa/results/eval-1-variant-c.json +198 -0
  41. package/scripts/gepa/results/eval-1-variant-d.json +198 -0
  42. package/scripts/gepa/state.json +44 -5
  43. package/scripts/install-claude-hooks-auto.js +176 -44
  44. package/templates/claude-hooks/auto-checkpoint.js +174 -0
  45. package/templates/claude-hooks/chime-on-stop.sh +22 -0
  46. package/templates/claude-hooks/session-rescue.sh +15 -0
  47. package/templates/claude-hooks/stop-checkpoint.js +120 -0
@@ -13,7 +13,8 @@ class MCPToolDefinitions {
13
13
  ...this.getLinearTools(),
14
14
  ...this.getTraceTools(),
15
15
  ...this.getDiscoveryTools(),
16
- ...this.getEditTools()
16
+ ...this.getEditTools(),
17
+ ...this.getGraphitiTools()
17
18
  ];
18
19
  }
19
20
  /**
@@ -705,6 +706,55 @@ class MCPToolDefinitions {
705
706
  }
706
707
  ];
707
708
  }
709
+ /**
710
+ * Graphiti knowledge graph tools
711
+ */
712
+ getGraphitiTools() {
713
+ return [
714
+ {
715
+ name: "graphiti_status",
716
+ description: "Check Graphiti temporal knowledge graph connection status",
717
+ inputSchema: {
718
+ type: "object",
719
+ properties: {}
720
+ }
721
+ },
722
+ {
723
+ name: "graphiti_query",
724
+ description: "Query the Graphiti temporal knowledge graph for entities, relations, and episodes",
725
+ inputSchema: {
726
+ type: "object",
727
+ properties: {
728
+ query: {
729
+ type: "string",
730
+ description: "Semantic text query"
731
+ },
732
+ entityTypes: {
733
+ type: "array",
734
+ items: { type: "string" },
735
+ description: 'Entity types to filter (e.g., ["Person", "File", "Issue"])'
736
+ },
737
+ validFrom: {
738
+ type: "number",
739
+ description: "Start of time window (epoch ms)"
740
+ },
741
+ validTo: {
742
+ type: "number",
743
+ description: "End of time window (epoch ms)"
744
+ },
745
+ maxHops: {
746
+ type: "number",
747
+ description: "Graph traversal depth (default 2)"
748
+ },
749
+ k: {
750
+ type: "number",
751
+ description: "Top-k results (default 20)"
752
+ }
753
+ }
754
+ }
755
+ }
756
+ ];
757
+ }
708
758
  /**
709
759
  * Get tool definition by name
710
760
  */
@@ -728,6 +778,8 @@ class MCPToolDefinitions {
728
778
  return this.getDiscoveryTools();
729
779
  case "edit":
730
780
  return this.getEditTools();
781
+ case "graphiti":
782
+ return this.getGraphitiTools();
731
783
  default:
732
784
  return [];
733
785
  }
@@ -948,6 +948,25 @@ class ClaudeSkillsManager {
948
948
  };
949
949
  }
950
950
  }
951
+ case "agent": {
952
+ const { ParallelAgentSkill } = await import("./parallel-agent-skill.js");
953
+ const agentSkill = new ParallelAgentSkill(this.context);
954
+ const sub = args[0];
955
+ const agentOpts = { timeout: options?.timeout };
956
+ switch (sub) {
957
+ case "research":
958
+ return agentSkill.research(args.slice(1).join(" "), agentOpts);
959
+ case "maintain":
960
+ return agentSkill.maintain(args.slice(1).join(" "), agentOpts);
961
+ case "spec-run":
962
+ return agentSkill.specRun(args[1], agentOpts);
963
+ default:
964
+ return {
965
+ success: false,
966
+ message: `Unknown: agent ${sub}. Use: research|maintain|spec-run`
967
+ };
968
+ }
969
+ }
951
970
  case "linear-run": {
952
971
  if (!this.linearTaskRunner) {
953
972
  return {
@@ -983,7 +1002,15 @@ class ClaudeSkillsManager {
983
1002
  }
984
1003
  }
985
1004
  getAvailableSkills() {
986
- const skills = ["handoff", "checkpoint", "dig", "dashboard", "api", "spec"];
1005
+ const skills = [
1006
+ "handoff",
1007
+ "checkpoint",
1008
+ "dig",
1009
+ "dashboard",
1010
+ "api",
1011
+ "spec",
1012
+ "agent"
1013
+ ];
987
1014
  if (this.repoIngestionSkill) {
988
1015
  skills.push("repo");
989
1016
  }
@@ -1145,6 +1172,24 @@ Examples:
1145
1172
  /spec dev-spec
1146
1173
  /spec update prompt-plan "Initialize repository and tooling"
1147
1174
  /spec validate prompt-plan
1175
+ `;
1176
+ case "agent":
1177
+ return `
1178
+ /agent research "How does the FTS5 search work?"
1179
+ /agent maintain "Fix the deprecation warning in webhook.ts"
1180
+ /agent spec-run docs/specs/my-feature.md
1181
+
1182
+ Parallel agent skill (Willison patterns) \u2014 spawn isolated Claude agents:
1183
+ research \u2014 Explore codebase, save findings as a frame (read-only)
1184
+ maintain \u2014 Low-stakes fix, produces a .patch file
1185
+ spec-run \u2014 Implement a spec on a branch, validate with lint+test+build
1186
+
1187
+ Options:
1188
+ --timeout <ms> Agent timeout in milliseconds (default: 300000)
1189
+
1190
+ Each agent runs in a disposable /tmp workspace (git clone --depth=1).
1191
+ Patches: git apply .stackmemory/patches/<file>.patch
1192
+ Spec branches: cd /tmp/sm-spec-* && git log --oneline
1148
1193
  `;
1149
1194
  case "linear-run":
1150
1195
  return `
@@ -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
+ };