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