@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.
Files changed (225) hide show
  1. package/LICENSE +131 -64
  2. package/README.md +107 -24
  3. package/bin/claude-sm +16 -1
  4. package/bin/claude-smd +16 -1
  5. package/bin/codex-smd +16 -1
  6. package/bin/gemini-sm +16 -1
  7. package/bin/hermes-sm +21 -0
  8. package/bin/hermes-smd +21 -0
  9. package/bin/opencode-sm +16 -1
  10. package/dist/src/cli/claude-sm.js +266 -84
  11. package/dist/src/cli/codex-sm.js +225 -33
  12. package/dist/src/cli/commands/bench.js +209 -2
  13. package/dist/src/cli/commands/brain.js +206 -0
  14. package/dist/src/cli/commands/cache.js +126 -0
  15. package/dist/src/cli/commands/company-os.js +184 -0
  16. package/dist/src/cli/commands/context.js +5 -0
  17. package/dist/src/cli/commands/daemon.js +41 -0
  18. package/dist/src/cli/commands/handoff.js +40 -9
  19. package/dist/src/cli/commands/onboard.js +70 -3
  20. package/dist/src/cli/commands/operator.js +127 -0
  21. package/dist/src/cli/commands/optimize.js +117 -0
  22. package/dist/src/cli/commands/orchestrate.js +232 -5
  23. package/dist/src/cli/commands/orchestrator.js +315 -26
  24. package/dist/src/cli/commands/pack.js +322 -0
  25. package/dist/src/cli/commands/patterns.js +254 -0
  26. package/dist/src/cli/commands/portal.js +161 -0
  27. package/dist/src/cli/commands/scaffold.js +92 -0
  28. package/dist/src/cli/commands/search.js +40 -1
  29. package/dist/src/cli/commands/setup.js +178 -11
  30. package/dist/src/cli/commands/skills.js +10 -1
  31. package/dist/src/cli/commands/state.js +265 -0
  32. package/dist/src/cli/commands/sync.js +253 -0
  33. package/dist/src/cli/commands/tasks.js +130 -1
  34. package/dist/src/cli/commands/vision.js +221 -0
  35. package/dist/src/cli/gemini-sm.js +19 -29
  36. package/dist/src/cli/hermes-sm.js +224 -0
  37. package/dist/src/cli/index.js +105 -39
  38. package/dist/src/cli/opencode-sm.js +38 -21
  39. package/dist/src/cli/utils/determinism-watcher.js +66 -0
  40. package/dist/src/cli/utils/real-cli-bin.js +116 -0
  41. package/dist/src/core/brain/brain-store.js +187 -0
  42. package/dist/src/core/brain/brain-sync.js +193 -0
  43. package/dist/src/core/brain/index.js +78 -0
  44. package/dist/src/core/brain/types.js +10 -0
  45. package/dist/src/core/cache/content-cache.js +238 -0
  46. package/dist/src/{integrations/diffmem → core/cache}/index.js +5 -5
  47. package/dist/src/core/cache/token-estimator.js +39 -0
  48. package/dist/src/core/config/feature-flags.js +2 -6
  49. package/dist/src/core/context/frame-database.js +79 -27
  50. package/dist/src/core/context/recursive-context-manager.js +1 -1
  51. package/dist/src/core/context/rehydration.js +2 -1
  52. package/dist/src/core/cross-search/cross-project-search.js +269 -0
  53. package/dist/src/core/cross-search/index.js +10 -0
  54. package/dist/src/core/database/sqlite-adapter.js +14 -84
  55. package/dist/src/core/extensions/provider-adapter.js +5 -0
  56. package/dist/src/core/models/model-router.js +54 -2
  57. package/dist/src/core/models/provider-pricing.js +58 -4
  58. package/dist/src/core/monitoring/logger.js +2 -1
  59. package/dist/src/core/optimization/trace-optimizer.js +413 -0
  60. package/dist/src/core/patterns/index.js +22 -0
  61. package/dist/src/core/patterns/pattern-applier.js +39 -0
  62. package/dist/src/core/patterns/pattern-observer.js +157 -0
  63. package/dist/src/core/patterns/pattern-store.js +259 -0
  64. package/dist/src/core/patterns/types.js +19 -0
  65. package/dist/src/core/provenance/confidence-scorer.js +128 -0
  66. package/dist/src/core/provenance/index.js +40 -0
  67. package/dist/src/core/provenance/provenance-store.js +194 -0
  68. package/dist/src/core/provenance/types.js +82 -0
  69. package/dist/src/core/retrieval/llm-context-retrieval.js +5 -4
  70. package/dist/src/core/retrieval/unified-context-assembler.js +11 -66
  71. package/dist/src/core/session/project-handoff.js +64 -0
  72. package/dist/src/core/session/session-manager.js +28 -0
  73. package/dist/src/core/shared-state/canonical-store.js +564 -0
  74. package/dist/src/core/skill-packs/index.js +18 -0
  75. package/dist/src/core/skill-packs/parser.js +42 -0
  76. package/dist/src/core/skill-packs/registry.js +224 -0
  77. package/dist/src/core/skill-packs/types.js +79 -0
  78. package/dist/src/core/storage/cloud-sync-manager.js +116 -0
  79. package/dist/src/core/storage/cloud-sync.js +574 -0
  80. package/dist/src/core/storage/two-tier-storage.js +5 -1
  81. package/dist/src/core/tasks/master-tasks-template.js +43 -0
  82. package/dist/src/core/tasks/md-task-parser.js +138 -0
  83. package/dist/src/core/trace/trace-event-store.js +282 -0
  84. package/dist/src/core/vision/index.js +27 -0
  85. package/dist/src/core/vision/signals.js +79 -0
  86. package/dist/src/core/vision/types.js +22 -0
  87. package/dist/src/core/vision/vision-file.js +146 -0
  88. package/dist/src/core/vision/vision-loop.js +220 -0
  89. package/dist/src/core/wiki/wiki-compiler.js +103 -1
  90. package/dist/src/daemon/daemon-config.js +52 -0
  91. package/dist/src/daemon/services/desire-path-service.js +566 -0
  92. package/dist/src/daemon/services/github-service.js +126 -0
  93. package/dist/src/daemon/services/research-stream-service.js +320 -0
  94. package/dist/src/daemon/services/telemetry-service.js +192 -0
  95. package/dist/src/daemon/unified-daemon.js +58 -1
  96. package/dist/src/features/browser/cli-browser-agent.js +417 -0
  97. package/dist/src/features/browser/stagehand-workflows.js +578 -0
  98. package/dist/src/features/operator/adapter-factory.js +62 -0
  99. package/dist/src/features/operator/browser-adapter.js +109 -0
  100. package/dist/src/features/operator/desktop-adapter.js +125 -0
  101. package/dist/src/features/operator/index.js +39 -0
  102. package/dist/src/features/operator/llm-decision.js +137 -0
  103. package/dist/src/features/operator/operator-logger.js +92 -0
  104. package/dist/src/features/operator/overnight-runner.js +327 -0
  105. package/dist/src/features/operator/screen-adapter.js +91 -0
  106. package/dist/src/features/operator/session-manager.js +127 -0
  107. package/dist/src/features/operator/state-machine.js +227 -0
  108. package/dist/src/features/operator/task-queue.js +81 -0
  109. package/dist/src/features/portal/index.js +26 -0
  110. package/dist/src/features/portal/server.js +240 -0
  111. package/dist/src/features/portal/types.js +14 -0
  112. package/dist/src/features/portal/ui.js +195 -0
  113. package/dist/src/features/sweep/pty-wrapper.js +13 -5
  114. package/dist/src/features/tasks/task-aware-context.js +2 -1
  115. package/dist/src/features/tui/simple-monitor.js +0 -23
  116. package/dist/src/features/tui/swarm-monitor.js +8 -66
  117. package/dist/src/features/web/client/hooks/use-socket.js +12 -0
  118. package/dist/src/{core/merge/index.js → features/web/client/lib/utils.js} +8 -4
  119. package/dist/src/features/web/client/next-env.d.js +4 -0
  120. package/dist/src/features/web/client/stores/session-store.js +12 -0
  121. package/dist/src/features/web/server/gcp-billing.js +76 -0
  122. package/dist/src/features/web/server/index.js +10 -0
  123. package/dist/src/features/web/server/spend-calculator.js +228 -0
  124. package/dist/src/hooks/schemas.js +6 -1
  125. package/dist/src/integrations/anthropic/client.js +3 -2
  126. package/dist/src/integrations/claude-code/agent-bridge.js +0 -3
  127. package/dist/src/integrations/claude-code/subagent-client.js +307 -11
  128. package/dist/src/integrations/claude-code/task-coordinator.js +2 -1
  129. package/dist/src/integrations/github/pr-state.js +158 -0
  130. package/dist/src/integrations/linear/client.js +4 -1
  131. package/dist/src/integrations/linear/webhook-retry.js +196 -0
  132. package/dist/src/integrations/linear/webhook-server.js +18 -22
  133. package/dist/src/integrations/mcp/handlers/cloud-sync-handlers.js +101 -0
  134. package/dist/src/integrations/mcp/handlers/index.js +40 -84
  135. package/dist/src/integrations/mcp/server.js +542 -641
  136. package/dist/src/integrations/mcp/tool-alias-registry.js +297 -0
  137. package/dist/src/integrations/mcp/tool-definitions.js +152 -682
  138. package/dist/src/mcp/stackmemory-mcp-server.js +571 -231
  139. package/dist/src/orchestrators/multimodal/determinism.js +244 -0
  140. package/dist/src/orchestrators/multimodal/harness.js +149 -78
  141. package/dist/src/orchestrators/multimodal/providers.js +44 -3
  142. package/dist/src/skills/recursive-agent-orchestrator.js +2 -4
  143. package/dist/src/utils/hook-installer.js +0 -8
  144. package/dist/src/utils/process-cleanup.js +1 -7
  145. package/docs/README.md +42 -0
  146. package/docs/guides/README_INSTALL.md +208 -0
  147. package/package.json +27 -9
  148. package/packs/coding/python-fastapi/instructions.md +60 -0
  149. package/packs/coding/python-fastapi/pack.yaml +28 -0
  150. package/packs/coding/typescript-react/instructions.md +47 -0
  151. package/packs/coding/typescript-react/pack.yaml +28 -0
  152. package/packs/core/commands/capture.md +32 -0
  153. package/packs/core/commands/learn.md +73 -0
  154. package/packs/core/commands/next.md +36 -0
  155. package/packs/core/commands/restart.md +58 -0
  156. package/packs/core/commands/restore.md +29 -0
  157. package/packs/core/commands/start.md +57 -0
  158. package/packs/core/commands/stop.md +65 -0
  159. package/packs/core/commands/summary.md +40 -0
  160. package/packs/core/manifest.json +24 -0
  161. package/packs/ops/decision-recovery/instructions.md +65 -0
  162. package/packs/ops/decision-recovery/pack.yaml +89 -0
  163. package/scripts/claude-code-wrapper.sh +11 -0
  164. package/scripts/claude-sm-setup.sh +12 -1
  165. package/scripts/codex-wrapper.sh +11 -0
  166. package/scripts/git-hooks/branch-context-manager.sh +11 -0
  167. package/scripts/git-hooks/post-checkout-stackmemory.sh +11 -0
  168. package/scripts/git-hooks/post-commit-stackmemory.sh +11 -0
  169. package/scripts/git-hooks/pre-commit-stackmemory.sh +11 -0
  170. package/scripts/hooks/cleanup-shell.sh +12 -1
  171. package/scripts/hooks/task-complete.sh +12 -1
  172. package/scripts/install-code-execution-hooks.sh +12 -1
  173. package/scripts/install-sweep-hook.sh +12 -0
  174. package/scripts/install.sh +11 -0
  175. package/scripts/opencode-wrapper.sh +11 -0
  176. package/scripts/portal/cloud-init.yaml +69 -0
  177. package/scripts/portal/setup.sh +69 -0
  178. package/scripts/portal/stackmemory-portal.service +34 -0
  179. package/scripts/setup-claude-integration.sh +12 -1
  180. package/scripts/smoke-init-db.sh +23 -0
  181. package/scripts/stackmemory-daemon.sh +11 -0
  182. package/scripts/verify-dist.cjs +11 -4
  183. package/dist/src/cli/commands/ralph.js +0 -1053
  184. package/dist/src/cli/commands/team.js +0 -168
  185. package/dist/src/core/context/shared-context-layer.js +0 -620
  186. package/dist/src/core/context/stack-merge-resolver.js +0 -748
  187. package/dist/src/core/merge/conflict-detector.js +0 -430
  188. package/dist/src/core/merge/resolution-engine.js +0 -557
  189. package/dist/src/core/merge/stack-diff.js +0 -531
  190. package/dist/src/core/merge/unified-merge-resolver.js +0 -302
  191. package/dist/src/hooks/diffmem-hooks.js +0 -376
  192. package/dist/src/integrations/diffmem/client.js +0 -208
  193. package/dist/src/integrations/diffmem/config.js +0 -14
  194. package/dist/src/integrations/greptile/client.js +0 -101
  195. package/dist/src/integrations/greptile/config.js +0 -14
  196. package/dist/src/integrations/greptile/index.js +0 -11
  197. package/dist/src/integrations/mcp/handlers/cord-handlers.js +0 -397
  198. package/dist/src/integrations/mcp/handlers/diffmem-handlers.js +0 -455
  199. package/dist/src/integrations/mcp/handlers/greptile-handlers.js +0 -456
  200. package/dist/src/integrations/mcp/handlers/provider-handlers.js +0 -227
  201. package/dist/src/integrations/mcp/handlers/team-handlers.js +0 -211
  202. package/dist/src/integrations/ralph/bridge/ralph-stackmemory-bridge.js +0 -863
  203. package/dist/src/integrations/ralph/context/context-budget-manager.js +0 -308
  204. package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +0 -391
  205. package/dist/src/integrations/ralph/index.js +0 -17
  206. package/dist/src/integrations/ralph/learning/pattern-learner.js +0 -435
  207. package/dist/src/integrations/ralph/lifecycle/iteration-lifecycle.js +0 -448
  208. package/dist/src/integrations/ralph/loopmax.js +0 -488
  209. package/dist/src/integrations/ralph/monitoring/swarm-dashboard.js +0 -293
  210. package/dist/src/integrations/ralph/monitoring/swarm-registry.js +0 -107
  211. package/dist/src/integrations/ralph/orchestration/multi-loop-orchestrator.js +0 -508
  212. package/dist/src/integrations/ralph/patterns/compounding-engineering-pattern.js +0 -407
  213. package/dist/src/integrations/ralph/patterns/extended-coherence-sessions.js +0 -495
  214. package/dist/src/integrations/ralph/patterns/oracle-worker-pattern.js +0 -387
  215. package/dist/src/integrations/ralph/performance/performance-optimizer.js +0 -357
  216. package/dist/src/integrations/ralph/recovery/crash-recovery.js +0 -461
  217. package/dist/src/integrations/ralph/state/state-reconciler.js +0 -420
  218. package/dist/src/integrations/ralph/swarm/git-workflow-manager.js +0 -444
  219. package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +0 -1007
  220. package/dist/src/integrations/ralph/visualization/ralph-debugger.js +0 -635
  221. package/scripts/ralph-loop-implementation.js +0 -404
  222. /package/dist/src/core/{merge → cache}/types.js +0 -0
  223. /package/dist/src/{integrations/diffmem/types.js → core/storage/cloud-sync-types.js} +0 -0
  224. /package/dist/src/{integrations/greptile/types.js → core/trace/trace-event.js} +0 -0
  225. /package/dist/src/{integrations/ralph → features/operator}/types.js +0 -0
