ralph-starter 0.0.1 → 0.1.1-beta.1

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 (382) hide show
  1. package/README.md +707 -17
  2. package/dist/auth/browser.d.ts +10 -0
  3. package/dist/auth/browser.d.ts.map +1 -0
  4. package/dist/auth/browser.js +56 -0
  5. package/dist/auth/browser.js.map +1 -0
  6. package/dist/auth/index.d.ts +5 -0
  7. package/dist/auth/index.d.ts.map +1 -0
  8. package/dist/auth/index.js +5 -0
  9. package/dist/auth/index.js.map +1 -0
  10. package/dist/auth/oauth-server.d.ts +20 -0
  11. package/dist/auth/oauth-server.d.ts.map +1 -0
  12. package/dist/auth/oauth-server.js +168 -0
  13. package/dist/auth/oauth-server.js.map +1 -0
  14. package/dist/auth/pkce.d.ts +46 -0
  15. package/dist/auth/pkce.d.ts.map +1 -0
  16. package/dist/auth/pkce.js +57 -0
  17. package/dist/auth/pkce.js.map +1 -0
  18. package/dist/auth/providers/base.d.ts +52 -0
  19. package/dist/auth/providers/base.d.ts.map +1 -0
  20. package/dist/auth/providers/base.js +70 -0
  21. package/dist/auth/providers/base.js.map +1 -0
  22. package/dist/auth/providers/index.d.ts +23 -0
  23. package/dist/auth/providers/index.d.ts.map +1 -0
  24. package/dist/auth/providers/index.js +39 -0
  25. package/dist/auth/providers/index.js.map +1 -0
  26. package/dist/auth/providers/linear.d.ts +37 -0
  27. package/dist/auth/providers/linear.d.ts.map +1 -0
  28. package/dist/auth/providers/linear.js +46 -0
  29. package/dist/auth/providers/linear.js.map +1 -0
  30. package/dist/auth/providers/notion.d.ts +36 -0
  31. package/dist/auth/providers/notion.d.ts.map +1 -0
  32. package/dist/auth/providers/notion.js +75 -0
  33. package/dist/auth/providers/notion.js.map +1 -0
  34. package/dist/auth/providers/todoist.d.ts +29 -0
  35. package/dist/auth/providers/todoist.d.ts.map +1 -0
  36. package/dist/auth/providers/todoist.js +40 -0
  37. package/dist/auth/providers/todoist.js.map +1 -0
  38. package/dist/automation/git.d.ts +15 -0
  39. package/dist/automation/git.d.ts.map +1 -0
  40. package/dist/automation/git.js +73 -0
  41. package/dist/automation/git.js.map +1 -0
  42. package/dist/cli.d.ts +3 -0
  43. package/dist/cli.d.ts.map +1 -0
  44. package/dist/cli.js +192 -19
  45. package/dist/cli.js.map +1 -0
  46. package/dist/commands/auth.d.ts +14 -0
  47. package/dist/commands/auth.d.ts.map +1 -0
  48. package/dist/commands/auth.js +243 -0
  49. package/dist/commands/auth.js.map +1 -0
  50. package/dist/commands/check.d.ts +12 -0
  51. package/dist/commands/check.d.ts.map +1 -0
  52. package/dist/commands/check.js +124 -0
  53. package/dist/commands/check.js.map +1 -0
  54. package/dist/commands/config.d.ts +13 -0
  55. package/dist/commands/config.d.ts.map +1 -0
  56. package/dist/commands/config.js +374 -0
  57. package/dist/commands/config.js.map +1 -0
  58. package/dist/commands/init.d.ts +31 -0
  59. package/dist/commands/init.d.ts.map +1 -0
  60. package/dist/commands/init.js +353 -0
  61. package/dist/commands/init.js.map +1 -0
  62. package/dist/commands/integrations.d.ts +17 -0
  63. package/dist/commands/integrations.d.ts.map +1 -0
  64. package/dist/commands/integrations.js +193 -0
  65. package/dist/commands/integrations.js.map +1 -0
  66. package/dist/commands/plan.d.ts +5 -0
  67. package/dist/commands/plan.d.ts.map +1 -0
  68. package/dist/commands/plan.js +80 -0
  69. package/dist/commands/plan.js.map +1 -0
  70. package/dist/commands/run.d.ts +26 -0
  71. package/dist/commands/run.d.ts.map +1 -0
  72. package/dist/commands/run.js +351 -0
  73. package/dist/commands/run.js.map +1 -0
  74. package/dist/commands/scaffold.d.ts +9 -0
  75. package/dist/commands/scaffold.d.ts.map +1 -0
  76. package/dist/commands/scaffold.js +128 -0
  77. package/dist/commands/scaffold.js.map +1 -0
  78. package/dist/commands/setup.d.ts +12 -0
  79. package/dist/commands/setup.d.ts.map +1 -0
  80. package/dist/commands/setup.js +27 -0
  81. package/dist/commands/setup.js.map +1 -0
  82. package/dist/commands/skill.d.ts +6 -0
  83. package/dist/commands/skill.d.ts.map +1 -0
  84. package/dist/commands/skill.js +151 -0
  85. package/dist/commands/skill.js.map +1 -0
  86. package/dist/commands/source.d.ts +17 -0
  87. package/dist/commands/source.d.ts.map +1 -0
  88. package/dist/commands/source.js +173 -0
  89. package/dist/commands/source.js.map +1 -0
  90. package/dist/config/manager.d.ts +70 -0
  91. package/dist/config/manager.d.ts.map +1 -0
  92. package/dist/config/manager.js +227 -0
  93. package/dist/config/manager.js.map +1 -0
  94. package/dist/index.d.ts +26 -0
  95. package/dist/index.d.ts.map +1 -0
  96. package/dist/index.js +27 -2
  97. package/dist/index.js.map +1 -0
  98. package/dist/integrations/_template/auth.d.ts +29 -0
  99. package/dist/integrations/_template/auth.d.ts.map +1 -0
  100. package/dist/integrations/_template/auth.js +42 -0
  101. package/dist/integrations/_template/auth.js.map +1 -0
  102. package/dist/integrations/_template/index.d.ts +8 -0
  103. package/dist/integrations/_template/index.d.ts.map +1 -0
  104. package/dist/integrations/_template/index.js +8 -0
  105. package/dist/integrations/_template/index.js.map +1 -0
  106. package/dist/integrations/_template/source.d.ts +34 -0
  107. package/dist/integrations/_template/source.d.ts.map +1 -0
  108. package/dist/integrations/_template/source.js +124 -0
  109. package/dist/integrations/_template/source.js.map +1 -0
  110. package/dist/integrations/base.d.ts +158 -0
  111. package/dist/integrations/base.d.ts.map +1 -0
  112. package/dist/integrations/base.js +109 -0
  113. package/dist/integrations/base.js.map +1 -0
  114. package/dist/integrations/github/index.d.ts +8 -0
  115. package/dist/integrations/github/index.d.ts.map +1 -0
  116. package/dist/integrations/github/index.js +8 -0
  117. package/dist/integrations/github/index.js.map +1 -0
  118. package/dist/integrations/github/source.d.ts +26 -0
  119. package/dist/integrations/github/source.d.ts.map +1 -0
  120. package/dist/integrations/github/source.js +190 -0
  121. package/dist/integrations/github/source.js.map +1 -0
  122. package/dist/integrations/index.d.ts +37 -0
  123. package/dist/integrations/index.d.ts.map +1 -0
  124. package/dist/integrations/index.js +71 -0
  125. package/dist/integrations/index.js.map +1 -0
  126. package/dist/integrations/linear/auth.d.ts +37 -0
  127. package/dist/integrations/linear/auth.d.ts.map +1 -0
  128. package/dist/integrations/linear/auth.js +45 -0
  129. package/dist/integrations/linear/auth.js.map +1 -0
  130. package/dist/integrations/linear/index.d.ts +9 -0
  131. package/dist/integrations/linear/index.d.ts.map +1 -0
  132. package/dist/integrations/linear/index.js +9 -0
  133. package/dist/integrations/linear/index.js.map +1 -0
  134. package/dist/integrations/linear/source.d.ts +26 -0
  135. package/dist/integrations/linear/source.d.ts.map +1 -0
  136. package/dist/integrations/linear/source.js +248 -0
  137. package/dist/integrations/linear/source.js.map +1 -0
  138. package/dist/integrations/notion/index.d.ts +8 -0
  139. package/dist/integrations/notion/index.d.ts.map +1 -0
  140. package/dist/integrations/notion/index.js +8 -0
  141. package/dist/integrations/notion/index.js.map +1 -0
  142. package/dist/integrations/notion/source.d.ts +53 -0
  143. package/dist/integrations/notion/source.d.ts.map +1 -0
  144. package/dist/integrations/notion/source.js +463 -0
  145. package/dist/integrations/notion/source.js.map +1 -0
  146. package/dist/llm/api.d.ts +29 -0
  147. package/dist/llm/api.d.ts.map +1 -0
  148. package/dist/llm/api.js +152 -0
  149. package/dist/llm/api.js.map +1 -0
  150. package/dist/llm/index.d.ts +7 -0
  151. package/dist/llm/index.d.ts.map +1 -0
  152. package/dist/llm/index.js +7 -0
  153. package/dist/llm/index.js.map +1 -0
  154. package/dist/llm/providers.d.ts +24 -0
  155. package/dist/llm/providers.d.ts.map +1 -0
  156. package/dist/llm/providers.js +51 -0
  157. package/dist/llm/providers.js.map +1 -0
  158. package/dist/loop/__tests__/agents.test.d.ts +2 -0
  159. package/dist/loop/__tests__/agents.test.d.ts.map +1 -0
  160. package/dist/loop/__tests__/agents.test.js +183 -0
  161. package/dist/loop/__tests__/agents.test.js.map +1 -0
  162. package/dist/loop/__tests__/circuit-breaker.test.d.ts +2 -0
  163. package/dist/loop/__tests__/circuit-breaker.test.d.ts.map +1 -0
  164. package/dist/loop/__tests__/circuit-breaker.test.js +161 -0
  165. package/dist/loop/__tests__/circuit-breaker.test.js.map +1 -0
  166. package/dist/loop/__tests__/cost-tracker.test.d.ts +2 -0
  167. package/dist/loop/__tests__/cost-tracker.test.d.ts.map +1 -0
  168. package/dist/loop/__tests__/cost-tracker.test.js +200 -0
  169. package/dist/loop/__tests__/cost-tracker.test.js.map +1 -0
  170. package/dist/loop/__tests__/rate-limiter.test.d.ts +2 -0
  171. package/dist/loop/__tests__/rate-limiter.test.d.ts.map +1 -0
  172. package/dist/loop/__tests__/rate-limiter.test.js +198 -0
  173. package/dist/loop/__tests__/rate-limiter.test.js.map +1 -0
  174. package/dist/loop/__tests__/validation.test.d.ts +2 -0
  175. package/dist/loop/__tests__/validation.test.d.ts.map +1 -0
  176. package/dist/loop/__tests__/validation.test.js +234 -0
  177. package/dist/loop/__tests__/validation.test.js.map +1 -0
  178. package/dist/loop/agents.d.ts +26 -0
  179. package/dist/loop/agents.d.ts.map +1 -0
  180. package/dist/loop/agents.js +188 -0
  181. package/dist/loop/agents.js.map +1 -0
  182. package/dist/loop/circuit-breaker.d.ts +61 -0
  183. package/dist/loop/circuit-breaker.d.ts.map +1 -0
  184. package/dist/loop/circuit-breaker.js +143 -0
  185. package/dist/loop/circuit-breaker.js.map +1 -0
  186. package/dist/loop/cost-tracker.d.ts +90 -0
  187. package/dist/loop/cost-tracker.d.ts.map +1 -0
  188. package/dist/loop/cost-tracker.js +229 -0
  189. package/dist/loop/cost-tracker.js.map +1 -0
  190. package/dist/loop/estimator.d.ts +20 -0
  191. package/dist/loop/estimator.d.ts.map +1 -0
  192. package/dist/loop/estimator.js +123 -0
  193. package/dist/loop/estimator.js.map +1 -0
  194. package/dist/loop/executor.d.ts +44 -0
  195. package/dist/loop/executor.d.ts.map +1 -0
  196. package/dist/loop/executor.js +646 -0
  197. package/dist/loop/executor.js.map +1 -0
  198. package/dist/loop/progress.d.ts +34 -0
  199. package/dist/loop/progress.d.ts.map +1 -0
  200. package/dist/loop/progress.js +186 -0
  201. package/dist/loop/progress.js.map +1 -0
  202. package/dist/loop/rate-limiter.d.ts +71 -0
  203. package/dist/loop/rate-limiter.d.ts.map +1 -0
  204. package/dist/loop/rate-limiter.js +151 -0
  205. package/dist/loop/rate-limiter.js.map +1 -0
  206. package/dist/loop/semantic-analyzer.d.ts +33 -0
  207. package/dist/loop/semantic-analyzer.d.ts.map +1 -0
  208. package/dist/loop/semantic-analyzer.js +153 -0
  209. package/dist/loop/semantic-analyzer.js.map +1 -0
  210. package/dist/loop/skills.d.ts +29 -0
  211. package/dist/loop/skills.d.ts.map +1 -0
  212. package/dist/loop/skills.js +174 -0
  213. package/dist/loop/skills.js.map +1 -0
  214. package/dist/loop/step-detector.d.ts +17 -0
  215. package/dist/loop/step-detector.d.ts.map +1 -0
  216. package/dist/loop/step-detector.js +280 -0
  217. package/dist/loop/step-detector.js.map +1 -0
  218. package/dist/loop/task-counter.d.ts +41 -0
  219. package/dist/loop/task-counter.d.ts.map +1 -0
  220. package/dist/loop/task-counter.js +99 -0
  221. package/dist/loop/task-counter.js.map +1 -0
  222. package/dist/loop/validation.d.ts +28 -0
  223. package/dist/loop/validation.d.ts.map +1 -0
  224. package/dist/loop/validation.js +138 -0
  225. package/dist/loop/validation.js.map +1 -0
  226. package/dist/mcp/core/init.d.ts +15 -0
  227. package/dist/mcp/core/init.d.ts.map +1 -0
  228. package/dist/mcp/core/init.js +272 -0
  229. package/dist/mcp/core/init.js.map +1 -0
  230. package/dist/mcp/core/plan.d.ts +15 -0
  231. package/dist/mcp/core/plan.d.ts.map +1 -0
  232. package/dist/mcp/core/plan.js +90 -0
  233. package/dist/mcp/core/plan.js.map +1 -0
  234. package/dist/mcp/core/run.d.ts +26 -0
  235. package/dist/mcp/core/run.d.ts.map +1 -0
  236. package/dist/mcp/core/run.js +114 -0
  237. package/dist/mcp/core/run.js.map +1 -0
  238. package/dist/mcp/prompts.d.ts +10 -0
  239. package/dist/mcp/prompts.d.ts.map +1 -0
  240. package/dist/mcp/prompts.js +163 -0
  241. package/dist/mcp/prompts.js.map +1 -0
  242. package/dist/mcp/resources.d.ts +16 -0
  243. package/dist/mcp/resources.d.ts.map +1 -0
  244. package/dist/mcp/resources.js +114 -0
  245. package/dist/mcp/resources.js.map +1 -0
  246. package/dist/mcp/server.d.ts +10 -0
  247. package/dist/mcp/server.d.ts.map +1 -0
  248. package/dist/mcp/server.js +73 -0
  249. package/dist/mcp/server.js.map +1 -0
  250. package/dist/mcp/tools.d.ts +15 -0
  251. package/dist/mcp/tools.d.ts.map +1 -0
  252. package/dist/mcp/tools.js +316 -0
  253. package/dist/mcp/tools.js.map +1 -0
  254. package/dist/presets/index.d.ts +36 -0
  255. package/dist/presets/index.d.ts.map +1 -0
  256. package/dist/presets/index.js +236 -0
  257. package/dist/presets/index.js.map +1 -0
  258. package/dist/setup/agent-detector.d.ts +57 -0
  259. package/dist/setup/agent-detector.d.ts.map +1 -0
  260. package/dist/setup/agent-detector.js +170 -0
  261. package/dist/setup/agent-detector.js.map +1 -0
  262. package/dist/setup/index.d.ts +7 -0
  263. package/dist/setup/index.d.ts.map +1 -0
  264. package/dist/setup/index.js +7 -0
  265. package/dist/setup/index.js.map +1 -0
  266. package/dist/setup/llm-tester.d.ts +33 -0
  267. package/dist/setup/llm-tester.d.ts.map +1 -0
  268. package/dist/setup/llm-tester.js +105 -0
  269. package/dist/setup/llm-tester.js.map +1 -0
  270. package/dist/setup/wizard.d.ts +19 -0
  271. package/dist/setup/wizard.d.ts.map +1 -0
  272. package/dist/setup/wizard.js +313 -0
  273. package/dist/setup/wizard.js.map +1 -0
  274. package/dist/sources/__tests__/file.test.d.ts +2 -0
  275. package/dist/sources/__tests__/file.test.d.ts.map +1 -0
  276. package/dist/sources/__tests__/file.test.js +126 -0
  277. package/dist/sources/__tests__/file.test.js.map +1 -0
  278. package/dist/sources/__tests__/github.test.d.ts +2 -0
  279. package/dist/sources/__tests__/github.test.d.ts.map +1 -0
  280. package/dist/sources/__tests__/github.test.js +157 -0
  281. package/dist/sources/__tests__/github.test.js.map +1 -0
  282. package/dist/sources/base.d.ts +72 -0
  283. package/dist/sources/base.d.ts.map +1 -0
  284. package/dist/sources/base.js +127 -0
  285. package/dist/sources/base.js.map +1 -0
  286. package/dist/sources/builtin/file.d.ts +21 -0
  287. package/dist/sources/builtin/file.d.ts.map +1 -0
  288. package/dist/sources/builtin/file.js +129 -0
  289. package/dist/sources/builtin/file.js.map +1 -0
  290. package/dist/sources/builtin/github-scraper.d.ts +65 -0
  291. package/dist/sources/builtin/github-scraper.d.ts.map +1 -0
  292. package/dist/sources/builtin/github-scraper.js +324 -0
  293. package/dist/sources/builtin/github-scraper.js.map +1 -0
  294. package/dist/sources/builtin/pdf.d.ts +24 -0
  295. package/dist/sources/builtin/pdf.d.ts.map +1 -0
  296. package/dist/sources/builtin/pdf.js +174 -0
  297. package/dist/sources/builtin/pdf.js.map +1 -0
  298. package/dist/sources/builtin/url.d.ts +47 -0
  299. package/dist/sources/builtin/url.d.ts.map +1 -0
  300. package/dist/sources/builtin/url.js +429 -0
  301. package/dist/sources/builtin/url.js.map +1 -0
  302. package/dist/sources/config.d.ts +72 -0
  303. package/dist/sources/config.d.ts.map +1 -0
  304. package/dist/sources/config.js +215 -0
  305. package/dist/sources/config.js.map +1 -0
  306. package/dist/sources/index.d.ts +47 -0
  307. package/dist/sources/index.d.ts.map +1 -0
  308. package/dist/sources/index.js +210 -0
  309. package/dist/sources/index.js.map +1 -0
  310. package/dist/sources/integrations/github.d.ts +24 -0
  311. package/dist/sources/integrations/github.d.ts.map +1 -0
  312. package/dist/sources/integrations/github.js +193 -0
  313. package/dist/sources/integrations/github.js.map +1 -0
  314. package/dist/sources/integrations/linear.d.ts +18 -0
  315. package/dist/sources/integrations/linear.d.ts.map +1 -0
  316. package/dist/sources/integrations/linear.js +197 -0
  317. package/dist/sources/integrations/linear.js.map +1 -0
  318. package/dist/sources/integrations/notion.d.ts +39 -0
  319. package/dist/sources/integrations/notion.d.ts.map +1 -0
  320. package/dist/sources/integrations/notion.js +343 -0
  321. package/dist/sources/integrations/notion.js.map +1 -0
  322. package/dist/sources/integrations/todoist.d.ts +18 -0
  323. package/dist/sources/integrations/todoist.d.ts.map +1 -0
  324. package/dist/sources/integrations/todoist.js +154 -0
  325. package/dist/sources/integrations/todoist.js.map +1 -0
  326. package/dist/sources/types.d.ts +106 -0
  327. package/dist/sources/types.d.ts.map +1 -0
  328. package/dist/sources/types.js +9 -0
  329. package/dist/sources/types.js.map +1 -0
  330. package/dist/ui/index.d.ts +3 -0
  331. package/dist/ui/index.d.ts.map +1 -0
  332. package/dist/ui/index.js +3 -0
  333. package/dist/ui/index.js.map +1 -0
  334. package/dist/ui/progress-renderer.d.ts +54 -0
  335. package/dist/ui/progress-renderer.d.ts.map +1 -0
  336. package/dist/ui/progress-renderer.js +118 -0
  337. package/dist/ui/progress-renderer.js.map +1 -0
  338. package/dist/ui/shimmer.d.ts +16 -0
  339. package/dist/ui/shimmer.d.ts.map +1 -0
  340. package/dist/ui/shimmer.js +31 -0
  341. package/dist/ui/shimmer.js.map +1 -0
  342. package/dist/wizard/ascii-art.d.ts +93 -0
  343. package/dist/wizard/ascii-art.d.ts.map +1 -0
  344. package/dist/wizard/ascii-art.js +378 -0
  345. package/dist/wizard/ascii-art.js.map +1 -0
  346. package/dist/wizard/idea-prompts.d.ts +18 -0
  347. package/dist/wizard/idea-prompts.d.ts.map +1 -0
  348. package/dist/wizard/idea-prompts.js +154 -0
  349. package/dist/wizard/idea-prompts.js.map +1 -0
  350. package/dist/wizard/idea-ui.d.ts +34 -0
  351. package/dist/wizard/idea-ui.d.ts.map +1 -0
  352. package/dist/wizard/idea-ui.js +225 -0
  353. package/dist/wizard/idea-ui.js.map +1 -0
  354. package/dist/wizard/ideas.d.ts +27 -0
  355. package/dist/wizard/ideas.d.ts.map +1 -0
  356. package/dist/wizard/ideas.js +511 -0
  357. package/dist/wizard/ideas.js.map +1 -0
  358. package/dist/wizard/index.d.ts +11 -0
  359. package/dist/wizard/index.d.ts.map +1 -0
  360. package/dist/wizard/index.js +472 -0
  361. package/dist/wizard/index.js.map +1 -0
  362. package/dist/wizard/llm.d.ts +14 -0
  363. package/dist/wizard/llm.d.ts.map +1 -0
  364. package/dist/wizard/llm.js +420 -0
  365. package/dist/wizard/llm.js.map +1 -0
  366. package/dist/wizard/prompts.d.ts +75 -0
  367. package/dist/wizard/prompts.d.ts.map +1 -0
  368. package/dist/wizard/prompts.js +455 -0
  369. package/dist/wizard/prompts.js.map +1 -0
  370. package/dist/wizard/spec-generator.d.ts +14 -0
  371. package/dist/wizard/spec-generator.d.ts.map +1 -0
  372. package/dist/wizard/spec-generator.js +200 -0
  373. package/dist/wizard/spec-generator.js.map +1 -0
  374. package/dist/wizard/types.d.ts +53 -0
  375. package/dist/wizard/types.d.ts.map +1 -0
  376. package/dist/wizard/types.js +10 -0
  377. package/dist/wizard/types.js.map +1 -0
  378. package/dist/wizard/ui.d.ts +57 -0
  379. package/dist/wizard/ui.d.ts.map +1 -0
  380. package/dist/wizard/ui.js +211 -0
  381. package/dist/wizard/ui.js.map +1 -0
  382. package/package.json +67 -8
