@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.
|
|
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",
|