@stackmemoryai/stackmemory 1.6.0 → 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.
- package/dist/src/cli/commands/orchestrate.js +334 -4
- package/dist/src/cli/commands/orchestrator.js +283 -24
- package/package.json +1 -2
- package/scripts/gepa/.before-optimize.md +0 -112
- package/scripts/gepa/README.md +0 -275
- package/scripts/gepa/config.json +0 -59
- package/scripts/gepa/evals/coding-tasks.jsonl +0 -5
- package/scripts/gepa/evals/fixtures/api-endpoint.ts +0 -31
- package/scripts/gepa/evals/fixtures/brittle-integration.ts +0 -38
- package/scripts/gepa/evals/fixtures/buggy-loop.js +0 -18
- package/scripts/gepa/evals/fixtures/callback-hell.js +0 -53
- package/scripts/gepa/evals/fixtures/fts5-triggers.sql +0 -23
- package/scripts/gepa/evals/fixtures/leaky-service.ts +0 -70
- package/scripts/gepa/evals/fixtures/mcp-dispatch-stub.ts +0 -39
- package/scripts/gepa/evals/fixtures/pr-diff.txt +0 -24
- package/scripts/gepa/evals/fixtures/unsafe-webhook.ts +0 -34
- package/scripts/gepa/evals/fixtures/unwrapped-db-op.ts +0 -42
- package/scripts/gepa/evals/stackmemory-tasks.jsonl +0 -8
- package/scripts/gepa/generations/gen-000/baseline.md +0 -112
- package/scripts/gepa/generations/gen-001/baseline.md +0 -112
- package/scripts/gepa/generations/gen-001/variant-a.md +0 -107
- package/scripts/gepa/generations/gen-001/variant-b.md +0 -216
- package/scripts/gepa/generations/gen-001/variant-c.md +0 -83
- package/scripts/gepa/generations/gen-001/variant-d.md +0 -90
- package/scripts/gepa/hooks/auto-optimize.js +0 -494
- package/scripts/gepa/hooks/eval-tracker.js +0 -203
- package/scripts/gepa/hooks/reflect.js +0 -350
- package/scripts/gepa/optimize.js +0 -853
- package/scripts/gepa/results/eval-1-baseline.json +0 -218
- package/scripts/gepa/results/eval-1-variant-a.json +0 -218
- package/scripts/gepa/results/eval-1-variant-b.json +0 -218
- package/scripts/gepa/results/eval-1-variant-c.json +0 -218
- package/scripts/gepa/results/eval-1-variant-d.json +0 -218
- package/scripts/gepa/state.json +0 -49
|
@@ -38,6 +38,124 @@ 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
|
+
}
|
|
79
|
+
function getRetryStrategy(issue, outcomes) {
|
|
80
|
+
if (!outcomes) {
|
|
81
|
+
const logPath = getOutcomesLogPath();
|
|
82
|
+
if (!existsSync(logPath)) {
|
|
83
|
+
return { shouldRetry: true, adjustments: [] };
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
const lines = readFileSync(logPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
87
|
+
outcomes = lines.map((l) => JSON.parse(l));
|
|
88
|
+
} catch {
|
|
89
|
+
return { shouldRetry: true, adjustments: [] };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const issueOutcomes = outcomes.filter((o) => o.issue === issue);
|
|
93
|
+
const relevant = issueOutcomes.length > 0 ? issueOutcomes : outcomes;
|
|
94
|
+
const failures = relevant.filter(
|
|
95
|
+
(o) => o.outcome === "failure" || o.outcome === "partial"
|
|
96
|
+
);
|
|
97
|
+
if (failures.length === 0) {
|
|
98
|
+
return { shouldRetry: true, adjustments: [] };
|
|
99
|
+
}
|
|
100
|
+
const lastFailure = failures[failures.length - 1];
|
|
101
|
+
if (lastFailure.errorTail && /429|rate.?limit|too many requests|usage.?limit|overloaded/i.test(
|
|
102
|
+
lastFailure.errorTail
|
|
103
|
+
)) {
|
|
104
|
+
return {
|
|
105
|
+
shouldRetry: false,
|
|
106
|
+
reason: "Last failure was a rate limit (429) \u2014 retrying immediately will not help",
|
|
107
|
+
adjustments: []
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (issueOutcomes.length > 0) {
|
|
111
|
+
const issueFailures = issueOutcomes.filter(
|
|
112
|
+
(o) => o.outcome === "failure" || o.outcome === "partial"
|
|
113
|
+
);
|
|
114
|
+
const phaseFailCounts = /* @__PURE__ */ new Map();
|
|
115
|
+
for (const f of issueFailures) {
|
|
116
|
+
phaseFailCounts.set(f.phase, (phaseFailCounts.get(f.phase) || 0) + 1);
|
|
117
|
+
}
|
|
118
|
+
for (const [phase, count] of phaseFailCounts) {
|
|
119
|
+
if (count >= 2) {
|
|
120
|
+
return {
|
|
121
|
+
shouldRetry: false,
|
|
122
|
+
reason: `Issue has failed ${count} times in '${phase}' phase \u2014 likely a structural problem`,
|
|
123
|
+
adjustments: []
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const adjustments = [];
|
|
129
|
+
if (lastFailure.errorTail) {
|
|
130
|
+
const tail = lastFailure.errorTail;
|
|
131
|
+
if (/timeout|timed?.?out|ETIMEDOUT|ESOCKETTIMEDOUT/i.test(tail)) {
|
|
132
|
+
adjustments.push(
|
|
133
|
+
"Previous attempt timed out. Work in smaller increments and commit partial progress early."
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
if (/lint|eslint|prettier|formatting|style.?error/i.test(tail)) {
|
|
137
|
+
adjustments.push(
|
|
138
|
+
"Previous attempt failed on linting. Run `npm run lint:fix` after each file change and fix all lint errors before committing."
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
if (/test.?fail|assertion|expect|vitest|jest|FAIL/i.test(tail)) {
|
|
142
|
+
adjustments.push(
|
|
143
|
+
"Previous attempt failed tests. Run tests incrementally after each change. Check existing test expectations before modifying code."
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
if (/build.?fail|tsc|type.?error|TS\d{4}|compile/i.test(tail)) {
|
|
147
|
+
adjustments.push(
|
|
148
|
+
"Previous attempt had build/type errors. Run `npm run build` after changes and fix type errors before committing."
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
if (/cannot find module|ERR_MODULE_NOT_FOUND|import/i.test(tail)) {
|
|
152
|
+
adjustments.push(
|
|
153
|
+
"Previous attempt had module resolution errors. Ensure all imports use .js extensions for relative paths (ESM)."
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return { shouldRetry: true, adjustments };
|
|
158
|
+
}
|
|
41
159
|
function findPackageRoot() {
|
|
42
160
|
const currentFile = fileURLToPath(import.meta.url);
|
|
43
161
|
let dir = dirname(currentFile);
|
|
@@ -136,6 +254,52 @@ function inferPhase(msg) {
|
|
|
136
254
|
}
|
|
137
255
|
return null;
|
|
138
256
|
}
|
|
257
|
+
const SIMPLE_LABELS = ["bug", "fix", "typo", "chore", "docs", "hotfix"];
|
|
258
|
+
const COMPLEX_LABELS = [
|
|
259
|
+
"feature",
|
|
260
|
+
"refactor",
|
|
261
|
+
"architecture",
|
|
262
|
+
"migration",
|
|
263
|
+
"security",
|
|
264
|
+
"performance"
|
|
265
|
+
];
|
|
266
|
+
function estimateIssueComplexity(issue, attempt) {
|
|
267
|
+
let score = 0;
|
|
268
|
+
const descLen = (issue.description || "").length;
|
|
269
|
+
if (descLen > 800) score += 2;
|
|
270
|
+
else if (descLen > 400) score += 1;
|
|
271
|
+
else if (descLen < 200) score -= 1;
|
|
272
|
+
const labelNames = (issue.labels || []).map((l) => l.name.toLowerCase());
|
|
273
|
+
for (const label of labelNames) {
|
|
274
|
+
if (SIMPLE_LABELS.some((s) => label.includes(s))) score -= 1;
|
|
275
|
+
if (COMPLEX_LABELS.some((c) => label.includes(c))) score += 2;
|
|
276
|
+
}
|
|
277
|
+
const titleLower = issue.title.toLowerCase();
|
|
278
|
+
if (SIMPLE_LABELS.some((s) => titleLower.includes(s))) score -= 1;
|
|
279
|
+
if (COMPLEX_LABELS.some((c) => titleLower.includes(c))) score += 1;
|
|
280
|
+
if (issue.priority === 1) score += 2;
|
|
281
|
+
else if (issue.priority === 2) score += 1;
|
|
282
|
+
if (issue.estimate) {
|
|
283
|
+
if (issue.estimate >= 5) score += 2;
|
|
284
|
+
else if (issue.estimate >= 3) score += 1;
|
|
285
|
+
else if (issue.estimate <= 1) score -= 1;
|
|
286
|
+
}
|
|
287
|
+
if (attempt && attempt > 1) score += 2;
|
|
288
|
+
if (score >= 3) return "complex";
|
|
289
|
+
if (score >= 1) return "moderate";
|
|
290
|
+
return "simple";
|
|
291
|
+
}
|
|
292
|
+
function selectModelForIssue(complexity, config) {
|
|
293
|
+
const modelSetting = config.model || "auto";
|
|
294
|
+
if (modelSetting !== "auto") return modelSetting;
|
|
295
|
+
switch (complexity) {
|
|
296
|
+
case "simple":
|
|
297
|
+
case "moderate":
|
|
298
|
+
return "claude-sonnet-4-20250514";
|
|
299
|
+
case "complex":
|
|
300
|
+
return "claude-opus-4-20250514";
|
|
301
|
+
}
|
|
302
|
+
}
|
|
139
303
|
const DEFAULT_CONFIG = {
|
|
140
304
|
activeStates: ["Todo"],
|
|
141
305
|
terminalStates: ["Done", "Cancelled", "Canceled", "Closed"],
|
|
@@ -645,6 +809,24 @@ class Conductor {
|
|
|
645
809
|
await this.runAgent(issue, run);
|
|
646
810
|
run.status = "completed";
|
|
647
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
|
+
}
|
|
648
830
|
logAgentOutcome({
|
|
649
831
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
650
832
|
issue: issue.identifier,
|
|
@@ -655,7 +837,9 @@ class Conductor {
|
|
|
655
837
|
filesModified: run.filesModified,
|
|
656
838
|
tokensUsed: run.tokensUsed,
|
|
657
839
|
durationMs: Date.now() - run.startedAt,
|
|
658
|
-
hasCommits: true
|
|
840
|
+
hasCommits: true,
|
|
841
|
+
labels: issue.labels.map((l) => l.name),
|
|
842
|
+
prUrl
|
|
659
843
|
});
|
|
660
844
|
await this.runHook(
|
|
661
845
|
"after-run",
|
|
@@ -678,6 +862,20 @@ class Conductor {
|
|
|
678
862
|
error: run.error,
|
|
679
863
|
attempt: run.attempt
|
|
680
864
|
});
|
|
865
|
+
logAgentOutcome({
|
|
866
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
867
|
+
issue: issue.identifier,
|
|
868
|
+
attempt: run.attempt,
|
|
869
|
+
outcome: "failure",
|
|
870
|
+
phase: run.phase,
|
|
871
|
+
toolCalls: run.toolCalls,
|
|
872
|
+
filesModified: run.filesModified,
|
|
873
|
+
tokensUsed: run.tokensUsed,
|
|
874
|
+
durationMs: Date.now() - run.startedAt,
|
|
875
|
+
hasCommits: false,
|
|
876
|
+
labels: issue.labels.map((l) => l.name),
|
|
877
|
+
errorTail: run.error?.slice(-500)
|
|
878
|
+
});
|
|
681
879
|
if (this.handleRateLimitError(run.error, issue.identifier)) {
|
|
682
880
|
throw err;
|
|
683
881
|
}
|
|
@@ -691,6 +889,23 @@ class Conductor {
|
|
|
691
889
|
});
|
|
692
890
|
}
|
|
693
891
|
if (run.attempt < maxAttempts && !this.stopping) {
|
|
892
|
+
const strategy = getRetryStrategy(issue.identifier);
|
|
893
|
+
if (!strategy.shouldRetry) {
|
|
894
|
+
console.log(
|
|
895
|
+
`[${issue.identifier}] Skipping retry: ${strategy.reason}`
|
|
896
|
+
);
|
|
897
|
+
logger.info("Retry skipped by intelligence", {
|
|
898
|
+
identifier: issue.identifier,
|
|
899
|
+
reason: strategy.reason
|
|
900
|
+
});
|
|
901
|
+
throw err;
|
|
902
|
+
}
|
|
903
|
+
if (strategy.adjustments.length > 0) {
|
|
904
|
+
run.retryAdjustments = strategy.adjustments;
|
|
905
|
+
console.log(
|
|
906
|
+
`[${issue.identifier}] Retry with adjustments: ${strategy.adjustments.length} hint(s)`
|
|
907
|
+
);
|
|
908
|
+
}
|
|
694
909
|
console.log(
|
|
695
910
|
`[${issue.identifier}] Failed (attempt ${run.attempt}), retrying...`
|
|
696
911
|
);
|
|
@@ -1024,25 +1239,33 @@ class Conductor {
|
|
|
1024
1239
|
*/
|
|
1025
1240
|
runAgentCLI(issue, run) {
|
|
1026
1241
|
return new Promise((resolve, reject) => {
|
|
1027
|
-
const prompt = this.buildPrompt(issue, run.attempt);
|
|
1242
|
+
const prompt = this.buildPrompt(issue, run.attempt, run.retryAdjustments);
|
|
1243
|
+
const complexity = estimateIssueComplexity(issue, run.attempt);
|
|
1244
|
+
const selectedModel = selectModelForIssue(complexity, this.config);
|
|
1245
|
+
logger.info("Agent model selected", {
|
|
1246
|
+
identifier: issue.identifier,
|
|
1247
|
+
complexity,
|
|
1248
|
+
model: selectedModel || "(default)",
|
|
1249
|
+
reason: this.config.model && this.config.model !== "auto" ? "config override" : `auto: ${complexity} issue`
|
|
1250
|
+
});
|
|
1028
1251
|
const claudeBin = this.findClaudeBinary();
|
|
1029
|
-
const
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
);
|
|
1252
|
+
const args = [
|
|
1253
|
+
"-p",
|
|
1254
|
+
"--output-format",
|
|
1255
|
+
"stream-json",
|
|
1256
|
+
"--dangerously-skip-permissions",
|
|
1257
|
+
"--settings",
|
|
1258
|
+
'{"hooks":{}}'
|
|
1259
|
+
];
|
|
1260
|
+
if (selectedModel) {
|
|
1261
|
+
args.push("--model", selectedModel);
|
|
1262
|
+
}
|
|
1263
|
+
args.push(prompt);
|
|
1264
|
+
const proc = spawn(claudeBin, args, {
|
|
1265
|
+
cwd: run.workspacePath,
|
|
1266
|
+
env: this.buildAgentEnv(issue, run),
|
|
1267
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1268
|
+
});
|
|
1046
1269
|
run.process = proc;
|
|
1047
1270
|
proc.stdin.end();
|
|
1048
1271
|
const logStream = this.openAgentLogStream(issue.identifier);
|
|
@@ -1146,7 +1369,7 @@ class Conductor {
|
|
|
1146
1369
|
*/
|
|
1147
1370
|
runAgentAdapter(issue, run) {
|
|
1148
1371
|
return new Promise((resolve, reject) => {
|
|
1149
|
-
const prompt = this.buildPrompt(issue, run.attempt);
|
|
1372
|
+
const prompt = this.buildPrompt(issue, run.attempt, run.retryAdjustments);
|
|
1150
1373
|
const proc = spawn("node", [this.config.appServerPath], {
|
|
1151
1374
|
cwd: run.workspacePath,
|
|
1152
1375
|
env: this.buildAgentEnv(issue, run),
|
|
@@ -1301,7 +1524,7 @@ class Conductor {
|
|
|
1301
1524
|
* Template variables: {{ISSUE_ID}}, {{TITLE}}, {{DESCRIPTION}},
|
|
1302
1525
|
* {{LABELS}}, {{PRIORITY}}, {{ATTEMPT}}, {{PRIOR_CONTEXT}}
|
|
1303
1526
|
*/
|
|
1304
|
-
buildPrompt(issue, attempt) {
|
|
1527
|
+
buildPrompt(issue, attempt, retryAdjustments) {
|
|
1305
1528
|
const templatePath = join(
|
|
1306
1529
|
homedir(),
|
|
1307
1530
|
".stackmemory",
|
|
@@ -1310,7 +1533,20 @@ class Conductor {
|
|
|
1310
1533
|
);
|
|
1311
1534
|
const priority = ["None", "Urgent", "High", "Medium", "Low"][issue.priority] || "None";
|
|
1312
1535
|
const labels = issue.labels.length > 0 ? issue.labels.map((l) => l.name).join(", ") : "";
|
|
1313
|
-
const
|
|
1536
|
+
const contextParts = [];
|
|
1537
|
+
if (attempt > 1) {
|
|
1538
|
+
contextParts.push(
|
|
1539
|
+
`This is attempt ${attempt}. Check .stackmemory/conductor-context.md for context from prior attempts.`
|
|
1540
|
+
);
|
|
1541
|
+
}
|
|
1542
|
+
if (retryAdjustments && retryAdjustments.length > 0) {
|
|
1543
|
+
contextParts.push(
|
|
1544
|
+
"## Retry Adjustments (from prior failure analysis)",
|
|
1545
|
+
"",
|
|
1546
|
+
...retryAdjustments.map((a) => `- ${a}`)
|
|
1547
|
+
);
|
|
1548
|
+
}
|
|
1549
|
+
const priorContext = contextParts.join("\n");
|
|
1314
1550
|
if (existsSync(templatePath)) {
|
|
1315
1551
|
try {
|
|
1316
1552
|
let template = readFileSync(templatePath, "utf-8");
|
|
@@ -1535,6 +1771,24 @@ class Conductor {
|
|
|
1535
1771
|
} catch {
|
|
1536
1772
|
}
|
|
1537
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
|
+
}
|
|
1538
1792
|
const durationMs = Date.now() - run.startedAt;
|
|
1539
1793
|
logAgentOutcome({
|
|
1540
1794
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1547,7 +1801,9 @@ class Conductor {
|
|
|
1547
1801
|
tokensUsed: run.tokensUsed,
|
|
1548
1802
|
durationMs,
|
|
1549
1803
|
hasCommits,
|
|
1550
|
-
|
|
1804
|
+
labels: run.issue.labels.map((l) => l.name),
|
|
1805
|
+
errorTail,
|
|
1806
|
+
prUrl
|
|
1551
1807
|
});
|
|
1552
1808
|
if (hasCommits) {
|
|
1553
1809
|
logger.info("Stale agent had commits, transitioning to In Review", {
|
|
@@ -1631,6 +1887,9 @@ class Conductor {
|
|
|
1631
1887
|
}
|
|
1632
1888
|
export {
|
|
1633
1889
|
Conductor,
|
|
1890
|
+
estimateIssueComplexity,
|
|
1634
1891
|
getAgentStatusDir,
|
|
1635
|
-
getOutcomesLogPath
|
|
1892
|
+
getOutcomesLogPath,
|
|
1893
|
+
getRetryStrategy,
|
|
1894
|
+
selectModelForIssue
|
|
1636
1895
|
};
|
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",
|
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
"files": [
|
|
21
21
|
"bin",
|
|
22
22
|
"dist/src",
|
|
23
|
-
"scripts/gepa",
|
|
24
23
|
"scripts/git-hooks",
|
|
25
24
|
"scripts/hooks",
|
|
26
25
|
"scripts/setup",
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
# CLAUDE.md
|
|
2
|
-
|
|
3
|
-
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
-
|
|
5
|
-
## Project Overview
|
|
6
|
-
|
|
7
|
-
Sol is the monorepo for **Rize**, an automatic time tracking application. The stack consists of:
|
|
8
|
-
- **api/** - Rails 7.1 GraphQL backend (Ruby 3.3.5)
|
|
9
|
-
- **web/** - Next.js 14 React web app (Node 22)
|
|
10
|
-
- **electron/** - Electron desktop app (Node 22)
|
|
11
|
-
- **services/** - Bun-based TypeScript event consumers/workers
|
|
12
|
-
- **vanity/** - Webflow marketing site scripts (deprecated)
|
|
13
|
-
- **voyager/** - Marketing website for home and landing pageas (Next.js)
|
|
14
|
-
- **puppet/** - Puppeteer server for images/PDFs
|
|
15
|
-
- **chrome/** - Chrome browser extension
|
|
16
|
-
- **docs/** - Docusaurus documentation site
|
|
17
|
-
- **zapier/** - Zapier integration
|
|
18
|
-
|
|
19
|
-
## Development Commands
|
|
20
|
-
|
|
21
|
-
### Starting Development Environment
|
|
22
|
-
```bash
|
|
23
|
-
# Start all services (requires iTerm2 on macOS)
|
|
24
|
-
./scripts/run-dev.sh
|
|
25
|
-
|
|
26
|
-
# Or start individually:
|
|
27
|
-
cd api && hivemind Procfile.dev # Rails + AnyCable + Sidekiq + Clockwork
|
|
28
|
-
cd web && npm run dev # Next.js dev server
|
|
29
|
-
cd electron && npm run dev # Electron with hot reload
|
|
30
|
-
cd services && hivemind Procfile.dev # Bun services
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
### Docker Dependencies (api/docker-compose.yml)
|
|
34
|
-
```bash
|
|
35
|
-
cd api && docker-compose up -d
|
|
36
|
-
# TimescaleDB: localhost:15432
|
|
37
|
-
# Redis: localhost:16379
|
|
38
|
-
# Kafka: localhost:9092
|
|
39
|
-
# MySQL: localhost:13306
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
### Testing
|
|
43
|
-
```bash
|
|
44
|
-
# API (RSpec)
|
|
45
|
-
cd api && bundle exec rspec
|
|
46
|
-
cd api && bundle exec rspec spec/path/to/file_spec.rb # Single file
|
|
47
|
-
cd api && bundle exec rspec spec/path/to/file_spec.rb:42 # Single test at line
|
|
48
|
-
|
|
49
|
-
# Electron (Jest)
|
|
50
|
-
cd electron && npm test
|
|
51
|
-
cd electron && npm run test:watch
|
|
52
|
-
cd electron && npm run test:coverage
|
|
53
|
-
|
|
54
|
-
# Web - no active tests (exits 0)
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
### Building
|
|
58
|
-
```bash
|
|
59
|
-
cd api && bundle install && rake db:migrate
|
|
60
|
-
cd web && npm run build # Runs gql-gen, tailwind, next build
|
|
61
|
-
cd electron && npm run build # Electron Forge make
|
|
62
|
-
cd services && bun install
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
### GraphQL Code Generation
|
|
66
|
-
```bash
|
|
67
|
-
cd web && npm run build # Includes gql codegen
|
|
68
|
-
cd electron && npm run dev # Runs gql codegen as part of dev
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
## Architecture
|
|
72
|
-
|
|
73
|
-
### GraphQL API Structure
|
|
74
|
-
The API exposes two GraphQL endpoints:
|
|
75
|
-
- **api/v1** - Public API (OAuth consumers, Zapier)
|
|
76
|
-
- **private/v1** - Private API (web, electron apps)
|
|
77
|
-
|
|
78
|
-
Located at `api/app/graphql/{api,private}/v1/`
|
|
79
|
-
|
|
80
|
-
### Real-time Communication
|
|
81
|
-
- **AnyCable** WebSocket server for subscriptions
|
|
82
|
-
- ActionCable channels in `api/app/channels/`
|
|
83
|
-
- WebSocket config: `api/config/cable.yml` and `api/config/anycable.yml`
|
|
84
|
-
|
|
85
|
-
### Background Jobs
|
|
86
|
-
- **Sidekiq** for async job processing (`api/config/sidekiq.yml`)
|
|
87
|
-
- **Clockwork** for scheduled jobs (`api/config/clock.rb`)
|
|
88
|
-
|
|
89
|
-
### Event Streaming
|
|
90
|
-
- **Kafka** for event publishing/consumption
|
|
91
|
-
- Services consume events via `services/consumers/`
|
|
92
|
-
- Kafka config: `api/config/initializers/kafka.rb`
|
|
93
|
-
|
|
94
|
-
### Databases
|
|
95
|
-
- **Primary PostgreSQL** - Main application data
|
|
96
|
-
- **TimescaleDB** - Time-series data (separate connection in `database.yml`)
|
|
97
|
-
- **MySQL** - Legacy/external integrations
|
|
98
|
-
- **Redis** - Caching, ActionCable, Sidekiq
|
|
99
|
-
|
|
100
|
-
## Style Guidelines
|
|
101
|
-
|
|
102
|
-
### JavaScript/TypeScript
|
|
103
|
-
- Use `test()` instead of `it()` in tests
|
|
104
|
-
- Use `toBeCalled()` instead of `toHaveBeenCalledWith()` in jest assertions
|
|
105
|
-
|
|
106
|
-
## Key Configuration Files
|
|
107
|
-
|
|
108
|
-
- `api/config/database.yml` - Database connections (primary + timescale)
|
|
109
|
-
- `api/config/cable.yml` - AnyCable WebSocket config
|
|
110
|
-
- `api/Procfile.dev` - Development processes (rails, anycable, sidekiq, clockwork)
|
|
111
|
-
- `sol.code-workspace` - VS Code multi-folder workspace
|
|
112
|
-
- Each project requires its own `.env` file (not in repo)
|