@@ -0,0 +1,646 @@
1
+ import { readdir, stat } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import chalk from 'chalk';
4
+ import ora from 'ora';
5
+ import { createPullRequest, gitCommit, gitPush, hasUncommittedChanges } from '../automation/git.js';
6
+ import { ProgressRenderer } from '../ui/progress-renderer.js';
7
+ import { runAgent } from './agents.js';
8
+ import { CircuitBreaker } from './circuit-breaker.js';
9
+ import { CostTracker, formatCost } from './cost-tracker.js';
10
+ import { estimateLoop, formatEstimateDetailed } from './estimator.js';
11
+ import { checkFileBasedCompletion, createProgressTracker } from './progress.js';
12
+ import { RateLimiter } from './rate-limiter.js';
13
+ import { analyzeResponse, hasExitSignal } from './semantic-analyzer.js';
14
+ import { detectClaudeSkills, formatSkillsForPrompt } from './skills.js';
15
+ import { detectStepFromOutput } from './step-detector.js';
16
+ import { getCurrentTask, parsePlanTasks } from './task-counter.js';
17
+ import { detectValidationCommands, formatValidationFeedback, runAllValidations, } from './validation.js';
18
+ /**
19
+ * Sleep for a given number of milliseconds
20
+ */
21
+ function sleep(ms) {
22
+ return new Promise((resolve) => setTimeout(resolve, ms));
23
+ }
24
+ /**
25
+ * Strip markdown formatting from task names
26
+ */
27
+ function cleanTaskName(name) {
28
+ return name
29
+ .replace(/\*\*/g, '') // Remove bold **
30
+ .replace(/\*/g, '') // Remove italic *
31
+ .replace(/`/g, '') // Remove code backticks
32
+ .trim();
33
+ }
34
+ /**
35
+ * Get the latest modification time in a directory (recursive)
36
+ */
37
+ async function getLatestMtime(dir) {
38
+ let latestMtime = 0;
39
+ try {
40
+ const entries = await readdir(dir, { withFileTypes: true });
41
+ for (const entry of entries) {
42
+ // Skip hidden files, node_modules, and .git
43
+ if (entry.name.startsWith('.') || entry.name === 'node_modules')
44
+ continue;
45
+ const fullPath = join(dir, entry.name);
46
+ try {
47
+ const stats = await stat(fullPath);
48
+ if (stats.mtimeMs > latestMtime) {
49
+ latestMtime = stats.mtimeMs;
50
+ }
51
+ if (entry.isDirectory()) {
52
+ const subMtime = await getLatestMtime(fullPath);
53
+ if (subMtime > latestMtime) {
54
+ latestMtime = subMtime;
55
+ }
56
+ }
57
+ }
58
+ catch {
59
+ // Ignore stat errors (file might have been deleted)
60
+ }
61
+ }
62
+ }
63
+ catch {
64
+ // Ignore readdir errors
65
+ }
66
+ return latestMtime;
67
+ }
68
+ /**
69
+ * Wait for filesystem to settle (no new writes)
70
+ */
71
+ async function waitForFilesystemQuiescence(dir, timeoutMs = 3000) {
72
+ const startTime = Date.now();
73
+ let lastMtime = 0;
74
+ while (Date.now() - startTime < timeoutMs) {
75
+ const currentMtime = await getLatestMtime(dir);
76
+ if (currentMtime === lastMtime && lastMtime > 0) {
77
+ // No changes for 500ms, check again
78
+ await sleep(500);
79
+ const afterWait = await getLatestMtime(dir);
80
+ if (afterWait === currentMtime) {
81
+ return; // Filesystem is stable
82
+ }
83
+ }
84
+ lastMtime = currentMtime;
85
+ await sleep(100);
86
+ }
87
+ }
88
+ // Completion markers that indicate the task is done
89
+ const COMPLETION_MARKERS = [
90
+ '<TASK_DONE>',
91
+ '<TASK_COMPLETE>',
92
+ 'TASK COMPLETED',
93
+ 'All tasks completed',
94
+ 'Successfully completed',
95
+ ];
96
+ // Blocked markers that indicate the task cannot continue
97
+ const BLOCKED_MARKERS = ['<TASK_BLOCKED>', 'TASK BLOCKED', 'Cannot proceed', 'Blocked:'];
98
+ function detectCompletion(output, options = {}) {
99
+ const { completionPromise, requireExitSignal = false, minCompletionIndicators = 1 } = options;
100
+ // 1. Check explicit completion promise first (highest priority)
101
+ if (completionPromise && output.includes(completionPromise)) {
102
+ return 'done';
103
+ }
104
+ // 2. Check for <promise>COMPLETE</promise> tag
105
+ if (/<promise>COMPLETE<\/promise>/i.test(output)) {
106
+ return 'done';
107
+ }
108
+ // 3. Use semantic analyzer for more nuanced detection
109
+ const analysis = analyzeResponse(output);
110
+ // Check for blocked status
111
+ if (analysis.stuckScore >= 0.7 && analysis.confidence !== 'low') {
112
+ return 'blocked';
113
+ }
114
+ // Check blocked markers (legacy support)
115
+ const upperOutput = output.toUpperCase();
116
+ for (const marker of BLOCKED_MARKERS) {
117
+ if (upperOutput.includes(marker.toUpperCase())) {
118
+ return 'blocked';
119
+ }
120
+ }
121
+ // Check for explicit EXIT_SIGNAL
122
+ const hasExplicitSignal = hasExitSignal(output);
123
+ // If exit signal is required, check for it
124
+ if (requireExitSignal) {
125
+ if (hasExplicitSignal && analysis.indicators.completion.length >= minCompletionIndicators) {
126
+ return 'done';
127
+ }
128
+ // Continue if no explicit signal
129
+ if (!hasExplicitSignal) {
130
+ return 'continue';
131
+ }
132
+ }
133
+ // Check completion indicators
134
+ if (analysis.completionScore >= 0.7 &&
135
+ analysis.indicators.completion.length >= minCompletionIndicators) {
136
+ return 'done';
137
+ }
138
+ // Explicit exit signals always count
139
+ if (hasExplicitSignal) {
140
+ return 'done';
141
+ }
142
+ // Legacy marker support
143
+ for (const marker of COMPLETION_MARKERS) {
144
+ if (upperOutput.includes(marker.toUpperCase())) {
145
+ return 'done';
146
+ }
147
+ }
148
+ return 'continue';
149
+ }
150
+ /**
151
+ * Get human-readable reason for completion (UX 3)
152
+ */
153
+ function getCompletionReason(output, options) {
154
+ const { completionPromise, requireExitSignal } = options;
155
+ // Check explicit completion promise first
156
+ if (completionPromise && output.includes(completionPromise)) {
157
+ return `Found completion promise: "${completionPromise}"`;
158
+ }
159
+ // Check for <promise>COMPLETE</promise> tag
160
+ if (/<promise>COMPLETE<\/promise>/i.test(output)) {
161
+ return 'Found <promise>COMPLETE</promise> marker';
162
+ }
163
+ // Check for explicit EXIT_SIGNAL
164
+ if (hasExitSignal(output)) {
165
+ return 'Found EXIT_SIGNAL: true';
166
+ }
167
+ // Check completion markers
168
+ const upperOutput = output.toUpperCase();
169
+ for (const marker of COMPLETION_MARKERS) {
170
+ if (upperOutput.includes(marker.toUpperCase())) {
171
+ return `Found completion marker: "${marker}"`;
172
+ }
173
+ }
174
+ // Use semantic analysis
175
+ const analysis = analyzeResponse(output);
176
+ if (analysis.completionScore >= 0.7) {
177
+ const indicators = analysis.indicators.completion.slice(0, 3);
178
+ return `Semantic analysis (${Math.round(analysis.completionScore * 100)}% confident): ${indicators.join(', ')}`;
179
+ }
180
+ return 'Task marked as complete by agent';
181
+ }
182
+ function summarizeChanges(output) {
183
+ // Try to extract a meaningful summary from the output
184
+ const lines = output.split('\n').filter((l) => l.trim());
185
+ // Look for common patterns
186
+ for (const line of lines) {
187
+ if (line.includes('Created') || line.includes('Added') || line.includes('Updated')) {
188
+ return line.slice(0, 50).trim();
189
+ }
190
+ }
191
+ // Fallback to first meaningful line
192
+ return lines[0]?.slice(0, 50) || 'Update from ralph loop';
193
+ }
194
+ export async function runLoop(options) {
195
+ const spinner = ora();
196
+ const maxIterations = options.maxIterations || 50;
197
+ const commits = [];
198
+ const startTime = Date.now();
199
+ let validationFailures = 0;
200
+ let exitReason = 'max_iterations';
201
+ let finalIteration = maxIterations;
202
+ // Initialize circuit breaker
203
+ const circuitBreaker = new CircuitBreaker(options.circuitBreaker);
204
+ // Initialize rate limiter
205
+ const rateLimiter = options.rateLimit
206
+ ? new RateLimiter({ maxCallsPerHour: options.rateLimit })
207
+ : null;
208
+ // Initialize progress tracker
209
+ const progressTracker = options.trackProgress
210
+ ? createProgressTracker(options.cwd, options.task)
211
+ : null;
212
+ // Initialize cost tracker
213
+ const costTracker = options.trackCost
214
+ ? new CostTracker({
215
+ model: options.model || 'claude-3-sonnet',
216
+ maxIterations: maxIterations,
217
+ })
218
+ : null;
219
+ // Detect validation commands if validation is enabled
220
+ const validationCommands = options.validate ? detectValidationCommands(options.cwd) : [];
221
+ // Detect Claude Code skills
222
+ const detectedSkills = detectClaudeSkills(options.cwd);
223
+ let taskWithSkills = options.task;
224
+ if (detectedSkills.length > 0) {
225
+ const skillsPrompt = formatSkillsForPrompt(detectedSkills);
226
+ taskWithSkills = `${options.task}\n\n${skillsPrompt}`;
227
+ }
228
+ // Completion detection options
229
+ const completionOptions = {
230
+ completionPromise: options.completionPromise,
231
+ requireExitSignal: options.requireExitSignal,
232
+ minCompletionIndicators: options.minCompletionIndicators,
233
+ };
234
+ // Get initial task count for estimates
235
+ const initialTaskCount = parsePlanTasks(options.cwd);
236
+ console.log();
237
+ console.log(chalk.cyan.bold('Starting Ralph Wiggum Loop'));
238
+ console.log(chalk.dim(`Agent: ${options.agent.name}`));
239
+ // Show task count and estimates if we have tasks
240
+ if (initialTaskCount.total > 0) {
241
+ console.log(chalk.dim(`Tasks: ${initialTaskCount.pending} pending, ${initialTaskCount.completed} completed`));
242
+ // Show estimate
243
+ const estimate = estimateLoop(initialTaskCount);
244
+ console.log();
245
+ console.log(chalk.yellow.bold('📋 Estimate:'));
246
+ for (const line of formatEstimateDetailed(estimate)) {
247
+ console.log(chalk.yellow(` ${line}`));
248
+ }
249
+ }
250
+ else {
251
+ console.log(chalk.dim(`Task: ${options.task.slice(0, 60)}${options.task.length > 60 ? '...' : ''}`));
252
+ }
253
+ console.log();
254
+ if (validationCommands.length > 0) {
255
+ console.log(chalk.dim(`Validation: ${validationCommands.map((c) => c.name).join(', ')}`));
256
+ }
257
+ if (detectedSkills.length > 0) {
258
+ console.log(chalk.dim(`Skills: ${detectedSkills.map((s) => s.name).join(', ')}`));
259
+ }
260
+ if (options.completionPromise) {
261
+ console.log(chalk.dim(`Completion promise: ${options.completionPromise}`));
262
+ }
263
+ if (rateLimiter) {
264
+ console.log(chalk.dim(`Rate limit: ${options.rateLimit}/hour`));
265
+ }
266
+ console.log();
267
+ // Track completed tasks to show progress diff between iterations
268
+ let previousCompletedTasks = initialTaskCount.completed;
269
+ for (let i = 1; i <= maxIterations; i++) {
270
+ const iterationStart = Date.now();
271
+ // Check circuit breaker
272
+ if (circuitBreaker.isTripped()) {
273
+ const reason = circuitBreaker.getTripReason();
274
+ spinner.fail(chalk.red(`Circuit breaker tripped: ${reason}`));
275
+ finalIteration = i - 1;
276
+ exitReason = 'circuit_breaker';
277
+ break;
278
+ }
279
+ // Check rate limiter with countdown (UX 10)
280
+ if (rateLimiter && !rateLimiter.canMakeCall()) {
281
+ const waitTimeMs = rateLimiter.getWaitTime();
282
+ const waitTimeSec = Math.ceil(waitTimeMs / 1000);
283
+ console.log(chalk.yellow(`\n⏳ Rate limited. Waiting ${waitTimeSec}s...`));
284
+ console.log(chalk.dim(rateLimiter.formatStats()));
285
+ // Show countdown
286
+ const countdownInterval = setInterval(() => {
287
+ const remaining = rateLimiter.getWaitTime();
288
+ if (remaining > 0) {
289
+ process.stdout.write(chalk.dim(`\r ⏱ ${Math.ceil(remaining / 1000)}s remaining... `));
290
+ }
291
+ }, 1000);
292
+ const acquired = await rateLimiter.waitAndAcquire(60000); // Wait up to 1 minute
293
+ clearInterval(countdownInterval);
294
+ process.stdout.write(`\r${' '.repeat(40)}\r`); // Clear countdown line
295
+ if (!acquired) {
296
+ console.log(chalk.red('✗ Rate limit timeout - stopping loop'));
297
+ finalIteration = i - 1;
298
+ exitReason = 'rate_limit';
299
+ break;
300
+ }
301
+ console.log(chalk.green('✓ Rate limit cleared, continuing...\n'));
302
+ }
303
+ else if (rateLimiter) {
304
+ rateLimiter.recordCall();
305
+ }
306
+ // Check for file-based completion signals
307
+ if (options.checkFileCompletion) {
308
+ const fileCompletion = await checkFileBasedCompletion(options.cwd);
309
+ if (fileCompletion.completed) {
310
+ spinner.succeed(chalk.green(`File-based completion: ${fileCompletion.reason}`));
311
+ finalIteration = i - 1;
312
+ exitReason = 'file_signal';
313
+ break;
314
+ }
315
+ }
316
+ // Log iteration warnings
317
+ const progressPercent = (i / maxIterations) * 100;
318
+ if (progressPercent >= 90 && progressPercent < 95) {
319
+ console.log(chalk.yellow(`⚠️ Warning: 90% of iterations used (${i}/${maxIterations})`));
320
+ }
321
+ else if (progressPercent >= 80 && progressPercent < 85) {
322
+ console.log(chalk.yellow(`⚠️ Warning: 80% of iterations used (${i}/${maxIterations})`));
323
+ }
324
+ // Track progress entry
325
+ let progressEntry = null;
326
+ if (progressTracker) {
327
+ progressEntry = {
328
+ timestamp: new Date().toISOString(),
329
+ iteration: i,
330
+ status: 'started',
331
+ summary: '',
332
+ };
333
+ }
334
+ // Get current task from implementation plan (re-parse to catch updates)
335
+ const currentTask = getCurrentTask(options.cwd);
336
+ const taskInfo = parsePlanTasks(options.cwd);
337
+ const totalTasks = taskInfo.total;
338
+ const completedTasks = taskInfo.completed;
339
+ // Check if tasks were completed since last iteration
340
+ const newlyCompleted = completedTasks - previousCompletedTasks;
341
+ if (newlyCompleted > 0 && i > 1) {
342
+ // Get names of newly completed tasks (strip markdown)
343
+ const completedNames = taskInfo.tasks
344
+ .filter((t) => t.completed && t.index >= previousCompletedTasks && t.index < completedTasks)
345
+ .map((t) => {
346
+ const clean = cleanTaskName(t.name);
347
+ return clean.length > 25 ? `${clean.slice(0, 22)}...` : clean;
348
+ });
349
+ if (completedNames.length > 0) {
350
+ console.log(chalk.green(` ✓ Completed ${newlyCompleted} task(s): ${completedNames.join(', ')}`));
351
+ }
352
+ }
353
+ previousCompletedTasks = completedTasks;
354
+ // Show loop header with task info
355
+ console.log(chalk.cyan(`\n═══════════════════════════════════════════════════════════════`));
356
+ if (currentTask && totalTasks > 0) {
357
+ const taskNum = completedTasks + 1;
358
+ const cleanName = cleanTaskName(currentTask.name);
359
+ const taskName = cleanName.length > 40 ? `${cleanName.slice(0, 37)}...` : cleanName;
360
+ console.log(chalk.cyan.bold(` Task ${taskNum}/${totalTasks} │ ${taskName}`));
361
+ }
362
+ else {
363
+ console.log(chalk.cyan.bold(` Loop ${i}/${maxIterations} │ Running ${options.agent.name}`));
364
+ }
365
+ console.log(chalk.cyan(`═══════════════════════════════════════════════════════════════\n`));
366
+ // Create progress renderer for this iteration
367
+ const iterProgress = new ProgressRenderer();
368
+ iterProgress.start('Working...');
369
+ // Run the agent with step detection (include skills in task)
370
+ const agentOptions = {
371
+ task: taskWithSkills,
372
+ cwd: options.cwd,
373
+ auto: options.auto,
374
+ maxTurns: 10, // Limit turns per loop iteration
375
+ streamOutput: false, // Don't dump raw JSON - use progress renderer instead
376
+ onOutput: (line) => {
377
+ const step = detectStepFromOutput(line);
378
+ if (step) {
379
+ iterProgress.updateStep(step);
380
+ }
381
+ },
382
+ };
383
+ const result = await runAgent(options.agent, agentOptions);
384
+ iterProgress.stop('Iteration complete');
385
+ // Track cost for this iteration (silent - summary shown at end)
386
+ if (costTracker) {
387
+ costTracker.recordIteration(options.task, result.output);
388
+ }
389
+ // Check for completion using enhanced detection
390
+ let status = detectCompletion(result.output, completionOptions);
391
+ // Verify completion - check if files were actually changed
392
+ if (status === 'done' && i === 1) {
393
+ // On first iteration, verify that files were actually created/modified
394
+ const hasChanges = await hasUncommittedChanges(options.cwd);
395
+ if (!hasChanges) {
396
+ console.log(chalk.yellow(' Agent reported done but no files changed - continuing...'));
397
+ status = 'continue';
398
+ }
399
+ else {
400
+ // Wait for filesystem to settle before declaring done
401
+ await waitForFilesystemQuiescence(options.cwd, 2000);
402
+ }
403
+ }
404
+ if (status === 'blocked') {
405
+ // Detect specific block reasons for better user feedback
406
+ const output = result.output.toLowerCase();
407
+ const isRateLimit = output.includes('rate limit') ||
408
+ output.includes('usage limit') ||
409
+ output.includes('100%') ||
410
+ output.includes('exceeded') ||
411
+ output.includes('too many requests');
412
+ const isPermission = output.includes('permission') || output.includes('unauthorized') || output.includes('403');
413
+ console.log();
414
+ if (isRateLimit) {
415
+ console.log(chalk.red.bold(' ⚠ Claude rate limit reached'));
416
+ console.log();
417
+ console.log(chalk.yellow(' Your Claude session usage is at 100%.'));
418
+ console.log(chalk.yellow(' Wait for your rate limit to reset, then run again:'));
419
+ console.log(chalk.dim(' ralph-starter run'));
420
+ console.log();
421
+ console.log(chalk.dim(' Tip: Check your limits at https://claude.ai/settings'));
422
+ }
423
+ else if (isPermission) {
424
+ console.log(chalk.red.bold(' ⚠ Permission denied'));
425
+ console.log();
426
+ console.log(chalk.yellow(' Claude Code requires permission to continue.'));
427
+ console.log(chalk.dim(' Run without --auto flag to approve permissions interactively.'));
428
+ }
429
+ else {
430
+ console.log(chalk.red(` ✗ Task blocked - cannot continue`));
431
+ console.log();
432
+ console.log(chalk.dim(' The AI agent was blocked from continuing.'));
433
+ console.log(chalk.dim(' This may be due to rate limits or permissions.'));
434
+ }
435
+ console.log();
436
+ if (progressTracker && progressEntry) {
437
+ progressEntry.status = 'blocked';
438
+ progressEntry.summary = isRateLimit
439
+ ? 'Rate limit reached'
440
+ : isPermission
441
+ ? 'Permission denied'
442
+ : 'Task blocked';
443
+ progressEntry.duration = Date.now() - iterationStart;
444
+ await progressTracker.appendEntry(progressEntry);
445
+ }
446
+ finalIteration = i;
447
+ exitReason = 'blocked';
448
+ return {
449
+ success: false,
450
+ iterations: i,
451
+ commits,
452
+ error: isRateLimit
453
+ ? 'Rate limit reached - wait and try again'
454
+ : isPermission
455
+ ? 'Permission denied'
456
+ : 'Task blocked - cannot continue',
457
+ exitReason,
458
+ stats: {
459
+ totalDuration: Date.now() - startTime,
460
+ avgIterationDuration: (Date.now() - startTime) / i,
461
+ validationFailures,
462
+ circuitBreakerStats: circuitBreaker.getStats(),
463
+ costStats: costTracker?.getStats(),
464
+ },
465
+ };
466
+ }
467
+ // Run validation (backpressure) if enabled and there are changes
468
+ let _validationPassed = true;
469
+ let validationResults = [];
470
+ if (validationCommands.length > 0 && (await hasUncommittedChanges(options.cwd))) {
471
+ spinner.start(chalk.yellow(`Loop ${i}: Running validation...`));
472
+ validationResults = await runAllValidations(options.cwd, validationCommands);
473
+ const allPassed = validationResults.every((r) => r.success);
474
+ if (!allPassed) {
475
+ _validationPassed = false;
476
+ validationFailures++;
477
+ const feedback = formatValidationFeedback(validationResults);
478
+ spinner.fail(chalk.red(`Loop ${i}: Validation failed`));
479
+ // Show which validations failed (UX 4: specific validation errors)
480
+ for (const vr of validationResults) {
481
+ if (!vr.success) {
482
+ console.log(chalk.red(` ✗ ${vr.command}`));
483
+ if (vr.error) {
484
+ const errorLines = vr.error.split('\n').slice(0, 5);
485
+ for (const line of errorLines) {
486
+ console.log(chalk.dim(` ${line}`));
487
+ }
488
+ }
489
+ else if (vr.output) {
490
+ const outputLines = vr.output.split('\n').slice(0, 5);
491
+ for (const line of outputLines) {
492
+ console.log(chalk.dim(` ${line}`));
493
+ }
494
+ }
495
+ }
496
+ }
497
+ // Record failure in circuit breaker
498
+ const errorMsg = validationResults
499
+ .filter((r) => !r.success)
500
+ .map((r) => r.error?.slice(0, 200) || r.output?.slice(0, 200) || r.command)
501
+ .join('\n');
502
+ const tripped = circuitBreaker.recordFailure(errorMsg);
503
+ if (tripped) {
504
+ const reason = circuitBreaker.getTripReason();
505
+ console.log(chalk.red(`Circuit breaker tripped: ${reason}`));
506
+ if (progressTracker && progressEntry) {
507
+ progressEntry.status = 'failed';
508
+ progressEntry.summary = `Circuit breaker tripped: ${reason}`;
509
+ progressEntry.validationResults = validationResults;
510
+ progressEntry.duration = Date.now() - iterationStart;
511
+ await progressTracker.appendEntry(progressEntry);
512
+ }
513
+ finalIteration = i;
514
+ exitReason = 'circuit_breaker';
515
+ break;
516
+ }
517
+ if (progressTracker && progressEntry) {
518
+ progressEntry.status = 'validation_failed';
519
+ progressEntry.summary = 'Validation failed';
520
+ progressEntry.validationResults = validationResults;
521
+ progressEntry.duration = Date.now() - iterationStart;
522
+ await progressTracker.appendEntry(progressEntry);
523
+ }
524
+ // Continue loop with validation feedback
525
+ taskWithSkills = `${taskWithSkills}\n\n${feedback}`;
526
+ continue; // Go to next iteration to fix issues
527
+ }
528
+ else {
529
+ // Validation passed - record success
530
+ spinner.succeed(chalk.green(`Loop ${i}: Validation passed`));
531
+ circuitBreaker.recordSuccess();
532
+ }
533
+ }
534
+ // Auto-commit if enabled and there are changes
535
+ let committed = false;
536
+ let commitMsg = '';
537
+ // Get current task info for display
538
+ const displayTaskInfo = parsePlanTasks(options.cwd);
539
+ const iterationLabel = displayTaskInfo.total > 0
540
+ ? `Task ${displayTaskInfo.completed}/${displayTaskInfo.total}`
541
+ : `Iteration ${i}`;
542
+ if (options.commit && (await hasUncommittedChanges(options.cwd))) {
543
+ const summary = summarizeChanges(result.output);
544
+ commitMsg = `feat: ${summary}`;
545
+ try {
546
+ await gitCommit(options.cwd, commitMsg);
547
+ commits.push(commitMsg);
548
+ committed = true;
549
+ console.log(chalk.green(`✓ ${iterationLabel}: Committed - ${commitMsg}`));
550
+ }
551
+ catch (_error) {
552
+ console.log(chalk.yellow(`⚠ ${iterationLabel}: Completed (commit failed)`));
553
+ }
554
+ }
555
+ else {
556
+ console.log(chalk.green(`✓ ${iterationLabel}: Completed`));
557
+ }
558
+ // Update progress entry
559
+ if (progressTracker && progressEntry) {
560
+ progressEntry.status = status === 'done' ? 'completed' : 'completed';
561
+ progressEntry.summary = summarizeChanges(result.output);
562
+ progressEntry.validationResults =
563
+ validationResults.length > 0 ? validationResults : undefined;
564
+ progressEntry.commitHash = committed ? commitMsg : undefined;
565
+ progressEntry.duration = Date.now() - iterationStart;
566
+ // Add cost info from tracker
567
+ if (costTracker) {
568
+ const lastCost = costTracker.getLastIterationCost();
569
+ if (lastCost) {
570
+ progressEntry.cost = lastCost.cost;
571
+ progressEntry.tokens = lastCost.tokens;
572
+ }
573
+ }
574
+ await progressTracker.appendEntry(progressEntry);
575
+ }
576
+ if (status === 'done') {
577
+ console.log();
578
+ console.log(chalk.green.bold('═══════════════════════════════════════════════════════════════'));
579
+ console.log(chalk.green.bold(' ✓ Task completed successfully!'));
580
+ console.log(chalk.green.bold('═══════════════════════════════════════════════════════════════'));
581
+ // Show completion reason (UX 3: clear completion signals)
582
+ const completionReason = getCompletionReason(result.output, completionOptions);
583
+ console.log(chalk.dim(` Reason: ${completionReason}`));
584
+ console.log(chalk.dim(` Iterations: ${i}`));
585
+ if (costTracker) {
586
+ const stats = costTracker.getStats();
587
+ console.log(chalk.dim(` Total cost: ${formatCost(stats.totalCost.totalCost)}`));
588
+ }
589
+ const duration = Date.now() - startTime;
590
+ const minutes = Math.floor(duration / 60000);
591
+ const seconds = Math.floor((duration % 60000) / 1000);
592
+ console.log(chalk.dim(` Time: ${minutes}m ${seconds}s`));
593
+ console.log();
594
+ finalIteration = i;
595
+ exitReason = 'completed';
596
+ break;
597
+ }
598
+ // Small delay between iterations
599
+ await new Promise((resolve) => setTimeout(resolve, 1000));
600
+ }
601
+ // Post-loop actions
602
+ if (options.push && commits.length > 0) {
603
+ spinner.start('Pushing to remote...');
604
+ try {
605
+ await gitPush(options.cwd);
606
+ spinner.succeed('Pushed to remote');
607
+ }
608
+ catch (_error) {
609
+ spinner.fail('Failed to push');
610
+ }
611
+ }
612
+ if (options.pr && commits.length > 0) {
613
+ spinner.start('Creating pull request...');
614
+ try {
615
+ const prUrl = await createPullRequest(options.cwd, {
616
+ title: options.prTitle || `Ralph: ${options.task.slice(0, 50)}`,
617
+ body: `Automated PR created by ralph-starter\n\n## Task\n${options.task}\n\n## Commits\n${commits.map((c) => `- ${c}`).join('\n')}`,
618
+ });
619
+ spinner.succeed(`Created PR: ${prUrl}`);
620
+ }
621
+ catch (_error) {
622
+ spinner.fail('Failed to create PR');
623
+ }
624
+ }
625
+ const totalDuration = Date.now() - startTime;
626
+ // Print cost summary if tracking enabled
627
+ if (costTracker) {
628
+ console.log();
629
+ console.log(chalk.cyan('💰 Cost Summary:'));
630
+ console.log(chalk.dim(costTracker.formatStats()));
631
+ }
632
+ return {
633
+ success: exitReason === 'completed' || exitReason === 'file_signal',
634
+ iterations: finalIteration,
635
+ commits,
636
+ exitReason,
637
+ stats: {
638
+ totalDuration,
639
+ avgIterationDuration: totalDuration / finalIteration,
640
+ validationFailures,
641
+ circuitBreakerStats: circuitBreaker.getStats(),
642
+ costStats: costTracker?.getStats(),
643
+ },
644
+ };
645
+ }
646
+ //# sourceMappingURL=executor.js.map