@@ -1,488 +0,0 @@
1
- import { fileURLToPath as __fileURLToPath } from 'url';
2
- import { dirname as __pathDirname } from 'path';
3
- const __filename = __fileURLToPath(import.meta.url);
4
- const __dirname = __pathDirname(__filename);
5
- import { spawn, execSync } from "child_process";
6
- import { existsSync, mkdirSync, writeFileSync, appendFileSync } from "fs";
7
- import { join } from "path";
8
- import { tmpdir } from "os";
9
- import { logger } from "../../core/monitoring/logger.js";
10
- const STUCK_TIMEOUT_MS = 5 * 60 * 1e3;
11
- const LOOP_COOLDOWN_MS = 3e3;
12
- const TMP_DRAFT_DIR = join(tmpdir(), "loopmax-drafts");
13
- class LoopMaxRunner {
14
- config;
15
- state;
16
- stateFile;
17
- logFile;
18
- activeProcess = null;
19
- stopped = false;
20
- workDir;
21
- constructor(config) {
22
- this.config = {
23
- task: config.task,
24
- criteria: config.criteria,
25
- cwd: config.cwd || process.cwd(),
26
- useWorktree: config.useWorktree ?? true,
27
- maxStuckBeforeRespawn: config.maxStuckBeforeRespawn ?? 3,
28
- maxLoops: config.maxLoops ?? 0,
29
- commitEvery: config.commitEvery ?? 25,
30
- model: config.model || "sonnet",
31
- verbose: config.verbose ?? true
32
- };
33
- if (!existsSync(TMP_DRAFT_DIR)) {
34
- mkdirSync(TMP_DRAFT_DIR, { recursive: true });
35
- }
36
- this.workDir = this.config.cwd;
37
- this.stateFile = join(TMP_DRAFT_DIR, `state-${Date.now()}.json`);
38
- this.logFile = join(TMP_DRAFT_DIR, `log-${Date.now()}.jsonl`);
39
- this.state = {
40
- task: this.config.task,
41
- criteria: this.config.criteria,
42
- startedAt: Date.now(),
43
- loop: 0,
44
- totalCommits: 0,
45
- iterations: [],
46
- status: "running"
47
- };
48
- this.saveState();
49
- this.writeHookState();
50
- }
51
- /** Main entry — runs forever until criteria met or stopped */
52
- async run() {
53
- this.log(`LoopMax starting: ${this.config.task}`);
54
- this.log(`Criteria: ${this.config.criteria}`);
55
- this.log(`State: ${this.stateFile}`);
56
- this.log(`Log: ${this.logFile}`);
57
- if (this.config.useWorktree) {
58
- await this.setupWorktree();
59
- }
60
- const cleanup = () => {
61
- this.stopped = true;
62
- this.commitAndSummarize("SIGINT received \u2014 saving progress");
63
- if (this.activeProcess) {
64
- this.activeProcess.kill("SIGTERM");
65
- }
66
- };
67
- process.on("SIGINT", cleanup);
68
- process.on("SIGTERM", cleanup);
69
- let consecutiveStuck = 0;
70
- while (!this.stopped) {
71
- if (this.config.maxLoops > 0 && this.state.loop >= this.config.maxLoops) {
72
- this.log(`Max loops (${this.config.maxLoops}) reached. Stopping.`);
73
- break;
74
- }
75
- this.state.loop++;
76
- this.log(`
77
- ${"=".repeat(60)}`);
78
- this.log(`LOOP ${this.state.loop} starting`);
79
- this.log(`${"=".repeat(60)}`);
80
- const iteration = await this.runOneLoop();
81
- this.state.iterations.push(iteration);
82
- if (iteration.stuck) {
83
- consecutiveStuck++;
84
- this.log(
85
- `Stuck count: ${consecutiveStuck}/${this.config.maxStuckBeforeRespawn}`
86
- );
87
- if (consecutiveStuck >= this.config.maxStuckBeforeRespawn) {
88
- this.log(
89
- "Max stuck reached \u2014 committing, summarizing, respawning fresh"
90
- );
91
- this.commitAndSummarize(
92
- `Stuck after ${consecutiveStuck} loops \u2014 saving checkpoint`
93
- );
94
- consecutiveStuck = 0;
95
- }
96
- } else {
97
- consecutiveStuck = 0;
98
- }
99
- if (await this.checkCriteria()) {
100
- this.log("ALL CRITERIA MET \u2014 loop complete!");
101
- this.state.status = "completed";
102
- this.commitAndSummarize("LoopMax complete \u2014 all criteria met");
103
- break;
104
- }
105
- this.saveState();
106
- if (!this.stopped) {
107
- this.log(
108
- `Cooling down ${LOOP_COOLDOWN_MS / 1e3}s before next loop...`
109
- );
110
- await sleep(LOOP_COOLDOWN_MS);
111
- }
112
- }
113
- if (this.stopped) {
114
- this.state.status = "stopped";
115
- }
116
- this.saveState();
117
- this.printSummary();
118
- process.removeListener("SIGINT", cleanup);
119
- process.removeListener("SIGTERM", cleanup);
120
- }
121
- /** Run a single Claude Code loop iteration */
122
- async runOneLoop() {
123
- const iteration = {
124
- loop: this.state.loop,
125
- pid: 0,
126
- startedAt: Date.now(),
127
- exitCode: null,
128
- commitsMade: 0,
129
- stuck: false
130
- };
131
- const prompt = this.buildPrompt();
132
- const draftFile = join(TMP_DRAFT_DIR, `prompt-loop-${this.state.loop}.md`);
133
- writeFileSync(draftFile, prompt);
134
- this.log(`Prompt saved to ${draftFile}`);
135
- try {
136
- const result = await this.spawnClaude(prompt);
137
- iteration.pid = result.pid;
138
- iteration.exitCode = result.exitCode;
139
- iteration.stuck = result.stuck;
140
- iteration.endedAt = Date.now();
141
- iteration.commitsMade = this.autoCommit(
142
- `loopmax: loop ${this.state.loop} (exit=${result.exitCode})`
143
- );
144
- this.state.totalCommits += iteration.commitsMade;
145
- } catch (err) {
146
- this.log(`Loop ${this.state.loop} error: ${err.message}`);
147
- iteration.exitCode = -1;
148
- iteration.endedAt = Date.now();
149
- iteration.stuck = true;
150
- iteration.commitsMade = this.autoCommit(
151
- `loopmax: loop ${this.state.loop} crashed \u2014 saving progress`
152
- );
153
- this.state.totalCommits += iteration.commitsMade;
154
- }
155
- appendFileSync(this.logFile, JSON.stringify(iteration) + "\n");
156
- return iteration;
157
- }
158
- /** Spawn claude -p with --dangerously-skip-permissions */
159
- spawnClaude(prompt) {
160
- return new Promise((resolve, reject) => {
161
- const args = [
162
- "-p",
163
- prompt,
164
- "--dangerously-skip-permissions",
165
- "--output-format",
166
- "text",
167
- "--model",
168
- this.config.model
169
- ];
170
- this.log(`Spawning: claude ${args.slice(0, 4).join(" ")} ...`);
171
- const child = spawn("claude", args, {
172
- cwd: this.workDir,
173
- stdio: ["ignore", "pipe", "pipe"],
174
- env: {
175
- ...process.env,
176
- LOOPMAX: "1",
177
- LOOPMAX_LOOP: String(this.state.loop),
178
- LOOPMAX_STATE: this.stateFile,
179
- LOOPMAX_TASK: this.config.task,
180
- LOOPMAX_CRITERIA: this.config.criteria,
181
- LOOPMAX_MODEL: this.config.model
182
- }
183
- });
184
- this.activeProcess = child;
185
- let lastOutputAt = Date.now();
186
- let stuck = false;
187
- let output = "";
188
- const stuckCheck = setInterval(() => {
189
- if (Date.now() - lastOutputAt > STUCK_TIMEOUT_MS) {
190
- this.log("Stuck detected (no output for 5min) \u2014 killing agent");
191
- stuck = true;
192
- child.kill("SIGTERM");
193
- setTimeout(() => {
194
- if (!child.killed) child.kill("SIGKILL");
195
- }, 5e3);
196
- }
197
- }, 3e4);
198
- child.stdout?.on("data", (data) => {
199
- lastOutputAt = Date.now();
200
- const text = data.toString();
201
- output += text;
202
- if (this.config.verbose) {
203
- process.stdout.write(text);
204
- }
205
- });
206
- child.stderr?.on("data", (data) => {
207
- lastOutputAt = Date.now();
208
- const text = data.toString();
209
- if (this.config.verbose) {
210
- process.stderr.write(text);
211
- }
212
- });
213
- child.on("error", (err) => {
214
- clearInterval(stuckCheck);
215
- this.activeProcess = null;
216
- reject(err);
217
- });
218
- child.on("close", (code) => {
219
- clearInterval(stuckCheck);
220
- this.activeProcess = null;
221
- const outputFile = join(
222
- TMP_DRAFT_DIR,
223
- `output-loop-${this.state.loop}.txt`
224
- );
225
- writeFileSync(outputFile, output);
226
- resolve({
227
- pid: child.pid || 0,
228
- exitCode: code ?? -1,
229
- stuck
230
- });
231
- });
232
- });
233
- }
234
- /** Build the prompt for Claude Code */
235
- buildPrompt() {
236
- const priorContext = this.getPriorContext();
237
- return [
238
- `# Task`,
239
- ``,
240
- this.config.task,
241
- ``,
242
- `# Completion Criteria`,
243
- ``,
244
- this.config.criteria,
245
- ``,
246
- `# Mode: LoopMax`,
247
- ``,
248
- `You are in LoopMax mode. Rules:`,
249
- `1. DO NOT PLAN. Just start coding immediately.`,
250
- `2. Run tests often. Fix what breaks. Repeat.`,
251
- `3. Commit to git frequently to preserve your work.`,
252
- `4. If tests pass and lint is clean, you're done.`,
253
- `5. If you get stuck, commit what you have and describe the blocker.`,
254
- `6. Save any drafts or experiments to /tmp/loopmax-drafts/`,
255
- `7. Be aggressive \u2014 try things, break things, fix things.`,
256
- `8. Do NOT ask for permission. Do NOT explain your reasoning at length.`,
257
- `9. Prefer action over analysis. Code over comments.`,
258
- ``,
259
- `# Working Directory`,
260
- ``,
261
- this.workDir,
262
- ``,
263
- priorContext ? `# Prior Context (from previous loops)
264
-
265
- ${priorContext}
266
- ` : "",
267
- `# GO. No planning. Just start.`
268
- ].filter(Boolean).join("\n");
269
- }
270
- /** Get summary of what happened in prior loops */
271
- getPriorContext() {
272
- if (this.state.iterations.length === 0) return "";
273
- const recent = this.state.iterations.slice(-3);
274
- const lines = recent.map((it) => {
275
- const duration = it.endedAt ? Math.round((it.endedAt - it.startedAt) / 1e3) : "?";
276
- const status = it.stuck ? "STUCK" : it.exitCode === 0 ? "OK" : `EXIT=${it.exitCode}`;
277
- return `- Loop ${it.loop}: ${status}, ${duration}s, ${it.commitsMade} commits${it.summary ? ` \u2014 ${it.summary}` : ""}`;
278
- });
279
- try {
280
- const log = execSync("git log --oneline -5", {
281
- cwd: this.workDir,
282
- encoding: "utf-8",
283
- timeout: 5e3
284
- }).trim();
285
- lines.push("", "Recent commits:", log);
286
- } catch {
287
- }
288
- try {
289
- execSync("npm run test:run", {
290
- cwd: this.workDir,
291
- stdio: "pipe",
292
- timeout: 12e4
293
- });
294
- lines.push("", "Tests: ALL PASSING");
295
- } catch (err) {
296
- const stderr = (err instanceof Error && "stderr" in err && err.stderr != null ? String(err.stderr) : "") || "";
297
- const lastLines = stderr.split("\n").slice(-10).join("\n");
298
- lines.push("", "Tests: FAILING", lastLines);
299
- }
300
- return lines.join("\n");
301
- }
302
- /** Check if completion criteria are met */
303
- async checkCriteria() {
304
- try {
305
- execSync("npm run test:run", {
306
- cwd: this.workDir,
307
- stdio: "pipe",
308
- timeout: 12e4
309
- });
310
- execSync("npm run lint", {
311
- cwd: this.workDir,
312
- stdio: "pipe",
313
- timeout: 6e4
314
- });
315
- execSync("npm run build", {
316
- cwd: this.workDir,
317
- stdio: "pipe",
318
- timeout: 6e4
319
- });
320
- return true;
321
- } catch {
322
- return false;
323
- }
324
- }
325
- /** Auto-commit any changes in the working directory */
326
- autoCommit(message) {
327
- try {
328
- const status = execSync("git status --porcelain", {
329
- cwd: this.workDir,
330
- encoding: "utf-8",
331
- timeout: 1e4
332
- }).trim();
333
- if (!status) return 0;
334
- execSync("git add -A", {
335
- cwd: this.workDir,
336
- stdio: "pipe",
337
- timeout: 1e4
338
- });
339
- execSync(`git commit -m "${message.replace(/"/g, '\\"')}"`, {
340
- cwd: this.workDir,
341
- stdio: "pipe",
342
- timeout: 1e4,
343
- env: {
344
- ...process.env,
345
- GIT_AUTHOR_NAME: "LoopMax",
346
- GIT_COMMITTER_NAME: "LoopMax"
347
- }
348
- });
349
- this.log(`Committed: ${message}`);
350
- return 1;
351
- } catch {
352
- return 0;
353
- }
354
- }
355
- /** Commit current state and write a summary */
356
- commitAndSummarize(reason) {
357
- this.log(`Checkpoint: ${reason}`);
358
- const summaryFile = join(
359
- TMP_DRAFT_DIR,
360
- `summary-loop-${this.state.loop}.md`
361
- );
362
- const summary = [
363
- `# LoopMax Checkpoint`,
364
- ``,
365
- `**Reason:** ${reason}`,
366
- `**Loop:** ${this.state.loop}`,
367
- `**Total commits:** ${this.state.totalCommits}`,
368
- `**Elapsed:** ${Math.round((Date.now() - this.state.startedAt) / 1e3)}s`,
369
- ``,
370
- `## Task`,
371
- this.config.task,
372
- ``,
373
- `## Status`,
374
- this.state.iterations.slice(-3).map((it) => {
375
- const status = it.stuck ? "STUCK" : it.exitCode === 0 ? "OK" : `EXIT=${it.exitCode}`;
376
- return `- Loop ${it.loop}: ${status}`;
377
- }).join("\n")
378
- ].join("\n");
379
- writeFileSync(summaryFile, summary);
380
- this.autoCommit(`loopmax: checkpoint \u2014 ${reason}`);
381
- this.saveState();
382
- }
383
- /** Set up a git worktree for isolated work */
384
- async setupWorktree() {
385
- const branch = `loopmax/${Date.now()}`;
386
- const worktreePath = join(tmpdir(), `loopmax-wt-${Date.now()}`);
387
- this.log(`Creating worktree at ${worktreePath} on branch ${branch}`);
388
- try {
389
- execSync(`git worktree add -b "${branch}" "${worktreePath}"`, {
390
- cwd: this.config.cwd,
391
- stdio: "pipe",
392
- timeout: 3e4
393
- });
394
- this.workDir = worktreePath;
395
- this.state.worktreePath = worktreePath;
396
- this.state.worktreeBranch = branch;
397
- if (existsSync(join(worktreePath, "package.json"))) {
398
- this.log("Installing dependencies in worktree...");
399
- try {
400
- execSync("npm install", {
401
- cwd: worktreePath,
402
- stdio: "pipe",
403
- timeout: 12e4
404
- });
405
- } catch {
406
- this.log("npm install failed \u2014 continuing anyway");
407
- }
408
- }
409
- this.log(`Worktree ready: ${worktreePath}`);
410
- } catch (err) {
411
- this.log(`Worktree creation failed: ${err.message}`);
412
- this.log("Falling back to working in current directory");
413
- this.config.useWorktree = false;
414
- }
415
- }
416
- /** Clean up worktree */
417
- async cleanup() {
418
- if (this.state.worktreePath && existsSync(this.state.worktreePath)) {
419
- this.log(`Cleaning up worktree: ${this.state.worktreePath}`);
420
- try {
421
- execSync(`git worktree remove "${this.state.worktreePath}" --force`, {
422
- cwd: this.config.cwd,
423
- stdio: "pipe",
424
- timeout: 3e4
425
- });
426
- } catch {
427
- this.log("Worktree cleanup failed \u2014 may need manual cleanup");
428
- }
429
- }
430
- }
431
- /** Save state to /tmp */
432
- saveState() {
433
- writeFileSync(this.stateFile, JSON.stringify(this.state, null, 2));
434
- }
435
- /** Write hook state so the Stop hook can respawn independently */
436
- writeHookState() {
437
- const hookState = {
438
- task: this.config.task,
439
- criteria: this.config.criteria,
440
- cwd: this.workDir,
441
- loop: this.state.loop,
442
- startedAt: this.state.startedAt,
443
- iterations: this.state.iterations,
444
- model: this.config.model,
445
- status: this.state.status
446
- };
447
- const hookStateFile = join(TMP_DRAFT_DIR, "hook-state.json");
448
- writeFileSync(hookStateFile, JSON.stringify(hookState, null, 2));
449
- }
450
- /** Print final summary */
451
- printSummary() {
452
- const elapsed = Math.round((Date.now() - this.state.startedAt) / 1e3);
453
- const successLoops = this.state.iterations.filter(
454
- (i) => i.exitCode === 0
455
- ).length;
456
- const stuckLoops = this.state.iterations.filter((i) => i.stuck).length;
457
- console.log("\n" + "=".repeat(60));
458
- console.log("LoopMax Summary");
459
- console.log("=".repeat(60));
460
- console.log(`Status: ${this.state.status}`);
461
- console.log(`Total loops: ${this.state.loop}`);
462
- console.log(`Successful: ${successLoops}`);
463
- console.log(`Stuck: ${stuckLoops}`);
464
- console.log(`Commits: ${this.state.totalCommits}`);
465
- console.log(`Elapsed: ${elapsed}s`);
466
- console.log(`State file: ${this.stateFile}`);
467
- console.log(`Log file: ${this.logFile}`);
468
- if (this.state.worktreePath) {
469
- console.log(`Worktree: ${this.state.worktreePath}`);
470
- console.log(`Branch: ${this.state.worktreeBranch}`);
471
- }
472
- console.log("=".repeat(60));
473
- }
474
- log(msg) {
475
- const ts = (/* @__PURE__ */ new Date()).toISOString().substring(11, 19);
476
- const line = `[${ts}] [loopmax] ${msg}`;
477
- if (this.config.verbose) {
478
- console.log(line);
479
- }
480
- logger.info(msg, { component: "loopmax", loop: this.state.loop });
481
- }
482
- }
483
- function sleep(ms) {
484
- return new Promise((resolve) => setTimeout(resolve, ms));
485
- }
486
- export {
487
- LoopMaxRunner
488
- };