@stackmemoryai/stackmemory 1.10.5 → 1.12.0
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/README.md +104 -23
- package/dist/src/cli/claude-sm.js +266 -84
- package/dist/src/cli/codex-sm.js +185 -33
- package/dist/src/cli/commands/bench.js +209 -2
- package/dist/src/cli/commands/cache.js +126 -0
- package/dist/src/cli/commands/daemon.js +41 -0
- package/dist/src/cli/commands/handoff.js +40 -9
- package/dist/src/cli/commands/onboard.js +70 -3
- package/dist/src/cli/commands/optimize.js +117 -0
- package/dist/src/cli/commands/orchestrate.js +230 -5
- package/dist/src/cli/commands/orchestrator.js +312 -24
- package/dist/src/cli/commands/pack.js +322 -0
- package/dist/src/cli/commands/search.js +40 -1
- package/dist/src/cli/commands/setup.js +177 -7
- package/dist/src/cli/commands/skills.js +10 -1
- package/dist/src/cli/commands/state.js +265 -0
- package/dist/src/cli/gemini-sm.js +19 -29
- package/dist/src/cli/index.js +90 -29
- package/dist/src/cli/opencode-sm.js +38 -21
- package/dist/src/cli/utils/determinism-watcher.js +66 -0
- package/dist/src/cli/utils/real-cli-bin.js +44 -0
- package/dist/src/core/cache/content-cache.js +238 -0
- package/dist/src/core/cache/index.js +11 -0
- package/dist/src/core/cache/token-estimator.js +16 -0
- package/dist/src/core/context/frame-database.js +38 -30
- package/dist/src/core/cross-search/cross-project-search.js +269 -0
- package/dist/src/core/{merge → cross-search}/index.js +6 -4
- package/dist/src/core/database/sqlite-adapter.js +0 -83
- package/dist/src/core/extensions/provider-adapter.js +5 -0
- package/dist/src/core/models/model-router.js +22 -2
- package/dist/src/core/monitoring/logger.js +2 -1
- package/dist/src/core/optimization/trace-optimizer.js +413 -0
- package/dist/src/core/provenance/confidence-scorer.js +128 -0
- package/dist/src/core/provenance/index.js +40 -0
- package/dist/src/core/provenance/provenance-store.js +194 -0
- package/dist/src/core/provenance/types.js +82 -0
- package/dist/src/core/session/project-handoff.js +64 -0
- package/dist/src/core/session/session-manager.js +28 -0
- package/dist/src/core/shared-state/canonical-store.js +564 -0
- package/dist/src/core/skill-packs/index.js +18 -0
- package/dist/src/core/skill-packs/parser.js +42 -0
- package/dist/src/core/skill-packs/registry.js +224 -0
- package/dist/src/core/skill-packs/types.js +66 -0
- package/dist/src/core/trace/trace-event-store.js +282 -0
- package/dist/src/core/trace/trace-event.js +4 -0
- package/dist/src/daemon/daemon-config.js +7 -0
- package/dist/src/daemon/services/github-service.js +126 -0
- package/dist/src/daemon/unified-daemon.js +30 -0
- package/dist/src/features/sweep/pty-wrapper.js +13 -5
- package/dist/src/hooks/schemas.js +2 -0
- package/dist/src/integrations/claude-code/subagent-client.js +89 -0
- package/dist/src/integrations/github/pr-state.js +158 -0
- package/dist/src/integrations/linear/client.js +4 -1
- package/dist/src/integrations/mcp/handlers/cross-search-handlers.js +188 -0
- package/dist/src/integrations/mcp/handlers/index.js +40 -59
- package/dist/src/integrations/mcp/server.js +425 -311
- package/dist/src/integrations/mcp/tool-alias-registry.js +370 -0
- package/dist/src/integrations/mcp/tool-definitions.js +98 -229
- package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +3 -40
- package/dist/src/integrations/ralph/learning/pattern-learner.js +1 -20
- package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +0 -2
- package/dist/src/mcp/stackmemory-mcp-server.js +315 -0
- package/dist/src/orchestrators/multimodal/determinism.js +243 -0
- package/dist/src/orchestrators/multimodal/harness.js +147 -77
- package/dist/src/orchestrators/multimodal/providers.js +44 -3
- package/dist/src/utils/hook-installer.js +0 -8
- package/package.json +10 -1
- package/packs/coding/python-fastapi/instructions.md +60 -0
- package/packs/coding/python-fastapi/pack.yaml +28 -0
- package/packs/coding/typescript-react/instructions.md +47 -0
- package/packs/coding/typescript-react/pack.yaml +28 -0
- package/packs/core/commands/capture.md +32 -0
- package/packs/core/commands/learn.md +73 -0
- package/packs/core/commands/next.md +36 -0
- package/packs/core/commands/restart.md +58 -0
- package/packs/core/commands/restore.md +29 -0
- package/packs/core/commands/start.md +57 -0
- package/packs/core/commands/stop.md +65 -0
- package/packs/core/commands/summary.md +40 -0
- package/packs/core/manifest.json +24 -0
- package/packs/ops/decision-recovery/instructions.md +65 -0
- package/packs/ops/decision-recovery/pack.yaml +89 -0
- package/dist/src/cli/commands/team.js +0 -168
- package/dist/src/core/context/shared-context-layer.js +0 -620
- package/dist/src/core/context/stack-merge-resolver.js +0 -748
- package/dist/src/core/merge/conflict-detector.js +0 -430
- package/dist/src/core/merge/resolution-engine.js +0 -557
- package/dist/src/core/merge/stack-diff.js +0 -531
- package/dist/src/core/merge/unified-merge-resolver.js +0 -302
- package/dist/src/integrations/mcp/handlers/cord-handlers.js +0 -397
- package/dist/src/integrations/mcp/handlers/team-handlers.js +0 -211
- /package/dist/src/core/{merge → cache}/types.js +0 -0
|
@@ -19,6 +19,7 @@ import { createReadStream } from "fs";
|
|
|
19
19
|
import { createInterface } from "readline";
|
|
20
20
|
import { fileURLToPath } from "url";
|
|
21
21
|
import { Transform } from "stream";
|
|
22
|
+
import { createHash } from "crypto";
|
|
22
23
|
import { logger } from "../../core/monitoring/logger.js";
|
|
23
24
|
import { isProcessAlive } from "../../utils/process-cleanup.js";
|
|
24
25
|
import {
|
|
@@ -34,6 +35,59 @@ import {
|
|
|
34
35
|
TraceCollector,
|
|
35
36
|
stringifyEventTruncated
|
|
36
37
|
} from "./conductor-traces.js";
|
|
38
|
+
const PROMPT_PHASES = [
|
|
39
|
+
"system",
|
|
40
|
+
"understand",
|
|
41
|
+
"implement",
|
|
42
|
+
"validate",
|
|
43
|
+
"deliver"
|
|
44
|
+
];
|
|
45
|
+
function buildPromptFromPhases(variables) {
|
|
46
|
+
const promptsDir = join(homedir(), ".stackmemory", "conductor", "prompts");
|
|
47
|
+
const systemPath = join(promptsDir, "system.md");
|
|
48
|
+
if (!existsSync(systemPath)) return null;
|
|
49
|
+
const versions = {};
|
|
50
|
+
const parts = [];
|
|
51
|
+
for (const phase of PROMPT_PHASES) {
|
|
52
|
+
const phasePath = join(promptsDir, `${phase}.md`);
|
|
53
|
+
if (!existsSync(phasePath)) continue;
|
|
54
|
+
let content = readFileSync(phasePath, "utf-8");
|
|
55
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
56
|
+
content = content.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
|
|
57
|
+
}
|
|
58
|
+
parts.push(content);
|
|
59
|
+
const hash = createHash("sha256").update(readFileSync(phasePath, "utf-8")).digest("hex").slice(0, 8);
|
|
60
|
+
versions[phase] = hash;
|
|
61
|
+
}
|
|
62
|
+
if (parts.length === 0) return null;
|
|
63
|
+
const dspyPath = join(
|
|
64
|
+
homedir(),
|
|
65
|
+
".stackmemory",
|
|
66
|
+
"dspy",
|
|
67
|
+
"optimized_state.json"
|
|
68
|
+
);
|
|
69
|
+
if (existsSync(dspyPath)) {
|
|
70
|
+
try {
|
|
71
|
+
const state = JSON.parse(readFileSync(dspyPath, "utf-8"));
|
|
72
|
+
for (const phase of PROMPT_PHASES) {
|
|
73
|
+
const sig = state[phase];
|
|
74
|
+
if (sig?.fewShotExamples?.length) {
|
|
75
|
+
const examples = sig.fewShotExamples.slice(0, 3).map(
|
|
76
|
+
(ex) => `<example>
|
|
77
|
+
Input: ${JSON.stringify(ex.input)}
|
|
78
|
+
Output: ${JSON.stringify(ex.output)}
|
|
79
|
+
</example>`
|
|
80
|
+
).join("\n");
|
|
81
|
+
parts.push(`
|
|
82
|
+
## Optimized Examples (${phase}):
|
|
83
|
+
${examples}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} catch {
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return { prompt: parts.join("\n\n"), versions };
|
|
90
|
+
}
|
|
37
91
|
function getOutcomesLogPath() {
|
|
38
92
|
return join(homedir(), ".stackmemory", "conductor", "outcomes.jsonl");
|
|
39
93
|
}
|
|
@@ -44,11 +98,6 @@ function logAgentOutcome(entry) {
|
|
|
44
98
|
}
|
|
45
99
|
function createPullRequest(opts) {
|
|
46
100
|
try {
|
|
47
|
-
execSync(`git push -u origin "${opts.branch}"`, {
|
|
48
|
-
cwd: opts.workspacePath,
|
|
49
|
-
stdio: "pipe",
|
|
50
|
-
timeout: 6e4
|
|
51
|
-
});
|
|
52
101
|
const prTitle = `feat(conductor): ${opts.issueId} \u2014 ${opts.title}`;
|
|
53
102
|
const prBody = [
|
|
54
103
|
"## Summary",
|
|
@@ -60,6 +109,33 @@ function createPullRequest(opts) {
|
|
|
60
109
|
"",
|
|
61
110
|
"_This PR was auto-created by StackMemory Conductor._"
|
|
62
111
|
].join("\n");
|
|
112
|
+
if (opts.useGitButler) {
|
|
113
|
+
execSync(`but push --branch "${opts.branch}"`, {
|
|
114
|
+
cwd: opts.workspacePath,
|
|
115
|
+
stdio: "pipe",
|
|
116
|
+
timeout: 6e4
|
|
117
|
+
});
|
|
118
|
+
const result2 = execSync(
|
|
119
|
+
`but pr create --branch "${opts.branch}" --title "${prTitle.replace(/"/g, '\\"')}" --body "${prBody.replace(/"/g, '\\"')}"`,
|
|
120
|
+
{
|
|
121
|
+
cwd: opts.workspacePath,
|
|
122
|
+
encoding: "utf-8",
|
|
123
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
124
|
+
timeout: 3e4
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
const prUrl2 = result2.trim();
|
|
128
|
+
logger.info("Created PR via GitButler", {
|
|
129
|
+
issueId: opts.issueId,
|
|
130
|
+
prUrl: prUrl2
|
|
131
|
+
});
|
|
132
|
+
return prUrl2;
|
|
133
|
+
}
|
|
134
|
+
execSync(`git push -u origin "${opts.branch}"`, {
|
|
135
|
+
cwd: opts.workspacePath,
|
|
136
|
+
stdio: "pipe",
|
|
137
|
+
timeout: 6e4
|
|
138
|
+
});
|
|
63
139
|
const result = execSync(
|
|
64
140
|
`gh pr create --base "${opts.baseBranch}" --head "${opts.branch}" --title "${prTitle.replace(/"/g, '\\"')}" --body "${prBody.replace(/"/g, '\\"')}"`,
|
|
65
141
|
{
|
|
@@ -158,8 +234,74 @@ function getRetryStrategy(issue, outcomes) {
|
|
|
158
234
|
);
|
|
159
235
|
}
|
|
160
236
|
}
|
|
237
|
+
const promptsDir = join(homedir(), ".stackmemory", "conductor", "prompts");
|
|
238
|
+
if (lastFailure?.phase && existsSync(join(promptsDir, "system.md"))) {
|
|
239
|
+
const phaseAssertions = getPhaseAssertions(
|
|
240
|
+
lastFailure.phase,
|
|
241
|
+
lastFailure.errorTail || ""
|
|
242
|
+
);
|
|
243
|
+
adjustments.push(...phaseAssertions);
|
|
244
|
+
}
|
|
161
245
|
return { shouldRetry: true, adjustments };
|
|
162
246
|
}
|
|
247
|
+
function getPhaseAssertions(phase, error) {
|
|
248
|
+
const assertions = [];
|
|
249
|
+
switch (phase) {
|
|
250
|
+
case "reading":
|
|
251
|
+
case "planning":
|
|
252
|
+
assertions.push(
|
|
253
|
+
"ASSERTION: Re-read the issue description completely before planning.",
|
|
254
|
+
"ASSERTION: List ALL files you plan to modify before starting implementation."
|
|
255
|
+
);
|
|
256
|
+
break;
|
|
257
|
+
case "implementing":
|
|
258
|
+
if (/scope|unrelated|refactor/i.test(error)) {
|
|
259
|
+
assertions.push(
|
|
260
|
+
"ASSERTION: Only modify files directly required by the issue. Do NOT refactor surrounding code."
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
if (/import|module|ESM/i.test(error)) {
|
|
264
|
+
assertions.push(
|
|
265
|
+
"ASSERTION: Every relative import MUST end with .js extension. Check ALL new imports."
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
assertions.push(
|
|
269
|
+
"ASSERTION: After implementing, review your diff \u2014 if any change is not required by the issue, revert it."
|
|
270
|
+
);
|
|
271
|
+
break;
|
|
272
|
+
case "testing":
|
|
273
|
+
case "linting":
|
|
274
|
+
case "building":
|
|
275
|
+
if (/lint|eslint/i.test(error)) {
|
|
276
|
+
assertions.push(
|
|
277
|
+
"ASSERTION: Run `npm run lint` IMMEDIATELY. Fix every error. Do NOT proceed until lint passes.",
|
|
278
|
+
"ASSERTION: Common lint fixes \u2014 catch {} not catch (_err) {}, remove unused imports, add .js to relative imports."
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
if (/test|vitest|jest|FAIL/i.test(error)) {
|
|
282
|
+
assertions.push(
|
|
283
|
+
"ASSERTION: Read the FULL test error output. Identify which assertion fails and why.",
|
|
284
|
+
"ASSERTION: If vi.clearAllMocks() is in beforeEach, re-set any mockReturnValue calls after it."
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
if (/build|tsc|type/i.test(error)) {
|
|
288
|
+
assertions.push(
|
|
289
|
+
"ASSERTION: Run `npm run build` and fix ALL TypeScript errors before committing."
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
assertions.push(
|
|
293
|
+
"ASSERTION: Do NOT use --no-verify to bypass pre-commit hooks. Fix the underlying issue."
|
|
294
|
+
);
|
|
295
|
+
break;
|
|
296
|
+
case "committing":
|
|
297
|
+
assertions.push(
|
|
298
|
+
"ASSERTION: Commit message must follow format: type(scope): description",
|
|
299
|
+
"ASSERTION: If pre-commit hook fails, fix the issue and create a NEW commit \u2014 do NOT amend."
|
|
300
|
+
);
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
return assertions;
|
|
304
|
+
}
|
|
163
305
|
function findPackageRoot() {
|
|
164
306
|
const currentFile = fileURLToPath(import.meta.url);
|
|
165
307
|
let dir = dirname(currentFile);
|
|
@@ -340,6 +482,8 @@ class Conductor {
|
|
|
340
482
|
stateCache = /* @__PURE__ */ new Map();
|
|
341
483
|
activeStatesLower;
|
|
342
484
|
terminalStatesLower;
|
|
485
|
+
/** Whether to use GitButler virtual branches instead of git worktrees */
|
|
486
|
+
useGitButler = false;
|
|
343
487
|
/** Global rate limit backoff state */
|
|
344
488
|
rateLimit = {
|
|
345
489
|
inBackoff: false,
|
|
@@ -397,21 +541,79 @@ class Conductor {
|
|
|
397
541
|
);
|
|
398
542
|
}
|
|
399
543
|
}
|
|
400
|
-
|
|
544
|
+
const wsMode = this.config.workspaceMode || "auto";
|
|
545
|
+
const laneMode = Boolean(this.config.laneBranch);
|
|
546
|
+
if (laneMode && wsMode === "gitbutler") {
|
|
547
|
+
throw new Error(
|
|
548
|
+
"--lane is only supported with git worktrees. Use --workspace-mode worktree or omit --workspace-mode."
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
if (!laneMode && (wsMode === "gitbutler" || wsMode === "auto")) {
|
|
552
|
+
try {
|
|
553
|
+
const butVersion = execSync("but --version", {
|
|
554
|
+
cwd: this.config.repoRoot,
|
|
555
|
+
encoding: "utf-8",
|
|
556
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
557
|
+
timeout: 5e3
|
|
558
|
+
}).trim();
|
|
559
|
+
const gbDir = join(this.config.repoRoot, ".git", "gitbutler");
|
|
560
|
+
if (wsMode === "gitbutler" || existsSync(gbDir)) {
|
|
561
|
+
this.useGitButler = true;
|
|
562
|
+
logger.info("Using GitButler virtual branches", {
|
|
563
|
+
version: butVersion
|
|
564
|
+
});
|
|
565
|
+
console.log(`[conductor] GitButler mode (${butVersion})`);
|
|
566
|
+
}
|
|
567
|
+
} catch {
|
|
568
|
+
if (wsMode === "gitbutler") {
|
|
569
|
+
throw new Error(
|
|
570
|
+
"GitButler CLI (but) not found. Install: brew install --cask gitbutler"
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
} else if (laneMode) {
|
|
575
|
+
logger.info("Lane mode enabled; using git worktrees", {
|
|
576
|
+
laneBranch: this.config.laneBranch
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
if (!this.useGitButler && !existsSync(this.config.workspaceRoot)) {
|
|
401
580
|
mkdirSync(this.config.workspaceRoot, { recursive: true });
|
|
402
581
|
}
|
|
403
582
|
this.client = await this.createLinearClient();
|
|
404
|
-
if (
|
|
583
|
+
if (this.client) {
|
|
405
584
|
try {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
585
|
+
if (this.config.teamId) {
|
|
586
|
+
const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-/i.test(this.config.teamId);
|
|
587
|
+
if (!isUuid) {
|
|
588
|
+
const teams = await this.client.getTeams();
|
|
589
|
+
const needle = this.config.teamId.toLowerCase();
|
|
590
|
+
const match = teams.find(
|
|
591
|
+
(t) => t.name.toLowerCase() === needle || t.key.toLowerCase() === needle
|
|
592
|
+
);
|
|
593
|
+
if (match) {
|
|
594
|
+
logger.info("Resolved team name to ID", {
|
|
595
|
+
name: this.config.teamId,
|
|
596
|
+
id: match.id,
|
|
597
|
+
key: match.key
|
|
598
|
+
});
|
|
599
|
+
this.config.teamId = match.id;
|
|
600
|
+
} else {
|
|
601
|
+
logger.warn("Team not found by name/key, will try as ID", {
|
|
602
|
+
team: this.config.teamId
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
} else {
|
|
607
|
+
const team = await this.client.getTeam();
|
|
608
|
+
this.config.teamId = team.id;
|
|
609
|
+
logger.info("Auto-detected Linear team", {
|
|
610
|
+
id: team.id,
|
|
611
|
+
name: team.name,
|
|
612
|
+
key: team.key
|
|
613
|
+
});
|
|
614
|
+
}
|
|
413
615
|
} catch (err) {
|
|
414
|
-
logger.warn("Failed to
|
|
616
|
+
logger.warn("Failed to resolve team", {
|
|
415
617
|
error: err.message
|
|
416
618
|
});
|
|
417
619
|
}
|
|
@@ -433,7 +635,9 @@ class Conductor {
|
|
|
433
635
|
try {
|
|
434
636
|
await this.poll();
|
|
435
637
|
} catch (err) {
|
|
436
|
-
|
|
638
|
+
const e = err;
|
|
639
|
+
logger.error("Initial poll failed", { error: e.message, stack: e.stack });
|
|
640
|
+
console.error(`[conductor] Initial poll failed: ${e.message}`);
|
|
437
641
|
}
|
|
438
642
|
await this.schedulePoll();
|
|
439
643
|
}
|
|
@@ -691,6 +895,7 @@ class Conductor {
|
|
|
691
895
|
const allCandidates = [];
|
|
692
896
|
const issues = await this.client.getIssues({
|
|
693
897
|
teamId: this.config.teamId,
|
|
898
|
+
stateType: "unstarted",
|
|
694
899
|
limit: 50
|
|
695
900
|
});
|
|
696
901
|
for (const issue of issues) {
|
|
@@ -824,7 +1029,8 @@ class Conductor {
|
|
|
824
1029
|
title: issue.title,
|
|
825
1030
|
filesModified: run.filesModified,
|
|
826
1031
|
toolCalls: run.toolCalls,
|
|
827
|
-
workspacePath: run.workspacePath
|
|
1032
|
+
workspacePath: run.workspacePath,
|
|
1033
|
+
useGitButler: this.useGitButler
|
|
828
1034
|
});
|
|
829
1035
|
if (url) {
|
|
830
1036
|
prUrl = url;
|
|
@@ -843,6 +1049,7 @@ class Conductor {
|
|
|
843
1049
|
durationMs: Date.now() - run.startedAt,
|
|
844
1050
|
hasCommits: true,
|
|
845
1051
|
labels: issue.labels.map((l) => l.name),
|
|
1052
|
+
promptVersions: this.lastPromptVersions,
|
|
846
1053
|
prUrl
|
|
847
1054
|
});
|
|
848
1055
|
await this.runHook(
|
|
@@ -878,6 +1085,7 @@ class Conductor {
|
|
|
878
1085
|
durationMs: Date.now() - run.startedAt,
|
|
879
1086
|
hasCommits: false,
|
|
880
1087
|
labels: issue.labels.map((l) => l.name),
|
|
1088
|
+
promptVersions: this.lastPromptVersions,
|
|
881
1089
|
errorTail: run.error?.slice(-500)
|
|
882
1090
|
});
|
|
883
1091
|
if (this.handleRateLimitError(run.error, issue.identifier)) {
|
|
@@ -1102,6 +1310,45 @@ class Conductor {
|
|
|
1102
1310
|
// ── Workspace Management ──
|
|
1103
1311
|
async createWorkspace(issue) {
|
|
1104
1312
|
const wsKey = this.sanitizeIdentifier(issue.identifier);
|
|
1313
|
+
if (this.useGitButler) {
|
|
1314
|
+
return this.createGitButlerBranch(issue, wsKey);
|
|
1315
|
+
}
|
|
1316
|
+
return this.createWorktree(issue, wsKey);
|
|
1317
|
+
}
|
|
1318
|
+
createGitButlerBranch(issue, wsKey) {
|
|
1319
|
+
if (this.config.laneBranch) {
|
|
1320
|
+
throw new Error(
|
|
1321
|
+
"Lane mode requires git worktrees; GitButler virtual branches are not supported."
|
|
1322
|
+
);
|
|
1323
|
+
}
|
|
1324
|
+
const branchName = `conductor/${wsKey}`;
|
|
1325
|
+
try {
|
|
1326
|
+
execSync("but pull", {
|
|
1327
|
+
cwd: this.config.repoRoot,
|
|
1328
|
+
stdio: "pipe",
|
|
1329
|
+
timeout: 3e4
|
|
1330
|
+
});
|
|
1331
|
+
} catch {
|
|
1332
|
+
}
|
|
1333
|
+
try {
|
|
1334
|
+
execSync(`but branch new "${branchName}"`, {
|
|
1335
|
+
cwd: this.config.repoRoot,
|
|
1336
|
+
stdio: "pipe",
|
|
1337
|
+
timeout: 1e4
|
|
1338
|
+
});
|
|
1339
|
+
logger.info("Created GitButler virtual branch", {
|
|
1340
|
+
identifier: issue.identifier,
|
|
1341
|
+
branch: branchName
|
|
1342
|
+
});
|
|
1343
|
+
} catch {
|
|
1344
|
+
logger.info("GitButler branch may already exist, reusing", {
|
|
1345
|
+
identifier: issue.identifier,
|
|
1346
|
+
branch: branchName
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
return this.config.repoRoot;
|
|
1350
|
+
}
|
|
1351
|
+
createWorktree(issue, wsKey) {
|
|
1105
1352
|
const wsPath = join(this.config.workspaceRoot, wsKey);
|
|
1106
1353
|
if (existsSync(wsPath)) {
|
|
1107
1354
|
logger.info("Reusing existing workspace", {
|
|
@@ -1110,7 +1357,9 @@ class Conductor {
|
|
|
1110
1357
|
});
|
|
1111
1358
|
return wsPath;
|
|
1112
1359
|
}
|
|
1113
|
-
const
|
|
1360
|
+
const lane = this.config.laneBranch;
|
|
1361
|
+
const branchName = lane ? `worktree-agent-${wsKey}` : `conductor/${wsKey}`;
|
|
1362
|
+
const startPoint = lane ? lane : `origin/${this.config.baseBranch}`;
|
|
1114
1363
|
try {
|
|
1115
1364
|
execSync("git fetch origin", {
|
|
1116
1365
|
cwd: this.config.repoRoot,
|
|
@@ -1118,7 +1367,7 @@ class Conductor {
|
|
|
1118
1367
|
timeout: 3e4
|
|
1119
1368
|
});
|
|
1120
1369
|
execSync(
|
|
1121
|
-
`git worktree add "${wsPath}" -b "${branchName}" "
|
|
1370
|
+
`git worktree add "${wsPath}" -b "${branchName}" "${startPoint}"`,
|
|
1122
1371
|
{
|
|
1123
1372
|
cwd: this.config.repoRoot,
|
|
1124
1373
|
stdio: "pipe",
|
|
@@ -1128,7 +1377,8 @@ class Conductor {
|
|
|
1128
1377
|
logger.info("Created workspace", {
|
|
1129
1378
|
identifier: issue.identifier,
|
|
1130
1379
|
path: wsPath,
|
|
1131
|
-
branch: branchName
|
|
1380
|
+
branch: branchName,
|
|
1381
|
+
lane: lane || null
|
|
1132
1382
|
});
|
|
1133
1383
|
} catch (err) {
|
|
1134
1384
|
try {
|
|
@@ -1147,6 +1397,25 @@ class Conductor {
|
|
|
1147
1397
|
}
|
|
1148
1398
|
async removeWorkspace(issue) {
|
|
1149
1399
|
const wsKey = this.sanitizeIdentifier(issue.identifier);
|
|
1400
|
+
const branchName = `conductor/${wsKey}`;
|
|
1401
|
+
if (this.useGitButler) {
|
|
1402
|
+
await this.runHook("before-remove", this.config.repoRoot, issue).catch(
|
|
1403
|
+
() => {
|
|
1404
|
+
}
|
|
1405
|
+
);
|
|
1406
|
+
try {
|
|
1407
|
+
execSync(`but unapply "${branchName}"`, {
|
|
1408
|
+
cwd: this.config.repoRoot,
|
|
1409
|
+
stdio: "pipe",
|
|
1410
|
+
timeout: 1e4
|
|
1411
|
+
});
|
|
1412
|
+
} catch {
|
|
1413
|
+
logger.debug("GitButler branch already unapplied", {
|
|
1414
|
+
identifier: issue.identifier
|
|
1415
|
+
});
|
|
1416
|
+
}
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1150
1419
|
const wsPath = join(this.config.workspaceRoot, wsKey);
|
|
1151
1420
|
if (!existsSync(wsPath)) return;
|
|
1152
1421
|
await this.runHook("before-remove", wsPath, issue).catch(() => {
|
|
@@ -1562,10 +1831,12 @@ class Conductor {
|
|
|
1562
1831
|
}
|
|
1563
1832
|
}
|
|
1564
1833
|
}
|
|
1834
|
+
/** Last prompt version hashes — set by buildPrompt, read by outcome logging */
|
|
1835
|
+
lastPromptVersions = {};
|
|
1565
1836
|
/**
|
|
1566
|
-
* Build the agent prompt.
|
|
1567
|
-
* ~/.stackmemory/conductor/
|
|
1568
|
-
*
|
|
1837
|
+
* Build the agent prompt. Tries decomposed phase files first
|
|
1838
|
+
* (~/.stackmemory/conductor/prompts/*.md), then typed templates,
|
|
1839
|
+
* then custom prompt-template.md, then default.
|
|
1569
1840
|
*
|
|
1570
1841
|
* Template variables: {{ISSUE_ID}}, {{TITLE}}, {{DESCRIPTION}},
|
|
1571
1842
|
* {{LABELS}}, {{PRIORITY}}, {{ATTEMPT}}, {{PRIOR_CONTEXT}}
|
|
@@ -1593,6 +1864,21 @@ class Conductor {
|
|
|
1593
1864
|
);
|
|
1594
1865
|
}
|
|
1595
1866
|
const priorContext = contextParts.join("\n");
|
|
1867
|
+
const variables = {
|
|
1868
|
+
ISSUE_ID: issue.identifier,
|
|
1869
|
+
TITLE: issue.title,
|
|
1870
|
+
DESCRIPTION: issue.description || "",
|
|
1871
|
+
LABELS: labels,
|
|
1872
|
+
PRIORITY: priority,
|
|
1873
|
+
SCOPE: issue.identifier.toLowerCase().replace(/-\d+$/, ""),
|
|
1874
|
+
ATTEMPT: String(attempt),
|
|
1875
|
+
PRIOR_CONTEXT: priorContext
|
|
1876
|
+
};
|
|
1877
|
+
const phaseResult = buildPromptFromPhases(variables);
|
|
1878
|
+
if (phaseResult) {
|
|
1879
|
+
this.lastPromptVersions = phaseResult.versions;
|
|
1880
|
+
return phaseResult.prompt;
|
|
1881
|
+
}
|
|
1596
1882
|
const templateDir = join(
|
|
1597
1883
|
__dirname,
|
|
1598
1884
|
"..",
|
|
@@ -1850,7 +2136,8 @@ class Conductor {
|
|
|
1850
2136
|
title: run.issue.title,
|
|
1851
2137
|
filesModified: run.filesModified,
|
|
1852
2138
|
toolCalls: run.toolCalls,
|
|
1853
|
-
workspacePath: wsPath
|
|
2139
|
+
workspacePath: wsPath,
|
|
2140
|
+
useGitButler: this.useGitButler
|
|
1854
2141
|
});
|
|
1855
2142
|
if (url) {
|
|
1856
2143
|
prUrl = url;
|
|
@@ -1870,6 +2157,7 @@ class Conductor {
|
|
|
1870
2157
|
durationMs,
|
|
1871
2158
|
hasCommits,
|
|
1872
2159
|
labels: run.issue.labels.map((l) => l.name),
|
|
2160
|
+
promptVersions: this.lastPromptVersions,
|
|
1873
2161
|
errorTail,
|
|
1874
2162
|
prUrl
|
|
1875
2163
|
});
|