@stackmemoryai/stackmemory 1.6.1 → 1.6.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.
@@ -394,6 +394,68 @@ function spawnClaudePrint(prompt, timeoutMs = 12e4) {
394
394
  child.stdin.end();
395
395
  });
396
396
  }
397
+ function printSimpleDiff(oldText, newText) {
398
+ const oldLines = oldText.split("\n");
399
+ const newLines = newText.split("\n");
400
+ const oldSet = new Set(oldLines);
401
+ const newSet = new Set(newLines);
402
+ const removed = oldLines.filter((l) => !newSet.has(l));
403
+ const added = newLines.filter((l) => !oldSet.has(l));
404
+ const removedSet = new Set(removed);
405
+ const addedSet = new Set(added);
406
+ const CONTEXT = 2;
407
+ const changedOld = /* @__PURE__ */ new Set();
408
+ for (let i = 0; i < oldLines.length; i++) {
409
+ if (removedSet.has(oldLines[i])) changedOld.add(i);
410
+ }
411
+ const changedNew = /* @__PURE__ */ new Set();
412
+ for (let i = 0; i < newLines.length; i++) {
413
+ if (addedSet.has(newLines[i])) changedNew.add(i);
414
+ }
415
+ let lastPrinted = -1;
416
+ for (let i = 0; i < oldLines.length; i++) {
417
+ let nearChange = false;
418
+ for (let j = Math.max(0, i - CONTEXT); j <= Math.min(oldLines.length - 1, i + CONTEXT); j++) {
419
+ if (changedOld.has(j)) {
420
+ nearChange = true;
421
+ break;
422
+ }
423
+ }
424
+ if (!nearChange) continue;
425
+ if (lastPrinted >= 0 && i > lastPrinted + 1) {
426
+ console.log(` ${c.d}...${c.r}`);
427
+ }
428
+ if (removedSet.has(oldLines[i])) {
429
+ console.log(` ${c.red}- ${oldLines[i]}${c.r}`);
430
+ } else {
431
+ console.log(` ${oldLines[i]}`);
432
+ }
433
+ lastPrinted = i;
434
+ }
435
+ if (removed.length > 0 && added.length > 0) {
436
+ console.log(` ${c.d}---${c.r}`);
437
+ }
438
+ lastPrinted = -1;
439
+ for (let i = 0; i < newLines.length; i++) {
440
+ let nearChange = false;
441
+ for (let j = Math.max(0, i - CONTEXT); j <= Math.min(newLines.length - 1, i + CONTEXT); j++) {
442
+ if (changedNew.has(j)) {
443
+ nearChange = true;
444
+ break;
445
+ }
446
+ }
447
+ if (!nearChange) continue;
448
+ if (lastPrinted >= 0 && i > lastPrinted + 1) {
449
+ console.log(` ${c.d}...${c.r}`);
450
+ }
451
+ if (addedSet.has(newLines[i])) {
452
+ console.log(` ${c.green}+ ${newLines[i]}${c.r}`);
453
+ } else {
454
+ console.log(` ${newLines[i]}`);
455
+ }
456
+ lastPrinted = i;
457
+ }
458
+ }
397
459
  async function evolvePromptTemplate(input) {
398
460
  const {
399
461
  templatePath,
@@ -402,7 +464,8 @@ async function evolvePromptTemplate(input) {
402
464
  failPhases,
403
465
  errorPatterns,
404
466
  recs,
405
- outcomes
467
+ outcomes,
468
+ dryRun
406
469
  } = input;
407
470
  let currentTemplate;
408
471
  if (existsSync(templatePath)) {
@@ -475,6 +538,17 @@ OUTPUT THE IMPROVED TEMPLATE:`;
475
538
  );
476
539
  return;
477
540
  }
541
+ if (dryRun) {
542
+ console.log(`
543
+ ${c.b}${c.cyan}Dry-run diff:${c.r}
544
+ `);
545
+ printSimpleDiff(currentTemplate, evolved.trim());
546
+ console.log(
547
+ `
548
+ ${c.d}Dry run \u2014 no files modified. Run without --dry-run to apply.${c.r}`
549
+ );
550
+ return;
551
+ }
478
552
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
479
553
  const backupPath = templatePath.replace(".md", `.backup-${timestamp}.md`);
480
554
  copyFileSync(templatePath, backupPath);
@@ -913,6 +987,10 @@ function createConductorCommands() {
913
987
  "--evolve",
914
988
  "Auto-mutate prompt template using GEPA-style evolution from failure data",
915
989
  false
990
+ ).option(
991
+ "--dry-run",
992
+ "Show evolved template without writing (use with --evolve)",
993
+ false
916
994
  ).option(
917
995
  "--predict",
918
996
  "Show difficulty predictions alongside actual outcomes",
@@ -1107,7 +1185,8 @@ function createConductorCommands() {
1107
1185
  failPhases,
1108
1186
  errorPatterns,
1109
1187
  recs,
1110
- outcomes
1188
+ outcomes,
1189
+ dryRun: options.dryRun
1111
1190
  });
1112
1191
  }
1113
1192
  if (options.predict) {
@@ -1329,6 +1408,9 @@ function createConductorCommands() {
1329
1408
  "--model <model>",
1330
1409
  'Model routing: "auto" (complexity-based) or a specific model ID',
1331
1410
  "auto"
1411
+ ).option(
1412
+ "--no-pr",
1413
+ "Disable automatic GitHub PR creation after agent success"
1332
1414
  ).action(async (options) => {
1333
1415
  ensureDefaultPromptTemplate();
1334
1416
  const conductor = new Conductor({
@@ -1344,7 +1426,8 @@ function createConductorCommands() {
1344
1426
  maxRetries: parseInt(options.retries, 10),
1345
1427
  turnTimeoutMs: parseInt(options.turnTimeout, 10),
1346
1428
  agentMode: options.mode === "adapter" ? "adapter" : "cli",
1347
- model: options.model
1429
+ model: options.model,
1430
+ autoPR: options.pr
1348
1431
  });
1349
1432
  await conductor.start();
1350
1433
  });
@@ -38,6 +38,44 @@ function logAgentOutcome(entry) {
38
38
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
39
39
  appendFileSync(getOutcomesLogPath(), JSON.stringify(entry) + "\n");
40
40
  }
41
+ function createPullRequest(opts) {
42
+ try {
43
+ execSync(`git push -u origin "${opts.branch}"`, {
44
+ cwd: opts.workspacePath,
45
+ stdio: "pipe",
46
+ timeout: 6e4
47
+ });
48
+ const prTitle = `feat(conductor): ${opts.issueId} \u2014 ${opts.title}`;
49
+ const prBody = [
50
+ "## Summary",
51
+ "",
52
+ `Automated PR from conductor agent for **${opts.issueId}**.`,
53
+ "",
54
+ `- **Files modified:** ${opts.filesModified}`,
55
+ `- **Tool calls:** ${opts.toolCalls}`,
56
+ "",
57
+ "_This PR was auto-created by StackMemory Conductor._"
58
+ ].join("\n");
59
+ const result = execSync(
60
+ `gh pr create --base "${opts.baseBranch}" --head "${opts.branch}" --title "${prTitle.replace(/"/g, '\\"')}" --body "${prBody.replace(/"/g, '\\"')}"`,
61
+ {
62
+ cwd: opts.workspacePath,
63
+ encoding: "utf-8",
64
+ stdio: ["pipe", "pipe", "pipe"],
65
+ timeout: 3e4
66
+ }
67
+ );
68
+ const prUrl = result.trim();
69
+ logger.info("Created PR", { issueId: opts.issueId, prUrl });
70
+ return prUrl;
71
+ } catch (err) {
72
+ logger.warn("Failed to create PR (best-effort)", {
73
+ issueId: opts.issueId,
74
+ error: err.message
75
+ });
76
+ return null;
77
+ }
78
+ }
41
79
  function getRetryStrategy(issue, outcomes) {
42
80
  if (!outcomes) {
43
81
  const logPath = getOutcomesLogPath();
@@ -771,6 +809,24 @@ class Conductor {
771
809
  await this.runAgent(issue, run);
772
810
  run.status = "completed";
773
811
  this.completeCount++;
812
+ let prUrl;
813
+ if (this.config.autoPR !== false) {
814
+ const wsKey = this.sanitizeIdentifier(issue.identifier);
815
+ const branchName = `conductor/${wsKey}`;
816
+ const url = createPullRequest({
817
+ branch: branchName,
818
+ baseBranch: this.config.baseBranch,
819
+ issueId: issue.identifier,
820
+ title: issue.title,
821
+ filesModified: run.filesModified,
822
+ toolCalls: run.toolCalls,
823
+ workspacePath: run.workspacePath
824
+ });
825
+ if (url) {
826
+ prUrl = url;
827
+ console.log(`[${issue.identifier}] PR created: ${url}`);
828
+ }
829
+ }
774
830
  logAgentOutcome({
775
831
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
776
832
  issue: issue.identifier,
@@ -782,7 +838,8 @@ class Conductor {
782
838
  tokensUsed: run.tokensUsed,
783
839
  durationMs: Date.now() - run.startedAt,
784
840
  hasCommits: true,
785
- labels: issue.labels.map((l) => l.name)
841
+ labels: issue.labels.map((l) => l.name),
842
+ prUrl
786
843
  });
787
844
  await this.runHook(
788
845
  "after-run",
@@ -1714,6 +1771,24 @@ class Conductor {
1714
1771
  } catch {
1715
1772
  }
1716
1773
  }
1774
+ let prUrl;
1775
+ if (hasCommits && this.config.autoPR !== false) {
1776
+ const wsKey = this.sanitizeIdentifier(run.issue.identifier);
1777
+ const branchName = `conductor/${wsKey}`;
1778
+ const url = createPullRequest({
1779
+ branch: branchName,
1780
+ baseBranch: this.config.baseBranch,
1781
+ issueId: run.issue.identifier,
1782
+ title: run.issue.title,
1783
+ filesModified: run.filesModified,
1784
+ toolCalls: run.toolCalls,
1785
+ workspacePath: wsPath
1786
+ });
1787
+ if (url) {
1788
+ prUrl = url;
1789
+ console.log(`[${run.issue.identifier}] PR created: ${url}`);
1790
+ }
1791
+ }
1717
1792
  const durationMs = Date.now() - run.startedAt;
1718
1793
  logAgentOutcome({
1719
1794
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -1727,7 +1802,8 @@ class Conductor {
1727
1802
  durationMs,
1728
1803
  hasCommits,
1729
1804
  labels: run.issue.labels.map((l) => l.name),
1730
- errorTail
1805
+ errorTail,
1806
+ prUrl
1731
1807
  });
1732
1808
  if (hasCommits) {
1733
1809
  logger.info("Stale agent had commits, transitioning to In Review", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackmemoryai/stackmemory",
3
- "version": "1.6.1",
3
+ "version": "1.6.2",
4
4
  "description": "Lossless, project-scoped memory for AI coding tools. Durable context across sessions with 56 MCP tools, FTS5 search, conductor orchestrator, loop/watch monitoring, snapshot capture, pre-flight overlap checks, Claude/Codex/OpenCode wrappers, Linear sync, and automatic hooks.",
5
5
  "engines": {
6
6
  "node": ">=20.0.0",